Merge branch '1.0.0pre19.x'
authorMark Wooding <mdw@distorted.org.uk>
Mon, 25 May 2020 15:33:19 +0000 (16:33 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Mon, 25 May 2020 15:33:19 +0000 (16:33 +0100)
* 1.0.0pre19.x:
  svc/connect.in: Squash newlines to spaces in `info' output.
  server/admin.c: Fix `=' vs `==' error in assertion.
  svc/tripe-ifup.in: Don't set remote IPv6 address until interface is up.

116 files changed:
.links
.mailmap [new file with mode: 0644]
.skelrc
Makefile.am
client/Makefile.am
client/tripectl.1.in
client/tripectl.c
common/Makefile.am
common/defs.man
common/protocol.h
common/slip.h
common/util.c
common/util.h
configure.ac
contrib/Makefile.am
contrib/README
contrib/greet.in
contrib/ipif-peers.in
contrib/knock.in
contrib/tripe-ipif.in
contrib/tripe-service.service.in
contrib/tripe-upstart.in
contrib/tripe.service.in
debian/.gitignore
debian/changelog
debian/compat
debian/control
debian/copyright
debian/pathmtu.copyright
debian/pkstream.copyright
debian/rules
debian/tripe.install
init/Makefile.am
keys/Makefile.am
keys/tests.at
keys/tripe-keys.8.in
keys/tripe-keys.conf.5.in
keys/tripe-keys.in
keys/tripe-keys.master
maint-utils/manfix
mon/Makefile.am
mon/tripemon.1.in
mon/tripemon.in
pathmtu/Makefile.am
pathmtu/pathmtu.1.in
pathmtu/pathmtu.c
peerdb/Makefile.am
peerdb/peers.cdb.5.in
peerdb/peers.in
peerdb/peers.in.5.in
peerdb/tripe-newpeers.8.in
peerdb/tripe-newpeers.in
pkstream/Makefile.am
pkstream/pkstream.1.in
pkstream/pkstream.c
priv/Makefile.am
priv/comm.c
priv/helper.c
priv/priv.h
priv/tripe-privhelper.8.in
proxy/Makefile.am
proxy/tripe-mitm.8.in
proxy/tripe-mitm.c
py/Makefile.am
py/rmcr.py
py/tripe.py.in
server/Makefile.am
server/addrmap.c
server/admin.c
server/bulkcrypto.c
server/chal.c
server/dh.c
server/keyexch.c
server/keymgmt.c
server/keyset.c
server/peer.c
server/privsep.c
server/servutil.c
server/standalone.c [new file with mode: 0644]
server/test.c [new file with mode: 0644]
server/tests.at
server/tripe-admin.5.in
server/tripe-service.7.in
server/tripe.8.in
server/tripe.c
server/tripe.h
server/tun-slip.c
server/tun-std.c
svc/Makefile.am
svc/connect.8.in
svc/connect.in
svc/conntrack.8.in
svc/conntrack.in
svc/tripe-ifup.8.in
svc/tripe-ifup.in
t/Makefile.am
t/atlocal.in
t/keyring-alpha
t/keyring-beta
t/keyring-beta-new
t/keyring-gamma [new file with mode: 0644]
uslip/Makefile.am
uslip/tripe-uslip.1.in
uslip/uslip.c
vars.am
wireshark/Makefile.am
wireshark/cap
wireshark/cap.dh [new file with mode: 0644]
wireshark/cap.ec [new file with mode: 0644]
wireshark/cap.knock [new file with mode: 0644]
wireshark/cap.x25519 [new file with mode: 0644]
wireshark/cap.x448 [new file with mode: 0644]
wireshark/capture-session [new file with mode: 0755]
wireshark/keyring [new file with mode: 0644]
wireshark/packet-tripe.c [deleted file]
wireshark/tripe.lua [new file with mode: 0644]

diff --git a/.links b/.links
index 098ccb8..8b3efa1 100644 (file)
--- a/.links
+++ b/.links
@@ -1,4 +1,4 @@
-COPYING
+COPYING=GPL-3
 config/confsubst
 t/autotest.am
 t/testsuite.at
diff --git a/.mailmap b/.mailmap
new file mode 100644 (file)
index 0000000..96fe7ad
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1 @@
+Mark Wooding <mdw@distorted.org.uk> <mdw>
diff --git a/.skelrc b/.skelrc
index 0811d49..df3212d 100644 (file)
--- a/.skelrc
+++ b/.skelrc
@@ -4,6 +4,7 @@
       (append
        '((author . "Straylight/Edgeware")
         (full-title . "Trivial IP Encryption (TrIPE)")
-        (program . "TrIPE"))
+        (program . "TrIPE")
+        (licence-text . "[[gpl-3]]"))
        skel-alist))
 
index e00e796..d8b5ac3 100644 (file)
@@ -9,19 +9,18 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 include $(top_srcdir)/vars.am
 
@@ -84,7 +83,7 @@ SUBDIRS                       += t
 ###--------------------------------------------------------------------------
 ### Package-configuration file.
 
-pkgconfigdir            = $(libdir)/pkgconfig
+pkgconfigdir            = $(datadir)/pkgconfig
 pkgconfig_DATA          = tripe.pc
 EXTRA_DIST             += tripe.pc.in
 CLEANFILES             += tripe.pc
index 6192137..7d056a6 100644 (file)
@@ -9,26 +9,25 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 include $(top_srcdir)/vars.am
 
 bin_PROGRAMS            =
 man_MANS                =
 
-LDADD                   = $(libtripe) $(mLib_LIBS)
+LDADD                   = $(libcommon) $(mLib_LIBS)
 
 ###--------------------------------------------------------------------------
 ### Client program.
index e01df12..7de21be 100644 (file)
@@ -9,19 +9,18 @@
 .\"
 .\" This file is part of Trivial IP Encryption (TrIPE).
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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.
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\" along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 .
 .\"--------------------------------------------------------------------------
 .so ../common/defs.man \" @@@PRE@@@
index e264a73..1b02123 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 /*----- Header files ------------------------------------------------------*/
index 1aa4ae1..388b2ef 100644 (file)
@@ -9,35 +9,34 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 include $(top_srcdir)/vars.am
 
-noinst_LIBRARIES        = libtripe.a
+noinst_LIBRARIES        = libcommon.a
 
 ###--------------------------------------------------------------------------
 ### Library.
 
-libtripe_a_SOURCES      =
+libcommon_a_SOURCES     =
 
 ## Protocol definitions.
-libtripe_a_SOURCES     += protocol.h
-libtripe_a_SOURCES     += slip.h
+libcommon_a_SOURCES    += protocol.h
+libcommon_a_SOURCES    += slip.h
 
 ## Miscellaneous utilties.
-libtripe_a_SOURCES     += util.c util.h
+libcommon_a_SOURCES    += util.c util.h
 
 ###--------------------------------------------------------------------------
 ### Documentation.
@@ -49,4 +48,3 @@ EXTRA_DIST            += defs.man
 EXTRA_DIST             += make-summary
 
 ###----- That's all, folks --------------------------------------------------
-
index ea198fe..161f4e3 100644 (file)
@@ -9,19 +9,18 @@
 .\"
 .\" This file is part of Trivial IP Encryption (TrIPE).
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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.
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\" along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 .
 .\"--------------------------------------------------------------------------
 .\" Preliminary definitions.
index dbb3bea..e40cf17 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 #ifndef TRIPE_PROTOCOL_H
 #define KX_REPLY 2u
 #define KX_SWITCH 3u
 #define KX_SWITCHOK 4u
-#define KX_NMSG 5u
+#define KX_TOKENRQ 5u
+#define KX_TOKEN 6u
+#define KX_KNOCK 7u
+#define KX_NMSG 8u
 
 /* --- Miscellaneous packets --- */
 
@@ -77,6 +79,7 @@
 #define MISC_EPING 3u                  /* Encrypted ping */
 #define MISC_EPONG 4u                  /* Encrypted ping response */
 #define MISC_GREET 5u                  /* A greeting from a NATed peer */
+#define MISC_BYE 6u                    /* Departure notification */
 
 /* --- Symmetric encryption and keysets --- *
  *
index aeead1e..b615dbc 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 #ifndef SLIP_H
index 3de554d..a3f931c 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 /*----- Header files ------------------------------------------------------*/
index b4c4505..b844945 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 #ifndef UTIL_H
index ce74aeb..47c2658 100644 (file)
@@ -9,19 +9,18 @@ dnl----- Licensing notice ---------------------------------------------------
 dnl
 dnl This file is part of Trivial IP Encryption (TrIPE).
 dnl
-dnl TrIPE is free software; you can redistribute it and/or modify
-dnl it under the terms of the GNU General Public License as published by
-dnl the Free Software Foundation; either version 2 of the License, or
-dnl (at your option) any later version.
+dnl TrIPE is free software: you can redistribute it and/or modify it under
+dnl the terms of the GNU General Public License as published by the Free
+dnl Software Foundation; either version 3 of the License, or (at your
+dnl option) any later version.
 dnl
-dnl TrIPE 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 TrIPE is distributed in the hope that it will be useful, but WITHOUT
+dnl ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+dnl FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+dnl for more details.
 dnl
 dnl You should have received a copy of the GNU General Public License
-dnl along with TrIPE; if not, write to the Free Software Foundation,
-dnl Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+dnl along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 dnl--------------------------------------------------------------------------
 dnl Initialization.
@@ -38,7 +37,7 @@ AM_PROG_CC_C_O
 AX_CFLAGS_WARN_ALL
 AX_TYPE_SOCKLEN_T
 AC_CANONICAL_HOST
-AM_PROG_LIBTOOL
+AC_PROG_RANLIB
 
 AC_CHECK_PROGS([AUTOM4TE], [autom4te])
 
@@ -64,8 +63,31 @@ case "$host_os" in
     ;;
 esac
 
-PKG_CHECK_MODULES([mLib], [mLib >= 2.2.1])
-PKG_CHECK_MODULES([catacomb], [catacomb >= 2.2.2-38])
+AC_CHECK_FUNCS([getifaddrs])
+
+AC_ARG_WITH([adns],
+  AS_HELP_STRING([--with-adns],
+                [use ADNS library for background name resolution]),
+  [want_adns=$withval],
+  [want_adns=auto])
+case $want_adns in
+  no) ;;
+  *) AC_CHECK_LIB([adns], [adns_submit], [have_adns=yes], [have_adns=no]) ;;
+esac
+AC_SUBST([ADNS_LIBS])
+case $want_adns,$have_adns in
+  yes,no)
+    AC_MSG_ERROR([ADNS library not found but explicitly requested])
+    ;;
+  yes,yes | auto,yes)
+    ADNS_LIBS="-ladns"
+    AC_DEFINE([HAVE_LIBADNS], [1],
+             [Define if the GNU adns library is available.])
+    ;;
+esac
+
+PKG_CHECK_MODULES([mLib], [mLib >= 2.4.1])
+PKG_CHECK_MODULES([catacomb], [catacomb >= 2.5.0])
 
 AM_CFLAGS="$AM_CFLAGS $mLib_CFLAGS $catacomb_CFLAGS"
 
@@ -210,35 +232,38 @@ AC_ARG_WITH([wireshark],
            esac],
            [wantshark=yes mustshark=no])
 
-case "$wantshark" in
-  yes)
-    PKG_CHECK_MODULES([WIRESHARK], [wireshark >= 1.12.1],
-       [haveshark=yes], [haveshark=no])
-    ;;
-  *)
-    haveshark=no
-    ;;
-esac
-
-case "$haveshark,$wireshark_plugindir" in
+case "$wantshark,$wireshark_plugindir" in
   yes,unknown)
     AC_CACHE_CHECK([where to put Wireshark plugins],
       [mdw_cv_wireshark_plugin_dir], [
       mdw_cv_wireshark_plugin_dir=$(
-       $PKG_CONFIG --variable=plugindir "wireshark >= 1.12.1")])
+       $PKG_CONFIG --variable=plugindir "wireshark >= 1.12.1")
+      dnl It seems that the Debian package has a habit of bungling the
+      dnl plugin path (#779788, #857729, ...).
+      case "$mdw_cv_wireshark_plugin_dir" in
+       /usr//usr/*)
+         mdw_cv_wireshark_plugin_dir=${mdw_cv_wireshark_plugin_dir#/usr/}
+         ;;
+      esac])
     case "$mdw_cv_wireshark_plugin_dir" in
-      /*) wireshark_plugindir=$mdw_cv_wireshark_plugin_dir ;;
+      /*)
+       if test ! -d "$mdw_cv_wireshark_plugin_dir"; then
+         AC_MSG_WARN([alleged Wireshark plugin directory $mdw_cv_wireshark_plugin_dir doesn't exist])
+         haveshark=no
+       else
+         wireshark_plugindir=$mdw_cv_wireshark_plugin_dir
+         haveshark=yes
+       fi
+       ;;
       *)
        AC_MSG_WARN([failed to read Wireshark plugin directory])
        haveshark=no
        ;;
     esac
     ;;
-esac
-
-dnl If we're still interested, find Glib.
-case "$haveshark" in
-  yes) AM_PATH_GLIB_2_0([2.4.0], [], [haveshark=false], [gmodule]) ;;
+  no,*)
+    haveshark=no
+    ;;
 esac
 
 case "$haveshark,$needshark" in
@@ -246,8 +271,6 @@ case "$haveshark,$needshark" in
     AC_MSG_ERROR([failed to configure Wireshark plugin])
     ;;
   yes,*)
-    WIRESHARK_CFLAGS="$GLIB_CFLAGS $WIRESHARK_CFLAGS"
-    AC_SUBST(WIRESHARK_CFLAGS)
     AC_SUBST(wireshark_plugindir)
     ;;
 esac
index 1fcf003..2d7a949 100644 (file)
@@ -9,19 +9,18 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 include $(top_srcdir)/vars.am
 
index b48c5a5..a3138d2 100644 (file)
@@ -10,12 +10,25 @@ ipif-peers
        Glue for attaching tripe to Ian Jackson's `userv-ipif' service.
 
 tripe-upstart
-       A configuration fragmwnt for starting Tripe under Maemo's ancient
+       A configuration fragment for starting Tripe under Maemo's ancient
        version of upstart.  It might work with other versions but it hasn't
        been tested.
 
+tripe.service
+tripe-service.service
+       Unit files for starting Tripe under systemd, for those with no choice
+       in the matter.  I strongly discourage the use of systemd.
+
 greet  A simple tool for stimulating a passive association by sending a
        `greet' packet.
 
-knock  A script which acts as a login shell for a `tripe' user, estabishing
-       dynamic assocations on demand.
+knock  A script which acts as an OpenSSH forced command or login shell for a
+       `tripe' user, estabishing dynamic assocations on demand.  Not
+       recommended for new deployments: use the `KNOCK' protocol instead
+       (see the `Dynamic connetion' section of connect(8), and the `ADD'
+       command in tripe-admin(5), for details).
+
+sshsvc.conf
+       A configuration script for sshsvc-mkauthkeys(1) (part of the
+       `distorted-utils' collection), for setting up an appropriate
+       `.ssh/authorized_keys' file to use the `knock' script.
index c84efdc..7bc678a 100644 (file)
@@ -9,9 +9,12 @@ from sys import argv
 def db64(s):
   return (s + '='*((-len(s))%4)).decode('base64')
 
-addr, chal = (lambda _, h, p, c: ((h, int(p)), db64(c)))(*argv)
-sk = S.socket(S.AF_INET, S.SOCK_DGRAM)
-sk.connect(addr)
+ai, chal = (lambda _, h, p, c:
+            (S.getaddrinfo(h, p, S.AF_UNSPEC, S.SOCK_DGRAM, S.IPPROTO_UDP,
+                             S.AI_NUMERICHOST | S.AI_NUMERICSERV)[0],
+             db64(c)))(*argv)
+sk = S.socket(ai[0], S.SOCK_DGRAM)
+sk.connect(ai[4])
 
 pkt = '\x25' + chal
 sk.send(pkt)
index 3b95a48..2bc1a97 100755 (executable)
@@ -9,19 +9,18 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 ###--------------------------------------------------------------------------
 ### Instructions.
index b1754d9..d7e0f0e 100755 (executable)
@@ -1,4 +1,26 @@
 #! /bin/sh
+###
+### SSH forced-command script for establishing dynamic associations.
+###
+### (c) 2012 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of Trivial IP Encryption (TrIPE).
+###
+### TrIPE 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.
+###
+### TrIPE 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 TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 set -e
 
index 5a620fa..3d2aa82 100755 (executable)
@@ -9,19 +9,18 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 ###--------------------------------------------------------------------------
 ### Instructions.
@@ -49,7 +48,9 @@
 ### field is used (a) by the accompanying `ipif-peers' script to set up the
 ### peer association, and (b) to determine the correct MTU to set; it
 ### should have the form ADDRESS[:PORT], where the PORT defaults to 4070 if
-### it's not given explicitly.
+### it's not given explicitly, and an IPv6 ADDRESS is enclosed in square
+### brackets (because of the stupid syntax decision to use colons in IPv6
+### address literals).
 ###
 ### Having done all of that, and having configured userv-ipif correctly,
 ### you should set TRIPE_SLIPIF=.../tripe-ipif and everything should just
@@ -90,19 +91,16 @@ esac
 echo "userv-$peer"
 
 ## Now we can interrogate the server without deadlocking it.
-algs=$(tripectl algs) tagsz=nil blksz=nil
+algs=$(tripectl algs) overhead=nil
 while read line; do
   for i in $line; do
-    case $i in
-      cipher-blksz=*) blksz=${i#*=} ;;
-      mac-tagsz=*) tagsz=${i#*=} ;;
-    esac
+    case $i in bulk-overhead=*) overhead=${i#*=} ;; esac
   done
 done <<EOF
 $algs
 EOF
-case ,$tagsz,$blksz, in
-  *,nil,*) echo >&2 "$quis[$$]: failed to discover cipher suite"; exit 1 ;;
+case $overhead in
+  nil) echo >&2 "$quis[$$]: failed to discover overhead"; exit 1 ;;
 esac
 
 ## Determine the remote address if none is specified; strip off a port number
@@ -112,18 +110,28 @@ case "$remote_ext" in
     addr=$(tripectl addr $peer)
     set -- $addr
     case $1 in
-      INET) remote_ext=$2 ;;
+      INET | INET6) remote_af=$1 remote_ext=$2 ;;
       *) echo >&2 "$quis: unexpected address family \`$1'"; exit 1 ;;
     esac
     ;;
+  \[*\]:*)
+    remote_af=INET6
+    remote_ext=${remote_ext#\[}
+    remote_ext=${remote_ext%\]:*}
+    ;;
   *:*)
+    remote_af=INET
     remote_ext=${remote_ext%:*}
     ;;
 esac
 
 ## Determine the MTU based on the path.
 pmtu=$(pathmtu $remote_ext)
-mtu=$(( $pmtu - 33 - $tagsz - $blksz ))
+case $remote_af in
+  INET) iphdrsz=20 ;;
+  INET6) iphdrsz=40 ;;
+esac
+mtu=$(( $pmtu - $iphdrsz - 8 - $overhead - 1 ))
 
 ## Obtain the tunnel and run it.
 now=$(date +"%Y-%m-%d %H:%M:%S")
index bdecb1c..0091f01 100644 (file)
@@ -1,6 +1,26 @@
 ;;; -*-conf-windows-*-
 ;;;
 ;;; systemd service configuration for tripe services [@service@]
+;;;
+;;; (c) 2014 Mark Wooding
+;;;
+
+;;;----- Licensing notice ---------------------------------------------------
+;;;
+;;; This file is part of Trivial IP Encryption (TrIPE).
+;;;
+;;; TrIPE 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.
+;;;
+;;; TrIPE 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 TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 [Unit]
 Description=Tripe service @service@: @descr@
index 8d558f0..390f189 100644 (file)
@@ -1,6 +1,26 @@
 ### -*-conf-*-
 ###
 ### Upstart control script for tripe.
+###
+### (c) 2012 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of Trivial IP Encryption (TrIPE).
+###
+### TrIPE 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.
+###
+### TrIPE 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 TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 ###--------------------------------------------------------------------------
 ### Identification.
index 7261c1a..0348527 100644 (file)
@@ -1,6 +1,26 @@
 ;;; -*-conf-windows-*-
 ;;;
 ;;; systemd service configuration for the main tripe server.
+;;;
+;;; (c) 2014 Mark Wooding
+;;;
+
+;;;----- Licensing notice ---------------------------------------------------
+;;;
+;;; This file is part of Trivial IP Encryption (TrIPE).
+;;;
+;;; TrIPE 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.
+;;;
+;;; TrIPE 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 TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 [Unit]
 Description=Tripe virtual private network server
index dcddb1d..40013f2 100644 (file)
@@ -17,6 +17,5 @@ tripe.init
 tripe-wireshark
 tripemon
 tripe-keys
-tripe-ethereal
 tripe-uslip
 tripe-peer-services
index 8a9ed7f..308c3e7 100644 (file)
@@ -1,3 +1,45 @@
+tripe (1.5.3) experimental; urgency=medium
+
+  * tripe-peer-services (tripe-newpeers): Fix crash when the database
+    contains `user' records.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Mon, 23 Sep 2019 11:10:20 +0100
+
+tripe (1.5.2) experimental; urgency=medium
+
+  * tripe-wireshark: Dissector package is necessarily architecture
+    specific.  Replace botched architecture-neutral version.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sun, 22 Sep 2019 16:22:19 +0100
+
+tripe (1.5.1) experimental; urgency=medium
+
+  * tripe: Fix almost completely unusable AEAD support (brown paper bag
+    moment).
+  * tripe: Document the errors about unsuitable AEAD schemes.
+  * tripe: Support AEAD schemes with smaller nonce spaces (down to 40
+    bits).
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sun, 22 Sep 2019 14:52:48 +0100
+
+tripe (1.5.0) experimental; urgency=medium
+
+  * Big version bump, because this really isn't a prerelease anymore.  And
+    there's lots of goodies in this version.
+  * New mobile-peer protocol `knock' is much faster and no longer requires
+    complex SSH setup.
+  * Support transport over IPv6.
+  * Support Catacomb AEAD schemes for bulk crypto.
+  * python-tripe: Fixed `TripeCommandDispatcher.eping' to send the correct
+    command.
+  * tripe-peer-services (connect): Report on connectivity statistics.
+  * tripe-wireshark: Replaced the old dissector with a new one written in
+    Lua, which understands the modern protocol.  It's unfortunately
+    slower, but actually works and isn't a nightmare to maintain.
+  * tripe-ethereal: Deleted this ancient transition package.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sun, 22 Sep 2019 01:49:03 +0100
+
 tripe (1.0.0pre19.1) experimental; urgency=medium
 
   * Packaging fixes.  (No code change.)
index 45a4fb7..f599e28 100644 (file)
@@ -1 +1 @@
-8
+10
index 9aaa629..0c25c55 100644 (file)
@@ -3,11 +3,11 @@ Section: net
 Priority: extra
 Maintainer: Mark Wooding <mdw@distorted.org.uk>
 XS-Python-Version: >= 2.4
-Build-Depends: debhelper (>= 9), pkg-config, curl, rsync,
+Build-Depends: debhelper (>= 10), pkg-config, curl, rsync,
        tshark, wireshark-dev (>= 1.12.1),
        python (>= 2.6.6-3~),
-       mlib-dev (>= 2.2.2),
-       catacomb-dev (>= 2.4.0), catacomb-bin (>= 2.1.4)
+       mlib-dev (>= 2.4.1),
+       catacomb-dev (>= 2.5.0), catacomb-bin (>= 2.1.4)
 Build-Depends-Indep: python-cdb, python-gtk2,
        python-mlib (>= 1.0.2), python-catacomb (>= 1.2.0)
 Standards-Version: 3.1.1
@@ -56,24 +56,12 @@ Description: Trivial IP Encryption: a simple virtual private network
 
 Package: tripe-wireshark
 Architecture: any
-Depends: wireshark-common (= ${tripe:Wireshark-Version}), ${shlibs:Depends}
+Depends: wireshark-common (= ${tripe:Wireshark-Version})
 Description: Trivial IP Encryption: a simple virtual private network
  TrIPE is a simple VPN protocol.  It uses cryptography to ensure secrecy
  and authenticity of packets it sends and receives.
  .
- This package contains the protocol analysis plug-in for Wireshark (the new
- name for Ethereal).
-
-Package: tripe-ethereal
-Architecture: all
-Depends: tripe-wireshark
-Description: Trivial IP Encryption: a simple virtual private network
- TrIPE is a simple VPN protocol.  It uses cryptography to ensure secrecy
- and authenticity of packets it sends and receives.
- .
- This is a dummy package to ease the transition to tripe-wireshark (since
- Wireshark is apparently the new name for Ethereal).  You should probably
- remove this package.
+ This package contains the protocol analysis plug-in for Wireshark.
 
 Package: tripemon
 Architecture: all
index a7e11e0..2a8df44 100644 (file)
@@ -1,16 +1,27 @@
-Tripe is copyright (c) 2003 Straylight/Edgeware.
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Copyright: 2001--2017 Straylight/Edgeware
+Upstream-Name: tripe
+Upstream-Contact: Mark Wooding <mdw@distorted.org.uk>
+Source: https://ftp.distorted.org.uk/pub/mdw/
+License: GPL-3.0+
 
-Tripe 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.
+Files: *
+Copyright: 2001--2017 Straylight/Edgeware
+License: GPL-3.0+
 
-Tripe 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 a copy of the GNU General Public License in
-/usr/share/common-licenses/GPL; if not, write to the Free Software
-Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
-USA.
+License: GPL-3.0+
+ TrIPE 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.
+ .
+ TrIPE 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 TrIPE.  If not, see <https://www.gnu.org/licenses/>.
+ .
+ On Debian systems, the full text of the GNU General Public License
+ version 2 can be found in the file `/usr/share/common-licenses/LGPL-2'.
index dcb9f2a..ee00f21 100644 (file)
@@ -1,16 +1,27 @@
-Pathmtu is copyright (c) 2003 Straylight/Edgeware.
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Copyright: 2008, 2009, 2012, 2014, 2016, 2017 Straylight/Edgeware
+Upstream-Name: pathmtu
+Upstream-Contact: Mark Wooding <mdw@distorted.org.uk>
+Source: https://ftp.distorted.org.uk/pub/mdw/
+License: GPL-3.0+
 
-Pathmtu 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.
+Files: *
+Copyright: 2008, 2009, 2012, 2014, 2016, 2017 Straylight/Edgeware
+License: GPL-3.0+
 
-Pathmtu 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 a copy of the GNU General Public License in
-/usr/share/common-licenses/GPL; if not, write to the Free Software
-Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
-USA.
+License: GPL-3.0+
+ Pathmtu 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.
+ .
+ Pathmtu 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 pathmtu.  If not, see <https://www.gnu.org/licenses/>.
+ .
+ On Debian systems, the full text of the GNU General Public License
+ version 2 can be found in the file `/usr/share/common-licenses/LGPL-2'.
index 5eb2517..8cb53e9 100644 (file)
@@ -1,16 +1,27 @@
-Pkstream is copyright (c) 2003 Straylight/Edgeware.
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Copyright: 2006--2008, 2010, 2012, 2013, 2017 Straylight/Edgeware
+Upstream-Name: pkstream
+Upstream-Contact: Mark Wooding <mdw@distorted.org.uk>
+Source: https://ftp.distorted.org.uk/pub/mdw/
+License: GPL-3.0+
 
-Pkstream 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.
+Files: *
+Copyright: 2006--2008, 2010, 2012, 2013, 2017 Straylight/Edgeware
+License: GPL-3.0+
 
-Pkstream 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 a copy of the GNU General Public License in
-/usr/share/common-licenses/GPL; if not, write to the Free Software
-Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
-USA.
+License: GPL-3.0+
+ Pkstream 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.
+ .
+ Pkstream 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 pkstream.  If not, see <https://www.gnu.org/licenses/>.
+ .
+ On Debian systems, the full text of the GNU General Public License
+ version 2 can be found in the file `/usr/share/common-licenses/LGPL-2'.
index 6507ea2..68e0a90 100755 (executable)
@@ -1,6 +1,6 @@
 #! /usr/bin/make -f
 
-DH_OPTIONS              = --parallel -Bdebian/build
+DH_OPTIONS              = --parallel --without=autoreconf -Bdebian/build
 
 ###--------------------------------------------------------------------------
 ### Configuration.
@@ -9,6 +9,7 @@ OVERRIDES               += auto_configure
 dh_auto_configure_OPTS  = --
 
 ## Various files and directories.
+dh_auto_configure_OPTS += --libdir="\$${prefix}/lib"
 dh_auto_configure_OPTS += --libexecdir="\$${libdir}/tripe"
 dh_auto_configure_OPTS += --with-configdir="/etc/tripe"
 dh_auto_configure_OPTS += --with-socketdir="/var/run"
@@ -28,9 +29,6 @@ dh-gencontrol-hook::
                sed -n 's/^Version: */tripe:Wireshark-Version=/p' \
                >> debian/tripe-wireshark.substvars
 
-OVERRIDES              += shlibdeps
-dh_shlibdeps_OPTS      += -Xwireshark/plugins
-
 ###--------------------------------------------------------------------------
 ### The startup script and related machinery.
 
index d6a2b21..d0c322c 100644 (file)
@@ -6,7 +6,7 @@ debian/tmp/usr/share/man/man7/tripe-service.7tripe
 debian/tmp/usr/share/man/man8/tripe.8tripe
 debian/tmp/usr/lib/tripe/tripe-privhelper
 debian/tmp/usr/share/man/man8/tripe-privhelper.8tripe
-debian/tmp/usr/lib/pkgconfig/tripe.pc
+debian/tmp/usr/share/pkgconfig/tripe.pc
 
 debian/build/contrib/tripe-ipif                /usr/share/doc/tripe/examples
 debian/build/contrib/ipif-peers                /usr/share/doc/tripe/examples
index 58af5d4..469d465 100644 (file)
@@ -9,19 +9,18 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 include $(top_srcdir)/vars.am
 
index 2c6b26b..5606dd0 100644 (file)
@@ -9,19 +9,18 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 include $(top_srcdir)/vars.am
 
index 94d4463..f94909d 100644 (file)
@@ -9,19 +9,18 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 AT_SETUP([key management])
 AT_KEYWORDS([keys python])
index 160e5be..bedea9c 100644 (file)
@@ -9,19 +9,18 @@
 .\"
 .\" This file is part of Trivial IP Encryption (TrIPE).
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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.
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\" along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 .
 .\"--------------------------------------------------------------------------
 .so ../common/defs.man \" @@@PRE@@@
@@ -240,7 +239,7 @@ TrIPE tunnel interface, given that the
 .I path-mtu
 between two peers is as specified.  The default is 1500, which is very
 commonly correct, but you should check using a tool such as
-.BR tracepath (8).
+.BR pathmtu (1).
 Getting the MTU too big will lead to unnecessary fragmentation of
 TrIPE's UDP datagrams; getting it too small will fail to utilize the
 underlying network effectively.  If in doubt, it's therefore better to
index 4fc0485..ce40baf 100644 (file)
@@ -9,19 +9,18 @@
 .\"
 .\" This file is part of Trivial IP Encryption (TrIPE).
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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.
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\" along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 .
 .\"--------------------------------------------------------------------------
 .so ../common/defs.man \" @@@PRE@@@
@@ -335,7 +334,7 @@ sig sig-genalg
 _
 kcdsa  dh
 dsa    dsa
-rsapcs1        rsa
+rsapkcs1       rsa
 rsapss rsa
 ecdsa  ec
 eckcdsa        ec
index cfa740b..4ec89e9 100644 (file)
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 ###--------------------------------------------------------------------------
 ### External dependencies.
@@ -167,7 +166,7 @@ def run(args):
   SYS.stdout.flush()
   rc = OS.spawnvp(OS.P_WAIT, args[0], args)
   if rc != 0:
-    raise SubprocessError, rc
+    raise SubprocessError(rc)
 
 def hexhyphens(bytes):
   """
@@ -218,7 +217,7 @@ def conf_read(f):
     if line[-1] == '\n': line = line[:-1]
     match = rx_keyval.match(line)
     if not match:
-      raise ConfigFileError, "%s:%d: bad line `%s'" % (f, lno, line)
+      raise ConfigFileError("%s:%d: bad line `%s'" % (f, lno, line))
     k, v = match.groups()
     conf[k] = conf_subst(v)
 
@@ -483,14 +482,14 @@ def cmd_update(args):
     OS.mkdir('tmp')
     OS.chdir('tmp')
     seq = int(conf['master-sequence'])
-    run('curl -s -o tripe-keys.tar.gz ${repos-url}')
-    run('curl -s -o tripe-keys.sig %s' % seqsubst('sig-url', seq))
+    run('curl -sL -o tripe-keys.tar.gz ${repos-url}')
+    run('curl -sL -o tripe-keys.sig %s' % seqsubst('sig-url', seq))
     run('tar xfz tripe-keys.tar.gz')
 
     ## Verify the signature
     want = C.bytes(rx_nonalpha.sub('', conf['hk-master']))
     got = fingerprint('repos/master.pub', 'master-%d' % seq)
-    if want != got: raise VerifyError
+    if want != got: raise VerifyError()
     run('''catsign -krepos/master.pub verify -avC -kmaster-%d
       -t${sig-fresh} tripe-keys.sig tripe-keys.tar.gz''' % seq)
 
index 8e16524..4fe35f3 100644 (file)
@@ -23,7 +23,7 @@
 
 ## Key-generation parameters for key exchange group.
 # kx-param = -LS -b3072 -B256
-# kx-param = -Pnist-p256
+# kx-param = -Cnist-p256
 # kx-param =
 
 ## Expiry time for peer key-exchange keys.
@@ -45,6 +45,7 @@
 
 ## How recently an archive must have been signed to be valid.
 # sig-fresh = always
+# sig-fresh = 28 days ago
 
 ## When the master signing key expires.
 # sig-expire = forever
index 42930a6..98574e1 100755 (executable)
 .\\"\
 .\\" This file is part of Trivial IP Encryption (TrIPE).\
 .\\"\
-.\\" TrIPE 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.\
+.\\" TrIPE 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.\
 .\\"\
-.\\" TrIPE 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.\
+.\\" TrIPE 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 TrIPE; if not, write to the Free Software Foundation,\
-.\\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.\
+.\\" along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.\
 .\
 .\\"--------------------------------------------------------------------------\
 .so ../common/defs.man \\"@@@PRE@@@\
index d2a92e0..dddc6ce 100644 (file)
@@ -9,19 +9,18 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 include $(top_srcdir)/vars.am
 
index c87a424..489d250 100644 (file)
@@ -9,20 +9,19 @@
 .\"
 .\" This file is part of Trivial IP Encryption (TrIPE).
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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.
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-.
+.\" along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
+x.
 .\"--------------------------------------------------------------------------
 .so ../common/defs.man \" @@@PRE@@@
 .
index 2462fff..11ee6dc 100644 (file)
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 ###--------------------------------------------------------------------------
 ### Dependencies.
@@ -41,7 +40,7 @@ import re as RX
 from cStringIO import StringIO
 
 try:
-  if OS.getenv('TRIPEMON_FORCE_GI'): raise ImportError
+  if OS.getenv('TRIPEMON_FORCE_GI'): raise ImportError()
   import pygtk
   pygtk.require('2.0')
   import gtk as G
@@ -324,13 +323,19 @@ class Peer (MonitorObject):
 
   def _setaddr(me, addr):
     """Set the peer's address."""
-    if addr[0] == 'INET':
-      ipaddr, port = addr[1:]
+    if addr[0] in ['INET', 'INET6']:
+      af, ipaddr, port = addr
       try:
-        name = S.gethostbyaddr(ipaddr)[0]
-        me.addr = 'INET %s:%s [%s]' % (name, port, ipaddr)
-      except S.herror:
-        me.addr = 'INET %s:%s' % (ipaddr, port)
+        name, _ = S.getnameinfo((ipaddr, int(port)),
+                                S.NI_NUMERICSERV | S.NI_NAMEREQD)
+      except S.gaierror:
+        me.addr = '%s %s%s%s:%s' % (af,
+                                    af == 'INET6' and '[' or '',
+                                    ipaddr,
+                                    af == 'INET6' and ']' or '',
+                                    port)
+      else:
+        me.addr = '%s %s:%s [%s]' % (af, name, port, ipaddr)
     else:
       me.addr = ' '.join(addr)
 
@@ -774,7 +779,7 @@ class ValidatingEntry (G.Entry):
     ValidationError.
     """
     if not me.validp:
-      raise ValidationError
+      raise ValidationError()
     return G.Entry.get_text(me)
 
 def numericvalidate(min = None, max = None):
@@ -791,19 +796,19 @@ def numericvalidate(min = None, max = None):
 ###--------------------------------------------------------------------------
 ### Various minor dialog boxen.
 
-GPL = """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.
+GPL = """\
+TrIPE 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.
+TrIPE 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."""
+along with TrIPE.  If not, see <https://www.gnu.org/licenses/>."""
 
 class AboutBox (G.AboutDialog, TrivialWindowMixin):
   """The program `About' box."""
@@ -1043,6 +1048,8 @@ class AddPeerDialog (MyDialog):
     * e_name, e_addr, e_port, c_keepalive, l_tunnel: widgets in the dialog
   """
 
+  AFS = ['ANY', 'INET', 'INET6']
+
   def __init__(me):
     """Initialize the dialogue."""
     MyDialog.__init__(me, 'Add peer',
@@ -1056,10 +1063,15 @@ class AddPeerDialog (MyDialog):
     table = GridPacker()
     me.vbox.pack_start(table, True, True, 0)
     me.e_name = table.labelled('Name',
-                               ValidatingEntry(r'^[^\s.:]+$', '', 16),
+                               ValidatingEntry(r'^[^\s:]+$', '', 16),
                                width = 3)
+    me.l_af = table.labelled('Family', combo_box_text(),
+                             newlinep = True, width = 3)
+    for af in me.AFS:
+      me.l_af.append_text(af)
+    me.l_af.set_active(0)
     me.e_addr = table.labelled('Address',
-                               ValidatingEntry(r'^[a-zA-Z0-9.-]+$', '', 24),
+                               ValidatingEntry(r'^[a-zA-Z0-9.-:]+$', '', 24),
                                newlinep = True)
     me.e_port = table.labelled('Port',
                                ValidatingEntry(numericvalidate(0, 65535),
@@ -1067,7 +1079,7 @@ class AddPeerDialog (MyDialog):
                                                5))
     me.l_tunnel = table.labelled('Tunnel', combo_box_text(),
                                  newlinep = True, width = 3)
-    me.tuns = conn.tunnels()
+    me.tuns = ['(Default)'] + conn.tunnels()
     for t in me.tuns:
       me.l_tunnel.append_text(t)
     me.l_tunnel.set_active(0)
@@ -1076,29 +1088,34 @@ class AddPeerDialog (MyDialog):
       tickybox.connect('toggled',
                        lambda t: target.set_sensitive (t.get_active()))
 
-    me.c_keepalive = G.CheckButton('Keepalives')
-    table.pack(me.c_keepalive, newlinep = True, xopt = G.FILL)
-    me.e_keepalive = ValidatingEntry(r'^\d+[hms]?$', '', 5)
-    me.e_keepalive.set_sensitive(False)
-    tickybox_sensitivity(me.c_keepalive, me.e_keepalive)
-    table.pack(me.e_keepalive, width = 3)
+    def optional_entry(label, rx_valid, width):
+      c = G.CheckButton(label)
+      table.pack(c, newlinep = True, xopt = G.FILL)
+      e = ValidatingEntry(rx_valid, '', width)
+      e.set_sensitive(False)
+      tickybox_sensitivity(c, e)
+      table.pack(e, width = 3)
+      return c, e
+
+    me.c_keepalive, me.e_keepalive = \
+      optional_entry('Keepalives', r'^\d+[hms]?$', 5)
+
+    me.c_cork = G.CheckButton('Cork')
+    table.pack(me.c_cork, newlinep = True, width = 4, xopt = G.FILL)
 
     me.c_mobile = G.CheckButton('Mobile')
     table.pack(me.c_mobile, newlinep = True, width = 4, xopt = G.FILL)
 
-    me.c_peerkey = G.CheckButton('Peer key tag')
-    table.pack(me.c_peerkey, newlinep = True, xopt = G.FILL)
-    me.e_peerkey = ValidatingEntry(r'^[^.:\s]+$', '', 16)
-    me.e_peerkey.set_sensitive(False)
-    tickybox_sensitivity(me.c_peerkey, me.e_peerkey)
-    table.pack(me.e_peerkey, width = 3)
+    me.c_ephem = G.CheckButton('Ephemeral')
+    table.pack(me.c_ephem, newlinep = True, width = 4, xopt = G.FILL)
+
+    me.c_peerkey, me.e_peerkey = \
+      optional_entry('Peer key tag', r'^[^.:\s]+$', 16)
+    me.c_privkey, me.e_privkey = \
+      optional_entry('Private key tag', r'^[^.:\s]+$', 16)
 
-    me.c_privkey = G.CheckButton('Private key tag')
-    table.pack(me.c_privkey, newlinep = True, xopt = G.FILL)
-    me.e_privkey = ValidatingEntry(r'^[^.:\s]+$', '', 16)
-    me.e_privkey.set_sensitive(False)
-    tickybox_sensitivity(me.c_privkey, me.e_privkey)
-    table.pack(me.e_privkey, width = 3)
+    me.c_knock, me.e_knock = \
+      optional_entry('Knock string', r'^[^:\s]+$', 16)
 
     me.show_all()
 
@@ -1106,16 +1123,23 @@ class AddPeerDialog (MyDialog):
     """Handle an OK press: create the peer."""
     try:
       t = me.l_tunnel.get_active()
+      afix = me.l_af.get_active()
       me._addpeer(me.e_name.get_text(),
+                  me.AFS[afix],
                   me.e_addr.get_text(),
                   me.e_port.get_text(),
                   keepalive = (me.c_keepalive.get_active() and
                                me.e_keepalive.get_text() or None),
                   tunnel = t and me.tuns[t] or None,
+                  cork = me.c_cork.get_active() or None,
+                  mobile = me.c_mobile.get_active() or None,
+                  ephemeral = me.c_ephem.get_active() or None,
                   key = (me.c_peerkey.get_active() and
                          me.e_peerkey.get_text() or None),
                   priv = (me.c_privkey.get_active() and
-                          me.e_privkey.get_text() or None))
+                          me.e_privkey.get_text() or None),
+                  knock = (me.c_knock.get_active() and
+                           me.e_knock.get_text() or None))
     except ValidationError:
       GDK.beep()
       return
@@ -1230,7 +1254,7 @@ def xlate_time(t):
   return '%04d:%02d:%02d %02d:%02d:%02d (%.1f %s ago)' % \
          (YY, MM, DD, hh, mm, ss, ago, unit)
 def xlate_bytes(b):
-  """Translate a number of bytes into something a human might want to read."""
+  """Translate a raw byte count into something a human might want to read."""
   suff = 'B'
   b = int(b)
   for s in 'KMG':
@@ -1252,21 +1276,40 @@ statsxlate = \
    ('ip-bytes-in', xlate_bytes),
    ('ip-bytes-out', xlate_bytes)]
 
+def format_stat(format, dict):
+  if callable(format): return format(dict)
+  else: return format % dict
+
 ## How to lay out the stats dialog.  Format is (LABEL, FORMAT): LABEL is
 ## the label to give the entry box; FORMAT is the format string to write into
 ## the entry.
-statslayout = \
-  [('Start time', '%(start-time)s'),
-   ('Private key', '%(current-key)s'),
-   ('Diffie-Hellman group',
+cryptolayout = \
+  [('Diffie-Hellman group',
     '%(kx-group)s '
     '(%(kx-group-order-bits)s-bit order, '
     '%(kx-group-elt-bits)s-bit elements)'),
-   ('Cipher',
-    '%(cipher)s (%(cipher-keysz)s-bit key, %(cipher-blksz)s-bit block)'),
-   ('Mac', '%(mac)s (%(mac-keysz)s-bit key, %(mac-tagsz)s-bit tag)'),
-   ('Hash', '%(hash)s (%(hash-sz)s-bit output)'),
-   ('Last key-exchange', '%(last-keyexch-time)s'),
+   ('Bulk crypto transform',
+    '%(bulk-transform)s (%(bulk-overhead)s byte overhead)'),
+   ('Data encryption', lambda d: '%s (%s; %s)' % (
+     d['cipher'],
+     '%d-bit key' % (8*int(d['cipher-keysz'])),
+     d.get('cipher-blksz', '0') == '0'
+       and 'stream cipher'
+       or '%d-bit block' % (8*int(d['cipher-blksz'])))),
+   ('Message authentication', lambda d: '%s (%s; %s)' % (
+     d['mac'],
+     d.get('mac-keysz') is None
+       and 'one-time MAC'
+       or '%d-bit key' % (8*int(d['mac-keysz'])),
+     '%d-bit tag' % (8*int(d['mac-tagsz'])))),
+   ('Hash', lambda d: '%s (%d-bit output)' %
+    (d['hash'], 8*int(d['hash-sz'])))]
+
+statslayout = \
+  [('Start time', '%(start-time)s'),
+   ('Private key', '%(current-key)s')] + \
+  cryptolayout + \
+  [('Last key-exchange', '%(last-keyexch-time)s'),
    ('Last packet', '%(last-packet-time)s'),
    ('Packets in/out',
     '%(packets-in)s (%(bytes-in)s) / %(packets-out)s (%(bytes-out)s)'),
@@ -1341,6 +1384,7 @@ class PeerWindow (TrivialWindow):
   def change(me):
     """Update the display in response to a notification."""
     me.e['Interface'].set_text(me.peer.ifname)
+    me.e['Address'].set_text(me.peer.addr)
 
   def _update(me):
     """
@@ -1355,7 +1399,7 @@ class PeerWindow (TrivialWindow):
         stat[s] = trans(stat[s])
       stat.update(me.peer.__dict__)
       for label, format in statslayout:
-        me.e[label].set_text(format % stat)
+        me.e[label].set_text(format_stat(format, stat))
       GL.timeout_add(1000, lambda: me.cr.switch() and False)
       me.cr.parent.switch()
     me.cr = None
@@ -1400,31 +1444,11 @@ class CryptoInfo (TrivialWindow):
     me.add(table)
 
     crypto = conn.algs()
-    table.info('Diffie-Hellman group',
-               '%s (%d-bit order, %d-bit elements)' %
-               (crypto['kx-group'],
-                int(crypto['kx-group-order-bits']),
-                int(crypto['kx-group-elt-bits'])),
-               len = 32)
-    table.info('Data encryption',
-               '%s (%d-bit key; %s)' %
-               (crypto['cipher'],
-                int(crypto['cipher-keysz']) * 8,
-                crypto['cipher-blksz'] == '0'
-                  and 'stream cipher'
-                  or '%d-bit block' % (int(crypto['cipher-blksz']) * 8)),
-               newlinep = True)
-    table.info('Message authentication',
-               '%s (%d-bit key; %d-bit tag)' %
-               (crypto['mac'],
-                int(crypto['mac-keysz']) * 8,
-                int(crypto['mac-tagsz']) * 8),
-               newlinep = True)
-    table.info('Hash function',
-               '%s (%d-bit output)' %
-               (crypto['hash'],
-                int(crypto['hash-sz']) * 8),
-               newlinep = True)
+    firstp = True
+    for label, format in cryptolayout:
+      table.info(label, format_stat(format, crypto),
+                 len = 42, newlinep = not firstp)
+      firstp = False
 
     me.show_all()
 
@@ -1612,6 +1636,7 @@ class MonitorWindow (MyWindow):
                                   '???', 'green', '???', 'green'])
     peer.win = WindowSlot(lambda: PeerWindow(peer))
     me.hook(peer.pinghook, me._ping)
+    me.hook(peer.changehook, lambda: me._change(peer))
     me.apchange()
 
   def delpeer(me, peer):
@@ -1752,6 +1777,10 @@ class MonitorWindow (MyWindow):
       me.listmodel[p.i][textcol] = '%.1f ms' % ps.tlast
       me.listmodel[p.i][colourcol] = 'black'
 
+  def _change(me, p):
+    """Hook: notified when the peer changes state."""
+    me.listmodel[p.i][1] = p.addr
+
   def setstatus(me, status):
     """Update the message in the status bar."""
     me.status.pop(0)
index 06af053..76a30f9 100644 (file)
@@ -9,19 +9,18 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 include $(top_srcdir)/vars.am
 
index a1eedf7..32fe121 100644 (file)
@@ -9,19 +9,18 @@
 .\"
 .\" This file is part of Trivial IP Encryption (TrIPE).
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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.
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\" along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 .
 .\"--------------------------------------------------------------------------
 .so ../common/defs.man \" @@@PRE@@@
@@ -38,6 +37,7 @@ pathmtu \- discover path MTU to a given host
 .SH "SYNOPSIS"
 .
 .B pathmtu
+.RB [ \-46v ]
 .RB [ \-H
 .IR header ]
 .RB [ \-m
@@ -105,13 +105,19 @@ Command-line options are as follows.
 Writes a brief description of the command-line options available to
 standard output and exits with status 0.
 .TP
-.B "\-v, \-\-version"
+.B "\-V, \-\-version"
 Writes tripe's version number to standard output and exits with status
 0.
 .TP
 .B "\-u, \-\-usage"
 Writes a brief usage summary to standard output and exits with status 0.
 .TP
+.B "\-4"
+Look up hostnames only as IPv4 addresses.
+.TP
+.B "\-6"
+Look up hostnames only as IPv6 addresses.
+.TP
 .BI "\-g, \-\-growth=" factor
 Sets the retransmit interval growth factor.  Each time a packet is
 retransmitted,
@@ -157,6 +163,12 @@ assumes that the timeout means that the remote host
 .I did
 receive the packet.  The default timeout is 8 seconds.
 .TP
+.B "\-v, \-\-verbose"
+Write a running human-readable commentary to standard error about the
+progress of the operation.  Usually,
+.B pathmtu
+does its job silently unless there are errors.
+.TP
 .BI "\-H, \-\-header=" header
 Sets the packet header, in hexadecimal.  If you set an explicit port
 number, it may be worth setting the packet header too, so as not to
index 9516cd2..9c5c829 100644 (file)
@@ -9,27 +9,22 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 /*----- Header files ------------------------------------------------------*/
 
-#if defined(linux)
-#  define _BSD_SOURCE
-#endif
-
 #include "config.h"
 
 #include <assert.h>
 #include <netinet/in_systm.h>
 #include <netinet/ip.h>
 #include <netinet/ip_icmp.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
 #include <netinet/udp.h>
 
-#include <net/if.h>
-#include <ifaddrs.h>
-#include <sys/ioctl.h>
+#ifdef HAVE_GETIFADDRS
+#  include <net/if.h>
+#  include <ifaddrs.h>
+#  include <sys/ioctl.h>
+#endif
 
 #include <mLib/alloc.h>
 #include <mLib/bits.h>
@@ -110,6 +109,31 @@ static double s2f(const char *s, const char *what)
 static void f2tv(struct timeval *tv, double t)
   { tv->tv_sec = t; tv->tv_usec = (t - tv->tv_sec)*MILLION; }
 
+union addr {
+  struct sockaddr sa;
+  struct sockaddr_in sin;
+  struct sockaddr_in6 sin6;
+};
+
+/* Check whether an address family is even slightly supported. */
+static int addrfamok(int af)
+{
+  switch (af) {
+    case AF_INET: case AF_INET6: return (1);
+    default: return (0);
+  }
+}
+
+/* Return the size of a socket address. */
+static size_t addrsz(const union addr *a)
+{
+  switch (a->sa.sa_family) {
+    case AF_INET: return (sizeof(a->sin));
+    case AF_INET6: return (sizeof(a->sin6));
+    default: abort();
+  }
+}
+
 /*----- Main algorithm skeleton -------------------------------------------*/
 
 struct param {
@@ -120,7 +144,7 @@ struct param {
   double timeout;                      /* Retransmission timeout */
   int seqoff;                          /* Offset to write sequence number */
   const struct probe_ops *pops;                /* Probe algorithm description */
-  struct sockaddr_in sin;              /* Destination address */
+  union addr a;                                /* Destination address */
 };
 
 struct probestate {
@@ -227,12 +251,19 @@ static int pathmtu(const struct param *pp)
   /* Build and connect a UDP socket.  We'll need this to know the local port
    * number to use if nothing else.  Set other stuff up.
    */
-  if ((sk = socket(PF_INET, SOCK_DGRAM, 0)) < 0) goto fail_0;
-  if (connect(sk, (struct sockaddr *)&pp->sin, sizeof(pp->sin))) goto fail_1;
+  if ((sk = socket(pp->a.sa.sa_family, SOCK_DGRAM, IPPROTO_UDP)) < 0)
+    goto fail_0;
+  if (connect(sk, &pp->a.sa, addrsz(&pp->a))) goto fail_1;
   st = xmalloc(pp->pops->statesz);
   if ((mtu = pp->pops->setup(st, sk, pp)) < 0) goto fail_2;
   ps.pp = pp; ps.q = rand() & 0xffff;
-  lo = 576; hi = mtu;
+  switch (pp->a.sa.sa_family) {
+    case AF_INET: lo = 576; break;
+    case AF_INET6: lo = 1280; break;
+    default: abort();
+  }
+  hi = mtu;
+  if (hi < lo) { errno = EMSGSIZE; return (-1); }
 
   /* And now we do a thing which is sort of like a binary search, except that
    * we also take explicit clues as establishing a new upper bound, and we
@@ -357,8 +388,10 @@ fail_0:
 
 /*----- Doing it the hard way ---------------------------------------------*/
 
+#ifdef HAVE_GETIFADDRS
+
 #if defined(linux) || defined(__OpenBSD__)
-#define IPHDR_SANE
+#  define IPHDR_SANE
 #endif
 
 #ifdef IPHDR_SANE
@@ -370,9 +403,27 @@ fail_0:
 #endif
 
 static int rawicmp = -1, rawudp = -1, rawerr = 0;
+static int rawicmp6 = -1, rawudp6 = -1, rawerr6 = 0;
 
 #define IPCK_INIT 0xffff
 
+/* Compare two addresses.  Maybe compare the port numbers too. */
+#define AEF_PORT 1u
+static int addreq(const union addr *a, const union addr *b, unsigned f)
+{
+  switch (a->sa.sa_family) {
+    case AF_INET:
+      return (a->sin.sin_addr.s_addr == b->sin.sin_addr.s_addr &&
+             (!(f&AEF_PORT) || a->sin.sin_port == b->sin.sin_port));
+    case AF_INET6:
+      return (!memcmp(a->sin6.sin6_addr.s6_addr,
+                     b->sin6.sin6_addr.s6_addr, 16) &&
+             (!(f&AEF_PORT) || a->sin6.sin6_port == b->sin6.sin6_port));
+    default:
+      abort();
+  }
+}
+
 /* Compute an IP checksum over some data.  This is a restartable interface:
  * initialize A to `IPCK_INIT' for the first call.
  */
@@ -390,13 +441,19 @@ static unsigned ipcksum(const void *buf, size_t n, unsigned a)
 /* TCP/UDP pseudoheader structure. */
 struct phdr {
   struct in_addr ph_src, ph_dst;
-  u_char ph_z, ph_p;
-  u_short ph_len;
+  uint8_t ph_z, ph_p;
+  uint16_t ph_len;
+};
+struct phdr6 {
+  struct in6_addr ph6_src, ph6_dst;
+  uint32_t ph6_len;
+  uint8_t ph6_z0, ph6_z1, ph6_z2, ph6_nxt;
 };
 
 struct raw_state {
-  struct sockaddr_in me, sin;
+  union addr me, a;
   int sk, rawicmp, rawudp;
+  uint16_t srcport, dstport;
   unsigned q;
 };
 
@@ -407,20 +464,60 @@ static int raw_setup(void *stv, int sk, const struct param *pp)
   int i, mtu = -1;
   struct ifaddrs *ifa, *ifaa, *ifap;
   struct ifreq ifr;
+  struct icmp6_filter f6;
 
-  /* If we couldn't acquire raw sockets, we fail here. */
-  if (rawerr) { errno = rawerr; goto fail_0; }
-  st->rawicmp = rawicmp; st->rawudp = rawudp; st->sk = sk;
+  /* Check that the address is OK, and that we have the necessary raw
+   * sockets.
+   *
+   * For IPv6, also set the filter so we don't get too many useless wakeups.
+   */
+  switch (pp->a.sa.sa_family) {
+    case AF_INET:
+      if (rawerr) { errno = rawerr; goto fail_0; }
+      st->rawicmp = rawicmp; st->rawudp = rawudp; st->sk = sk;
+      /* IPv4 filtering is available on Linux but isn't portable. */
+      break;
+    case AF_INET6:
+      if (rawerr6) { errno = rawerr6; goto fail_0; }
+      st->rawicmp = rawicmp6; st->rawudp = rawudp6; st->sk = sk;
+      ICMP6_FILTER_SETBLOCKALL(&f6);
+      ICMP6_FILTER_SETPASS(ICMP6_PACKET_TOO_BIG, &f6);
+      ICMP6_FILTER_SETPASS(ICMP6_DST_UNREACH, &f6);
+      if (setsockopt(st->rawicmp, IPPROTO_ICMPV6, ICMP6_FILTER,
+                    &f6, sizeof(f6))) {
+       die(EXIT_FAILURE, "failed to set icmpv6 filter: %s",
+           strerror(errno));
+      }
+      break;
+    default:
+      errno = EPFNOSUPPORT; goto fail_0;
+  }
 
   /* Initialize the sequence number. */
   st->q = rand() & 0xffff;
 
   /* Snaffle the local and remote address and port number. */
-  st->sin = pp->sin;
+  st->a = pp->a;
   sz = sizeof(st->me);
-  if (getsockname(sk, (struct sockaddr *)&st->me, &sz))
+  if (getsockname(sk, &st->me.sa, &sz))
     goto fail_0;
 
+  /* Only now do some fiddling because Linux doesn't like port numbers in
+   * IPv6 raw destination addresses...
+   */
+  switch (pp->a.sa.sa_family) {
+    case AF_INET:
+      st->srcport = st->me.sin.sin_port; st->me.sin.sin_port = 0;
+      st->dstport =  st->a.sin.sin_port;  st->a.sin.sin_port = 0;
+      break;
+    case AF_INET6:
+      st->srcport = st->me.sin6.sin6_port; st->me.sin6.sin6_port = 0;
+      st->dstport =  st->a.sin6.sin6_port;  st->a.sin6.sin6_port = 0;
+      break;
+    default:
+      abort();
+  }
+
   /* There isn't a portable way to force the DF flag onto a packet through
    * UDP, or even through raw IP, unless we write the entire IP header
    * ourselves.  This is somewhat annoying, especially since we have an
@@ -439,10 +536,9 @@ static int raw_setup(void *stv, int sk, const struct param *pp)
   for (i = 0; i < 2; i++) {
     for (ifap = 0, ifa = ifaa; ifa; ifa = ifa->ifa_next) {
       if (!(ifa->ifa_flags & IFF_UP) || !ifa->ifa_addr ||
-         ifa->ifa_addr->sa_family != AF_INET ||
+         ifa->ifa_addr->sa_family != st->me.sa.sa_family ||
          (i == 0 &&
-          ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr !=
-               st->me.sin_addr.s_addr) ||
+          !addreq((union addr *)ifa->ifa_addr, &st->me, 0)) ||
          (i == 1 && ifap && strcmp(ifap->ifa_name, ifa->ifa_name) == 0) ||
          strlen(ifa->ifa_name) >= sizeof(ifr.ifr_name))
        continue;
@@ -475,51 +571,97 @@ static int raw_xmit(void *stv, int mtu)
   struct raw_state *st = stv;
   unsigned char b[65536], *p;
   struct ip *ip;
+  struct ip6_hdr *ip6;
   struct udphdr *udp;
   struct phdr ph;
+  struct phdr6 ph6;
   unsigned ck;
 
-  /* Build the IP header. */
-  ip = (struct ip *)b;
-  ip->ip_v = 4;
-  ip->ip_hl = sizeof(*ip)/4;
-  ip->ip_tos = IPTOS_RELIABILITY;
-  ip->ip_len = sane_htons(mtu);
-  STEP(st->q); ip->ip_id = htons(st->q);
-  ip->ip_off = sane_htons(0 | IP_DF);
-  ip->ip_ttl = 64;
-  ip->ip_p = IPPROTO_UDP;
-  ip->ip_sum = 0;
-  ip->ip_src = st->me.sin_addr;
-  ip->ip_dst = st->sin.sin_addr;
-
-  /* Build a UDP packet in the output buffer. */
-  udp = (struct udphdr *)(ip + 1);
-  udp->uh_sport = st->me.sin_port;
-  udp->uh_dport = st->sin.sin_port;
-  udp->uh_ulen = htons(mtu - sizeof(*ip));
-  udp->uh_sum = 0;
-
-  /* Copy the payload. */
-  p = (unsigned char *)(udp + 1);
-  memcpy(p, buf, mtu - (p - b));
-
-  /* Calculate the UDP checksum. */
-  ph.ph_src = ip->ip_src;
-  ph.ph_dst = ip->ip_dst;
-  ph.ph_z = 0;
-  ph.ph_p = IPPROTO_UDP;
-  ph.ph_len = udp->uh_ulen;
-  ck = IPCK_INIT;
-  ck = ipcksum(&ph, sizeof(ph), ck);
-  ck = ipcksum(udp, mtu - sizeof(*ip), ck);
-  udp->uh_sum = htons(ck);
+  switch (st->a.sa.sa_family) {
+
+    case AF_INET:
+
+      /* Build the IP header. */
+      ip = (struct ip *)b;
+      ip->ip_v = 4;
+      ip->ip_hl = sizeof(*ip)/4;
+      ip->ip_tos = IPTOS_RELIABILITY;
+      ip->ip_len = sane_htons(mtu);
+      STEP(st->q); ip->ip_id = htons(st->q);
+      ip->ip_off = sane_htons(0 | IP_DF);
+      ip->ip_ttl = 64;
+      ip->ip_p = IPPROTO_UDP;
+      ip->ip_sum = 0;
+      ip->ip_src = st->me.sin.sin_addr;
+      ip->ip_dst = st->a.sin.sin_addr;
+
+      /* Build a UDP packet in the output buffer. */
+      udp = (struct udphdr *)(ip + 1);
+      udp->uh_sport = st->srcport;
+      udp->uh_dport = st->dstport;
+      udp->uh_ulen = htons(mtu - sizeof(*ip));
+      udp->uh_sum = 0;
+
+      /* Copy the payload. */
+      p = (unsigned char *)(udp + 1);
+      memcpy(p, buf, mtu - (p - b));
+
+      /* Calculate the UDP checksum. */
+      ph.ph_src = ip->ip_src;
+      ph.ph_dst = ip->ip_dst;
+      ph.ph_z = 0;
+      ph.ph_p = IPPROTO_UDP;
+      ph.ph_len = udp->uh_ulen;
+      ck = IPCK_INIT;
+      ck = ipcksum(&ph, sizeof(ph), ck);
+      ck = ipcksum(udp, mtu - sizeof(*ip), ck);
+      udp->uh_sum = htons(ck);
+
+      break;
+
+    case AF_INET6:
+
+      /* Build the IP header. */
+      ip6 = (struct ip6_hdr *)b;
+      STEP(st->q); ip6->ip6_flow = htonl(0x60000000 | st->q);
+      ip6->ip6_plen = htons(mtu - sizeof(*ip6));
+      ip6->ip6_nxt = IPPROTO_UDP;
+      ip6->ip6_hlim = 64;
+      ip6->ip6_src = st->me.sin6.sin6_addr;
+      ip6->ip6_dst = st->a.sin6.sin6_addr;
+
+      /* Build a UDP packet in the output buffer. */
+      udp = (struct udphdr *)(ip6 + 1);
+      udp->uh_sport = st->srcport;
+      udp->uh_dport = st->dstport;
+      udp->uh_ulen = htons(mtu - sizeof(*ip6));
+      udp->uh_sum = 0;
+
+      /* Copy the payload. */
+      p = (unsigned char *)(udp + 1);
+      memcpy(p, buf, mtu - (p - b));
+
+      /* Calculate the UDP checksum. */
+      ph6.ph6_src = ip6->ip6_src;
+      ph6.ph6_dst = ip6->ip6_dst;
+      ph6.ph6_len = udp->uh_ulen;
+      ph6.ph6_z0 = ph6.ph6_z1 = ph6.ph6_z2 = 0;
+      ph6.ph6_nxt = IPPROTO_UDP;
+      ck = IPCK_INIT;
+      ck = ipcksum(&ph6, sizeof(ph6), ck);
+      ck = ipcksum(udp, mtu - sizeof(*ip6), ck);
+      udp->uh_sum = htons(ck);
+
+      break;
+
+    default:
+      abort();
+  }
 
   /* Send the whole thing off.  If we're too big for the interface then we
    * might need to trim immediately.
    */
-  if (sendto(st->rawudp, b, mtu, 0,
-            (struct sockaddr *)&st->sin, sizeof(st->sin)) < 0) {
+  if (sendto(st->rawudp, b, mtu, 0, &st->a.sa, addrsz(&st->a)) < 0) {
     if (errno == EMSGSIZE) return (RC_LOWER);
     else goto fail_0;
   }
@@ -536,45 +678,100 @@ static int raw_selproc(void *stv, fd_set *fd_in, struct probestate *ps)
   struct raw_state *st = stv;
   unsigned char b[65536];
   struct ip *ip;
+  struct ip6_hdr *ip6;
   struct icmp *icmp;
+  struct icmp6_hdr *icmp6;
   struct udphdr *udp;
+  const unsigned char *payload;
   ssize_t n;
 
   /* An ICMP packet: see what's inside. */
   if (FD_ISSET(st->rawicmp, fd_in)) {
     if ((n = read(st->rawicmp, b, sizeof(b))) < 0) goto fail_0;
 
-    ip = (struct ip *)b;
-    if (n < sizeof(*ip) || n < sizeof(4*ip->ip_hl) ||
-       ip->ip_v != 4 || ip->ip_p != IPPROTO_ICMP)
-      goto skip_icmp;
-    n -= sizeof(4*ip->ip_hl);
-
-    icmp = (struct icmp *)(b + 4*ip->ip_hl);
-    if (n < sizeof(*icmp) || icmp->icmp_type != ICMP_UNREACH)
-      goto skip_icmp;
-    n -= offsetof(struct icmp, icmp_ip);
-
-    ip = &icmp->icmp_ip;
-    if (n < sizeof(*ip) ||
-       ip->ip_p != IPPROTO_UDP || ip->ip_hl != sizeof(*ip)/4 ||
-       ip->ip_id != htons(st->q) ||
-       ip->ip_src.s_addr != st->me.sin_addr.s_addr ||
-       ip->ip_dst.s_addr != st->sin.sin_addr.s_addr)
-      goto skip_icmp;
-    n -= sizeof(*ip);
-
-    udp = (struct udphdr *)(ip + 1);
-    if (n < sizeof(udp) || udp->uh_sport != st->me.sin_port ||
-       udp->uh_dport != st->sin.sin_port)
-      goto skip_icmp;
-    n -= sizeof(*udp);
-
-    if (icmp->icmp_code == ICMP_UNREACH_PORT) return (RC_HIGHER);
-    else if (icmp->icmp_code != ICMP_UNREACH_NEEDFRAG) goto skip_icmp;
-    else if (icmp->icmp_nextmtu) return (htons(icmp->icmp_nextmtu));
-    else return (RC_LOWER);
+    switch (st->me.sa.sa_family) {
+
+      case AF_INET:
+
+       ip = (struct ip *)b;
+       if (n < sizeof(*ip) || n < sizeof(4*ip->ip_hl) ||
+           ip->ip_v != 4 || ip->ip_p != IPPROTO_ICMP)
+         goto skip_icmp;
+       n -= sizeof(4*ip->ip_hl);
+
+       icmp = (struct icmp *)(b + 4*ip->ip_hl);
+       if (n < sizeof(*icmp) || icmp->icmp_type != ICMP_UNREACH)
+         goto skip_icmp;
+       n -= offsetof(struct icmp, icmp_ip);
+
+       ip = &icmp->icmp_ip;
+       if (n < sizeof(*ip) ||
+           ip->ip_p != IPPROTO_UDP || ip->ip_hl != sizeof(*ip)/4 ||
+           ip->ip_id != htons(st->q) ||
+           ip->ip_src.s_addr != st->me.sin.sin_addr.s_addr ||
+           ip->ip_dst.s_addr != st->a.sin.sin_addr.s_addr)
+         goto skip_icmp;
+       n -= sizeof(*ip);
+
+       udp = (struct udphdr *)(ip + 1);
+       if (n < sizeof(*udp) || udp->uh_sport != st->srcport ||
+           udp->uh_dport != st->dstport)
+         goto skip_icmp;
+       n -= sizeof(*udp);
+
+       payload = (const unsigned char *)(udp + 1);
+       if (!mypacketp(ps, payload, n)) goto skip_icmp;
+
+       if (icmp->icmp_code == ICMP_UNREACH_PORT) return (RC_HIGHER);
+       else if (icmp->icmp_code != ICMP_UNREACH_NEEDFRAG) goto skip_icmp;
+       else if (icmp->icmp_nextmtu) return (htons(icmp->icmp_nextmtu));
+       else return (RC_LOWER);
+
+       break;
+
+      case AF_INET6:
+       icmp6 = (struct icmp6_hdr *)b;
+       if (n < sizeof(*icmp6) ||
+           (icmp6->icmp6_type != ICMP6_PACKET_TOO_BIG &&
+            icmp6->icmp6_type != ICMP6_DST_UNREACH))
+         goto skip_icmp;
+       n -= sizeof(*icmp6);
+
+       ip6 = (struct ip6_hdr *)(icmp6 + 1);
+       if (n < sizeof(*ip6) || ip6->ip6_nxt != IPPROTO_UDP ||
+           memcmp(ip6->ip6_src.s6_addr,
+                  st->me.sin6.sin6_addr.s6_addr, 16) ||
+           memcmp(ip6->ip6_dst.s6_addr,
+                  st->a.sin6.sin6_addr.s6_addr, 16) ||
+           (ntohl(ip6->ip6_flow)&0xffff) != st->q)
+         goto skip_icmp;
+       n -= sizeof(*ip6);
+
+       udp = (struct udphdr *)(ip6 + 1);
+       if (n < sizeof(*udp) || udp->uh_sport != st->srcport ||
+           udp->uh_dport != st->dstport)
+         goto skip_icmp;
+       n -= sizeof(*udp);
+
+       payload = (const unsigned char *)(udp + 1);
+       if (!mypacketp(ps, payload, n)) goto skip_icmp;
+
+       if (icmp6->icmp6_type == ICMP6_PACKET_TOO_BIG)
+         return (ntohs(icmp6->icmp6_mtu));
+       else switch (icmp6->icmp6_code) {
+           case ICMP6_DST_UNREACH_ADMIN:
+           case ICMP6_DST_UNREACH_NOPORT:
+             return (RC_HIGHER);
+           default:
+             goto skip_icmp;
+         }
+       break;
+
+      default:
+       abort();
+    }
   }
+
 skip_icmp:;
 
   /* If we got a reply to the current probe then we're good.  If we got an
@@ -601,6 +798,8 @@ static const struct probe_ops raw_ops = {
 #undef OPS_CHAIN
 #define OPS_CHAIN &raw_ops
 
+#endif
+
 /*----- Doing the job on Linux --------------------------------------------*/
 
 #if defined(linux)
@@ -610,7 +809,9 @@ static const struct probe_ops raw_ops = {
 #endif
 
 struct linux_state {
+  int sol, so_mtu_discover, so_mtu;
   int sk;
+  size_t hdrlen;
 };
 
 static int linux_setup(void *stv, int sk, const struct param *pp)
@@ -619,17 +820,36 @@ static int linux_setup(void *stv, int sk, const struct param *pp)
   int i, mtu;
   socklen_t sz;
 
+  /* Check that the address is OK. */
+  switch (pp->a.sa.sa_family) {
+    case AF_INET:
+      st->sol = IPPROTO_IP;
+      st->so_mtu_discover = IP_MTU_DISCOVER;
+      st->so_mtu = IP_MTU;
+      st->hdrlen = 28;
+      break;
+    case AF_INET6:
+      st->sol = IPPROTO_IPV6;
+      st->so_mtu_discover = IPV6_MTU_DISCOVER;
+      st->so_mtu = IPV6_MTU;
+      st->hdrlen = 48;
+      break;
+    default:
+      errno = EPFNOSUPPORT;
+      return (-1);
+  }
+
   /* Snaffle the UDP socket. */
   st->sk = sk;
 
   /* Turn on kernel path-MTU discovery and force DF on. */
   i = IP_PMTUDISC_PROBE;
-  if (setsockopt(st->sk, IPPROTO_IP, IP_MTU_DISCOVER, &i, sizeof(i)))
+  if (setsockopt(st->sk, st->sol, st->so_mtu_discover, &i, sizeof(i)))
     return (-1);
 
   /* Read the initial MTU guess back and report it. */
   sz = sizeof(mtu);
-  if (getsockopt(st->sk, IPPROTO_IP, IP_MTU, &mtu, &sz))
+  if (getsockopt(st->sk, st->sol, st->so_mtu, &mtu, &sz))
     return (-1);
 
   /* Done. */
@@ -646,7 +866,7 @@ static int linux_xmit(void *stv, int mtu)
   struct linux_state *st = stv;
 
   /* Write the packet. */
-  if (write(st->sk, buf, mtu - 28) >= 0) return (RC_OK);
+  if (write(st->sk, buf, mtu - st->hdrlen) >= 0) return (RC_OK);
   else if (errno == EMSGSIZE) return (RC_LOWER);
   else return (RC_FAIL);
 }
@@ -673,7 +893,7 @@ static int linux_selproc(void *stv, fd_set *fd_in, struct probestate *ps)
        errno == ECONNREFUSED || errno == EHOSTUNREACH)
       return (RC_HIGHER);
     sz = sizeof(mtu);
-    if (getsockopt(st->sk, IPPROTO_IP, IP_MTU, &mtu, &sz))
+    if (getsockopt(st->sk, st->sol, st->so_mtu, &mtu, &sz))
       return (RC_FAIL);
     return (mtu);
   }
@@ -700,7 +920,7 @@ static void version(FILE *fp)
 
 static void usage(FILE *fp)
 {
-  pquis(fp, "Usage: $ [-H HEADER] [-m METHOD]\n\
+  pquis(fp, "Usage: $ [-46v] [-H HEADER] [-m METHOD]\n\
         [-r SECS] [-g FACTOR] [-t SECS] HOST [PORT]\n");
 }
 
@@ -716,13 +936,16 @@ static void help(FILE *fp)
 Options in full:\n\
 \n\
 -h, --help             Show this help text.\n\
--v, --version          Show version number.\n\
+-V, --version          Show version number.\n\
 -u, --usage            Show brief usage message.\n\
 \n\
+-4, --ipv4             Restrict to IPv4 only.\n\
+-6, --ipv6             Restrict to IPv6 only.\n\
 -g, --growth=FACTOR    Growth factor for retransmit interval.\n\
 -m, --method=METHOD    Use METHOD to probe for MTU.\n\
 -r, --retransmit=SECS  Retransmit if no reply after SEC.\n\
 -t, --timeout=SECS     Give up expecting a reply after SECS.\n\
+-v, --verbose          Write a running commentary to stderr.\n\
 -H, --header=HEX       Packet header, in hexadecimal.\n\
 \n\
 Probe methods:\n\
@@ -739,30 +962,39 @@ int main(int argc, char *argv[])
   hex_ctx hc;
   dstr d = DSTR_INIT;
   size_t sz;
-  int i;
-  unsigned long u;
-  char *q;
-  struct hostent *h;
-  struct servent *s;
+  int i, err;
+  struct addrinfo aihint = { 0 }, *ailist, *ai;
+  const char *host, *svc = "7";
   unsigned f = 0;
 
 #define f_bogus 1u
 
+#ifdef HAVE_GETIFADDRS
   if ((rawicmp = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0 ||
       (rawudp = socket(PF_INET, SOCK_RAW, IPPROTO_UDP)) < 0)
     rawerr = errno;
+  if ((rawicmp6 = socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0 ||
+      (rawudp6 = socket(PF_INET6, SOCK_RAW, IPPROTO_RAW)) < 0)
+    rawerr6 = errno;
+#endif
   if (setuid(getuid()))
     abort();
 
   ego(argv[0]);
   fillbuffer(buf, sizeof(buf));
-  pp.sin.sin_port = htons(7);
+
+  aihint.ai_family = AF_UNSPEC;
+  aihint.ai_protocol = IPPROTO_UDP;
+  aihint.ai_socktype = SOCK_DGRAM;
+  aihint.ai_flags = AI_ADDRCONFIG;
 
   for (;;) {
     static const struct option opts[] = {
       { "help",                0,              0,      'h' },
       { "version",     0,              0,      'V' },
       { "usage",       0,              0,      'u' },
+      { "ipv4",                0,              0,      '4' },
+      { "ipv6",                0,              0,      '6' },
       { "header",      OPTF_ARGREQ,    0,      'H' },
       { "growth",      OPTF_ARGREQ,    0,      'g' },
       { "method",      OPTF_ARGREQ,    0,      'm' },
@@ -772,7 +1004,7 @@ int main(int argc, char *argv[])
       { 0,             0,              0,      0 }
     };
 
-    i = mdwopt(argc, argv, "hVu" "H:g:m:r:t:v", opts, 0, 0, 0);
+    i = mdwopt(argc, argv, "hVu" "46H:g:m:r:t:v", opts, 0, 0, 0);
     if (i < 0) break;
     switch (i) {
       case 'h': help(stdout); exit(0);
@@ -789,6 +1021,8 @@ int main(int argc, char *argv[])
        pp.seqoff = sz;
        break;
 
+      case '4': aihint.ai_family = AF_INET; break;
+      case '6': aihint.ai_family = AF_INET6; break;
       case 'g': pp.regr = s2f(optarg, "retransmit growth factor"); break;
       case 'r': pp.retx = s2f(optarg, "retransmit interval"); break;
       case 't': pp.timeout = s2f(optarg, "timeout"); break;
@@ -813,25 +1047,17 @@ int main(int argc, char *argv[])
     exit(EXIT_FAILURE);
   }
 
-  if ((h = gethostbyname(*argv)) == 0)
-    die(EXIT_FAILURE, "unknown host `%s': %s", *argv, hstrerror(h_errno));
-  if (h->h_addrtype != AF_INET)
-    die(EXIT_FAILURE, "unsupported address family for host `%s'", *argv);
-  memcpy(&pp.sin.sin_addr, h->h_addr, sizeof(struct in_addr));
-  argv++; argc--;
-
-  if (*argv) {
-    errno = 0;
-    u = strtoul(*argv, &q, 0);
-    if (!errno && !*q)
-      pp.sin.sin_port = htons(u);
-    else if ((s = getservbyname(*argv, "udp")) == 0)
-      die(EXIT_FAILURE, "unknown UDP service `%s'", *argv);
-    else
-      pp.sin.sin_port = s->s_port;
+  host = argv[0];
+  if (argv[1]) svc = argv[1];
+  if ((err = getaddrinfo(host, svc, &aihint, &ailist)) != 0) {
+    die(EXIT_FAILURE, "unknown host `%s' or service `%s': %s",
+       host, svc, gai_strerror(err));
   }
+  for (ai = ailist; ai && !addrfamok(ai->ai_family); ai = ai->ai_next);
+  if (!ai) die(EXIT_FAILURE, "no supported address families for `%s'", host);
+  assert(ai->ai_addrlen <= sizeof(pp.a));
+  memcpy(&pp.a, ai->ai_addr, ai->ai_addrlen);
 
-  pp.sin.sin_family = AF_INET;
   i = pathmtu(&pp);
   if (i < 0)
     die(EXIT_FAILURE, "failed to discover MTU: %s", strerror(errno));
index 20e1430..0cf5609 100644 (file)
@@ -9,19 +9,18 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 include $(top_srcdir)/vars.am
 
index 09913ad..100584a 100644 (file)
@@ -9,22 +9,21 @@
 .\"
 .\" This file is part of Trivial IP Encryption (TrIPE).
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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.
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\" along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 .
 .\"--------------------------------------------------------------------------
-.so ../defs.man.in \"@@@PRE@@@
+.so ../common/defs.man \"@@@PRE@@@
 .
 .\"--------------------------------------------------------------------------
 .TH peers.cdb 5tripe "27 March 2008" "Straylight/Edgeware" "TrIPE: Trivial IP Encryption"
index b170157..75c039d 100644 (file)
@@ -57,6 +57,10 @@ host = override-me
 ;; the remote peer.
 peer = INET $[$(host)] $(port)
 
+;; ephemeral: whether to send the peer a disconnection notification, or
+;; react to one from the peer.
+ephemeral = nil
+
 ;;;--------------------------------------------------------------------------
 ;;; Temporary association defaults.
 ;;;
@@ -81,9 +85,29 @@ retries = 5
 ;;; The parameters here affect peers to whom dynamic connections are made.
 ;;; The user and connect parameters probably need customizing.
 
-[@DYNAMIC]
+[@EPHEMERAL]
 @inherit = @ACTIVE, @WATCH
 
+;; ephemeral: whether to send the peer a disconnection notification, or
+;; react to one from the peer.
+ephemeral = t
+
+;; every: interval for checking that this connection is alive.
+every = 30s
+
+[@KNOCK]
+@inherit = @EPHEMERAL
+
+;; keepalive: how often to send NOP packets to keep the connection alive, at
+;; least in the minds of intermediate stateful firewalls and NAT routers.
+keepalive = 2m
+
+;; knock: peer-name string to send to the peer.
+knock = $(myhost)
+
+[@DYNAMIC]
+@inherit = @EPHEMERAL
+
 ;; cork: whether to wait for a key-exchange packet from the peer before
 ;; sending one of our own.
 cork = t
@@ -102,9 +126,6 @@ disconnect = ssh -q $(ssh-user)@$[$(host)] goodbye
 ;; least in the minds of intermediate stateful firewalls and NAT routers.
 keepalive = 2m
 
-;; every: interval for checking that this connection is alive.
-every = 30s
-
 ;;;--------------------------------------------------------------------------
 ;;; Passive-peers defaults.
 ;;;
@@ -113,7 +134,7 @@ every = 30s
 ;;; of the parameters and these defaults are probably pretty good.
 
 [@PASSIVE]
-@inherit = @GLOBAL, @WATCH
+@inherit = @WATCH
 
 ;; peer: mark this entry as being a passive peer.
 peer = PASSIVE
index 9ce9561..d638b07 100644 (file)
@@ -9,22 +9,21 @@
 .\"
 .\" This file is part of Trivial IP Encryption (TrIPE).
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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.
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\" along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 .
 .\"--------------------------------------------------------------------------
-.so ../defs.man.in \"@@@PRE@@@
+.so ../common/defs.man \"@@@PRE@@@
 .
 .\"--------------------------------------------------------------------------
 .TH peers.in 5tripe "27 March 2008" "Straylight/Edgeware" "TrIPE: Trivial IP Encryption"
@@ -84,14 +83,33 @@ is replaced by the value assigned to the given
 .IR key .
 .hP \*o
 An occurrence of
-.BI $[ host ]
+.BI $ flags [ host ]
 is replaced by the IP address of the named
 .IR host .
 Note that
 .I host
 may itself contain
 .BI $( key )
-substitutions.
+substitutions.  The
+.I flags
+consist of zero or more of the following characters:
+.RB ` 4 '
+looks up the
+.IR host 's
+IPv4 address(es);
+.RB ` 6 '
+looks up the
+.IR host 's
+IPv6 address(es);
+and
+.RB ` * '
+returns all of the found addresses, separated by spaces, rather than
+just the first one.  If neither address family is requested, then
+.RB ` 46 '
+is assumed.  IPv6 address lookup of names, rather than address literals,
+depends on the external
+.BR adnshost (1)
+program; if it is not present then only IPv4 lookups will be performed.
 .PP
 There is a simple concept of
 .I inheritance
@@ -127,9 +145,23 @@ section (though falling back to scanning parent sections).  For
 example, given the sections
 .VS
 [parent]
-detail = in parent
+detail = in $(name)
 blurb = expand $(detail)
+
+[child]
+@inherit = parent
 .VE
+the key
+.B blurb
+takes the value
+.RB ` "expand in parent" '
+in section
+.BR parent ,
+and
+.RB ` "expand in child" '
+in section
+.BR child .
+.PP
 Apart from its effect on lookups, as just described, the
 .B @inherit
 key is entirely ignored.  In particular, it is never written to the
@@ -162,6 +194,12 @@ Don't initiate immediate key exchange.  Used by
 Shell command for closing down connection to this peer.  Used by
 .BR connect (8).
 .TP
+.B ephemeral
+Mark the peer as ephemeral: see
+.BR tripe-admin (5)
+for what this means.  Used by
+.BR connect (8).
+.TP
 .B every
 Interval for checking that the peer is still alive and well.  Used by
 .BR connect (8).
@@ -194,6 +232,10 @@ Interval for sending keepalive pings.  Used by
 Key tag to use to authenticate the peer.  Used by
 .BR connect (8).
 .TP
+.B knock
+Knock string to send when establishing a dynamic connection.  Used by
+.BR connect (8).
+.TP
 .B mobile
 Peer's IP address is highly volatile.  Used by
 .BR connect (8).
@@ -271,9 +313,9 @@ semicolons
 .RB ` ; '
 rather than ampersands
 .RB ` & '.
-The
-.B @inherit
-key-value pair is not written to the database.
+Keys whose names begin with
+.RB ` @ '
+are not written to the database.
 .hP \*o
 Other sections are written to peer-type database records, named
 .BI P name \fR,
index 3fa4ccb..6ab2f9a 100644 (file)
@@ -9,22 +9,21 @@
 .\"
 .\" This file is part of Trivial IP Encryption (TrIPE).
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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.
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\" along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 .
 .\"--------------------------------------------------------------------------
-.so ../defs.man.in \"@@@PRE@@@
+.so ../common/defs.man \"@@@PRE@@@
 .
 .\"--------------------------------------------------------------------------
 .TH tripe-newpeers 8tripe "11 December 2008" "Straylight/Edgeware" "TrIPE: Trivial IP Encryption"
index a40d438..f3aed82 100644 (file)
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 VERSION = '@VERSION@'
 
 ###--------------------------------------------------------------------------
 ### External dependencies.
 
-import ConfigParser as CP
 import mLib as M
 from optparse import OptionParser
 import cdb as CDB
 from sys import stdin, stdout, exit, argv
+import subprocess as SUB
 import re as RX
 import os as OS
+import errno as E
+import fcntl as F
+import socket as S
+from cStringIO import StringIO
 
 ###--------------------------------------------------------------------------
 ### Utilities.
@@ -49,10 +52,64 @@ class CDBFake (object):
   def finish(me):
     pass
 
+class ExpectedError (Exception): pass
+
 ###--------------------------------------------------------------------------
 ### A bulk DNS resolver.
 
-class BulkResolver (object):
+class ResolverFailure (ExpectedError):
+  def __init__(me, host, msg):
+    me.host = host
+    me.msg = msg
+  def __str__(me):
+    return "failed to resolve `%s': %s" % (me.host, me.msg)
+
+class ResolvingHost (object):
+  """
+  A host name which is being looked up by a bulk-resolver instance.
+
+  Most notably, this is where the flag-handling logic lives for the
+  $FLAGS[HOSTNAME] syntax.
+  """
+
+  def __init__(me, name):
+    """Make a new resolving-host object for the host NAME."""
+    me.name = name
+    me.addr = { 'INET': [], 'INET6': [] }
+    me.failure = None
+
+  def addaddr(me, af, addr):
+    """
+    Add the address ADDR with address family AF.
+
+    The address family may be `INET' or `INET6'.
+    """
+    me.addr[af].append(addr)
+
+  def failed(me, msg):
+    """
+    Report that resolution of this host failed, with a human-readable MSG.
+    """
+    me.failure = msg
+
+  def get(me, flags):
+    """Return a list of addresses according to the FLAGS string."""
+    if me.failure is not None: raise ResolverFailure(me.name, me.failure)
+    aa = []
+    a4 = me.addr['INET']
+    a6 = me.addr['INET6']
+    all, any = False, False
+    for ch in flags:
+      if ch == '*': all = True
+      elif ch == '4': aa += a4; any = True
+      elif ch == '6': aa += a6; any = True
+      else: raise ValueError("unknown address-resolution flag `%s'" % ch)
+    if not any: aa = a4 + a6
+    if not aa: raise ResolverFailure(me.name, 'no matching addresses found')
+    if not all: aa = [aa[0]]
+    return aa
+
+class BaseBulkResolver (object):
   """
   Resolve a number of DNS names in parallel.
 
@@ -70,50 +127,234 @@ class BulkResolver (object):
 
   def __init__(me):
     """Initialize the resolver."""
-    me._resolvers = {}
     me._namemap = {}
 
-  def prepare(me, host):
-    """Prime the resolver to resolve the name HOST."""
-    if host not in me._resolvers:
-      me._resolvers[host] = M.SelResolveByName \
-                            (host,
-                             lambda name, alias, addr:
-                               me._resolved(host, addr[0]),
-                             lambda: me._resolved(host, None))
+  def prepare(me, name):
+    """Prime the resolver to resolve the given host NAME."""
+    if name not in me._namemap:
+      me._namemap[name] = host = ResolvingHost(name)
+      try:
+        ailist = S.getaddrinfo(name, None, S.AF_UNSPEC, S.SOCK_DGRAM, 0,
+                               S.AI_NUMERICHOST | S.AI_NUMERICSERV)
+      except S.gaierror:
+        me._prepare(host, name)
+      else:
+        for af, skty, proto, cname, sa in ailist:
+          if af == S.AF_INET: host.addaddr('INET', sa[0])
+          elif af == S.AF_INET6: host.addaddr('INET6', sa[0])
+
+  def lookup(me, name, flags):
+    """Fetch the address corresponding to the host NAME."""
+    return me._namemap[name].get(flags)
+
+class BresBulkResolver (BaseBulkResolver):
+  """
+  A BulkResolver using mLib's `bres' background resolver.
+
+  This is always available (and might use ADNS), but only does IPv4.
+  """
+
+  def __init__(me):
+    super(BresBulkResolver, me).__init__()
+    """Initialize the resolver."""
+    me._noutstand = 0
+
+  def _prepare(me, host, name):
+    """Arrange to resolve a NAME, reporting the results to HOST."""
+    host._resolv = M.SelResolveByName(
+      name,
+      lambda cname, alias, addr: me._resolved(host, cname, addr),
+      lambda: me._resolved(host, None, []))
+    me._noutstand += 1
 
   def run(me):
     """Run the background DNS resolver until it's finished."""
-    while me._resolvers:
-      M.select()
+    while me._noutstand: M.select()
+
+  def _resolved(me, host, cname, addr):
+    """Callback function: remember that ADDRs are the addresses for HOST."""
+    if not addr:
+      host.failed('(unknown failure)')
+    else:
+      if cname is not None: host.name = cname
+      for a in addr: host.addaddr('INET', a)
+    host._resolv = None
+    me._noutstand -= 1
+
+class AdnsBulkResolver (BaseBulkResolver):
+  """
+  A BulkResolver using ADNS, via the `adnshost' command-line tool.
 
-  def lookup(me, host):
+  This can do simultaneous IPv4 and IPv6 lookups and is quite shiny.
+  """
+
+  def __init__(me):
+    """Initialize the resolver."""
+
+    super(AdnsBulkResolver, me).__init__()
+
+    ## Start the external resolver process.
+    me._kid = SUB.Popen(['adnshost', '-afs'],
+                        stdin = SUB.PIPE, stdout = SUB.PIPE)
+
+    ## Set up the machinery for feeding input to the resolver.
+    me._in = me._kid.stdin
+    M.fdflags(me._in, fbic = OS.O_NONBLOCK, fxor = OS.O_NONBLOCK)
+    me._insel = M.SelFile(me._in.fileno(), M.SEL_WRITE, me._write)
+    me._inbuf, me._inoff, me._inlen = '', 0, 0
+    me._idmap = {}
+    me._nextid = 0
+
+    ## Set up the machinery for collecting the resolver's output.
+    me._out = me._kid.stdout
+    M.fdflags(me._out, fbic = OS.O_NONBLOCK, fxor = OS.O_NONBLOCK)
+    me._outline = M.SelLineBuffer(me._out,
+                                  lineproc = me._hostline, eofproc = me._eof)
+    me._outline.enable()
+
+    ## It's not finished yet.
+    me._done = False
+
+  def _prepare(me, host, name):
+    """Arrange for the resolver to resolve the name NAME."""
+
+    ## Work out the next job id, and associate that with the host record.
+    host.id = me._nextid; me._nextid += 1
+    me._namemap[name] = me._idmap[host.id] = host
+
+    ## Feed the name to the resolver process.
+    me._inbuf += name + '\n'
+    me._inlen += len(name) + 1
+    if not me._insel.activep: me._insel.enable()
+    while me._inoff < me._inlen: M.select()
+
+  def _write(me):
+    """Write material from `_inbuf' to the resolver when it's ready."""
+
+    ## Try to feed some more material to the resolver.
+    try: n = OS.write(me._in.fileno(), me._inbuf[me._inoff:])
+    except OSError, e:
+      if e.errno == E.EAGAIN or e.errno == E.EWOULDBLOCK: return
+      else: raise
+
+    ## If we're done, then clear the buffer.
+    me._inoff += n
+    if me._inoff >= me._inlen:
+      me._insel.disable()
+      me._inbuf, me._inoff, me._inlen = '', 0, 0
+
+  def _eof(me):
+    """Notice that the resolver has finished."""
+    me._outline.disable()
+    me._done = True
+    me._kid.wait()
+
+  def run(me):
     """
-    Fetch the address corresponding to HOST.
+    Tell the resolver it has all of our input now, and wait for it to finish.
     """
-    addr = me._namemap[host]
-    if addr is None:
-      raise KeyError, host
-    return addr
-
-  def _resolved(me, host, addr):
-    """Callback function: remember that ADDR is the address for HOST."""
-    me._namemap[host] = addr
-    del me._resolvers[host]
+    me._in.close()
+    while not me._done: M.select()
+    if me._idmap:
+      raise Exception('adnshost failed to process all the requests')
+
+  def _hostline(me, line):
+    """Handle a host line from the resolver."""
+
+    ## Parse the line into fields.
+    (id, nrrs, stty, stocde, stmsg, owner, cname, ststr), _ = \
+        M.split(line, quotep = True)
+    id, nrrs = int(id), int(nrrs)
+
+    ## Find the right record.
+    host = me._idmap[id]
+    if stty != 'ok': host.failed(ststr)
+
+    ## Stash away the canonical name of the host.
+    host.name = cname == '$' and owner or cname
+
+    ## If there are no record lines to come, then remove this record from the
+    ## list of outstanding jobs.  Otherwise, switch to the handler for record
+    ## lines.
+    if not nrrs:
+      del me._idmap[id]
+    else:
+      me._outline.lineproc = me._rrline
+      me._nrrs = nrrs
+      me._outhost = host
+
+  def _rrline(me, line):
+    """Handle a record line from the resolver."""
+
+    ## Parse the line into fields.
+    ww, _ = M.split(line, quotep = True)
+    owner, type, af = ww[:3]
+
+    ## If this is an address record, and it looks like an interesting address
+    ## type, then stash the address.
+    if type == 'A' and (af == 'INET' or af == 'INET6'):
+      me._outhost.addaddr(af, ww[3])
+
+    ## Update the parser state.  If there are no more records for this job
+    ## then mark the job as done and switch back to expecting a host line.
+    me._nrrs -= 1
+    if not me._nrrs:
+      me._outline.lineproc = me._hostline
+      del me._idmap[me._outhost.id]
+      me._outhost = None
+
+## Select a bulk resolver.  If `adnshost' exists then we might as well use
+## it.
+BulkResolver = BresBulkResolver
+try:
+  p = SUB.Popen(['adnshost', '--version'],
+                stdin = SUB.PIPE, stdout = SUB.PIPE, stderr = SUB.PIPE)
+  _out, _err = p.communicate()
+  st = p.wait()
+  if st == 0: BulkResolver = AdnsBulkResolver
+except OSError:
+  pass
 
 ###--------------------------------------------------------------------------
 ### The configuration parser.
 
+## Match a comment or empty line.
+RX_COMMENT = RX.compile(r'(?x) ^ \s* (?: $ | [;#])')
+
+## Match a section group header.
+RX_GRPHDR = RX.compile(r'(?x) ^ \s* \[ (.*) \] \s* $')
+
+## Match an assignment line.
+RX_ASSGN = RX.compile(r'''(?x) ^
+        ([^\s:=] (?: [^:=]* [^\s:=])?)
+        \s* [:=] \s*
+        (| \S | \S.*\S)
+        \s* $''')
+
+## Match a continuation line.
+RX_CONT = RX.compile(r'''(?x) ^ \s+
+        (| \S | \S.*\S)
+        \s* $''')
+
 ## Match a $(VAR) configuration variable reference; group 1 is the VAR.
-r_ref = RX.compile(r'\$\(([^)]+)\)')
+RX_REF = RX.compile(r'(?x) \$ \( ([^)]+) \)')
+
+## Match a $FLAGS[HOST] name resolution reference; group 1 are the flags;
+## group 2 is the HOST.
+RX_RESOLVE = RX.compile(r'(?x) \$ ([46*]*) \[ ([^]]+) \]')
 
-## Match a $[HOST] name resolution reference; group 1 is the HOST.
-r_resolve = RX.compile(r'\$\[([^]]+)\]')
+class ConfigSyntaxError (ExpectedError):
+  def __init__(me, fname, lno, msg):
+    me.fname = fname
+    me.lno = lno
+    me.msg = msg
+  def __str__(me):
+    return '%s:%d: %s' % (me.fname, me.lno, me.msg)
 
 def _fmt_path(path):
   return ' -> '.join(["`%s'" % hop for hop in path])
 
-class AmbiguousOptionError (Exception):
+class AmbiguousOptionError (ExpectedError):
   def __init__(me, key, patha, vala, pathb, valb):
     me.key = key
     me.patha, me.vala = patha, vala
@@ -123,7 +364,7 @@ class AmbiguousOptionError (Exception):
         "path %s yields `%s' but %s yields `%s'" % \
         (me.key, _fmt_path(me.patha), me.vala, _fmt_path(me.pathb), me.valb)
 
-class InheritanceCycleError (Exception):
+class InheritanceCycleError (ExpectedError):
   def __init__(me, key, path):
     me.key = key
     me.path = path
@@ -131,202 +372,311 @@ class InheritanceCycleError (Exception):
     return "Found a cycle %s looking up key `%s'" % \
         (_fmt_path(me.path), me.key)
 
-class MissingKeyException (Exception):
+class MissingSectionException (ExpectedError):
+  def __init__(me, sec):
+    me.sec = sec
+  def __str__(me):
+    return "Section `%s' not found" % (me.sec)
+
+class MissingKeyException (ExpectedError):
   def __init__(me, sec, key):
     me.sec = sec
     me.key = key
   def __str__(me):
     return "Key `%s' not found in section `%s'" % (me.key, me.sec)
 
-class MyConfigParser (CP.RawConfigParser):
+class ConfigSection (object):
   """
-  A more advanced configuration parser.
-
-  This has two major enhancements over the standard ConfigParser which are
-  relevant to us.
-
-    * It recognizes `@inherits' keys and follows them when expanding a
-      value.
-
-    * It recognizes `$(VAR)' references to configuration variables during
-      expansion and processes them correctly.
-
-    * It recognizes `$[HOST]' name-resolver requests and handles them
-      correctly.
+  A section in a configuration parser.
 
-  Use:
-
-    1. Call read(FILENAME) and/or read(FP, [FILENAME]) to slurp in the
-       configuration data.
-
-    2. Call resolve() to collect the hostnames which need to be resolved and
-       actually do the name resolution.
-
-    3. Call get(SECTION, ITEM) to collect the results, or items(SECTION) to
-       iterate over them.
+  This is where a lot of the nitty-gritty stuff actually happens.  The
+  `MyConfigParser' knows a lot about the internals of this class, which saves
+  on building a complicated interface.
   """
 
-  def __init__(me):
-    """
-    Initialize a new, empty configuration parser.
-    """
-    CP.RawConfigParser.__init__(me)
-    me._resolver = BulkResolver()
-
-  def resolve(me):
-    """
-    Works out all of the hostnames which need resolving and resolves them.
-
-    Until you call this, attempts to fetch configuration items which need to
-    resolve hostnames will fail!
-    """
-    for sec in me.sections():
-      for key, value in me.items(sec, resolvep = False):
-        for match in r_resolve.finditer(value):
-          me._resolver.prepare(match.group(1))
-    me._resolver.run()
-
-  def _expand(me, sec, string, resolvep):
+  def __init__(me, name, cp):
+    """Initialize a new, empty section with a given NAME and parent CP."""
+
+    ## The cache maps item keys to entries, which consist of a pair of
+    ## objects.  There are four possible states for a cache entry:
+    ##
+    ##   * missing -- there is no entry at all with this key, so we must
+    ##     search for it;
+    ##
+    ##   * None, None -- we are actively trying to resolve this key, so if we
+    ##     encounter this state, we have found a cycle in the inheritance
+    ##     graph;
+    ##
+    ##   * None, [] -- we know that this key isn't reachable through any of
+    ##     our parents;
+    ##
+    ##   * VALUE, PATH -- we know that the key resolves to VALUE, along the
+    ##     PATH from us (exclusive) to the defining parent (inclusive).
+    me.name = name
+    me._itemmap = dict()
+    me._cache = dict()
+    me._cp = cp
+
+  def _expand(me, string, resolvep):
     """
-    Expands $(...) and (optionally) $[...] placeholders in STRING.
+    Expands $(...) and (optionally) $FLAGS[...] placeholders in STRING.
 
-    The SEC is the configuration section from which to satisfy $(...)
-    requests.  RESOLVEP is a boolean switch: do we bother to tax the resolver
-    or not?  This is turned off by the resolve() method while it's collecting
-    hostnames to be resolved.
+    RESOLVEP is a boolean switch: do we bother to tax the resolver or not?
+    This is turned off by MyConfigParser's resolve() method while it's
+    collecting hostnames to be resolved.
     """
-    string = r_ref.sub \
-             (lambda m: me.get(sec, m.group(1), resolvep), string)
+    string = RX_REF.sub(lambda m: me.get(m.group(1), resolvep), string)
     if resolvep:
-      string = r_resolve.sub(lambda m: me._resolver.lookup(m.group(1)),
-                             string)
+      string = RX_RESOLVE.sub(
+        lambda m: ' '.join(me._cp._resolver.lookup(m.group(2), m.group(1))),
+        string)
     return string
 
-  def has_option(me, sec, key):
-    """
-    Decide whether section SEC has a configuration key KEY.
-
-    This version of the method properly handles the @inherit key.
-    """
-    return key == 'name' or me._get(sec, key)[0] is not None
+  def _parents(me):
+    """Yield this section's parents."""
+    try: names = me._itemmap['@inherit']
+    except KeyError: return
+    for name in names.replace(',', ' ').split():
+      yield me._cp.section(name)
 
-  def _get(me, sec, key, map = None, path = None):
+  def _get(me, key, path = None):
     """
     Low-level option-fetching method.
 
-    Fetch the value for the named KEY from section SEC, or maybe
-    (recursively) a section which SEC inherits from.
+    Fetch the value for the named KEY in this section, or maybe (recursively)
+    a section which it inherits from.
 
     Returns a pair VALUE, PATH.  The value is not expanded; nor do we check
     for the special `name' key.  The caller is expected to do these things.
     Returns None if no value could be found.
     """
 
-    ## If we weren't given a memoization map or path, then we'd better make
-    ## one.
-    if map is None: map = {}
+    ## If we weren't given a path, then we'd better make one.
     if path is None: path = []
 
-    ## If we've been this way before on another pass through then return the
-    ## value we found then.  If we're still thinking about it then we've
-    ## found a cycle.
-    path.append(sec)
+    ## Extend the path to cover us, but remember to remove us again when
+    ## we've finished.  If we need to pass the current path back upwards,
+    ## then remember to take a copy.
+    path.append(me.name)
     try:
-      threadp, value = map[sec]
-    except KeyError:
-      pass
-    else:
-      if threadp:
-        raise InheritanceCycleError, (key, path)
 
-    ## See whether the answer is ready waiting for us.
-    try:
-      v = CP.RawConfigParser.get(me, sec, key)
-    except CP.NoOptionError:
-      pass
-    else:
-      p = path[:]
-      path.pop()
-      return v, p
-
-    ## No, apparently, not.  Find out our list of parents.
-    try:
-      parents = CP.RawConfigParser.get(me, sec, '@inherit').\
-          replace(',', ' ').split()
-    except CP.NoOptionError:
-      parents = []
-
-    ## Initially we have no idea.
-    value = None
-    winner = None
-
-    ## Go through our parents and ask them what they think.
-    map[sec] = True, None
-    for p in parents:
-
-      ## See whether we get an answer.  If not, keep on going.
-      v, pp = me._get(p, key, map, path)
-      if v is None: continue
-
-      ## If we got an answer, check that it matches any previous ones.
-      if value is None:
-        value = v
-        winner = pp
-      elif value != v:
-        raise AmbiguousOptionError, (key, winner, value, pp, v)
+      ## If we've been this way before on another pass through then return the
+      ## value we found then.  If we're still thinking about it then we've
+      ## found a cycle.
+      try: v, p = me._cache[key]
+      except KeyError: pass
+      else:
+        if p is None: raise InheritanceCycleError(key, path[:])
+        else: return v, path + p
 
-    ## That's the best we could manage.
-    path.pop()
-    map[sec] = False, value
-    return value, winner
+      ## See whether the answer is ready waiting for us.
+      try: v = me._itemmap[key]
+      except KeyError: pass
+      else:
+        p = path[:]
+        me._cache[key] = v, []
+        return v, p
+
+      ## Initially we have no idea.
+      value = None
+      winner = []
+
+      ## Go through our parents and ask them what they think.
+      me._cache[key] = None, None
+      for p in me._parents():
+
+        ## See whether we get an answer.  If not, keep on going.
+        v, pp = p._get(key, path)
+        if v is None: continue
+
+        ## If we got an answer, check that it matches any previous ones.
+        if value is None:
+          value = v
+          winner = pp
+        elif value != v:
+          raise AmbiguousOptionError(key, winner, value, pp, v)
+
+      ## That's the best we could manage.
+      me._cache[key] = value, winner[len(path):]
+      return value, winner
+
+    finally:
+      ## Remove us from the path again.
+      path.pop()
 
-  def get(me, sec, key, resolvep = True):
+  def get(me, key, resolvep = True):
     """
-    Retrieve the value of KEY from section SEC.
+    Retrieve the value of KEY from this section.
     """
 
     ## Special handling for the `name' key.
     if key == 'name':
-      try: value = CP.RawConfigParser.get(me, sec, key)
-      except CP.NoOptionError: value = sec
+      value = me._itemmap.get('name', me.name)
+    elif key == '@inherits':
+      try: return me._itemmap['@inherits']
+      except KeyError: raise MissingKeyException(me.name, key)
     else:
-      value, _ = me._get(sec, key)
+      value, _ = me._get(key)
       if value is None:
-        raise MissingKeyException, (sec, key)
+        raise MissingKeyException(me.name, key)
 
     ## Expand the value and return it.
-    return me._expand(sec, value, resolvep)
+    return me._expand(value, resolvep)
 
-  def items(me, sec, resolvep = True):
+  def items(me, resolvep = True):
     """
-    Return a list of (NAME, VALUE) items in section SEC.
-
-    This extends the default method by handling the inheritance chain.
+    Yield a list of item names in the section.
     """
 
     ## Initialize for a depth-first walk of the inheritance graph.
-    d = {}
-    visited = {}
-    basesec = sec
-    stack = [sec]
+    seen = { 'name': True }
+    visiting = { me.name: True }
+    stack = [me]
 
     ## Visit nodes, collecting their keys.  Don't believe the values:
     ## resolving inheritance is too hard to do like this.
     while stack:
       sec = stack.pop()
-      if sec in visited: continue
-      visited[sec] = True
-
-      for key, value in CP.RawConfigParser.items(me, sec):
-        if key == '@inherit': stack += value.replace(',', ' ').split()
-        else: d[key] = None
+      for p in sec._parents():
+        if p.name not in visiting:
+          stack.append(p); visiting[p.name] = True
 
-    ## Now collect the values for the known keys, one by one.
-    items = []
-    for key in d: items.append((key, me.get(basesec, key, resolvep)))
+      for key in sec._itemmap.iterkeys(): seen[key] = None
 
     ## And we're done.
-    return items
+    return seen.iterkeys()
+
+class MyConfigParser (object):
+  """
+  A more advanced configuration parser.
+
+  This has four major enhancements over the standard ConfigParser which are
+  relevant to us.
+
+    * It recognizes `@inherits' keys and follows them when expanding a
+      value.
+
+    * It recognizes `$(VAR)' references to configuration variables during
+      expansion and processes them correctly.
+
+    * It recognizes `$FLAGS[HOST]' name-resolver requests and handles them
+      correctly.  FLAGS consists of characters `4' (IPv4 addresses), `6'
+      (IPv6 addresses), and `*' (all, space-separated, rather than just the
+      first).
+
+    * Its parsing behaviour is well-defined.
+
+  Use:
+
+    1. Call parse(FILENAME) to slurp in the configuration data.
+
+    2. Call resolve() to collect the hostnames which need to be resolved and
+       actually do the name resolution.
+
+    3. Call sections() to get a list of the configuration sections, or
+       section(NAME) to find a named section.
+
+    4. Call get(ITEM) on a section to collect the results, or items() to
+       iterate over them.
+  """
+
+  def __init__(me):
+    """
+    Initialize a new, empty configuration parser.
+    """
+    me._sectmap = dict()
+    me._resolver = BulkResolver()
+
+  def parse(me, f):
+    """
+    Parse configuration from a file F.
+    """
+
+    ## Initial parser state.
+    sect = None
+    key = None
+    val = None
+    lno = 0
+
+    ## An unpleasant hack.  Python makes it hard to capture a value in a
+    ## variable and examine it in a single action, and this is the best that
+    ## I came up with.
+    m = [None]
+    def match(rx): m[0] = rx.match(line); return m[0]
+
+    ## Commit a key's value when we've determined that there are no further
+    ## continuation lines.
+    def flush():
+      if key is not None: sect._itemmap[key] = val.getvalue()
+
+    ## Work through all of the input lines.
+    for line in f:
+      lno += 1
+
+      if match(RX_COMMENT):
+        ## A comment or a blank line.  Nothing doing.  (This means that we
+        ## leave out blank lines which look like they might be continuation
+        ## lines.)
+
+        pass
+
+      elif match(RX_GRPHDR):
+        ## A section header.  Flush out any previous value and set up the new
+        ## group.
+
+        flush()
+        name = m[0].group(1)
+        try: sect = me._sectmap[name]
+        except KeyError: sect = me._sectmap[name] = ConfigSection(name, me)
+        key = None
+
+      elif match(RX_ASSGN):
+        ## A new assignment.  Flush out the old one, and set up to store this
+        ## one.
+
+        if sect is None:
+          raise ConfigSyntaxError(f.name, lno, 'no active section to update')
+        flush()
+        key = m[0].group(1)
+        val = StringIO(); val.write(m[0].group(2))
+
+      elif match(RX_CONT):
+        ## A continuation line.  Accumulate the value.
+
+        if key is None:
+          raise ConfigSyntaxError(f.name, lno, 'no config value to continue')
+        val.write('\n'); val.write(m[0].group(1))
+
+      else:
+        ## Something else.
+
+        raise ConfigSyntaxError(f.name, lno, 'incomprehensible line')
+
+    ## Don't forget to commit any final value material.
+    flush()
+
+  def section(me, name):
+    """Return a ConfigSection with the given NAME."""
+    try: return me._sectmap[name]
+    except KeyError: raise MissingSectionException(name)
+
+  def sections(me):
+    """Yield the known sections."""
+    return me._sectmap.itervalues()
+
+  def resolve(me):
+    """
+    Works out all of the hostnames which need resolving and resolves them.
+
+    Until you call this, attempts to fetch configuration items which need to
+    resolve hostnames will fail!
+    """
+    for sec in me.sections():
+      for key in sec.items():
+        value = sec.get(key, resolvep = False)
+        for match in RX_RESOLVE.finditer(value):
+          me._resolver.prepare(match.group(2))
+    me._resolver.run()
 
 ###--------------------------------------------------------------------------
 ### Command-line handling.
@@ -383,7 +733,7 @@ def getconf(args):
   """
   conf = MyConfigParser()
   for f in inputiter(args):
-    conf.readfp(f)
+    conf.parse(f)
   conf.resolve()
   return conf
 
@@ -394,22 +744,24 @@ def output(conf, cdb):
   This is where the special `user' and `auto' database entries get set.
   """
   auto = []
-  for sec in sorted(conf.sections()):
-    if sec.startswith('@'):
+  for sec in sorted(conf.sections(), key = lambda sec: sec.name):
+    if sec.name.startswith('@'):
       continue
-    elif sec.startswith('$'):
-      label = sec
+    elif sec.name.startswith('$'):
+      label = sec.name
     else:
-      label = 'P%s' % sec
-      if conf.has_option(sec, 'auto') and \
-         conf.get(sec, 'auto') in ('y', 'yes', 't', 'true', '1', 'on'):
-        auto.append(sec)
-      if conf.has_option(sec, 'user'):
-        cdb.add('U%s' % conf.get(sec, 'user'), sec)
-    url = M.URLEncode(laxp = True, semip = True)
-    for key, value in sorted(conf.items(sec), key = lambda (k, v): k):
+      label = 'P%s' % sec.name
+      try: a = sec.get('auto')
+      except MissingKeyException: pass
+      else:
+        if a in ('y', 'yes', 't', 'true', '1', 'on'): auto.append(sec.name)
+      try: u = sec.get('user')
+      except MissingKeyException: pass
+      else: cdb.add('U%s' % u, sec.name)
+    url = M.URLEncode(semip = True)
+    for key in sorted(sec.items()):
       if not key.startswith('@'):
-        url.encode(key, ' '.join(M.split(value)[0]))
+        url.encode(key, sec.get(key))
     cdb.add(label, url.result)
   cdb.add('%AUTO', ' '.join(auto))
   cdb.finish()
@@ -421,8 +773,12 @@ def main():
     cdb = CDB.cdbmake(opts.cdbfile, opts.cdbfile + '.new')
   else:
     cdb = CDBFake()
-  conf = getconf(args[1:])
-  output(conf, cdb)
+  try:
+    conf = getconf(args[1:])
+    output(conf, cdb)
+  except ExpectedError, e:
+    M.moan(str(e))
+    exit(2)
 
 if __name__ == '__main__':
   main()
index a6cebdd..5ffafab 100644 (file)
@@ -9,19 +9,18 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 include $(top_srcdir)/vars.am
 
index 6bbe46b..757e5cd 100644 (file)
@@ -9,19 +9,18 @@
 .\"
 .\" This file is part of Trivial IP Encryption (TrIPE).
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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.
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\" along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 .
 .\"--------------------------------------------------------------------------
 .so ../common/defs.man \" @@@PRE@@@
@@ -38,6 +37,7 @@ pkstream \- forward UDP packets over streams
 .SH "SYNOPSIS"
 .
 .B pkstream
+.RB [ \-46 ]
 .RB [ \-l
 .IR port ]
 .RB [ \-p
@@ -98,6 +98,12 @@ version number to standard output and exits with status 0.
 .B "\-u, \-\-usage"
 Writes a brief usage summary to standard output and exits with status 0.
 .TP
+.B "\-4"
+Look up hostnames only as IPv4 addresses.
+.TP
+.B "\-6"
+Look up hostnames only as IPv6 addresses.
+.TP
 .BI "\-l, \-\-listen=" port
 Listen for connections on the given TCP
 .IR port .
index 4a1ccf5..8b2a058 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 /*----- Header files ------------------------------------------------------*/
@@ -46,6 +45,7 @@
 
 #include <mLib/alloc.h>
 #include <mLib/bits.h>
+#include <mLib/darray.h>
 #include <mLib/dstr.h>
 #include <mLib/fdflags.h>
 #include <mLib/mdwopt.h>
 
 /*----- Data structures ---------------------------------------------------*/
 
+typedef union addr {
+  struct sockaddr sa;
+  struct sockaddr_in sin;
+  struct sockaddr_in6 sin6;
+} addr;
+
+DA_DECL(addr_v, addr);
+DA_DECL(str_v, const char *);
+
 typedef struct pk {
   struct pk *next;                     /* Next packet in the chain */
   octet *p, *o;                                /* Buffer start and current posn */
@@ -74,9 +83,10 @@ typedef struct pkstream {
 } pkstream;
 
 typedef struct connwait {
-  sel_file a;                          /* Selector */
-  struct sockaddr_in me;               /* Who I'm meant to be */
-  struct in_addr peer;                 /* Who my peer is */
+  unsigned f;                          /* Various flags */
+#define cwf_port 1u                    /*   Port is defined => listen */
+  sel_file *sfv;                       /* Selectors */
+  addr_v me, peer;                    /* Who I'm meant to be; who peer is */
 } connwait;
 
 /*----- Static variables --------------------------------------------------*/
@@ -84,7 +94,7 @@ typedef struct connwait {
 static sel_state sel;
 static connwait cw;
 static int fd_udp;
-static size_t pk_nmax = 128, pk_szmax = 1024 * 1024;
+static size_t pk_nmax = 128, pk_szmax = 1024*1024;
 
 /*----- Main code ---------------------------------------------------------*/
 
@@ -94,6 +104,111 @@ static int nonblockify(int fd)
 static int cloexec(int fd)
   { return (fdflags(fd, 0, 0, FD_CLOEXEC, FD_CLOEXEC)); }
 
+static socklen_t addrsz(const addr *a)
+{
+  switch (a->sa.sa_family) {
+    case AF_INET: return sizeof(a->sin);
+    case AF_INET6: return sizeof(a->sin6);
+    default: abort();
+  }
+}
+
+static int knownafp(int af)
+{
+  switch (af) {
+    case AF_INET: case AF_INET6: return (1);
+    default: return (0);
+  }
+}
+
+static int initsock(int fd, int af)
+{
+  int yes = 1;
+
+  switch (af) {
+    case AF_INET: break;
+    case AF_INET6:
+      if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)))
+       return (-1);
+      break;
+    default: abort();
+  }
+  return (0);
+}
+
+static const char *addrstr(const addr *a)
+{
+  static char buf[128];
+  socklen_t n = sizeof(buf);
+
+  if (getnameinfo(&a->sa, addrsz(a), buf, n, 0, 0, NI_NUMERICHOST))
+    return ("<addrstr failed>");
+  return (buf);
+}
+
+static int addreq(const addr *a, const addr *b)
+{
+  if (a->sa.sa_family != b->sa.sa_family) return (0);
+  switch (a->sa.sa_family) {
+    case AF_INET:
+      return (a->sin.sin_addr.s_addr == b->sin.sin_addr.s_addr);
+    case AF_INET6:
+      return (!memcmp(a->sin6.sin6_addr.s6_addr,
+                     b->sin6.sin6_addr.s6_addr,
+                     16) &&
+             a->sin6.sin6_scope_id == b->sin6.sin6_scope_id);
+    default:
+      abort();
+  }
+}
+
+static void initaddr(addr *a, int af)
+{
+  a->sa.sa_family = af;
+  switch (af) {
+    case AF_INET:
+      a->sin.sin_addr.s_addr = INADDR_ANY;
+      a->sin.sin_port = 0;
+      break;
+    case AF_INET6:
+      memset(a->sin6.sin6_addr.s6_addr, 0, 16);
+      a->sin6.sin6_port = 0;
+      a->sin6.sin6_flowinfo = 0;
+      a->sin6.sin6_scope_id = 0;
+      break;
+    default:
+      abort();
+  }
+}
+
+#define caf_addr 1u
+#define caf_port 2u
+static void copyaddr(addr *a, const struct sockaddr *sa, unsigned f)
+{
+  const struct sockaddr_in *sin;
+  const struct sockaddr_in6 *sin6;
+
+  a->sa.sa_family = sa->sa_family;
+  switch (sa->sa_family) {
+    case AF_INET:
+      sin = (const struct sockaddr_in *)sa;
+      if (f&caf_addr) a->sin.sin_addr = sin->sin_addr;
+      if (f&caf_port) a->sin.sin_port = sin->sin_port;
+      break;
+    case AF_INET6:
+      sin6 = (const struct sockaddr_in6 *)sa;
+      if (f&caf_addr) {
+       a->sin6.sin6_addr = sin6->sin6_addr;
+       a->sin6.sin6_scope_id = sin6->sin6_scope_id;
+      }
+      if (f&caf_port) a->sin6.sin6_port = sin6->sin6_port;
+      /* ??? flowinfo? */
+      break;
+    default:
+      abort();
+  }
+}
+
 static void dolisten(void);
 
 static void doclose(pkstream *p)
@@ -102,20 +217,16 @@ static void doclose(pkstream *p)
   close(p->w.fd);
   close(p->p.reader.fd);
   selpk_destroy(&p->p);
-  if (!(p->f & PKF_FULL))
-    sel_rmfile(&p->r);
-  if (p->npk)
-    sel_rmfile(&p->w);
+  if (!(p->f&PKF_FULL)) sel_rmfile(&p->r);
+  if (p->npk) sel_rmfile(&p->w);
   for (pk = p->pks; pk; pk = ppk) {
     ppk = pk->next;
     xfree(pk->p);
     xfree(pk);
   }
   xfree(p);
-  if (cw.me.sin_port != 0)
-    dolisten();
-  else
-    exit(0);
+  if (cw.f&cwf_port) dolisten();
+  else exit(0);
 }
 
 static void rdtcp(octet *b, size_t sz, pkbuf *pk, size_t *k, void *vp)
@@ -123,10 +234,7 @@ static void rdtcp(octet *b, size_t sz, pkbuf *pk, size_t *k, void *vp)
   pkstream *p = vp;
   size_t pksz;
 
-  if (!sz) {
-    doclose(p);
-    return;
-  }
+  if (!sz) { doclose(p); return; }
   pksz = LOAD16(b);
   if (pksz + 2 == sz) {
     DISCARD(write(fd_udp, b + 2, pksz));
@@ -152,8 +260,7 @@ static void wrtcp(int fd, unsigned mode, void *vp)
   }
 
   if ((n = writev(fd, iov, i)) < 0) {
-    if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
-      return;
+    if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return;
     moan("couldn't write to TCP socket: %s", strerror(errno));
     doclose(p);
     return;
@@ -174,14 +281,9 @@ static void wrtcp(int fd, unsigned mode, void *vp)
     }
   }
   p->pks = pk;
-  if (!pk) {
-    p->pk_tail = &p->pks;
-    sel_rmfile(&p->w);
-  }
-  if ((p->f & PKF_FULL) && p->npk < pk_nmax && p->szpk < pk_szmax) {
-    p->f &= ~PKF_FULL;
-    sel_addfile(&p->r);
-  }
+  if (!pk) { p->pk_tail = &p->pks; sel_rmfile(&p->w); }
+  if ((p->f&PKF_FULL) && p->npk < pk_nmax && p->szpk < pk_szmax)
+    { p->f &= ~PKF_FULL; sel_addfile(&p->r); }
 }
 
 static void rdudp(int fd, unsigned mode, void *vp)
@@ -206,15 +308,12 @@ static void rdudp(int fd, unsigned mode, void *vp)
   pk->n = n + 2;
   *p->pk_tail = pk;
   p->pk_tail = &pk->next;
-  if (!p->npk)
-    sel_addfile(&p->w);
+  if (!p->npk) sel_addfile(&p->w);
   sel_force(&p->w);
   p->npk++;
   p->szpk += n + 2;
-  if (p->npk >= pk_nmax || p->szpk >= pk_szmax) {
-    sel_rmfile(&p->r);
-    p->f |= PKF_FULL;
-  }
+  if (p->npk >= pk_nmax || p->szpk >= pk_szmax)
+    { sel_rmfile(&p->r); p->f |= PKF_FULL; }
 }
 
 static void dofwd(int fd_in, int fd_out)
@@ -234,82 +333,109 @@ static void dofwd(int fd_in, int fd_out)
 static void doaccept(int fd_s, unsigned mode, void *p)
 {
   int fd;
-  struct sockaddr_in sin;
-  socklen_t sz = sizeof(sin);
+  addr a;
+  socklen_t sz = sizeof(a);
+  size_t i, n;
 
-  if ((fd = accept(fd_s, (struct sockaddr *)&sin, &sz)) < 0) {
-    if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
-      return;
+  if ((fd = accept(fd_s, &a.sa, &sz)) < 0) {
+    if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return;
     moan("couldn't accept incoming connection: %s", strerror(errno));
     return;
   }
-  if (cw.peer.s_addr != INADDR_ANY &&
-      cw.peer.s_addr != sin.sin_addr.s_addr) {
-    close(fd);
-    moan("rejecting connection from %s", inet_ntoa(sin.sin_addr));
-    return;
-  }
+  n = DA_LEN(&cw.peer);
+  if (!n) goto match;
+  for (i = 0; i < n; i++) if (addreq(&a, &DA(&cw.peer)[i])) goto match;
+  moan("rejecting connection from %s", addrstr(&a));
+  close(fd); return;
+match:
   if (nonblockify(fd) || cloexec(fd)) {
-    close(fd);
     moan("couldn't accept incoming connection: %s", strerror(errno));
-    return;
+    close(fd); return;
   }
   dofwd(fd, fd);
-  close(fd_s);
-  sel_rmfile(&cw.a);
+  n = DA_LEN(&cw.me);
+  for (i = 0; i < n; i++) { close(cw.sfv[i].fd); sel_rmfile(&cw.sfv[i]); }
 }
 
-static void dolisten(void)
+static void dolisten1(const addr *a, sel_file *sf)
 {
   int fd;
   int opt = 1;
 
-  if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0 ||
+  if ((fd = socket(a->sa.sa_family, SOCK_STREAM, IPPROTO_TCP)) < 0 ||
       setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) ||
-      bind(fd, (struct sockaddr *)&cw.me, sizeof(cw.me)) ||
+      initsock(fd, a->sa.sa_family) ||
+      bind(fd, &a->sa, addrsz(a)) ||
       listen(fd, 1) || nonblockify(fd) || cloexec(fd))
     die(1, "couldn't set up listening socket: %s", strerror(errno));
-  sel_initfile(&sel, &cw.a, fd, SEL_READ, doaccept, 0);
-  sel_addfile(&cw.a);
+  sel_initfile(&sel, sf, fd, SEL_READ, doaccept, 0);
+  sel_addfile(sf);
+}
+
+static void dolisten(void)
+{
+  size_t i, n;
+
+  n = DA_LEN(&cw.me);
+  for (i = 0; i < n; i++)
+    dolisten1(&DA(&cw.me)[i], &cw.sfv[i]);
 }
 
-static void parseaddr(const char *pp, struct in_addr *a, unsigned short *pt)
+static void pushaddrs(addr_v *av, const struct addrinfo *ailist)
 {
-  char *p = xstrdup(pp);
-  char *q = 0;
-  if (a && pt) {
-    strtok(p, ":");
-    q = strtok(0, "");
-    if (!q)
-      die(1, "missing port number in address `%s'", p);
-  } else if (pt) {
-    q = p;
+  const struct addrinfo *ai;
+  size_t i, n;
+
+  for (ai = ailist, n = 0; ai; ai = ai->ai_next)
+    if (knownafp(ai->ai_family)) n++;
+  DA_ENSURE(av, n);
+  for (i = DA_LEN(av), ai = ailist; ai; ai = ai->ai_next) {
+    if (!knownafp(ai->ai_family)) continue;
+    initaddr(&DA(av)[i], ai->ai_family);
+    copyaddr(&DA(av)[i++], ai->ai_addr, caf_addr | caf_port);
   }
+  DA_EXTEND(av, n);
+}
 
-  if (a) {
-    struct hostent *h;
-    if ((h = gethostbyname(p)) == 0)
-      die(1, "unknown host `%s'", p);
-    memcpy(a, h->h_addr, sizeof(*a));
+#define paf_parse 1u
+static void parseaddr(const struct addrinfo *aihint,
+                     const char *host, const char *svc, unsigned f,
+                     struct addrinfo **ai_out)
+{
+  char *alloc = 0, *sep;
+  int err;
+
+  if (f&paf_parse) {
+    alloc = xstrdup(host);
+    if (alloc[0] != '[') {
+      if ((sep = strchr(alloc, ':')) == 0)
+       die(1, "missing port number in address `%s'", host);
+      host = alloc; *sep = 0; svc = sep + 1;
+    } else {
+      if ((sep = strchr(alloc, ']')) == 0 || sep[1] != ':')
+       die(1, "bad syntax in address `%s:'", host);
+      host = alloc + 1; *sep = 0; svc = sep + 2;
+    }
   }
 
-  if (pt) {
-    struct servent *s;
-    char *qq;
-    unsigned long n;
-    if ((s = getservbyname(q, "tcp")) != 0)
-      *pt = s->s_port;
-    else if ((n = strtoul(q, &qq, 0)) == 0 || *qq || n > 0xffff)
-      die(1, "bad port number `%s'", q);
+  err = getaddrinfo(host, svc, aihint, ai_out);
+  if (err) {
+    if (host && svc) {
+      die(1, "failed to resolve hostname `%s', service `%s': %s",
+         host, svc, gai_strerror(err));
+    } else if (host)
+      die(1, "failed to resolve hostname `%s': %s", host, gai_strerror(err));
     else
-      *pt = htons(n);
+      die(1, "failed to resolve service `%s': %s", svc, gai_strerror(err));
   }
+
+  xfree(alloc);
 }
 
 static void usage(FILE *fp)
 {
   pquis(fp,
-       "Usage: $ [-l PORT] [-b ADDR] [-p ADDR] [-c ADDR:PORT]\n\
+       "Usage: $ [-46] [-l PORT] [-b ADDR] [-p ADDR] [-c ADDR:PORT]\n\
        ADDR:PORT ADDR:PORT\n");
 }
 
@@ -328,6 +454,8 @@ Options:\n\
 -v, --version          Display version number.\n\
 -u, --usage            Display pointless usage message.\n\
 \n\
+-4, --ipv4             Restrict to IPv4 only.\n\
+-6, --ipv6             Restrict to IPv6 only.\n\
 -l, --listen=PORT      Listen for connections to TCP PORT.\n\
 -p, --peer=ADDR                Only accept connections from IP ADDR.\n\
 -b, --bind=ADDR                Bind to ADDR before connecting.\n\
@@ -341,29 +469,29 @@ stdout; though it can use TCP sockets instead.\n\
 int main(int argc, char *argv[])
 {
   unsigned f = 0;
-  unsigned short pt;
-  struct sockaddr_in connaddr, bindaddr;
-  struct sockaddr_in udp_me, udp_peer;
+  str_v bindhosts = DA_INIT, peerhosts = DA_INIT;
+  const char *bindsvc = 0;
+  addr bindaddr;
+  const char *connhost = 0;
+  struct addrinfo aihint = { 0 }, *ai, *ailist;
+  int af = AF_UNSPEC;
+  int fd = -1;
   int len = 65536;
+  size_t i, n;
 
 #define f_bogus 1u
 
+  cw.f = 0;
+
   ego(argv[0]);
-  bindaddr.sin_family = AF_INET;
-  bindaddr.sin_addr.s_addr = INADDR_ANY;
-  bindaddr.sin_port = 0;
-  connaddr.sin_family = AF_INET;
-  connaddr.sin_addr.s_addr = INADDR_ANY;
-  cw.me.sin_family = AF_INET;
-  cw.me.sin_addr.s_addr = INADDR_ANY;
-  cw.me.sin_port = 0;
-  cw.peer.s_addr = INADDR_ANY;
   sel_init(&sel);
   for (;;) {
     static struct option opt[] = {
       { "help",                        0,              0,      'h' },
       { "version",             0,              0,      'v' },
       { "usage",               0,              0,      'u' },
+      { "ipv4",                        0,              0,      '4' },
+      { "ipv6",                        0,              0,      '6' },
       { "listen",              OPTF_ARGREQ,    0,      'l' },
       { "peer",                        OPTF_ARGREQ,    0,      'p' },
       { "bind",                        OPTF_ARGREQ,    0,      'b' },
@@ -372,73 +500,130 @@ int main(int argc, char *argv[])
     };
     int i;
 
-    i = mdwopt(argc, argv, "hvul:p:b:c:", opt, 0, 0, 0);
+    i = mdwopt(argc, argv, "hvu46l:p:b:c:", opt, 0, 0, 0);
     if (i < 0)
       break;
     switch (i) {
-      case 'h':
-       help(stdout);
-       exit(0);
-      case 'v':
-       version(stdout);
-       exit(0);
-      case 'u':
-       usage(stdout);
-       exit(0);
-      case 'l':
-       parseaddr(optarg, 0, &pt);
-       cw.me.sin_port = pt;
-       break;
-      case 'p':
-       parseaddr(optarg, &cw.peer, 0);
-       break;
-      case 'b':
-       parseaddr(optarg, &bindaddr.sin_addr, 0);
-       cw.me.sin_addr = bindaddr.sin_addr;
-       break;
-      case 'c':
-       parseaddr(optarg, &connaddr.sin_addr, &pt);
-       connaddr.sin_port = pt;
-       break;
-      default:
-       f |= f_bogus;
-       break;
+      case 'h': help(stdout); exit(0);
+      case 'v': version(stdout); exit(0);
+      case 'u': usage(stdout); exit(0);
+      case '4': af = AF_INET; break;
+      case '6': af = AF_INET6; break;
+      case 'l': bindsvc = optarg; break;
+      case 'p': DA_PUSH(&peerhosts, optarg); break;
+      case 'b': DA_PUSH(&bindhosts, optarg); break;
+      case 'c': connhost = optarg; break;
+      default: f |= f_bogus; break;
     }
   }
-  if (optind + 2 != argc || (f & f_bogus)) {
-    usage(stderr);
-    exit(1);
+  if (optind + 2 != argc || (f&f_bogus)) { usage(stderr); exit(1); }
+
+  if (DA_LEN(&bindhosts) && !bindsvc && !connhost)
+    die(1, "bind addr only makes sense when listening or connecting");
+  if (DA_LEN(&peerhosts) && !bindsvc)
+    die(1, "peer addr only makes sense when listening");
+  if (bindsvc && connhost)
+    die(1, "can't listen and connect");
+
+  aihint.ai_family = af;
+  DA_CREATE(&cw.me); DA_CREATE(&cw.peer);
+
+  n = DA_LEN(&bindhosts);
+  if (n || bindsvc) {
+    aihint.ai_socktype = SOCK_STREAM;
+    aihint.ai_protocol = IPPROTO_TCP;
+    aihint.ai_flags = AI_ADDRCONFIG | AI_PASSIVE;
+    if (!n) {
+      parseaddr(&aihint, 0, bindsvc, 0, &ailist);
+      pushaddrs(&cw.me, ailist);
+      freeaddrinfo(ailist);
+    } else if (!bindsvc) {
+      if (n != 1) die(1, "can only bind to one address as client");
+      parseaddr(&aihint, DA(&bindhosts)[0], 0, 0, &ailist);
+      for (ai = ailist; ai && !knownafp(ai->ai_family); ai = ai->ai_next);
+      if (!ai)
+       die(1, "no usable addresses returned for `%s'", DA(&bindhosts)[0]);
+      initaddr(&bindaddr, ai->ai_family);
+      copyaddr(&bindaddr, ai->ai_addr, caf_addr);
+      aihint.ai_family = ai->ai_family;
+      freeaddrinfo(ailist);
+    } else for (i = 0; i < n; i++) {
+      parseaddr(&aihint, DA(&bindhosts)[i], bindsvc, 0, &ailist);
+      pushaddrs(&cw.me, ailist);
+      freeaddrinfo(ailist);
+    }
+    if (bindsvc) {
+      cw.f |= cwf_port;
+      n = DA_LEN(&cw.me);
+      cw.sfv = xmalloc(n*sizeof(*cw.sfv));
+    }
   }
 
-  udp_me.sin_family = udp_peer.sin_family = AF_INET;
-  parseaddr(argv[optind], &udp_me.sin_addr, &pt);
-  udp_me.sin_port = pt;
-  parseaddr(argv[optind + 1], &udp_peer.sin_addr, &pt);
-  udp_peer.sin_port = pt;
+  n = DA_LEN(&peerhosts);
+  if (n) {
+    aihint.ai_socktype = SOCK_STREAM;
+    aihint.ai_protocol = IPPROTO_TCP;
+    aihint.ai_flags = AI_ADDRCONFIG;
+    for (i = 0; i < n; i++) {
+      parseaddr(&aihint, DA(&peerhosts)[i], 0, 0, &ailist);
+      pushaddrs(&cw.peer, ailist);
+      freeaddrinfo(ailist);
+    }
+    if (!DA_LEN(&cw.peer)) die(1, "no usable peer addresses");
+  }
+
+  if (connhost) {
+    aihint.ai_socktype = SOCK_STREAM;
+    aihint.ai_protocol = IPPROTO_TCP;
+    aihint.ai_flags = AI_ADDRCONFIG;
+    parseaddr(&aihint, connhost, 0, paf_parse, &ailist);
+
+    for (ai = ailist; ai; ai = ai->ai_next) {
+      if ((fd = socket(ai->ai_family, SOCK_STREAM, IPPROTO_TCP)) >= 0 &&
+         !initsock(fd, ai->ai_family) &&
+         (!DA_LEN(&bindhosts) ||
+          !bind(fd, &bindaddr.sa, addrsz(&bindaddr))) &&
+         !connect(fd, ai->ai_addr, ai->ai_addrlen))
+       goto conn_tcp;
+      if (fd >= 0) close(fd);
+    }
+    die(1, "couldn't connect to TCP server: %s", strerror(errno));
+  conn_tcp:
+    if (nonblockify(fd) || cloexec(fd))
+      die(1, "couldn't connect to TCP server: %s", strerror(errno));
+  }
 
-  if ((fd_udp = socket(PF_INET, SOCK_DGRAM, 0)) < 0 ||
-      bind(fd_udp, (struct sockaddr *)&udp_me, sizeof(udp_me)) ||
-      connect(fd_udp, (struct sockaddr *)&udp_peer, sizeof(udp_peer)) ||
+  aihint.ai_family = af;
+  aihint.ai_socktype = SOCK_DGRAM;
+  aihint.ai_protocol = IPPROTO_UDP;
+  aihint.ai_flags = AI_ADDRCONFIG | AI_PASSIVE;
+  parseaddr(&aihint, argv[optind], 0, paf_parse, &ailist);
+  for (ai = ailist; ai && !knownafp(ai->ai_family); ai = ai->ai_next);
+  if (!ai) die(1, "no usable addresses returned for `%s'", argv[optind]);
+  if ((fd_udp = socket(ai->ai_family, SOCK_DGRAM, IPPROTO_UDP)) < 0 ||
+      initsock(fd_udp, ai->ai_family) ||
+      nonblockify(fd_udp) || cloexec(fd_udp) ||
       setsockopt(fd_udp, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len)) ||
       setsockopt(fd_udp, SOL_SOCKET, SO_SNDBUF, &len, sizeof(len)) ||
-      nonblockify(fd_udp) || cloexec(fd_udp))
+      bind(fd_udp, ai->ai_addr, ai->ai_addrlen))
     die(1, "couldn't set up UDP socket: %s", strerror(errno));
+  freeaddrinfo(ailist);
+  aihint.ai_family = ai->ai_family;
+  aihint.ai_flags = AI_ADDRCONFIG;
+  parseaddr(&aihint, argv[optind + 1], 0, paf_parse, &ailist);
+  for (ai = ailist; ai; ai = ai->ai_next)
+    if (!connect(fd_udp, ai->ai_addr, ai->ai_addrlen)) goto conn_udp;
+  die(1, "couldn't set up UDP socket: %s", strerror(errno));
+conn_udp:
+
+  if (bindsvc) dolisten();
+  else if (connhost) dofwd(fd, fd);
+  else dofwd(STDIN_FILENO, STDOUT_FILENO);
 
-  if (cw.me.sin_port != 0)
-    dolisten();
-  else if (connaddr.sin_addr.s_addr != INADDR_ANY) {
-    int fd;
-    if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0 ||
-       bind(fd, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) ||
-       connect(fd, (struct sockaddr *)&connaddr, sizeof(connaddr)) ||
-       nonblockify(fd) || cloexec(fd))
-      die(1, "couldn't connect to TCP server: %s", strerror(errno));
-    dofwd(fd, fd);
-  } else
-    dofwd(STDIN_FILENO, STDOUT_FILENO);
-
-  for (;;)
-    sel_select(&sel);
+  for (;;) {
+    if (sel_select(&sel) && errno != EINTR)
+      die(1, "select failed: %s", strerror(errno));
+  }
   return (0);
 }
 
index 80b935d..4598e9c 100644 (file)
@@ -9,19 +9,18 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 include $(top_srcdir)/vars.am
 
@@ -29,7 +28,7 @@ noinst_LIBRARIES       = libpriv.a
 libexec_PROGRAMS        = tripe-privhelper
 man_MANS                =
 
-LDADD                   = libpriv.a $(libtripe) $(mLib_LIBS)
+LDADD                   = libpriv.a $(libcommon) $(mLib_LIBS)
 
 ###--------------------------------------------------------------------------
 ### Library.
index 316f6c8..becb492 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 /*----- Header files ------------------------------------------------------*/
index 400fa28..4367bb5 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 /*----- Header files ------------------------------------------------------*/
index 0223cd0..d547d14 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 #ifndef PRIV_H
index 2837206..ddb723c 100644 (file)
@@ -9,19 +9,18 @@
 .\"
 .\" This file is part of Trivial IP Encryption (TrIPE).
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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.
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\" along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 .
 .\"--------------------------------------------------------------------------
 .so ../common/defs.man \" @@@PRE@@@
index 4aac127..4ab20a2 100644 (file)
@@ -9,19 +9,18 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 include $(top_srcdir)/vars.am
 
index 0d883da..ade7dc0 100644 (file)
@@ -9,19 +9,18 @@
 .\"
 .\" This file is part of Trivial IP Encryption (TrIPE).
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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.
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\" along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 .
 .\"--------------------------------------------------------------------------
 .so ../common/defs.man \" @@@PRE@@@
@@ -59,6 +58,9 @@ The command line contains a sequence of directives, each of which has
 the form
 .IB command : arg \c
 .BR : ...
+(The delimiter character can be changed using the
+.B \-d
+command-line option.)
 A list of directives can be stored in a file, one per line, and included
 using the
 .B include
@@ -77,6 +79,12 @@ successfully.
 .B "\-u, \-\-usage"
 Write a usage message to standard output, and exit successfully.
 .TP
+.BI "\-d, \-\-delimiter=" char
+Use
+.I char
+as the delimiter to separate argument names in directives, rather than
+.RB ` : '.
+.TP
 .BI "\-k, \-\-keyring=" file
 Read keys from
 .IR file .
@@ -86,6 +94,14 @@ in the current directory.
 .SS "Directives"
 A directive is ignored if it is empty, or if its first character is a
 .RB ` # '.
+Directives consist of a name followed by zero or more arguments,
+separated by a delimiter character.  The default delimiter is
+.RB ` : ',
+but this can be overridden using the
+.B \-d
+option (see above); this manual uses
+.RB ` : '
+consistently as the delimiter character.
 The following directives are recognized.
 .TP
 .BI peer: name : local-port : remote-addr : remote-port
@@ -98,13 +114,23 @@ on
 The
 .I name
 identifies the public key which that peer uses to authenticate itself.
+(Currently this is checked, but not used for anything.)
 Both
 .I local-port
 and
 .I remote-port
-must be numbers;
+may be numbers or UDP service names;
 .I remote-addr
-may be a hostname or an IP address in dotted-quad format.  Exactly two
+may be a hostname, an IPv4 address in dotted-quad format, or an IPv6
+address in hex-and-colons format (this last obviously requires selecting
+a different delimeter character).  Additionally,
+.I local-port
+may be a string of the form
+.BI ? file
+to get the kernel to allocate an unused port number, and then write the
+port to the named
+.IR file .
+Exactly two
 .B peer
 directives must be present.  The one first registered is the
 .I left
@@ -114,6 +140,16 @@ peer.  The two peers must use
 .I different
 local ports.
 .TP
+.BI peer4: name : local-port : remote-addr : remote-port
+As for
+.I peer
+(see above), but force the use of IPv4.
+.TP
+.BI peer6: name : local-port : remote-addr : remote-port
+As for
+.I peer
+(see above), but force the use of IPv6.
+.TP
 .BI include: file
 Read more directives from
 .IR file .
@@ -129,7 +165,7 @@ description of filters below for more details.
 .BI lfilt: filter : args : \fR...
 Apply a given filter to packets received from the left peer.
 .TP
-.BI lfilt: filter : args :\fR...
+.BI rfilt: filter : args :\fR...
 Apply a given filter to packets received from the right peer.
 .TP
 .BI next: tag :\fR...
index a9f8491..648296e 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 /*----- Header files ------------------------------------------------------*/
@@ -63,9 +62,9 @@
 #include <catacomb/mprand.h>
 #include <catacomb/dh.h>
 
+#include <catacomb/chacha20.h>
 #include <catacomb/noise.h>
 #include <catacomb/rand.h>
-#include <catacomb/rc4.h>
 
 #include "util.h"
 
@@ -73,7 +72,6 @@
 
 typedef struct peer {
   sel_file sf;
-  dh_pub kpub;
   const char *name;
   struct filter *f;
 } peer;
@@ -99,6 +97,7 @@ static peer peers[2];
 static unsigned npeer = 0;
 static key_file keys;
 static grand *rng;
+static const char *delim = ":";
 
 #define PASS(f, buf, sz) ((f) ? (f)->func((f), (buf), (sz)) : (void)0)
 #define RND(i) (rng->ops->range(rng, (i)))
@@ -116,52 +115,76 @@ static void dopacket(int fd, unsigned mode, void *vv)
   }
 }
 
-static void addpeer(unsigned ac, char **av)
+static void addpeer_common(const char *cmd, int af, unsigned ac, char **av)
 {
-  key_packstruct kps[DH_PUBFETCHSZ];
-  key_packdef *kp;
-  struct hostent *h;
-  struct sockaddr_in sin;
-  int len = PKBUFSZ;
+  struct addrinfo aihint = { 0 }, *ai0, *ai1;
+  int len = PKBUFSZ, yes = 1;
+  const char *outf, *serv;
+  FILE *fp;
+  union {
+    struct sockaddr sa;
+    struct sockaddr_in sin;
+    struct sockaddr_in6 sin6;
+  } addr;
+  socklen_t salen;
+  int err;
   peer *p;
-  int fd;
-  int e;
+  int fd, port;
 
-  if (ac != 4)
-    die(1, "syntax: peer:NAME:PORT:ADDR:PORT");
-  if (npeer >= 2)
-    die(1, "enough peers already");
+  if (ac != 4) die(1, "syntax: %s:NAME:PORT:ADDR:PORT", cmd);
+  if (!key_bytag(&keys, av[0])) die(1, "no key named `%s'", av[0]);
   p = &peers[npeer++];
   p->name = xstrdup(av[0]);
-  kp = key_fetchinit(dh_pubfetch, kps, &p->kpub);
-  e = key_fetchbyname(kp, &keys, av[0]);
-  key_fetchdone(kp);
-  if (e)
-    die(1, "key_fetch `%s': %s", av[0], key_strerror(e));
-  if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
+  aihint.ai_family = af;
+  aihint.ai_socktype = SOCK_DGRAM;
+  aihint.ai_flags = AI_ADDRCONFIG;
+  if ((err = getaddrinfo(av[2], av[3], &aihint, &ai1)) != 0)
+    die(1, "getaddrinfo(`%s', `%s'): %s", av[2], av[3], gai_strerror(err));
+  if (*av[1] == '?') { serv = "0"; outf = av[1] + 1; }
+  else { serv = av[1]; outf = 0; }
+  aihint.ai_family = ai1->ai_family;
+  aihint.ai_flags = AI_ADDRCONFIG | AI_PASSIVE;
+  if ((err = getaddrinfo(0, serv, &aihint, &ai0)) != 0)
+    die(1, "getaddrinfo(passive, `%s'): %s", av[1], gai_strerror(err));
+  if ((fd = socket(ai1->ai_family, SOCK_DGRAM, ai1->ai_protocol)) < 0)
     die(1, "socket: %s", strerror(errno));
-  fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
-  memset(&sin, 0, sizeof(sin));
-  sin.sin_family = AF_INET;
-  sin.sin_addr.s_addr = INADDR_ANY;
-  sin.sin_port = htons(atoi(av[1]));
-  if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)))
+  if (ai1->ai_family == AF_INET6) {
+    if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)))
+      die(1, "setsockopt: %s", strerror(errno));
+  }
+  if (bind(fd, ai0->ai_addr, ai0->ai_addrlen))
     die(1, "bind: %s", strerror(errno));
-  memset(&sin, 0, sizeof(sin));
-  sin.sin_family = AF_INET;
-  if ((h = gethostbyname(av[2])) == 0)
-    die(1, "gethostbyname `%s'", av[2]);
   if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len)) ||
       setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &len, sizeof(len)))
     die(1, "setsockopt: %s", strerror(errno));
-  memcpy(&sin.sin_addr, h->h_addr, sizeof(sin.sin_addr));
-  sin.sin_port = htons(atoi(av[3]));
-  if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)))
+  if (connect(fd, ai1->ai_addr, ai1->ai_addrlen))
     die(1, "connect: %s", strerror(errno));
+  if (outf) {
+    salen = sizeof(addr);
+    if (getsockname(fd, &addr.sa, &salen))
+      die(1, "getsockname: %s", strerror(errno));
+    switch (addr.sa.sa_family) {
+      case AF_INET: port = ntohs(addr.sin.sin_port); break;
+      case AF_INET6: port = ntohs(addr.sin6.sin6_port); break;
+      default: assert(0);
+    }
+    fp = fopen(outf, "w");
+    if (!fp) die(1, "fopen(%s): %s", outf, strerror(errno));
+    fprintf(fp, "%d\n", port);
+    fclose(fp);
+  }
   sel_initfile(&sel, &p->sf, fd, SEL_READ, dopacket, p);
   sel_addfile(&p->sf);
+  freeaddrinfo(ai0); freeaddrinfo(ai1);
 }
 
+static void addpeer(unsigned ac, char **av)
+  { addpeer_common("peer", AF_UNSPEC, ac, av); }
+static void addpeer4(unsigned ac, char **av)
+  { addpeer_common("peer4", AF_INET, ac, av); }
+static void addpeer6(unsigned ac, char **av)
+  { addpeer_common("peer6", AF_INET6, ac, av); }
+
 /*----- Fork filter -------------------------------------------------------*/
 
 typedef struct forknode {
@@ -192,8 +215,7 @@ static void dofork(filter *f, const octet *buf, size_t sz)
 static void addfork(filter *f, unsigned ac, char **av)
 {
   forkfilt *ff;
-  if (ac != 1)
-    die(1, "syntax: filt:fork:NAME");
+  if (ac != 1) die(1, "syntax: filt:fork:NAME");
   ff = CREATE(forkfilt);
   ff->name = xstrdup(av[0]);
   ff->fn = 0;
@@ -209,23 +231,18 @@ static void nextfork(unsigned ac, char **av)
   forknode *fn, **ffn;
   peer *p;
 
-  if (ac < 1)
-    die(1, "syntax: next:NAME:...");
+  if (ac < 1) die(1, "syntax: next:NAME:...");
   for (i = 0; i < 2; i++) {
     p = &peers[i];
     for (f = p->f; f; f = f->next) {
-      if (f->func != dofork)
-       continue;
+      if (f->func != dofork) continue;
       ff = f->state;
-      for (j = 0; j < ac; j++) {
-       if (strcmp(av[j], ff->name) == 0)
-         goto match;
-      }
+      for (j = 0; j < ac; j++)
+       if (strcmp(av[j], ff->name) == 0) goto match;
       continue;
     match:
       fn = CREATE(forknode);
-      for (ffn = &ff->fn; *ffn; ffn = &(*ffn)->next)
-       ;
+      for (ffn = &ff->fn; *ffn; ffn = &(*ffn)->next);
       fn->f = f->next;
       f->next = 0;
       fn->next = 0;
@@ -256,13 +273,10 @@ static void docorrupt(filter *f, const octet *buf, size_t sz)
 static void addcorrupt(filter *f, unsigned ac, char **av)
 {
   corrupt *c;
-  if (ac > 1)
-    die(1, "syntax: filt:corrupt[:P-CORRUPT]");
+  if (ac > 1) die(1, "syntax: filt:corrupt[:P-CORRUPT]");
   c = CREATE(corrupt);
-  if (ac > 0)
-    c->p_corrupt = atoi(av[0]);
-  else
-    c->p_corrupt = 5;
+  if (ac > 0) c->p_corrupt = atoi(av[0]);
+  else c->p_corrupt = 5;
   f->state = c;
   f->func = docorrupt;
 }
@@ -277,22 +291,17 @@ static void dodrop(filter *f, const octet *buf, size_t sz)
 {
   drop *d = f->state;
 
-  if (!RND(d->p_drop))
-    puts("drop packet");
-  else
-    PASS(f->next, buf, sz);
+  if (!RND(d->p_drop)) puts("drop packet");
+  else PASS(f->next, buf, sz);
 }
 
 static void adddrop(filter *f, unsigned ac, char **av)
 {
   drop *d;
-  if (ac > 1)
-    die(1, "syntax: filt:drop[:P-DROP]");
+  if (ac > 1) die(1, "syntax: filt:drop[:P-DROP]");
   d = CREATE(drop);
-  if (ac > 0)
-    d->p_drop = atoi(av[0]);
-  else
-    d->p_drop = 5;
+  if (ac > 0) d->p_drop = atoi(av[0]);
+  else d->p_drop = 5;
   f->state = d;
   f->func = dodrop;
 }
@@ -341,6 +350,8 @@ static void dsend(delaynode *dn, unsigned force)
 {
   delay *d = dn->d;
   delaynode *ddn;
+  unsigned i;
+
   fputs(" send...\n", stdout);
   assert(dn->buf);
   PASS(d->f->next, dn->buf, dn->sz);
@@ -363,7 +374,7 @@ static void dsend(delaynode *dn, unsigned force)
       ddn->flag = 0;
       printf(" move id %u from slot %u to slot %u", ddn->seq, ddn->i, dn->i);
     }
-    { unsigned i; for (i = 0; i < d->n; i++) assert(d->q[i].buf); }
+    for (i = 0; i < d->n; i++) assert(d->q[i].buf);
     fputs(" remove", stdout);
   }
 }
@@ -408,19 +419,14 @@ static void adddelay(filter *f, unsigned ac, char **av)
   delay *d;
   unsigned i;
 
-  if (ac < 1 || ac > 3)
-    die(1, "syntax: filt:delay:QLEN[:MILLIS:P-REPLAY]");
+  if (ac < 1 || ac > 3) die(1, "syntax: filt:delay:QLEN[:MILLIS:P-REPLAY]");
   d = CREATE(delay);
   d->max = atoi(av[0]);
-  if (ac > 1)
-    d->t = strtoul(av[1], 0, 10);
-  else
-    d->t = 100;
+  if (ac > 1) d->t = strtoul(av[1], 0, 10);
+  else d->t = 100;
   d->t *= 1000;
-  if (ac > 2)
-    d->p_replay = atoi(av[2]);
-  else
-    d->p_replay = 20;
+  if (ac > 2) d->p_replay = atoi(av[2]);
+  else d->p_replay = 20;
   d->n = 0;
   d->q = xmalloc(d->max * sizeof(delaynode));
   d->f = f;
@@ -444,8 +450,7 @@ static void dosend(filter *f, const octet *buf, size_t sz)
 
 static void addsend(filter *f, unsigned ac, char **av)
 {
-  if (ac)
-    die(1, "syntax: filt:send");
+  if (ac) die(1, "syntax: filt:send");
   f->func = dosend;
 }
 
@@ -465,21 +470,16 @@ static void dofilter(peer *from, peer *to, unsigned ac, char **av)
 {
   filter **ff, *f = CREATE(filter);
   const struct filtab *ft;
-  if (ac < 1)
-    die(1, "syntax: {l,r,}filt:NAME:...");
+  if (ac < 1) die(1, "syntax: {l,r,}filt:NAME:...");
   f->next = 0;
   f->p_from = from;
   f->p_to = to;
   f->state = 0;
-  for (ff = &from->f; *ff; ff = &(*ff)->next)
-    ;
+  for (ff = &from->f; *ff; ff = &(*ff)->next);
   *ff = f;
-  for (ft = filtab; ft->name; ft++) {
-    if (strcmp(av[0], ft->name) == 0) {
-      ft->func(f, ac - 1, av + 1);
-      return;
-    }
-  }
+  for (ft = filtab; ft->name; ft++)
+    if (strcmp(av[0], ft->name) == 0)
+      { ft->func(f, ac - 1, av + 1); return; }
   die(1, "unknown filter `%s'", av[0]);
 }
 
@@ -508,8 +508,7 @@ static void floodtimer(struct timeval *tv, void *vv)
   sz /= 2;
 
   rng->ops->fill(rng, buf, sz);
-  if (f->type < 0x100)
-    buf[0] = f->type;
+  if (f->type < 0x100) buf[0] = f->type;
   puts("flood packet");
   PASS(f->p->f, buf, sz);
   setflood(f);
@@ -526,22 +525,15 @@ static void setflood(flood *f)
 static void doflood(peer *p, unsigned ac, char **av)
 {
   flood *f;
-  if (ac > 3)
-    die(1, "syntax: flood[:TYPE:MILLIS:SIZE]");
+  if (ac > 3) die(1, "syntax: flood[:TYPE:MILLIS:SIZE]");
   f = CREATE(flood);
   f->p = p;
-  if (ac > 0)
-    f->type = strtoul(av[0], 0, 16);
-  else
-    f->type = 0x100;
-  if (ac > 1)
-    f->t = atoi(av[1]);
-  else
-    f->t = 10;
-  if (ac > 2)
-    f->sz = atoi(av[2]);
-  else
-    f->sz = 128;
+  if (ac > 0) f->type = strtoul(av[0], 0, 16);
+  else f->type = 0x100;
+  if (ac > 1) f->t = atoi(av[1]);
+  else f->t = 10;
+  if (ac > 2) f->sz = atoi(av[2]);
+  else f->sz = 128;
   f->t *= 1000;
   setflood(f);
 }
@@ -576,15 +568,11 @@ static void include(unsigned ac, char **av)
 {
   FILE *fp;
   dstr d = DSTR_INIT;
-  if (!ac)
-    die(1, "syntax: include:FILE:...");
+  if (!ac) die(1, "syntax: include:FILE:...");
   while (*av) {
     if ((fp = fopen(*av, "r")) == 0)
       die(1, "fopen `%s': %s", *av, strerror(errno));
-    while (dstr_putline(&d, fp) != EOF) {
-      parse(d.buf);
-      DRESET(&d);
-    }
+    while (dstr_putline(&d, fp) != EOF) { parse(d.buf); DRESET(&d); }
     fclose(fp);
     av++;
   }
@@ -595,6 +583,8 @@ const struct cmdtab {
   void (*func)(unsigned /*ac*/, char **/*av*/);
 } cmdtab[] = {
   { "peer",    addpeer },
+  { "peer4",   addpeer4 },
+  { "peer6",   addpeer6 },
   { "include", include },
   { "filt",    addfilter },
   { "lfilt",   addlfilter },
@@ -614,12 +604,11 @@ static void parse(char *p)
   unsigned c = 0;
   const struct cmdtab *ct;
 
-  p = strtok(p, ":");
-  if (!p || *p == '#')
-    return;
+  p = strtok(p, delim);
+  if (!p || *p == '#') return;
   do {
     v[c++] = p;
-    p = strtok(0, ":");
+    p = strtok(0, delim);
   } while (p && c < AVMAX - 1);
   v[c] = 0;
   for (ct = cmdtab; ct->name; ct++) {
@@ -637,7 +626,7 @@ static void version(FILE *fp)
   { pquis(fp, "$, TrIPE version " VERSION "\n"); }
 
 static void usage(FILE *fp)
-  { pquis(fp, "Usage: $ [-k KEYRING] DIRECTIVE...\n"); }
+  { pquis(fp, "Usage: $ [-d CHAR] [-k KEYRING] DIRECTIVE...\n"); }
 
 static void help(FILE *fp)
 {
@@ -651,10 +640,11 @@ Options:\n\
 -v, --version          Show the version number.\n\
 -u, --usage            Show terse usage summary.\n\
 \n\
+-d, --delimiter=CHAR   Use CHAR rather than `:' as delimiter.\n\
 -k, --keyring=FILE     Fetch keys from FILE.\n\
 \n\
 Directives:\n\
-  peer:NAME:LOCAL-PORT:REMOTE-ADDR:REMOTE-PORT\n\
+  peer{,4,6}:NAME:LOCAL-PORT:REMOTE-ADDR:REMOTE-PORT\n\
   include:FILE\n\
   {,l,r}filt:FILTER:ARGS:...\n\
   next:TAG\n\
@@ -674,7 +664,8 @@ int main(int argc, char *argv[])
   const char *kfname = "keyring.pub";
   int i;
   unsigned f = 0;
-  char buf[16];
+  char buf[32];
+  static octet zero[CHACHA_NONCESZ];
 
 #define f_bogus 1u
 
@@ -684,46 +675,39 @@ int main(int argc, char *argv[])
       { "help",                0,              0,      'h' },
       { "version",     0,              0,      'v' },
       { "usage",       0,              0,      'u' },
+      { "delimiter",   OPTF_ARGREQ,    0,      'd' },
       { "keyring",     OPTF_ARGREQ,    0,      'k' },
       { 0,             0,              0,      0 }
     };
-    if ((i = mdwopt(argc, argv, "hvuk:", opt, 0, 0, 0)) < 0)
-      break;
+    if ((i = mdwopt(argc, argv, "hvud:k:", opt, 0, 0, 0)) < 0) break;
     switch (i) {
-      case 'h':
-       help(stdout);
-       exit(0);
-      case 'v':
-       version(stdout);
-       exit(0);
-      case 'u':
-       usage(stdout);
-       exit(0);
-      case 'k':
-       kfname = optarg;
-       break;
-      default:
-       f |= f_bogus;
+      case 'h': help(stdout); exit(0);
+      case 'v': version(stdout); exit(0);
+      case 'u': usage(stdout); exit(0);
+      case 'd':
+       if (!optarg[0] || optarg[1])
+         die(1, "delimiter must be a single character");
+       delim = optarg;
        break;
+      case 'k': kfname = optarg; break;
+      default: f |= f_bogus; break;
     }
   }
-  if (f & f_bogus) {
-    usage(stderr);
-    exit(1);
-  }
+  if (f & f_bogus) { usage(stderr); exit(1); }
+
   rand_noisesrc(RAND_GLOBAL, &noise_source);
-  rand_seed(RAND_GLOBAL, 160);
+  rand_seed(RAND_GLOBAL, 256);
   rand_get(RAND_GLOBAL, buf, sizeof(buf));
-  rng = rc4_rand(buf, sizeof(buf));
+  rng = chacha20_rand(buf, sizeof(buf), zero);
   sel_init(&sel);
   if (key_open(&keys, kfname, KOPEN_READ, key_moan, 0))
     die(1, "couldn't open `%s': %s", kfname, strerror(errno));
-  for (i = optind; i < argc; i++)
-    parse(argv[i]);
-  if (npeer != 2)
-    die(1, "need two peers");
-  for (;;)
-    sel_select(&sel);
+  for (i = optind; i < argc; i++) parse(argv[i]);
+  if (npeer != 2) die(1, "need two peers");
+  for (;;) {
+    if (sel_select(&sel) && errno != EINTR)
+      die(1, "select failed: %s", strerror(errno));
+  }
 
 #undef f_bogus
 }
index 03163ba..bca6a31 100644 (file)
@@ -9,19 +9,18 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 include $(top_srcdir)/vars.am
 
index 70df8f3..6c846cb 100644 (file)
@@ -9,19 +9,18 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 __pychecker__ = 'self=me'
 
@@ -51,7 +50,7 @@ def _switchto(cr, arg = None, exc = None):
   global active
   _debug('> _switchto(%s, %s, %s)' % (cr, arg, exc))
   if not cr.livep:
-    raise ValueError, 'coroutine is dead'
+    raise ValueError('coroutine is dead')
   cr._arg = arg
   cr._exc = exc
   if cr is active:
index ae88ba3..a9be668 100644 (file)
@@ -9,19 +9,18 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 """
 This module provides classes and functions for connecting to a running tripe
@@ -110,7 +109,7 @@ import os as OS
 
 try:
   if OS.getenv('TRIPE_FORCE_RMCR') is not None:
-    raise ImportError
+    raise ImportError()
   from py.magic import greenlet as _Coroutine
 except ImportError:
   from rmcr import Coroutine as _Coroutine
@@ -509,7 +508,7 @@ class TripeCommandIterator (object):
     """
     me.dcr = Coroutine.getcurrent().parent
     if me.dcr is None:
-      raise ValueError, 'must invoke from coroutine'
+      raise ValueError('must invoke from coroutine')
     me.filter = filter or (lambda x: x)
     if bg:
       words = [words[0], '-background', dispatcher.bgtag()] + list(words[1:])
@@ -533,17 +532,17 @@ class TripeCommandIterator (object):
     if code == 'INFO':
       return me.filter(rest)
     elif code == 'OK':
-      raise StopIteration
+      raise StopIteration()
     elif code == 'CONNERR':
       if rest is None:
-        raise TripeConnectionError, 'connection terminated by user'
+        raise TripeConnectionError('connection terminated by user')
       else:
         raise rest
     elif code == 'FAIL':
       raise TripeError(*rest)
     else:
-      raise TripeInternalError \
-            ('unexpected tripe response %r' % ([code] + rest))
+      raise TripeInternalError('unexpected tripe response %r' %
+                               ([code] + rest))
 
 ### Simple utility functions for the TripeCommandIterator convenience
 ### methods.
@@ -839,7 +838,8 @@ class TripeCommandDispatcher (TripeConnection):
                               *['ADD'] +
                               _kwopts(kw, ['tunnel', 'keepalive',
                                            'key', 'priv', 'cork',
-                                           'mobile']) +
+                                           'mobile', 'knock',
+                                           'ephemeral']) +
                               [peer] +
                               list(addr)))
   def addr(me, peer):
@@ -853,7 +853,7 @@ class TripeCommandDispatcher (TripeConnection):
     return _simple(me.command('DAEMON'))
   def eping(me, peer, **kw):
     return _oneline(me.command(bg = True,
-                               *['PING'] +
+                               *['EPING'] +
                                _kwopts(kw, ['timeout']) +
                                [peer]))
   def forcekx(me, peer):
@@ -879,8 +879,10 @@ class TripeCommandDispatcher (TripeConnection):
                                *['PING'] +
                                _kwopts(kw, ['timeout']) +
                                [peer]))
-  def port(me):
-    return _oneline(me.command('PORT', filter = _tokenjoin))
+  def port(me, af = None):
+    return _oneline(me.command('PORT',
+                               *((af is not None) and [af] or []),
+                               filter = _tokenjoin))
   def quit(me):
     return _simple(me.command('QUIT'))
   def reload(me):
@@ -1173,7 +1175,7 @@ class TripeServiceCommand (object):
     """
     if (me.min is not None and len(args) < me.min) or \
        (me.max is not None and len(args) > me.max):
-      raise TripeSyntaxError
+      raise TripeSyntaxError()
     me.func(*args)
 
 class TripeServiceJob (Coroutine):
@@ -1384,12 +1386,12 @@ class OptParse (object):
     if len(me.args) == 0 or \
        len(me.args[0]) < 2 or \
        not me.args[0].startswith('-'):
-      raise StopIteration
+      raise StopIteration()
     opt = me.args.pop(0)
     if opt == '--':
-      raise StopIteration
+      raise StopIteration()
     if opt not in me.allowed:
-      raise TripeSyntaxError
+      raise TripeSyntaxError()
     return opt
 
   def arg(me):
@@ -1399,7 +1401,7 @@ class OptParse (object):
     If none is available, raise `TripeSyntaxError'.
     """
     if len(me.args) == 0:
-      raise TripeSyntaxError
+      raise TripeSyntaxError()
     return me.args.pop(0)
 
   def rest(me, min = None, max = None):
@@ -1411,7 +1413,7 @@ class OptParse (object):
     """
     if (min is not None and len(me.args) < min) or \
        (max is not None and len(me.args) > max):
-      raise TripeSyntaxError
+      raise TripeSyntaxError()
     return me.args
 
 ###----- That's all, folks --------------------------------------------------
index a5cfdc4..96c78d3 100644 (file)
@@ -9,54 +9,61 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 include $(top_srcdir)/vars.am
 
 sbin_PROGRAMS           =
+noinst_LIBRARIES        =
+noinst_PROGRAMS                 =
 man_MANS                =
 
-LDADD                   = $(libtripe) $(libpriv) $(catacomb_LIBS)
+LDADD                   = $(libcommon) $(libpriv) \
+                               $(catacomb_LIBS) $(ADNS_LIBS)
 
 ###--------------------------------------------------------------------------
 ### The main server.
 
-sbin_PROGRAMS          += tripe
-
-tripe_SOURCES           =
+## Build a library, for special effects.
+noinst_LIBRARIES       += libtripe.a
+libtripe_a_SOURCES      =
 
 ## Main header file.
-tripe_SOURCES          += tripe.h
+libtripe_a_SOURCES     += tripe.h
 
 ## Main sources.
-tripe_SOURCES          += servutil.c
-tripe_SOURCES          += addrmap.c
-tripe_SOURCES          += keymgmt.c
-tripe_SOURCES          += bulkcrypto.c
-tripe_SOURCES          += dh.c
-tripe_SOURCES          += keyset.c
-tripe_SOURCES          += keyexch.c
-tripe_SOURCES          += chal.c
-tripe_SOURCES          += peer.c
-tripe_SOURCES          += privsep.c
-tripe_SOURCES          += admin.c
-tripe_SOURCES          += tripe.c
+libtripe_a_SOURCES     += servutil.c
+libtripe_a_SOURCES     += addrmap.c
+libtripe_a_SOURCES     += keymgmt.c
+libtripe_a_SOURCES     += bulkcrypto.c
+libtripe_a_SOURCES     += dh.c
+libtripe_a_SOURCES     += keyset.c
+libtripe_a_SOURCES     += keyexch.c
+libtripe_a_SOURCES     += chal.c
+libtripe_a_SOURCES     += peer.c
+libtripe_a_SOURCES     += privsep.c
+libtripe_a_SOURCES     += admin.c
+libtripe_a_SOURCES     += tripe.c
 
 ## Tunnel drivers.
-tripe_SOURCES          += tun-std.c
-tripe_SOURCES          += tun-slip.c
+libtripe_a_SOURCES     += tun-std.c
+libtripe_a_SOURCES     += tun-slip.c
+
+## The main server.
+sbin_PROGRAMS          += tripe
+tripe_SOURCES           = standalone.c
+tripe_LDADD             = libtripe.a $(LDADD)
 
 ## Server manual page.
 man_MANS               += tripe.8tripe
@@ -73,4 +80,11 @@ man_MANS             += tripe-service.7tripe
 CLEANFILES             += tripe-service.7tripe
 EXTRA_DIST             += tripe-service.7.in
 
+###--------------------------------------------------------------------------
+### Unit-test program.
+
+noinst_PROGRAMS                += tripe-test
+tripe_test_SOURCES      = test.c
+tripe_test_LDADD        = libtripe.a $(LDADD)
+
 ###----- That's all, folks --------------------------------------------------
index 0ab72e9..76a358e 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 /*----- Header files ------------------------------------------------------*/
@@ -73,13 +72,23 @@ void am_destroy(addrmap *m)
  * Returns:    The hash of the address.
  */
 
-uint32 hash(const addr *a)
+static uint32 hash(const addr *a)
 {
+  size_t i;
+  uint32 h;
+
   switch (a->sa.sa_family) {
     case AF_INET:
-      return (U32((AF_INET * 0x4eaac1b7ul) +
-                 (a->sin.sin_addr.s_addr * 0xa5dbc837) +
-                 (a->sin.sin_port * 0x3b049e83)));
+      return (U32(0x4eaac1b7ul*AF_INET +
+                 0xa5dbc837ul*a->sin.sin_addr.s_addr +
+                 0x3b049e83ul*a->sin.sin_port));
+    case AF_INET6:
+      for (i = 0, h = 0; i < 16; i++)
+       h = 0x6bd26a67ul*h + a->sin6.sin6_addr.s6_addr[i];
+      return (U32(0x4eaac1b7ul*AF_INET6 +
+                 0xa5dbc837ul*h +
+                 0x1d94eab4ul*a->sin6.sin6_scope_id +
+                 0x3b049e83ul*a->sin6.sin6_port));
     default:
       abort();
   }
@@ -92,7 +101,7 @@ uint32 hash(const addr *a)
  * Returns:    Nonzero if the addresses are equal.
  */
 
-int addreq(const addr *a, const addr *b)
+static int addreq(const addr *a, const addr *b)
 {
   if (a->sa.sa_family != b->sa.sa_family)
     return (0);
@@ -100,6 +109,10 @@ int addreq(const addr *a, const addr *b)
     case AF_INET:
       return (a->sin.sin_addr.s_addr == b->sin.sin_addr.s_addr &&
              a->sin.sin_port == b->sin.sin_port);
+    case AF_INET6:
+      return (!memcmp(a->sin6.sin6_addr.s6_addr,
+                     b->sin6.sin6_addr.s6_addr, 16) &&
+             a->sin6.sin6_port == b->sin6.sin6_port);
     default:
       abort();
   }
index 3076d93..b661e62 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 /*----- Header files ------------------------------------------------------*/
@@ -62,10 +61,14 @@ static const trace_opt w_opts[] = {
 
 /*----- Static variables --------------------------------------------------*/
 
+#ifdef HAVE_LIBADNS
+  static adns_state ads;
+  sel_hook hook;
+#endif
 static admin *admins;
 static admin *a_dead;
 static sel_file sock;
-static const char *sockname;
+static const char *sockname = 0;
 static sym_table a_svcs;
 static unsigned flags = 0;
 static admin *a_stdin = 0;
@@ -257,7 +260,9 @@ static void a_flush(int fd, unsigned mode, void *v)
  *
  *               * "?PEER" PEER -- peer's name
  *
- *               * "?ERRNO" ERRNO -- system error code
+ *               * "?ERR" CODE -- system error code
+ *
+ *               * "?ERRNO" -- system error code from @errno@
  *
  *               * "[!]..." ... -- @dstr_putf@-like string as single token
  */
@@ -273,32 +278,38 @@ void a_vformat(dstr *d, const char *fmt, va_list *ap)
     } else if (*fmt == '?') {
       if (strcmp(fmt, "?ADDR") == 0) {
        const addr *a = va_arg(*ap, const addr *);
-       switch (a->sa.sa_family) {
-         case AF_INET:
-           u_quotify(d, "INET");
-           u_quotify(d, inet_ntoa(a->sin.sin_addr));
-           dstr_putf(d, " %u", (unsigned)ntohs(a->sin.sin_port));
-           break;
-         default:
-           abort();
+       char name[NI_MAXHOST], serv[NI_MAXSERV];
+       int ix, err;
+       if ((err = getnameinfo(&a->sa, addrsz(a),
+                              name, sizeof(name), serv, sizeof(serv),
+                              (NI_NUMERICHOST | NI_NUMERICSERV |
+                               NI_DGRAM)))) {
+         dstr_putf(d, " E%d", err);
+         u_quotify(d, gai_strerror(err));
+       } else {
+         ix = afix(a->sa.sa_family); assert(ix >= 0);
+         u_quotify(d, aftab[ix].name);
+         u_quotify(d, name);
+         u_quotify(d, serv);
        }
       } else if (strcmp(fmt, "?B64") == 0) {
        const octet *p = va_arg(*ap, const octet *);
        size_t n = va_arg(*ap, size_t);
-       base64_ctx b64;
+       codec *b64 = base64_class.encoder(CDCF_NOEQPAD, "", 0);
        dstr_putc(d, ' ');
-       base64_init(&b64);
-       b64.indent = "";
-       b64.maxline = 0;
-       base64_encode(&b64, p, n, d);
-       base64_encode(&b64, 0, 0, d);
-       while (d->len && d->buf[d->len - 1] == '=') d->len--;
+       b64->ops->code(b64, p, n, d);
+       b64->ops->code(b64, 0, 0, d);
+       b64->ops->destroy(b64);
       } else if (strcmp(fmt, "?TOKENS") == 0) {
        const char *const *av = va_arg(*ap, const char *const *);
        while (*av) u_quotify(d, *av++);
       } else if (strcmp(fmt, "?PEER") == 0)
        u_quotify(d, p_name(va_arg(*ap, peer *)));
-      else if (strcmp(fmt, "?ERRNO") == 0) {
+      else if (strcmp(fmt, "?ERR") == 0) {
+       int e = va_arg(*ap, int);
+       dstr_putf(d, " E%d", e);
+       u_quotify(d, strerror(e));
+      } else if (strcmp(fmt, "?ERRNO") == 0) {
        dstr_putf(d, " E%d", errno);
        u_quotify(d, strerror(errno));
       } else
@@ -542,24 +553,6 @@ void a_notify(const char *fmt, ...)
   va_end(ap);
 }
 
-/* --- @a_quit@ --- *
- *
- * Arguments:  ---
- *
- * Returns:    ---
- *
- * Use:                Shuts things down nicely.
- */
-
-void a_quit(void)
-{
-  close(sock.fd);
-  unlink(sockname);
-  FOREACH_PEER(p, { p_destroy(p); });
-  ps_quit();
-  exit(0);
-}
-
 /* --- @a_sigdie@ --- *
  *
  * Arguments:  @int sig@ = signal number
@@ -584,7 +577,7 @@ static void a_sigdie(int sig, void *v)
       break;
   }
   a_warn("SERVER", "quit", "signal", "%s", p, A_END);
-  a_quit();
+  lp_end();
 }
 
 /* --- @a_sighup@ --- *
@@ -1010,6 +1003,101 @@ static void a_svcrelease(admin_service *svc)
 
 /*----- Name resolution operations ----------------------------------------*/
 
+#ifdef HAVE_LIBADNS
+
+/* --- @before_select@ --- *
+ *
+ * Arguments:  @sel_state *s@ = the @sel@ multiplexor (unused)
+ *             @sel_args *a@ = input to @select@, to be updated
+ *             @void *p@ = a context pointer (unused)
+ *
+ * Returns:    ---
+ *
+ * Use:                An I/O multiplexor hook, called just before waiting for I/O
+ *             events.
+ *
+ *             Currently its main purpose is to wire ADNS into the event
+ *             loop.
+ */
+
+static void before_select(sel_state *s, sel_args *a, void *p)
+{
+  struct timeval now;
+  adns_query q;
+  adns_answer *n;
+  admin_resop *r;
+  int any = 0;
+
+  /* --- Check for name-resolution progress --- *
+   *
+   * If there is any, then clobber the timeout: one of the resolver
+   * callbacks might have renewed its interest in a file descriptor, but too
+   * late to affect this @select@ call.
+   *
+   * I think, strictly, this is an mLib bug, but it's cheap enough to hack
+   * around here.  Fixing it will wait for mLib 3.
+   */
+
+  for (;;) {
+    q = 0;
+    if (adns_check(ads, &q, &n, &p)) break;
+    r = p;
+    any = 1;
+    if (n->status != adns_s_ok) {
+      T( trace(T_ADMIN, "admin: resop %s failed: %s",
+              BGTAG(r), adns_strerror(n->status)); )
+      a_bgfail(&r->bg, "resolve-error", "%s", r->addr, A_END);
+      r->func(r, ARES_FAIL);
+    } else {
+      T( trace(T_ADMIN, "admin: resop %s ok", BGTAG(r)); )
+      assert(n->type == adns_r_addr);
+      assert(n->nrrs > 0);
+      assert(n->rrs.addr[0].len <= sizeof(r->sa));
+      memcpy(&r->sa, &n->rrs.addr[0].addr, n->rrs.addr[0].len);
+      setport(&r->sa, r->port);
+      r->func(r, ARES_OK);
+    }
+    free(n);
+    sel_rmtimer(&r->t);
+    xfree(r->addr);
+    a_bgrelease(&r->bg);
+  }
+
+  if (any) { a->tvp = &a->tv; a->tv.tv_sec = 0; a->tv.tv_usec = 0; }
+
+  gettimeofday(&now, 0);
+  adns_beforeselect(ads, &a->maxfd,
+                   &a->fd[SEL_READ], &a->fd[SEL_WRITE], &a->fd[SEL_EXC],
+                   &a->tvp, &a->tv, &now);
+}
+
+/* --- @after_select@ --- *
+ *
+ * Arguments:  @sel_state *s@ = the @sel@ multiplexor (unused)
+ *             @sel_args *a@ = input to @select@, to be updated
+ *             @void *p@ = a context pointer (unused)
+ *
+ * Returns:    ---
+ *
+ * Use:                An I/O multiplexor hook, called just after waiting for I/O
+ *             events.
+ *
+ *             Currently its main purpose is to wire ADNS into the event
+ *             loop.
+ */
+
+static void after_select(sel_state *s, sel_args *a, void *p)
+{
+  struct timeval now;
+
+  gettimeofday(&now, 0);
+  adns_afterselect(ads, a->maxfd,
+                  &a->fd[SEL_READ], &a->fd[SEL_WRITE], &a->fd[SEL_EXC],
+                  &now);
+}
+
+#else
+
 /* --- @a_resolved@ --- *
  *
  * Arguments:  @struct hostent *h@ = pointer to resolved hostname
@@ -1024,13 +1112,17 @@ static void a_resolved(struct hostent *h, void *v)
 {
   admin_resop *r = v;
 
-  T( trace(T_ADMIN, "admin: resop %s resolved", BGTAG(r)); )
   QUICKRAND;
   if (!h) {
+    T( trace(T_ADMIN, "admin: resop %s failed: %s",
+            BGTAG(r), hstrerror(h_errno)); )
     a_bgfail(&r->bg, "resolve-error", "%s", r->addr, A_END);
     r->func(r, ARES_FAIL);
   } else {
+    T( trace(T_ADMIN, "admin: resop %s ok", BGTAG(r)); )
+    r->sa.sin.sin_family = AF_INET;
     memcpy(&r->sa.sin.sin_addr, h->h_addr, sizeof(struct in_addr));
+    setport(&r->sa, r->port);
     r->func(r, ARES_OK);
   }
   sel_rmtimer(&r->t);
@@ -1038,6 +1130,8 @@ static void a_resolved(struct hostent *h, void *v)
   a_bgrelease(&r->bg);
 }
 
+#endif
+
 /* --- @a_restimer@ --- *
  *
  * Arguments:  @struct timeval *tv@ = timer
@@ -1055,7 +1149,11 @@ static void a_restimer(struct timeval *tv, void *v)
   T( trace(T_ADMIN, "admin: resop %s timeout", BGTAG(r)); )
   a_bgfail(&r->bg, "resolver-timeout", "%s", r->addr, A_END);
   r->func(r, ARES_FAIL);
+#ifdef HAVE_LIBADNS
+  adns_cancel(r->q);
+#else
   bres_abort(&r->r);
+#endif
   xfree(r->addr);
   a_bgrelease(&r->bg);
 }
@@ -1077,7 +1175,11 @@ static void a_rescancel(admin_bgop *bg)
   r->func(r, ARES_FAIL);
   sel_rmtimer(&r->t);
   xfree(r->addr);
+#ifdef HAVE_LIBADNS
+  adns_cancel(r->q);
+#else
   bres_abort(&r->r);
+#endif
 }
 
 /* --- @a_resolve@ --- *
@@ -1100,21 +1202,39 @@ static void a_resolve(admin *a, admin_resop *r, const char *tag,
 {
   struct timeval tv;
   unsigned long pt;
+  int af = AF_UNSPEC;
+  const char *fam = "ANY";
   char *p;
-  int i = 0;
+  int i = 0, j;
+  struct addrinfo *ai, *ailist, aihint = { 0 };
+#ifdef HAVE_LIBADNS
+  int err;
+  adns_queryflags qf;
+#endif
 
   /* --- Fill in the easy bits of address --- */
 
   r->bg.tag = "<starting>";
   r->addr = 0;
   r->func = func;
-  if (mystrieq(av[i], "inet")) i++;
+  if (mystrieq(av[i], "any"))
+    { fam = "ANY"; af = AF_UNSPEC; i++; }
+  else for (j = 0; j < NADDRFAM; j++) {
+    if (mystrieq(av[i], aftab[j].name)) {
+      if (udpsock[j].sf.fd < 0) {
+       a_fail(a, "disabled-address-family", "%s", aftab[j].name, A_END);
+       goto fail;
+      }
+      fam = aftab[j].name;
+      af = aftab[j].af;
+      i++;
+      break;
+    }
+  }
   if (ac - i != 1 && ac - i != 2) {
-    a_fail(a, "bad-addr-syntax", "[inet] ADDRESS [PORT]", A_END);
+    a_fail(a, "bad-addr-syntax", "[FAMILY] ADDRESS [PORT]", A_END);
     goto fail;
   }
-  r->sa.sin.sin_family = AF_INET;
-  r->sasz = sizeof(r->sa.sin);
   r->addr = xstrdup(av[i]);
   if (!av[i + 1])
     pt = TRIPE_PORT;
@@ -1133,7 +1253,7 @@ static void a_resolve(admin *a, admin_resop *r, const char *tag,
     a_fail(a, "invalid-port", "%lu", pt, A_END);
     goto fail;
   }
-  r->sa.sin.sin_port = htons(pt);
+  r->port = pt;
 
   /* --- Report backgrounding --- *
    *
@@ -1143,14 +1263,33 @@ static void a_resolve(admin *a, admin_resop *r, const char *tag,
 
   if (a_bgadd(a, &r->bg, tag, a_rescancel))
     goto fail;
-  T( trace(T_ADMIN, "admin: %u, resop %s, hostname `%s'",
-          a->seq, BGTAG(r), r->addr); )
+  T( trace(T_ADMIN, "admin: %u, resop %s, hostname `%s', family `%s'",
+          a->seq, BGTAG(r), r->addr, fam); )
 
   /* --- If the name is numeric, do it the easy way --- */
 
-  if (inet_aton(av[i], &r->sa.sin.sin_addr)) {
-    T( trace(T_ADMIN, "admin: resop %s done the easy way", BGTAG(r)); )
-    func(r, ARES_OK);
+  aihint.ai_family = af;
+  aihint.ai_socktype = SOCK_DGRAM;
+  aihint.ai_protocol = IPPROTO_UDP;
+  aihint.ai_flags = AI_NUMERICHOST;
+  if (!getaddrinfo(av[i], 0, &aihint, &ailist)) {
+    for (ai = ailist; ai; ai = ai->ai_next) {
+      if ((j = afix(ai->ai_family)) >= 0 && udpsock[j].sf.fd >= 0)
+       break;
+    }
+    if (!ai) {
+      T( trace(T_ADMIN, "admin: resop %s failed: "
+              "no suitable addresses returned", BGTAG(r)); )
+      a_bgfail(&r->bg, "resolve-error", "%s" , r->addr, A_END);
+      func(r, ARES_FAIL);
+    } else {
+      T( trace(T_ADMIN, "admin: resop %s done the easy way", BGTAG(r)); )
+      assert(ai->ai_addrlen <= sizeof(r->sa));
+      memcpy(&r->sa, ai->ai_addr, ai->ai_addrlen);
+      setport(&r->sa, r->port);
+      func(r, ARES_OK);
+    }
+    freeaddrinfo(ailist);
     xfree(r->addr);
     a_bgrelease(&r->bg);
     return;
@@ -1161,13 +1300,43 @@ static void a_resolve(admin *a, admin_resop *r, const char *tag,
   gettimeofday(&tv, 0);
   tv.tv_sec += T_RESOLVE;
   sel_addtimer(&sel, &r->t, &tv, a_restimer, r);
+#ifdef HAVE_LIBADNS
+  qf = adns_qf_search;
+  for (j = 0; j < NADDRFAM; j++) {
+    if ((af == AF_UNSPEC || af == aftab[i].af) && udpsock[j].sf.fd >= 0)
+      qf |= aftab[j].qf;
+  }
+  if ((err = adns_submit(ads, r->addr, adns_r_addr, qf, r, &r->q)) != 0) {
+    T( trace(T_ADMIN, "admin: resop %s adns_submit failed: %s",
+            BGTAG(r), strerror(err)); )
+    a_bgfail(&r->bg, "resolve-error", "%s", r->addr, A_END);
+    goto fail_release;
+  }
+#else
+  if (af != AF_UNSPEC && af != AF_INET) {
+    T( trace(T_ADMIN, "admin: resop %s failed: unsupported address family",
+            BGTAG(r)); )
+    a_bgfail(&r->bg, "resolve-error", "%s", r->addr, A_END);
+    goto fail_release;
+  }
+  if (udpsock[AFIX_INET].sf.fd < 0) {
+    a_bgfail(&r->bg, "disabled-address-family", "INET", A_END);
+    goto fail_release;
+  }
   bres_byname(&r->r, r->addr, a_resolved, r);
+#endif
   return;
 
 fail:
   func(r, ARES_FAIL);
   if (r->addr) xfree(r->addr);
   xfree(r);
+  return;
+
+fail_release:
+  func(r, ARES_FAIL);
+  xfree(r->addr);
+  a_bgrelease(&r->bg);
 }
 
 /*----- Option parsing ----------------------------------------------------*/
@@ -1227,7 +1396,6 @@ static void a_doadd(admin_resop *r, int rc)
   T( trace(T_ADMIN, "admin: done add op %s", BGTAG(add)); )
 
   if (rc == ARES_OK) {
-    add->peer.sasz = add->r.sasz;
     add->peer.sa = add->r.sa;
     if (p_findbyaddr(&add->r.sa))
       a_bgfail(&add->r.bg, "peer-addr-exists", "?ADDR", &add->r.sa, A_END);
@@ -1240,6 +1408,8 @@ static void a_doadd(admin_resop *r, int rc)
   }
 
   if (add->peer.tag) xfree(add->peer.tag);
+  if (add->peer.privtag) xfree(add->peer.privtag);
+  if (add->peer.knock) xfree(add->peer.knock);
   xfree(add->peer.name);
 }
 
@@ -1258,6 +1428,7 @@ static void acmd_add(admin *a, unsigned ac, char *av[])
 {
   const char *tag = 0;
   admin_addop *add;
+  const tunnel_ops *tops;
 
   /* --- Set stuff up --- */
 
@@ -1265,8 +1436,9 @@ static void acmd_add(admin *a, unsigned ac, char *av[])
   add->peer.name = 0;
   add->peer.tag = 0;
   add->peer.privtag = 0;
+  add->peer.knock = 0;
   add->peer.t_ka = 0;
-  add->peer.tops = tun_default;
+  add->peer.tops = p_dflttun();
   add->peer.f = 0;
 
   /* --- Parse options --- */
@@ -1274,31 +1446,26 @@ static void acmd_add(admin *a, unsigned ac, char *av[])
   OPTIONS(ac, av, {
     OPTARG("-background", arg, { tag = arg; })
     OPTARG("-tunnel", arg, {
-      unsigned i;
-      for (i = 0;; i++) {
-       if (!tunnels[i]) {
-         a_fail(a, "unknown-tunnel", "%s", arg, A_END);
-         goto fail;
-       }
-       if (mystrieq(arg, tunnels[i]->name)) {
-         add->peer.tops = tunnels[i];
-         break;
-       }
-      }
+      if ((tops = p_findtun(arg)) == 0)
+       { a_fail(a, "unknown-tunnel", "%s", arg, A_END); goto fail; }
+      add->peer.tops = tops;
     })
     OPTTIME("-keepalive", t, { add->peer.t_ka = t; })
     OPT("-cork", { add->peer.f |= KXF_CORK; })
+    OPT("-ephemeral", { add->peer.f |= PSF_EPHEM; })
     OPTARG("-key", arg, {
-      if (add->peer.tag)
-       xfree(add->peer.tag);
+      if (add->peer.tag) xfree(add->peer.tag);
       add->peer.tag = xstrdup(arg);
     })
     OPT("-mobile", { add->peer.f |= PSF_MOBILE; })
     OPTARG("-priv", arg, {
-      if (add->peer.privtag)
-       xfree(add->peer.privtag);
+      if (add->peer.privtag) xfree(add->peer.privtag);
       add->peer.privtag = xstrdup(arg);
     })
+    OPTARG("-knock", arg, {
+      if (add->peer.knock) xfree(add->peer.knock);
+      add->peer.knock = xstrdup(arg);
+    })
   });
 
   /* --- Make sure someone's not got there already --- */
@@ -1325,6 +1492,7 @@ fail:
   if (add->peer.name) xfree(add->peer.name);
   if (add->peer.tag) xfree(add->peer.tag);
   if (add->peer.privtag) xfree(add->peer.privtag);
+  if (add->peer.knock) xfree(add->peer.knock);
   xfree(add);
   return;
 }
@@ -1673,7 +1841,27 @@ static void acmd_warn(admin *a, unsigned ac, char *av[])
   { alertcmd(a, AF_WARN, AF_WARN, "WARN", av); }
 
 static void acmd_port(admin *a, unsigned ac, char *av[])
-  { a_info(a, "%u", p_port(), A_END); a_ok(a); }
+{
+  int i;
+
+  if (ac) {
+    for (i = 0; i < NADDRFAM; i++)
+      if (mystrieq(av[0], aftab[i].name)) goto found;
+    a_fail(a, "unknown-address-family", "%s", av[0], A_END);
+    return;
+  found:
+    if (udpsock[i].sf.fd < 0) {
+      a_fail(a, "disabled-address-family", "%s", aftab[i].name, A_END);
+      return;
+    }
+  } else {
+    for (i = 0; i < NADDRFAM; i++)
+      if (udpsock[i].sf.fd >= 0) goto found;
+    abort();
+  }
+  a_info(a, "%u", udpsock[i].port, A_END);
+  a_ok(a);
+}
 
 static void acmd_daemon(admin *a, unsigned ac, char *av[])
 {
@@ -1783,42 +1971,50 @@ static void acmd_getchal(admin *a, unsigned ac, char *av[])
   buf b;
 
   buf_init(&b, buf_i, PKBUFSZ);
-  c_new(&b);
+  c_new(0, 0, &b);
   a_info(a, "?B64", BBASE(&b), (size_t)BLEN(&b), A_END);
   a_ok(a);
 }
 
 static void acmd_checkchal(admin *a, unsigned ac, char *av[])
 {
-  base64_ctx b64;
+  codec *b64 = base64_class.decoder(CDCF_NOEQPAD);
+  int err;
   buf b;
   dstr d = DSTR_INIT;
 
-  base64_init(&b64);
-  base64_decode(&b64, av[0], strlen(av[0]), &d);
-  base64_decode(&b64, 0, 0, &d);
-  buf_init(&b, d.buf, d.len);
-  if (c_check(&b) || BBAD(&b) || BLEFT(&b))
-    a_fail(a, "invalid-challenge", A_END);
-  else
-    a_ok(a);
+  if ((err = b64->ops->code(b64, av[0], strlen(av[0]), &d)) != 0 ||
+      (err = b64->ops->code(b64, 0, 0, &d)) != 0)
+    a_fail(a, "bad-base64", "%s", codec_strerror(err), A_END);
+  else {
+    buf_init(&b, d.buf, d.len);
+    if (c_check(0, 0, &b) || BBAD(&b) || BLEFT(&b))
+      a_fail(a, "invalid-challenge", A_END);
+    else
+      a_ok(a);
+  }
+  b64->ops->destroy(b64);
   dstr_destroy(&d);
 }
 
 static void acmd_greet(admin *a, unsigned ac, char *av[])
 {
   peer *p;
-  base64_ctx b64;
+  int err;
+  codec *b64;
   dstr d = DSTR_INIT;
 
-  if ((p = a_findpeer(a, av[0])) != 0) {
-    base64_init(&b64);
-    base64_decode(&b64, av[1], strlen(av[1]), &d);
-    base64_decode(&b64, 0, 0, &d);
+  if ((p = a_findpeer(a, av[0])) == 0) return;
+  b64 = base64_class.decoder(CDCF_NOEQPAD);
+  if ((err = b64->ops->code(b64, av[1], strlen(av[1]), &d)) != 0 ||
+      (err = b64->ops->code(b64, 0, 0, &d)) != 0)
+    a_fail(a, "bad-base64", "%s", codec_strerror(err), A_END);
+  else {
     p_greet(p, d.buf, d.len);
-    dstr_destroy(&d);
     a_ok(a);
   }
+  b64->ops->destroy(b64);
+  dstr_destroy(&d);
 }
 
 static void acmd_addr(admin *a, unsigned ac, char *av[])
@@ -1828,7 +2024,6 @@ static void acmd_addr(admin *a, unsigned ac, char *av[])
 
   if ((p = a_findpeer(a, av[0])) != 0) {
     ad = p_addr(p);
-    assert(ad->sa.sa_family == AF_INET);
     a_info(a, "?ADDR", ad, A_END);
     a_ok(a);
   }
@@ -1843,12 +2038,17 @@ static void acmd_peerinfo(admin *a, unsigned ac, char *av[])
   if ((p = a_findpeer(a, av[0])) != 0) {
     ps = p_spec(p);
     a_info(a, "tunnel=%s", ps->tops->name, A_END);
+    if (ps->knock) a_info(a, "knock=%s", ps->knock, A_END);
     a_info(a, "key=%s", p_tag(p),
           "current-key=%s", p->kx.kpub->tag, A_END);
     if ((ptag = p_privtag(p)) == 0) ptag = "(default)";
     a_info(a, "private-key=%s", ptag,
           "current-private-key=%s", p->kx.kpriv->tag, A_END);
     a_info(a, "keepalive=%lu", ps->t_ka, A_END);
+    a_info(a, "corked=%s", BOOL(p->kx.f&KXF_CORK),
+          "mobile=%s", BOOL(ps->f&PSF_MOBILE),
+          "ephemeral=%s", BOOL(ps->f&PSF_EPHEM),
+          A_END);
     a_ok(a);
   }
 }
@@ -1903,7 +2103,7 @@ static void acmd_kill(admin *a, unsigned ac, char *av[])
   peer *p;
 
   if ((p = a_findpeer(a, av[0])) != 0) {
-    p_destroy(p);
+    p_destroy(p, 1);
     a_ok(a);
   }
 }
@@ -1925,7 +2125,7 @@ static void acmd_quit(admin *a, unsigned ac, char *av[])
 {
   a_warn("SERVER", "quit", "admin-request", A_END);
   a_ok(a);
-  a_quit();
+  lp_end();
 }
 
 static void acmd_version(admin *a, unsigned ac, char *av[])
@@ -1936,10 +2136,7 @@ static void acmd_version(admin *a, unsigned ac, char *av[])
 
 static void acmd_tunnels(admin *a, unsigned ac, char *av[])
 {
-  int i;
-
-  for (i = 0; tunnels[i]; i++)
-    a_info(a, "%s", tunnels[i]->name, A_END);
+  FOREACH_TUN(tops, { a_info(a, "%s", tops->name, A_END); });
   a_ok(a);
 }
 
@@ -1973,11 +2170,12 @@ static const acmd acmdtab[] = {
   { "notify",  "MESSAGE ...",          1,      0xffff, acmd_notify },
   { "peerinfo",        "PEER",                 1,      1,      acmd_peerinfo },
   { "ping",    "[OPTIONS] PEER",       1,      0xffff, acmd_ping },
-  { "port",    0,                      0,      0,      acmd_port },
+  { "port",    "[FAMILY]",             0,      1,      acmd_port },
   { "quit",    0,                      0,      0,      acmd_quit },
   { "reload",  0,                      0,      0,      acmd_reload },
   { "servinfo",        0,                      0,      0,      acmd_servinfo },
   { "setifname", "PEER NEW-NAME",      2,      2,      acmd_setifname },
+  { "stats",   "PEER",                 1,      1,      acmd_stats },
   { "svcclaim",        "SERVICE VERSION",      2,      2,      acmd_svcclaim },
   { "svcensure", "SERVICE [VERSION]",  1,      2,      acmd_svcensure },
   { "svcfail", "JOBID TOKENS...",      1,      0xffff, acmd_svcfail },
@@ -1988,7 +2186,6 @@ static const acmd acmdtab[] = {
   { "svcrelease", "SERVICE",           1,      1,      acmd_svcrelease },
   { "svcsubmit", "[OPTIONS] SERVICE TOKENS...",
                                        2,      0xffff, acmd_svcsubmit },
-  { "stats",   "PEER",                 1,      1,      acmd_stats },
 #ifndef NTRACE
   { "trace",   "[OPTIONS]",            0,      1,      acmd_trace },
 #endif
@@ -2053,7 +2250,7 @@ static void a_destroypending(void)
       if (a->f & AF_FOREGROUND) {
        T( trace(T_ADMIN, "admin: foreground client quit: shutting down"); )
        a_warn("SERVER", "quit", "foreground-eof", A_END);
-       a_quit();
+       lp_end();
       }
 
       /* --- Abort any background jobs in progress --- */
@@ -2184,7 +2381,10 @@ static void a_line(char *p, size_t len, void *vp)
  *
  * Returns:    ---
  *
- * Use:                Creates a new admin connection.
+ * Use:                Creates a new admin connection.  It's safe to call this
+ *             before @a_init@ -- and, indeed, this makes sense if you also
+ *             call @a_switcherr@ to report initialization errors through
+ *             the administration machinery.
  */
 
 void a_create(int fd_in, int fd_out, unsigned f)
@@ -2262,36 +2462,33 @@ void a_preselect(void) { if (a_dead) a_destroypending(); }
 
 void a_daemon(void) { flags |= F_DAEMON; }
 
-/* --- @a_init@ --- *
+/* --- @a_listen@ --- *
  *
  * Arguments:  @const char *name@ = socket name to create
  *             @uid_t u@ = user to own the socket
  *             @gid_t g@ = group to own the socket
  *             @mode_t m@ = permissions to set on the socket
  *
- * Returns:    ---
+ * Returns:    Zero on success, @-1@ on failure.
  *
  * Use:                Creates the admin listening socket.
  */
 
-void a_init(const char *name, uid_t u, gid_t g, mode_t m)
+int a_listen(const char *name, uid_t u, gid_t g, mode_t m)
 {
   int fd;
   int n = 5;
   struct sockaddr_un sun;
-  struct sigaction sa;
   size_t sz;
   mode_t omask;
 
-  /* --- Create services table --- */
-
-  sym_create(&a_svcs);
-
   /* --- Set up the socket address --- */
 
   sz = strlen(name) + 1;
-  if (sz > sizeof(sun.sun_path))
-    die(EXIT_FAILURE, "socket name `%s' too long", name);
+  if (sz > sizeof(sun.sun_path)) {
+    a_warn("ADMIN", "admin-socket", "%s", name, "name-too-long", A_END);
+    goto fail_0;
+  }
   BURN(sun);
   sun.sun_family = AF_UNIX;
   memcpy(sun.sun_path, name, sz);
@@ -2301,66 +2498,187 @@ void a_init(const char *name, uid_t u, gid_t g, mode_t m)
 
   omask = umask(0077);
 again:
-  if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
-    die(EXIT_FAILURE, "couldn't create socket: %s", strerror(errno));
+  if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
+    a_warn("ADMIN", "admin-socket", "%s", sun.sun_path,
+          "create-failed", "?ERRNO", A_END);
+    goto fail_1;
+  }
   if (bind(fd, (struct sockaddr *)&sun, sz) < 0) {
     struct stat st;
     int e = errno;
     if (errno != EADDRINUSE) {
-      die(EXIT_FAILURE, "couldn't bind to address `%s': %s",
-         sun.sun_path, strerror(e));
+      a_warn("ADMIN", "admin-socket", "%s", sun.sun_path,
+            "bind-failed", "?ERRNO", A_END);
+      goto fail_2;
+    }
+    if (!n) {
+      a_warn("ADMIN", "admin-socket", "%s", sun.sun_path,
+            "too-many-retries", A_END);
+      goto fail_2;
     }
-    if (!n)
-      die(EXIT_FAILURE, "too many retries; giving up");
     n--;
     if (!connect(fd, (struct sockaddr *)&sun, sz)) {
-      die(EXIT_FAILURE, "server already listening on admin socket `%s'",
-         sun.sun_path);
+      a_warn("ADMIN", "admin-socket", "%s", sun.sun_path,
+            "already-in-use", A_END);
+      goto fail_2;
+    }
+    if (errno != ECONNREFUSED) {
+      a_warn("ADMIN", "admin-socket", "%s", sun.sun_path,
+            "bind-failed", "?ERR", e, A_END);
+      goto fail_2;
     }
-    if (errno != ECONNREFUSED)
-      die(EXIT_FAILURE, "couldn't bind to address: %s", strerror(e));
     if (stat(sun.sun_path, &st)) {
-      die(EXIT_FAILURE, "couldn't stat `%s': %s",
-         sun.sun_path, strerror(errno));
+      if (errno == ENOENT) { close(fd); goto again; }
+      a_warn("ADMIN", "admin-socket", "%s", sun.sun_path,
+            "stat-failed", "?ERRNO", A_END);
+      goto fail_2;
+    }
+    if (!S_ISSOCK(st.st_mode)) {
+      a_warn("ADMIN", "admin-socket", "%s", sun.sun_path,
+            "not-a-socket", A_END);
+      goto fail_2;
     }
-    if (!S_ISSOCK(st.st_mode))
-      die(EXIT_FAILURE, "object `%s' isn't a socket", sun.sun_path);
     T( trace(T_ADMIN, "admin: stale socket found; removing it"); )
     unlink(sun.sun_path);
     close(fd);
     goto again;
   }
   if (chown(sun.sun_path, u, g)) {
-    die(EXIT_FAILURE, "failed to set socket owner: %s",
-       strerror(errno));
+    a_warn("ADMIN", "admin-socket", "%s", sun.sun_path,
+          "chown-failed", "?ERRNO", A_END);
+    goto fail_3;
   }
   if (chmod(sun.sun_path, m)) {
-    die(EXIT_FAILURE, "failed to set socket permissions: %s",
-       strerror(errno));
+    a_warn("ADMIN", "admin-socket", "%s", sun.sun_path,
+          "chmod-failed", "?ERRNO", A_END);
+    goto fail_3;
   }
-  umask(omask);
   fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
-  if (listen(fd, 5))
-    die(EXIT_FAILURE, "couldn't listen on socket: %s", strerror(errno));
+  if (listen(fd, 5)) {
+    a_warn("ADMIN", "admin-socket", "%s", sun.sun_path,
+          "listen-failed", "?ERRNO", A_END);
+    goto fail_3;
+  }
+  umask(omask);
 
   /* --- Listen to the socket --- */
 
   sel_initfile(&sel, &sock, fd, SEL_READ, a_accept, 0);
   sel_addfile(&sock);
   sockname = name;
-  bres_init(&sel);
+
+  return (0);
+
+  /* --- Clean up if things go sideways --- */
+
+fail_3:
+  unlink(sun.sun_path);
+fail_2:
+  close(fd);
+fail_1:
+  umask(omask);
+fail_0:
+  return (-1);
+}
+
+/* --- @a_unlisten@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Stops listening to the administration socket and removes it.
+ */
+
+void a_unlisten(void)
+{
+  if (!sockname) return;
+  sel_rmfile(&sock);
+  unlink(sockname);
+  close(sock.fd);
+}
+
+/* --- @a_switcherr@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Arrange to report warnings, trace messages, etc. to
+ *             administration clients rather than the standard-error stream.
+ *
+ *             Obviously this makes no sense unless there is at least one
+ *             client established.  Calling @a_listen@ won't help with this,
+ *             because the earliest a new client can connect is during the
+ *             first select-loop iteration, which is too late: some initial
+ *             client must have been added manually using @a_create@.
+ */
+
+void a_switcherr(void)
+{
   T( trace_custom(a_trace, 0);
      trace(T_ADMIN, "admin: enabled custom tracing"); )
   flags |= F_INIT;
+}
 
-  /* --- Set up signal handlers --- */
+/* --- @a_signals@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Establishes handlers for the obvious signals.
+ */
+
+void a_signals(void)
+{
+  struct sigaction sa;
 
   sig_add(&s_term, SIGTERM, a_sigdie, 0);
   sig_add(&s_hup, SIGHUP, a_sighup, 0);
-  signal(SIGPIPE, SIG_IGN);
   sigaction(SIGINT, 0, &sa);
   if (sa.sa_handler != SIG_IGN)
     sig_add(&s_int, SIGINT, a_sigdie, 0);
 }
 
+/* --- @a_init@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    Zero on success, @-1@ on failure.
+ *
+ * Use:                Creates the admin listening socket.
+ */
+
+int a_init(void)
+{
+#ifdef HAVE_LIBADNS
+  int err;
+#endif
+
+  /* --- Prepare the background name resolver --- */
+
+#ifdef HAVE_LIBADNS
+  if ((err = adns_init(&ads,
+                      (adns_if_permit_ipv4 | adns_if_permit_ipv6 |
+                       adns_if_noserverwarn | adns_if_nosigpipe |
+                       adns_if_noautosys),
+                      0)) != 0) {
+    a_warn("ADMIN", "adns-init-failed", "?ERRNO", A_END);
+    return (-1);
+  }
+  sel_addhook(&sel, &hook, before_select, after_select, 0);
+#else
+  bres_init(&sel);
+#endif
+
+  /* --- Create services table --- */
+
+  sym_create(&a_svcs);
+
+  /* --- All done --- */
+
+  return (0);
+}
+
 /*----- That's all, folks -------------------------------------------------*/
index 365aa17..4c6be32 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 /*----- Header files ------------------------------------------------------*/
 
 #define TRACE_MACERR(pmac, tagsz) do { IF_TRACING(T_KEYSET, {          \
   trace(T_KEYSET, "keyset: incorrect MAC: decryption failed");         \
-  trace_block(T_CRYPTO, "crypto: expected MAC", (pmac), (tagsz));      \
+  trace_block(T_CRYPTO, "crypto: provided MAC", (pmac), (tagsz));      \
 }) } while (0)
 
+/* --- @derivekey@ --- *
+ *
+ * Arguments:  @octet *k@ = pointer to an output buffer of at least
+ *                     @MAXHASHSZ@ bytes
+ *             @size_t ksz@ = actual size wanted (for tracing)
+ *             @const deriveargs@ = derivation parameters, as passed into
+ *                     @genkeys@
+ *             @int dir@ = direction for the key (@DIR_IN@ or @DIR_OUT@)
+ *             @const char *what@ = label for the key (input to derivation)
+ *
+ * Returns:    ---
+ *
+ * Use:                Derives a session key, for use on incoming or outgoing data.
+ */
+
+static void derivekey(octet *k, size_t ksz, const deriveargs *a,
+                     int dir, const char *what)
+{
+  const gchash *hc = a->hc;
+  ghash *h;
+
+  assert(ksz <= hc->hashsz);
+  assert(hc->hashsz <= MAXHASHSZ);
+  h = GH_INIT(hc);
+  GH_HASH(h, a->what, strlen(a->what)); GH_HASH(h, what, strlen(what) + 1);
+  switch (dir) {
+    case DIR_IN:
+      if (a->x) GH_HASH(h, a->k, a->x);
+      if (a->y != a->x) GH_HASH(h, a->k + a->x, a->y - a->x);
+      break;
+    case DIR_OUT:
+      if (a->y != a->x) GH_HASH(h, a->k + a->x, a->y - a->x);
+      if (a->x) GH_HASH(h, a->k, a->x);
+      break;
+    default:
+      abort();
+  }
+  GH_HASH(h, a->k + a->y, a->z - a->y);
+  GH_DONE(h, k);
+  GH_DESTROY(h);
+  IF_TRACING(T_KEYSET, { IF_TRACING(T_CRYPTO, {
+    char _buf[32];
+    sprintf(_buf, "crypto: %s key %s", dir ? "outgoing" : "incoming", what);
+    trace_block(T_CRYPTO, _buf, k, ksz);
+  }) })
+}
+
 /*----- Common functionality for generic-composition transforms -----------*/
 
 #define CHECK_MAC(h, pmac, tagsz) do {                                 \
@@ -73,7 +119,7 @@ typedef struct gencomp_algs {
 
 typedef struct gencomp_chal {
   bulkchal _b;
-  gmac *m; size_t tagsz;
+  gmac *m;
 } gencomp_chal;
 
 static int gencomp_getalgs(gencomp_algs *a, const algswitch *asw,
@@ -205,25 +251,26 @@ static bulkchal *gencomp_genchal(const gencomp_algs *a)
   return (&gc->_b);
 }
 
-static int gencomp_chaltag(bulkchal *bc, const void *m, size_t msz, void *t)
+static int gencomp_chaltag(bulkchal *bc, const void *m, size_t msz,
+                          uint32 seq, void *t)
 {
   gencomp_chal *gc = (gencomp_chal *)bc;
   ghash *h = GM_INIT(gc->m);
 
-  GH_HASH(h, m, msz);
+  GH_HASHU32(h, seq); if (msz) GH_HASH(h, m, msz);
   memcpy(t, GH_DONE(h, 0), bc->tagsz);
   GH_DESTROY(h);
   return (0);
 }
 
 static int gencomp_chalvrf(bulkchal *bc, const void *m, size_t msz,
-                          const void *t)
+                          uint32 seq, const void *t)
 {
   gencomp_chal *gc = (gencomp_chal *)bc;
   ghash *h = GM_INIT(gc->m);
   int ok;
 
-  GH_HASH(h, m, msz);
+  GH_HASHU32(h, seq); if (msz) GH_HASH(h, m, msz);
   ok = ct_memeq(GH_DONE(h, 0), t, gc->_b.tagsz);
   GH_DESTROY(h);
   return (ok ? 0 : -1);
@@ -238,10 +285,10 @@ static void gencomp_freechal(bulkchal *bc)
  * encrypt the input message with the cipher, and format the type, sequence
  * number, IV, and ciphertext as follows.
  *
- *             +------+ +------+---...---+------...------+
- *             | type | | seq  |   iv    |   ciphertext  |
- *             +------+ +------+---...---+------...------+
- *                32       32     blksz         sz
+ *             +--------+ +--------+---...---+------...------+
+ *             |  type  | |  seq   |   iv    |   ciphertext  |
+ *             +--------+ +--------+---...---+------...------+
+ *                 32         32      blksz         sz
  *
  * All of this is fed into the MAC to compute a tag.  The type is not
  * transmitted: the other end knows what type of message it expects, and the
@@ -249,10 +296,10 @@ static void gencomp_freechal(bulkchal *bc)
  * kind of ciphertext has been substituted.  The tag is prepended to the
  * remainder, to yield the finished cryptogram, as follows.
  *
- *             +---...---+------+---...---+------...------+
- *             |   tag   | seq  |   iv    |   ciphertext  |
- *             +---...---+------+---...---+------...------+
- *                tagsz     32     blksz         sz
+ *             +---...---+--------+---...---+------...------+
+ *             |   tag   |  seq   |   iv    |   ciphertext  |
+ *             +---...---+--------+---...---+------...------+
+ *                tagsz      32      blksz         sz
  *
  * Decryption: checks the overall size, verifies the tag, then decrypts the
  * ciphertext and extracts the sequence number.
@@ -310,7 +357,7 @@ static size_t v0_overhead(const bulkalgs *aa)
 static size_t v0_expsz(const bulkalgs *aa)
   { const v0_algs *a = (const v0_algs *)aa; return (gencomp_expsz(&a->ga)); }
 
-static bulkctx *v0_genkeys(const bulkalgs *aa, const struct rawkey *rk)
+static bulkctx *v0_genkeys(const bulkalgs *aa, const deriveargs *da)
 {
   const v0_algs *a = (const v0_algs *)aa;
   v0_ctx *bc = CREATE(v0_ctx);
@@ -319,9 +366,10 @@ static bulkctx *v0_genkeys(const bulkalgs *aa, const struct rawkey *rk)
 
   bc->tagsz = a->ga.tagsz;
   for (i = 0; i < NDIR; i++) {
-    ks_derivekey(k, a->ga.cksz, rk, i, "encryption");
+    if (!(da->f&(1 << i))) { bc->d[i].c = 0; bc->d[i].m = 0; continue; }
+    derivekey(k, a->ga.cksz, da, i, "encryption");
     bc->d[i].c = GC_INIT(a->ga.c, k, a->ga.cksz);
-    ks_derivekey(k, a->ga.mksz, rk, i, "integrity");
+    derivekey(k, a->ga.mksz, da, i, "integrity");
     bc->d[i].m = GM_KEY(a->ga.m, k, a->ga.mksz);
   }
   return (&bc->_b);
@@ -345,8 +393,8 @@ static void v0_freectx(bulkctx *bbc)
   int i;
 
   for (i = 0; i < NDIR; i++) {
-    GC_DESTROY(bc->d[i].c);
-    GM_DESTROY(bc->d[i].m);
+    if (bc->d[i].c) GC_DESTROY(bc->d[i].c);
+    if (bc->d[i].m) GM_DESTROY(bc->d[i].m);
   }
   DESTROY(bc);
 }
@@ -360,10 +408,13 @@ static int v0_encrypt(bulkctx *bbc, unsigned ty,
   const octet *p = BCUR(b);
   size_t sz = BLEFT(b);
   octet *qmac, *qseq, *qiv, *qpk;
-  size_t ivsz = GC_CLASS(c)->blksz;
+  size_t ivsz;
   size_t tagsz = bc->tagsz;
   octet t[4];
 
+  assert(c);
+  ivsz = GC_CLASS(c)->blksz;
+
   /* --- Determine the ciphertext layout --- */
 
   if (buf_ensure(bb, tagsz + SEQSZ + ivsz + sz)) return (0);
@@ -420,10 +471,13 @@ static int v0_decrypt(bulkctx *bbc, unsigned ty,
   octet *q = BCUR(bb);
   ghash *h;
   gcipher *c = bc->d[DIR_IN].c;
-  size_t ivsz = GC_CLASS(c)->blksz;
+  size_t ivsz;
   size_t tagsz = bc->tagsz;
   octet t[4];
 
+  assert(c);
+  ivsz = GC_CLASS(c)->blksz;
+
   /* --- Break up the packet into its components --- */
 
   if (psz < ivsz + SEQSZ + tagsz) {
@@ -473,10 +527,10 @@ static int v0_decrypt(bulkctx *bbc, unsigned ty,
  *
  * So, a MAC is computed over
  *
- *             +------+ +------+------...------+
- *             | type | | seq  |   ciphertext  |
- *             +------+ +------+------...------+
- *                32       32         sz
+ *             +--------+ +--------+------...------+
+ *             |  type  | |  seq   |   ciphertext  |
+ *             +--------+ +--------+------...------+
+ *                 32         32          sz
  *
  * and we actually transmit the following as the cryptogram.
  *
@@ -537,7 +591,8 @@ static void iiv_tracealgs(const bulkalgs *aa)
   const iiv_algs *a = (const iiv_algs *)aa;
 
   gencomp_tracealgs(&a->ga);
-  trace(T_CRYPTO, "crypto: blkc = %.*s", strlen(a->b->name) - 4, a->b->name);
+  trace(T_CRYPTO,
+       "crypto: blkc = %.*s", (int)strlen(a->b->name) - 4, a->b->name);
 }
 #endif
 
@@ -587,7 +642,7 @@ static size_t iiv_expsz(const bulkalgs *aa)
   return (gencomp_expsz(&a->ga));
 }
 
-static bulkctx *iiv_genkeys(const bulkalgs *aa, const struct rawkey *rk)
+static bulkctx *iiv_genkeys(const bulkalgs *aa, const deriveargs *da)
 {
   const iiv_algs *a = (const iiv_algs *)aa;
   iiv_ctx *bc = CREATE(iiv_ctx);
@@ -596,11 +651,13 @@ static bulkctx *iiv_genkeys(const bulkalgs *aa, const struct rawkey *rk)
 
   bc->tagsz = a->ga.tagsz;
   for (i = 0; i < NDIR; i++) {
-    ks_derivekey(k, a->ga.cksz, rk, i, "encryption");
+    if (!(da->f&(1 << i)))
+      { bc->d[i].c = 0; bc->d[i].b = 0; bc->d[i].m = 0; continue; }
+    derivekey(k, a->ga.cksz, da, i, "encryption");
     bc->d[i].c = GC_INIT(a->ga.c, k, a->ga.cksz);
-    ks_derivekey(k, a->bksz, rk, i, "blkc");
+    derivekey(k, a->bksz, da, i, "blkc");
     bc->d[i].b = GC_INIT(a->b, k, a->bksz);
-    ks_derivekey(k, a->ga.mksz, rk, i, "integrity");
+    derivekey(k, a->ga.mksz, da, i, "integrity");
     bc->d[i].m = GM_KEY(a->ga.m, k, a->ga.mksz);
   }
   return (&bc->_b);
@@ -624,9 +681,9 @@ static void iiv_freectx(bulkctx *bbc)
   int i;
 
   for (i = 0; i < NDIR; i++) {
-    GC_DESTROY(bc->d[i].c);
-    GC_DESTROY(bc->d[i].b);
-    GM_DESTROY(bc->d[i].m);
+    if (bc->d[i].c) GC_DESTROY(bc->d[i].c);
+    if (bc->d[i].b) GC_DESTROY(bc->d[i].b);
+    if (bc->d[i].m) GM_DESTROY(bc->d[i].m);
   }
   DESTROY(bc);
 }
@@ -644,10 +701,14 @@ static int iiv_encrypt(bulkctx *bbc, unsigned ty,
   const octet *p = BCUR(b);
   size_t sz = BLEFT(b);
   octet *qmac, *qseq, *qpk;
-  size_t ivsz = GC_CLASS(c)->blksz, blkcsz = GC_CLASS(blkc)->blksz;
+  size_t ivsz, blkcsz;
   size_t tagsz = bc->tagsz;
   octet t[4];
 
+  assert(c); assert(blkc);
+  ivsz = GC_CLASS(c)->blksz;
+  blkcsz = GC_CLASS(blkc)->blksz;
+
   /* --- Determine the ciphertext layout --- */
 
   if (buf_ensure(bb, tagsz + SEQSZ + sz)) return (0);
@@ -707,10 +768,14 @@ static int iiv_decrypt(bulkctx *bbc, unsigned ty,
   octet *q = BCUR(bb);
   ghash *h;
   gcipher *c = bc->d[DIR_IN].c, *blkc = bc->d[DIR_IN].b;
-  size_t ivsz = GC_CLASS(c)->blksz, blkcsz = GC_CLASS(blkc)->blksz;
+  size_t ivsz, blkcsz;
   size_t tagsz = bc->tagsz;
   octet t[4];
 
+  assert(c); assert(blkc);
+  ivsz = GC_CLASS(c)->blksz;
+  blkcsz = GC_CLASS(blkc)->blksz;
+
   /* --- Break up the packet into its components --- */
 
   if (psz < SEQSZ + tagsz) {
@@ -749,122 +814,164 @@ static int iiv_decrypt(bulkctx *bbc, unsigned ty,
   return (0);
 }
 
-/*----- The NaCl box transform --------------------------------------------*
+/*----- The AEAD transform ------------------------------------------------*
  *
- * This transform is very similar to the NaCl `crypto_secretbox' transform
- * described in Bernstein, `Cryptography in NaCl', with the difference that,
- * rather than using XSalsa20, we use either Salsa20/r or ChaChar, because we
- * have no need of XSalsa20's extended nonce.  The default cipher is Salsa20.
+ * This transform uses a general authenticated encryption scheme (the
+ * additional data isn't necessary).  Good options include
+ * `chacha20-poly1305' or `rijndael-ocb3'.
  *
- * Salsa20 and ChaCha accept a 64-bit nonce.  The low 32 bits are the
- * sequence number, and the high 32 bits are the type, both big-endian.
+ * To be acceptable, the scheme must accept at least a 40-bit nonce.  (All of
+ * Catacomb's current AEAD schemes are suitable.)  The low 32 bits are the
+ * sequence number.  The type is written to the next 8--32 bytes: if the
+ * nonce size is 64 bits or more (preferred, for compatibility reasons) then
+ * the type is written as 32 bits, and the remaining space is padded with
+ * zero bytes; otherwise, the type is right-aligned in the remaining space.
+ * Both fields are big-endian.
  *
- *             +------+------+
- *             | seq  | type |
- *             +------+------+
- *                32     32
+ *             +--------+--+
+ *             |  seq   |ty|
+ *             +--------+--+
+ *                 32    8
  *
- * A stream is generated by concatenating the raw output blocks generated
- * with this nonce and successive counter values starting from zero.  The
- * first 32 bytes of the stream are used as a key for Poly1305: the first 16
- * bytes are the universal hash key r, and the second 16 bytes are the mask
- * value s.
+ *             +--------+----+
+ *             |  seq   | ty |
+ *             +--------+----+
+ *                 32     16
  *
- *             +------+------+ +------...------+
- *             |  r   |  s   | |   keystream   |
- *             +------+------+ +------...------+
- *               128    128           sz
+ *             +--------+------+
+ *             |  seq   | type |
+ *             +--------+------+
+ *                 32      24
  *
- * The remainder of the stream is XORed with the incoming plaintext to form a
- * ciphertext with the same length.  The ciphertext (only) is then tagged
- * using Poly1305.  The tag, sequence number, and ciphertext are concatenated
- * in this order, and transmitted.
+ *             +--------+--------+---...---+
+ *             |  seq   |  type  |    0    |
+ *             +--------+--------+---...---+
+ *                 32       32     nsz - 64
  *
+ * The ciphertext is formatted as
  *
- *             +---...---+------+------...------+
- *             |   tag   | seq  |   ciphertext  |
- *             +---...---+------+------...------+
- *                 128     32          sz
+ *             +---...---+--------+------...------+
+ *             |   tag   |  seq   |   ciphertext  |
+ *             +---...---+--------+------...------+
+ *                tagsz      32          sz
  *
- * Note that there is no need to authenticate the type separately, since it
- * was used to select the cipher nonce, and hence the Poly1305 key.  The
- * Poly1305 tag length is fixed.
  */
 
-typedef struct naclbox_algs {
+#define AEAD_NONCEMAX 64
+
+typedef struct aead_algs {
   bulkalgs _b;
-  const gccipher *c; size_t cksz;
-} naclbox_algs;
+  const gcaead *c;
+  size_t ksz, nsz, tsz;
+} aead_algs;
 
-typedef struct naclbox_ctx {
+typedef struct aead_ctx {
   bulkctx _b;
-  struct { gcipher *c; } d[NDIR];
-} naclbox_ctx;
-
+  struct { gaead_key *k; } d[NDIR];
+  size_t nsz, tsz;
+} aead_ctx;
 
-static bulkalgs *naclbox_getalgs(const algswitch *asw, dstr *e,
-                                key_file *kf, key *k)
+static bulkalgs *aead_getalgs(const algswitch *asw, dstr *e,
+                             key_file *kf, key *k)
 {
-  naclbox_algs *a = CREATE(naclbox_algs);
+  aead_algs *a = CREATE(aead_algs);
   const char *p;
   char *qq;
+  gaead_key *kk = 0;
+  size_t ksz;
+  size_t csz = 0;
   unsigned long n;
 
   /* --- Collect the selected cipher and check that it's supported --- */
 
-  p = key_getattr(kf, k, "cipher");
-  if (!p || strcmp(p, "salsa20") == 0) a->c = &salsa20;
-  else if (strcmp(p, "salsa20/12") == 0) a->c = &salsa2012;
-  else if (strcmp(p, "salsa20/8") == 0) a->c = &salsa208;
-  else if (strcmp(p, "chacha20") == 0) a->c = &chacha20;
-  else if (strcmp(p, "chacha12") == 0) a->c = &chacha12;
-  else if (strcmp(p, "chacha8") == 0) a->c = &chacha8;
-  else {
-    a_format(e, "unknown-cipher", "%s", p, A_END);
+  p = key_getattr(kf, k, "cipher"); if (!p) p = "rijndael-ocb3";
+  a->c = gaead_byname(p);
+  if (!a->c) { a_format(e, "unknown-cipher", "%s", p, A_END); goto fail; }
+  if (a->c->f&AEADF_NOAAD) {
+    a_format(e, "unsuitable-aead-cipher", "%s", p, "no-aad", A_END);
+    goto fail;
+  }
+  a->nsz = keysz_pad(8, a->c->noncesz);
+  if (!a->nsz) a->nsz = keysz_pad(5, a->c->noncesz);
+  if (!a->nsz) {
+    a_format(e, "unsuitable-aead-cipher", "%s", p, "nonce-too-small", A_END);
+    goto fail;
+  } else if (a->nsz > AEAD_NONCEMAX) {
+    a_format(e, "unsuitable-aead-cipher", "%s", p, "nonce-too-large", A_END);
     goto fail;
   }
 
-  /* --- Collect the selected MAC, and check the tag length --- */
+  /* --- Collect the selected MAC, and check the tag length --- *
+   *
+   * Of course, there isn't a separate MAC, so only accept `aead'.
+   */
 
-  p = key_getattr(kf, k, "mac");
+  p = key_getattr(kf, k, "tagsz");
+  if (!p) {
+    p = key_getattr(kf, k, "mac");
+    if (!p) ;
+    else if (strncmp(p, "aead", 4) != 0 || (p[4] && p[4] != '/'))
+      { a_format(e, "unknown-mac", "%s", p, A_END); goto fail; }
+    else if (p[4] == '/') p += 5;
+    else p = 0;
+  }
   if (!p)
-    ;
-  else if (strncmp(p, "poly1305", 8) != 0 || (p[8] && p[8] != '/')) {
-    a_format(e, "unknown-mac", "%s", p, A_END);
-    goto fail;
-  } else if (p[8] == '/') {
-    n = strtoul(p + 9, &qq, 0);
+    a->tsz = keysz(0, a->c->tagsz);
+  else {
+    n = strtoul(p, &qq, 0);
     if (*qq) {
-      a_format(e, "bad-tag-length-string", "%s", p + 9, A_END);
+      a_format(e, "bad-tag-length-string", "%s", p, A_END);
       goto fail;
     }
-    if (n != 128) {
-      a_format(e, "bad-tag-length", "%lu", n, A_END);
+    if (n%8 || (a->tsz = keysz(n/8, a->c->tagsz)) == 0)
+      { a_format(e, "bad-tag-length", "%lu", n, A_END); goto fail; }
+  }
+
+  /* --- Check that an empty message gives an empty ciphertext --- *
+   *
+   * This is necessary for producing challenges.  If the overhead is zero
+   * then we're fine; otherwise, we have to check the hard way.
+   */
+
+  if (a->c->ohd) {
+    ksz = keysz(0, a->c->keysz);
+    memset(buf_t, 0, ksz > a->nsz ? ksz : a->nsz);
+    kk = GAEAD_KEY(a->c, buf_t, ksz);
+    if (gaead_encrypt(kk, buf_t, a->nsz,
+                     buf_t, ksz,
+                     0, 0,
+                     buf_t, &csz,
+                     buf_t, a->tsz)) {
+      a_format(e, "unsuitable-aead-cipher", "%s", a->c->name,
+              "nonempty-ciphertext-for-empty-message", A_END);
       goto fail;
     }
+    GAEAD_DESTROY(kk); kk = 0;
   }
 
   return (&a->_b);
 fail:
+  if (kk) GAEAD_DESTROY(kk);
   DESTROY(a);
   return (0);
 }
 
 #ifndef NTRACE
-static void naclbox_tracealgs(const bulkalgs *aa)
+static void aead_tracealgs(const bulkalgs *aa)
 {
-  const naclbox_algs *a = (const naclbox_algs *)aa;
+  const aead_algs *a = (const aead_algs *)aa;
 
   trace(T_CRYPTO, "crypto: cipher = %s", a->c->name);
-  trace(T_CRYPTO, "crypto: mac = poly1305/128");
+  trace(T_CRYPTO, "crypto: noncesz = %lu", (unsigned long)a->nsz);
+  trace(T_CRYPTO, "crypto: tagsz = %lu", (unsigned long)a->tsz);
 }
 #endif
 
-static int naclbox_checkalgs(bulkalgs *aa, const algswitch *asw, dstr *e)
+static int aead_checkalgs(bulkalgs *aa, const algswitch *asw, dstr *e)
 {
-  naclbox_algs *a = (naclbox_algs *)aa;
+  aead_algs *a = (aead_algs *)aa;
 
-  if ((a->cksz = keysz(asw->hashsz, a->c->keysz)) == 0) {
+  if ((a->ksz = keysz(asw->hashsz, a->c->keysz)) == 0) {
     a_format(e, "cipher", "%s", a->c->name,
             "no-key-size", "%lu", (unsigned long)asw->hashsz,
             A_END);
@@ -873,196 +980,388 @@ static int naclbox_checkalgs(bulkalgs *aa, const algswitch *asw, dstr *e)
   return (0);
 }
 
-static int naclbox_samealgsp(const bulkalgs *aa, const bulkalgs *bb)
+static int aead_samealgsp(const bulkalgs *aa, const bulkalgs *bb)
 {
-  const naclbox_algs *a = (const naclbox_algs *)aa,
-    *b = (const naclbox_algs *)bb;
-  return (a->c == b->c);
+  const aead_algs *a = (const aead_algs *)aa,
+    *b = (const aead_algs *)bb;
+  return (a->c == b->c && a->tsz == b->tsz);
 }
 
-static void naclbox_alginfo(const bulkalgs *aa, admin *adm)
+static void aead_alginfo(const bulkalgs *aa, admin *adm)
 {
-  const naclbox_algs *a = (const naclbox_algs *)aa;
-  a_info(adm, "cipher=%s", a->c->name, "cipher-keysz=32", A_END);
-  a_info(adm, "mac=poly1305", "mac-tagsz=16", A_END);
+  const aead_algs *a = (const aead_algs *)aa;
+  a_info(adm, "cipher=%s", a->c->name,
+        "cipher-keysz=%lu", (unsigned long)a->ksz,
+        A_END);
+  a_info(adm, "mac=aead", "mac-tagsz=%lu", (unsigned long)a->tsz, A_END);
 }
 
-static size_t naclbox_overhead(const bulkalgs *aa)
-  { return (POLY1305_TAGSZ + SEQSZ); }
+static size_t aead_overhead(const bulkalgs *aa)
+{
+  const aead_algs *a = (const aead_algs *)aa;
+  return (a->tsz + SEQSZ + a->c->ohd);
+}
 
-static size_t naclbox_expsz(const bulkalgs *aa)
-  { return (MEG(2048)); }
+static size_t aead_expsz(const bulkalgs *aa)
+{
+  const aead_algs *a = (const aead_algs *)aa;
+  return (a->c->blksz < 16 ? MEG(64) : MEG(2048));
+}
 
-static bulkctx *naclbox_genkeys(const bulkalgs *aa, const struct rawkey *rk)
+static bulkctx *aead_genkeys(const bulkalgs *aa, const deriveargs *da)
 {
-  const naclbox_algs *a = (const naclbox_algs *)aa;
-  naclbox_ctx *bc = CREATE(naclbox_ctx);
+  const aead_algs *a = (const aead_algs *)aa;
+  aead_ctx *bc = CREATE(aead_ctx);
   octet k[MAXHASHSZ];
   int i;
 
   for (i = 0; i < NDIR; i++) {
-    ks_derivekey(k, a->cksz, rk, i, "encryption");
-    bc->d[i].c = GC_INIT(a->c, k, a->cksz);
+    if (!(da->f&(1 << i))) { bc->d[i].k = 0; continue; }
+    derivekey(k, a->ksz, da, i, "encryption");
+    bc->d[i].k = GAEAD_KEY(a->c, k, a->ksz);
   }
+  bc->nsz = a->nsz; bc->tsz = a->tsz;
   return (&bc->_b);
 }
 
-typedef struct naclbox_chal {
+typedef struct aead_chal {
   bulkchal _b;
-  gcipher *c;
-} naclbox_chal;
+  gaead_key *k;
+} aead_chal;
 
-static bulkchal *naclbox_genchal(const bulkalgs *aa)
+static bulkchal *aead_genchal(const bulkalgs *aa)
 {
-  const naclbox_algs *a = (const naclbox_algs *)aa;
-  naclbox_chal *c = CREATE(naclbox_chal);
-  rand_get(RAND_GLOBAL, buf_t, a->cksz);
-  c->c = GC_INIT(a->c, buf_t, a->cksz);
+  const aead_algs *a = (const aead_algs *)aa;
+  aead_chal *c = CREATE(aead_chal);
+  rand_get(RAND_GLOBAL, buf_t, a->ksz);
+  c->k = GAEAD_KEY(a->c, buf_t, a->ksz);
   IF_TRACING(T_CHAL, {
     trace(T_CHAL, "chal: generated new challenge key");
-    trace_block(T_CRYPTO, "chal: new key", buf_t, a->cksz);
+    trace_block(T_CRYPTO, "chal: new key", buf_t, a->ksz);
   })
-  c->_b.tagsz = 16;
+  c->_b.tagsz = a->tsz;
   return (&c->_b);
 }
 
-static int naclbox_chaltag(bulkchal *bc, const void *m, size_t msz, void *t)
+static int aead_chaltag(bulkchal *bc, const void *m, size_t msz,
+                       uint32 seq, void *t)
 {
-  naclbox_chal *c = (naclbox_chal *)bc;
-  octet b0[SALSA20_NONCESZ];
-  assert(msz <= sizeof(b0));
-  memcpy(b0, m, msz); memset(b0 + msz, 0, sizeof(b0) - msz);
-  GC_SETIV(c->c, b0);
-  GC_ENCRYPT(c->c, 0, t, c->_b.tagsz);
+  aead_chal *c = (aead_chal *)bc;
+  octet b[AEAD_NONCEMAX];
+  size_t nsz = keysz_pad(4, c->k->ops->c->noncesz);
+  size_t csz = 0;
+  int rc;
+
+  assert(nsz); assert(nsz <= sizeof(b));
+  memset(b, 0, nsz - 4); STORE32(b + nsz - 4, seq);
+  rc = gaead_encrypt(c->k, b, nsz, m, msz, 0, 0,
+                    buf_t, &csz, t, c->_b.tagsz);
+  assert(!rc);
   return (0);
 }
 
-static int naclbox_chalvrf(bulkchal *bc, const void *m, size_t msz,
-                          const void *t)
+static int aead_chalvrf(bulkchal *bc, const void *m, size_t msz,
+                          uint32 seq, const void *t)
 {
-  naclbox_chal *c = (naclbox_chal *)bc;
-  octet b0[SALSA20_NONCESZ], b1[16];
-  assert(msz <= sizeof(b0)); assert(c->_b.tagsz <= sizeof(b1));
-  memcpy(b0, m, msz); memset(b0 + msz, 0, sizeof(b0) - msz);
-  GC_SETIV(c->c, b0);
-  GC_ENCRYPT(c->c, 0, b1, c->_b.tagsz);
-  return (ct_memeq(t, b1, c->_b.tagsz) ? 0 : -1);
+  aead_chal *c = (aead_chal *)bc;
+  octet b[AEAD_NONCEMAX];
+  size_t nsz = keysz(4, c->k->ops->c->noncesz);
+  size_t psz = 0;
+  int rc;
+
+  assert(nsz); assert(nsz <= sizeof(b));
+  memset(b, 0, nsz - 4); STORE32(b + nsz - 4, seq);
+  rc = gaead_decrypt(c->k, b, nsz, m, msz, 0, 0,
+                    buf_t, &psz, t, c->_b.tagsz);
+  assert(rc >= 0);
+  return (rc == 1 ? 0 : -1);
 }
 
-static void naclbox_freechal(bulkchal *bc)
-  { naclbox_chal *c = (naclbox_chal *)bc; GC_DESTROY(c->c); DESTROY(c); }
+static void aead_freechal(bulkchal *bc)
+  { aead_chal *c = (aead_chal *)bc; GAEAD_DESTROY(c->k); DESTROY(c); }
 
-static void naclbox_freealgs(bulkalgs *aa)
-  { naclbox_algs *a = (naclbox_algs *)aa; DESTROY(a); }
+static void aead_freealgs(bulkalgs *aa)
+  { aead_algs *a = (aead_algs *)aa; DESTROY(a); }
 
-static void naclbox_freectx(bulkctx *bbc)
+static void aead_freectx(bulkctx *bbc)
 {
-  naclbox_ctx *bc = (naclbox_ctx *)bbc;
+  aead_ctx *bc = (aead_ctx *)bbc;
   int i;
 
-  for (i = 0; i < NDIR; i++) GC_DESTROY(bc->d[i].c);
+  for (i = 0; i < NDIR; i++) { if (bc->d[i].k) GAEAD_DESTROY(bc->d[i].k); }
   DESTROY(bc);
 }
 
-static int naclbox_encrypt(bulkctx *bbc, unsigned ty,
-                          buf *b, buf *bb, uint32 seq)
+static void aead_fmtnonce(aead_ctx *bc, octet *n, uint32 seq, unsigned ty)
 {
-  naclbox_ctx *bc = (naclbox_ctx *)bbc;
-  gcipher *c = bc->d[DIR_OUT].c;
-  poly1305_key polyk;
-  poly1305_ctx poly;
+  assert(bc->nsz <= AEAD_NONCEMAX); assert(ty <= 255);
+  STORE32(n, seq);
+  switch (bc->nsz) {
+    case 5: STORE8(n + SEQSZ, ty); break;
+    case 6: STORE16(n + SEQSZ, ty); break;
+    case 7: STORE24(n + SEQSZ, ty); break;
+    default: memset(n + 8, 0, bc->nsz - 8); /* and continue */
+    case 8: STORE32(n + SEQSZ, ty); break;
+  }
+  TRACE_IV(n, bc->nsz);
+}
+
+static int aead_encrypt(bulkctx *bbc, unsigned ty,
+                       buf *b, buf *bb, uint32 seq)
+{
+  aead_ctx *bc = (aead_ctx *)bbc;
   const octet *p = BCUR(b);
+  gaead_key *k = bc->d[DIR_OUT].k;
   size_t sz = BLEFT(b);
+  size_t csz = sz + k->ops->c->ohd;
   octet *qmac, *qseq, *qpk;
+  octet n[AEAD_NONCEMAX];
+  int rc;
 
-  /* --- Determine the ciphertext layout --- */
-
-  if (buf_ensure(bb, POLY1305_TAGSZ + SEQSZ + sz)) return (0);
-  qmac = BCUR(bb); qseq = qmac + POLY1305_TAGSZ; qpk = qseq + SEQSZ;
-  BSTEP(bb, POLY1305_TAGSZ + SEQSZ + sz);
-
-  /* --- Construct and set the nonce --- */
+  assert(k);
 
+  if (buf_ensure(bb, bc->tsz + SEQSZ + csz)) return (0);
+  qmac = BCUR(bb); qseq = qmac + bc->tsz; qpk = qseq + SEQSZ;
   STORE32(qseq, seq);
-  memcpy(buf_u, qseq, SEQSZ); STORE32(buf_u + SEQSZ, ty);
-  GC_SETIV(c, buf_u);
-  TRACE_IV(buf_u, SALSA20_NONCESZ);
 
-  /* --- Determine the MAC key --- */
-
-  GC_ENCRYPT(c, 0, buf_u, POLY1305_KEYSZ + POLY1305_MASKSZ);
-  poly1305_keyinit(&polyk, buf_u, POLY1305_KEYSZ);
-  poly1305_macinit(&poly, &polyk, buf_u + POLY1305_KEYSZ);
-
-  /* --- Encrypt the message --- */
-
-  GC_ENCRYPT(c, p, qpk, sz);
-  TRACE_CT(qpk, sz);
-
-  /* --- Compute the MAC --- */
-
-  poly1305_hash(&poly, qpk, sz);
-  poly1305_done(&poly, qmac);
-  TRACE_MAC(qmac, POLY1305_TAGSZ);
-
-  /* --- We're done --- */
+  aead_fmtnonce(bc, n, seq, ty);
+  rc = gaead_encrypt(k, n, bc->nsz, 0, 0, p, sz, qpk, &csz, qmac, bc->tsz);
+  assert(!rc);
+  BSTEP(bb, bc->tsz + SEQSZ + csz);
+  TRACE_CT(qpk, csz);
+  TRACE_MAC(qmac, bc->tsz);
 
   return (0);
 }
 
-static int naclbox_decrypt(bulkctx *bbc, unsigned ty,
-                      buf *b, buf *bb, uint32 *seq)
+static int aead_decrypt(bulkctx *bbc, unsigned ty,
+                      buf *b, buf *bb, uint32 *seq_out)
 {
-  naclbox_ctx *bc = (naclbox_ctx *)bbc;
-  gcipher *c = bc->d[DIR_IN].c;
-  poly1305_key polyk;
-  poly1305_ctx poly;
+  aead_ctx *bc = (aead_ctx *)bbc;
+  gaead_key *k = bc->d[DIR_IN].k;
   const octet *pmac, *pseq, *ppk;
+  uint32 seq;
   size_t psz = BLEFT(b);
   size_t sz;
   octet *q = BCUR(bb);
+  octet n[AEAD_NONCEMAX];
+  int rc;
 
-  /* --- Break up the packet into its components --- */
+  assert(k);
 
-  if (psz < SEQSZ + POLY1305_TAGSZ) {
+  if (psz < bc->tsz + SEQSZ) {
     T( trace(T_KEYSET, "keyset: block too small for keyset"); )
     return (KSERR_MALFORMED);
   }
-  sz = psz - SEQSZ - POLY1305_TAGSZ;
-  pmac = BCUR(b); pseq = pmac + POLY1305_TAGSZ; ppk = pseq + SEQSZ;
+  sz = psz - bc->tsz - SEQSZ;
+  pmac = BCUR(b); pseq = pmac + bc->tsz; ppk = pseq + SEQSZ;
+  seq = LOAD32(pseq);
 
-  /* --- Construct and set the nonce --- */
+  aead_fmtnonce(bc, n, seq, ty);
+  rc = gaead_decrypt(k, n, bc->nsz, 0, 0, ppk, sz, q, &sz, pmac, bc->tsz);
+  assert(rc >= 0);
+  if (!rc) { TRACE_MACERR(pmac, bc->tsz); return (KSERR_DECRYPT); }
 
-  memcpy(buf_u, pseq, SEQSZ); STORE32(buf_u + SEQSZ, ty);
-  GC_SETIV(c, buf_u);
-  TRACE_IV(buf_u, SALSA20_NONCESZ);
+  *seq_out = seq;
+  BSTEP(bb, sz);
+  return (0);
+}
 
-  /* --- Determine the MAC key --- */
+/*----- The NaCl box transform --------------------------------------------*
+ *
+ * This transform is very similar to the NaCl `crypto_secretbox' transform
+ * described in Bernstein, `Cryptography in NaCl', with the difference that,
+ * rather than using XSalsa20, we use either Salsa20/r or ChaChar, because we
+ * have no need of XSalsa20's extended nonce.  The default cipher is Salsa20.
+ *
+ * Salsa20 and ChaCha accept a 64-bit nonce.  The low 32 bits are the
+ * sequence number, and the high 32 bits are the type, both big-endian.
+ *
+ *             +--------+--------+
+ *             |  seq   |  type  |
+ *             +--------+--------+
+ *                 32       32
+ *
+ * A stream is generated by concatenating the raw output blocks generated
+ * with this nonce and successive counter values starting from zero.  The
+ * first 32 bytes of the stream are used as a key for Poly1305: the first 16
+ * bytes are the universal hash key r, and the second 16 bytes are the mask
+ * value s.
+ *
+ *             +------+------+ +------...------+
+ *             |  r   |  s   | |   keystream   |
+ *             +------+------+ +------...------+
+ *               128    128           sz
+ *
+ * The remainder of the stream is XORed with the incoming plaintext to form a
+ * ciphertext with the same length.  The ciphertext (only) is then tagged
+ * using Poly1305.  The tag, sequence number, and ciphertext are concatenated
+ * in this order, and transmitted.
+ *
+ *
+ *             +---...---+------+------...------+
+ *             |   tag   | seq  |   ciphertext  |
+ *             +---...---+------+------...------+
+ *                 128     32          sz
+ *
+ * Note that there is no need to authenticate the type separately, since it
+ * was used to select the cipher nonce, and hence the Poly1305 key.  The
+ * Poly1305 tag length is fixed.
+ */
 
-  GC_ENCRYPT(c, 0, buf_u, POLY1305_KEYSZ + POLY1305_MASKSZ);
-  poly1305_keyinit(&polyk, buf_u, POLY1305_KEYSZ);
-  poly1305_macinit(&poly, &polyk, buf_u + POLY1305_KEYSZ);
+typedef struct naclbox_algs {
+  aead_algs _b;
+  const gccipher *c;
+} naclbox_algs;
 
-  /* --- Verify the MAC on the packet --- */
+static bulkalgs *naclbox_getalgs(const algswitch *asw, dstr *e,
+                                key_file *kf, key *k)
+{
+  naclbox_algs *a = CREATE(naclbox_algs);
+  const char *p;
+  char *qq;
+  unsigned long n;
+
+  /* --- Collect the selected cipher and check that it's supported --- */
 
-  poly1305_hash(&poly, ppk, sz);
-  poly1305_done(&poly, buf_u);
-  if (!ct_memeq(buf_u, pmac, POLY1305_TAGSZ)) {
-    TRACE_MACERR(pmac, POLY1305_TAGSZ);
-    return (KSERR_DECRYPT);
+  p = key_getattr(kf, k, "cipher");
+  if (!p || strcmp(p, "salsa20") == 0)
+    { a->_b.c = &salsa20_naclbox; a->c = &salsa20; }
+  else if (strcmp(p, "salsa20/12") == 0)
+    { a->_b.c = &salsa2012_naclbox; a->c = &salsa2012; }
+  else if (strcmp(p, "salsa20/8") == 0)
+    { a->_b.c = &salsa208_naclbox; a->c = &salsa208; }
+  else if (strcmp(p, "chacha20") == 0)
+    { a->_b.c = &chacha20_naclbox; a->c = &chacha20; }
+  else if (strcmp(p, "chacha12") == 0)
+    { a->_b.c = &chacha12_naclbox; a->c = &chacha12; }
+  else if (strcmp(p, "chacha8") == 0)
+    { a->_b.c = &chacha8_naclbox; a->c = &chacha8; }
+  else {
+    a_format(e, "unknown-cipher", "%s", p, A_END);
+    goto fail;
   }
+  a->_b.nsz = 8;
 
-  /* --- Decrypt the packet --- */
+  /* --- Collect the selected MAC, and check the tag length --- */
 
-  GC_DECRYPT(c, ppk, q, sz);
+  p = key_getattr(kf, k, "mac");
+  if (!p)
+    ;
+  else if (strncmp(p, "poly1305", 8) != 0 || (p[8] && p[8] != '/')) {
+    a_format(e, "unknown-mac", "%s", p, A_END);
+    goto fail;
+  } else if (p[8] == '/') {
+    n = strtoul(p + 9, &qq, 0);
+    if (*qq) {
+      a_format(e, "bad-tag-length-string", "%s", p + 9, A_END);
+      goto fail;
+    }
+    if (n != 128) {
+      a_format(e, "bad-tag-length", "%lu", n, A_END);
+      goto fail;
+    }
+  }
+  a->_b.tsz = 16;
 
-  /* --- Finished --- */
+  return (&a->_b._b);
+fail:
+  DESTROY(a);
+  return (0);
+}
 
-  *seq = LOAD32(pseq);
-  BSTEP(bb, sz);
+#ifndef NTRACE
+static void naclbox_tracealgs(const bulkalgs *aa)
+{
+  const naclbox_algs *a = (const naclbox_algs *)aa;
+
+  trace(T_CRYPTO, "crypto: cipher = %s", a->c->name);
+  trace(T_CRYPTO, "crypto: mac = poly1305/128");
+}
+#endif
+
+#define naclbox_checkalgs aead_checkalgs
+#define naclbox_samealgsp aead_samealgsp
+
+static void naclbox_alginfo(const bulkalgs *aa, admin *adm)
+{
+  const naclbox_algs *a = (const naclbox_algs *)aa;
+  a_info(adm, "cipher=%s", a->c->name, "cipher-keysz=32", A_END);
+  a_info(adm, "mac=poly1305", "mac-tagsz=16", A_END);
+}
+
+#define naclbox_overhead aead_overhead
+#define naclbox_expsz aead_expsz
+#define naclbox_genkeys aead_genkeys
+
+typedef struct naclbox_chal {
+  bulkchal _b;
+  gcipher *c;
+} naclbox_chal;
+
+static bulkchal *naclbox_genchal(const bulkalgs *aa)
+{
+  const naclbox_algs *a = (const naclbox_algs *)aa;
+  naclbox_chal *c = CREATE(naclbox_chal);
+  rand_get(RAND_GLOBAL, buf_t, a->_b.ksz);
+  c->c = GC_INIT(a->c, buf_t, a->_b.ksz);
+  IF_TRACING(T_CHAL, {
+    trace(T_CHAL, "chal: generated new challenge key");
+    trace_block(T_CRYPTO, "chal: new key", buf_t, a->_b.ksz);
+  })
+  c->_b.tagsz = POLY1305_TAGSZ;
+  return (&c->_b);
+}
+
+static int naclbox_chaltag(bulkchal *bc, const void *m, size_t msz,
+                          uint32 seq, void *t)
+{
+  naclbox_chal *c = (naclbox_chal *)bc;
+  poly1305_key pk;
+  poly1305_ctx pm;
+  octet b[POLY1305_KEYSZ + POLY1305_MASKSZ];
+
+  STATIC_ASSERT(SALSA20_NONCESZ <= sizeof(b), "Need more space for nonce");
+
+  memset(b, 0, SALSA20_NONCESZ - 4); STORE32(b + SALSA20_NONCESZ - 4, seq);
+  GC_SETIV(c->c, b); GC_ENCRYPT(c->c, 0, b, sizeof(b));
+  poly1305_keyinit(&pk, b, POLY1305_KEYSZ);
+  poly1305_macinit(&pm, &pk, b + POLY1305_KEYSZ);
+  if (msz) poly1305_hash(&pm, m, msz);
+  poly1305_done(&pm, t);
   return (0);
 }
 
+static int naclbox_chalvrf(bulkchal *bc, const void *m, size_t msz,
+                          uint32 seq, const void *t)
+{
+  naclbox_chal *c = (naclbox_chal *)bc;
+  poly1305_key pk;
+  poly1305_ctx pm;
+  octet b[POLY1305_KEYSZ + POLY1305_MASKSZ];
+
+  STATIC_ASSERT(SALSA20_NONCESZ <= sizeof(b), "Need more space for nonce");
+  STATIC_ASSERT(POLY1305_TAGSZ <= sizeof(b), "Need more space for tag");
+
+  memset(b, 0, SALSA20_NONCESZ - 4); STORE32(b + SALSA20_NONCESZ - 4, seq);
+  GC_SETIV(c->c, b); GC_ENCRYPT(c->c, 0, b, sizeof(b));
+  poly1305_keyinit(&pk, b, POLY1305_KEYSZ);
+  poly1305_macinit(&pm, &pk, b + POLY1305_KEYSZ);
+  if (msz) poly1305_hash(&pm, m, msz);
+  poly1305_done(&pm, b);
+  return (ct_memeq(t, b, POLY1305_TAGSZ) ? 0 : -1);
+}
+
+static void naclbox_freechal(bulkchal *bc)
+  { naclbox_chal *c = (naclbox_chal *)bc; GC_DESTROY(c->c); DESTROY(c); }
+
+static void naclbox_freealgs(bulkalgs *aa)
+  { naclbox_algs *a = (naclbox_algs *)aa; DESTROY(a); }
+
+#define naclbox_freectx aead_freectx
+#define naclbox_encrypt aead_encrypt
+#define naclbox_decrypt aead_decrypt
+
 /*----- Bulk crypto transform table ---------------------------------------*/
 
 const bulkops bulktab[] = {
@@ -1079,6 +1378,7 @@ const bulkops bulktab[] = {
 
   BULK("v0", v0),
   BULK("iiv", iiv),
+  BULK("aead", aead),
   BULK("naclbox", naclbox),
 
 #undef BULK
index 71fde49..68d7f04 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 /*----- Header files ------------------------------------------------------*/
 
 /*----- Static variables --------------------------------------------------*/
 
-static bulkchal *bulk;
+static bulkchal *bchal;
 static uint32 oseq;
 static seqwin iseq;
 
-/*----- Main code ---------------------------------------------------------*/
+/*----- Challenges --------------------------------------------------------*/
 
 /* --- @c_genkey@ --- *
  *
@@ -47,72 +46,84 @@ static seqwin iseq;
 
 static void c_genkey(void)
 {
-  if (bulk && bulk->ops == master->algs.bulk->ops && oseq < 0x07ffffff)
-    return;
-  if (bulk) bulk->ops->freechal(bulk);
-  bulk = master->algs.bulk->ops->genchal(master->algs.bulk);
-  bulk->ops = master->algs.bulk->ops;
+  bulkalgs *bulk = master->algs.bulk;
+  if (bchal && bchal->ops == bulk->ops && oseq < 0x07ffffff) return;
+  if (bchal) bchal->ops->freechal(bchal);
+  bchal = bulk->ops->genchal(bulk);
+  bchal->ops = bulk->ops;
   oseq = 0;
   seq_reset(&iseq);
 }
 
 /* --- @c_new@ --- *
  *
- * Arguments:  @buf *b@ = where to put the challenge
+ * Arguments:  @const void *m@ = pointer to associated message, or null
+ *             @size_t msz@ = length of associated message
+ *             @buf *b@ = where to put the challenge
  *
  * Returns:    Zero if OK, nonzero on error.
  *
  * Use:                Issues a new challenge.
  */
 
-int c_new(buf *b)
+int c_new(const void *m, size_t msz, buf *b)
 {
-  octet *p;
+  const octet *p;
+  octet *t;
+  int rc;
 
   c_genkey();
   p = BCUR(b);
-  if (buf_putu32(b, oseq++) || !buf_get(b, bulk->tagsz)) return (-1);
-  if (bulk->ops->chaltag(bulk, p, 4, p + 4)) return (-1);
+  if (buf_putu32(b, oseq) || (t = buf_get(b, bchal->tagsz)) == 0)
+    { rc = -1; goto done; }
+  if (bchal->ops->chaltag(bchal, m, msz, oseq, t)) { rc = -1; goto done; }
   IF_TRACING(T_CHAL, {
-    trace(T_CHAL, "chal: issuing challenge %lu", (unsigned long)(oseq - 1));
+    trace(T_CHAL, "chal: issuing challenge %lu", (unsigned long)oseq);
+    if (msz) trace_block(T_CRYPTO, "chal: message block", m, msz);
     trace_block(T_CRYPTO, "chal: challenge block", p, BCUR(b) - p);
   })
-  return (0);
+  rc = 0;
+done:
+  oseq++;
+  return (rc);
 }
 
 /* --- @c_check@ --- *
  *
- * Arguments:  @buf *b@ = where to find the challenge
+ * Arguments:  @const void *m@ = pointer to associated message, or null
+ *             @size_t msz@ = length of associated message
+ *             @buf *b@ = where to find the challenge
  *
  * Returns:    Zero if OK, nonzero if it didn't work.
  *
  * Use:                Checks a challenge.  On failure, the buffer is broken.
  */
 
-int c_check(buf *b)
+int c_check(const void *m, size_t msz, buf *b)
 {
-  const octet *p;
-  size_t sz;
+  const octet *p, *t;
   uint32 seq;
 
-  if (!bulk) {
+  if (!bchal) {
     a_warn("CHAL", "impossible-challenge", A_END);
     goto fail;
   }
-  sz = 4 + bulk->tagsz;
-  if ((p = buf_get(b, sz)) == 0) {
+  p = BCUR(b);
+  if (buf_getu32(b, &seq) || (t = buf_get(b, bchal->tagsz)) == 0) {
     a_warn("CHAL", "invalid-challenge", A_END);
     goto fail;
   }
-  IF_TRACING(T_CHAL, trace_block(T_CRYPTO, "chal: check challenge", p, sz); )
-  if (bulk->ops->chalvrf(bulk, p, 4, p + 4)) {
+  IF_TRACING(T_CHAL, {
+    trace(T_CHAL, "chal: checking challenge, seq = %lu", (unsigned long)seq);
+    if (msz) trace_block(T_CRYPTO, "chal: message block", m, msz);
+    trace_block(T_CRYPTO, "chal: check challenge", p, BCUR(b) - p);
+  })
+  if (bchal->ops->chalvrf(bchal, m, msz, seq, t)) {
     a_warn("CHAL", "incorrect-tag", A_END);
     goto fail;
   }
-  seq = LOAD32(p);
-  if (seq_check(&iseq, seq, "CHAL"))
-    goto fail;
-  T( trace(T_CHAL, "chal: checked challenge %lu", (unsigned long)seq); )
+  if (seq_check(&iseq, seq, "CHAL")) goto fail;
+  T( trace(T_CHAL, "chal: challenge ok"); )
   return (0);
 
 fail:
index 5c27a07..f8862ee 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 /*----- Header files ------------------------------------------------------*/
@@ -702,7 +701,9 @@ static void ecdh_freege(const dhgrp *gg, dhge *YY)
   }                                                                    \
                                                                        \
   KLOAD(xdh, xdh, XDH,                                                 \
-       { kd->grp = CREATE(dhgrp); kd->grp->scsz = 32; },               \
+       { kd->grp = CREATE(dhgrp);                                      \
+         kd->grp->scsz = XDH##_KEYSZ;                                  \
+       },                                                              \
        { if ((kd->k = xdh##_bintosc(&p.priv)) == 0) {                  \
            a_format(e, "bad-private-key", A_END);                      \
            goto fail;                                                  \
index fb0e453..b141100 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 /*----- Header files ------------------------------------------------------*/
  *
  * %$\cookie{kx-switch-ok}, E_K(u_A))$%
  *     Switch received.  Committed; send data; move to @KXS_SWITCH@.
+ *
+ * %$\cookie{kx-token-request}, u, E_L(n)$%
+ *     %$L = H(u, u^\alpha)$%, and %$n$% is a string of the form
+ *     `[PEER.]KEYTAG'.  Expect %$\cookie{kx-token}$% by return.
+ *
+ * %$\cookie{kx-token}, v, E_{L'}(t)$%
+ *     %$L' = H(v, v^\alpha)$%, and %$t$% is a token associated with %$n$%
+ *     (see %$\cookie{kx-token-request}$% above).
+ *
+ * %$\cookie{kx-knock}, u, E_L(n, t), r_A$%
+ *     %$L$%, %$n$% and %$t$% are as %$\cookie{kx-token}$% and
+ *     %$\cookie{kx-token-request}$%; %$r_A$% is as in
+ *     %$\cookie{kx-pre-challenge}$%.  If the token %$t$% doesn't match
+ *     %$n$%, then warn and discard.  If a peer named PEER (or KEYTAG)
+ *     exists then proceed as for %$\cookie{kx-pre-challenge}$%.  Otherwise
+ *     issue a notification `NOTE KNOCK PEER ADDR...' and discard.
  */
 
 /*----- Static tables -----------------------------------------------------*/
 
 static const char *const pkname[] = {
-  "pre-challenge", "challenge", "reply", "switch-rq", "switch-ok"
+  "pre-challenge", "challenge", "reply", "switch-rq", "switch-ok",
+  "token-rq", "token", "knock"
 };
 
 /*----- Various utilities -------------------------------------------------*/
@@ -169,7 +185,7 @@ static void mpmask(buf *b, const dhgrp *g, const dhsc *x, size_t n,
  */
 
 static dhsc *mpunmask(const dhgrp *g, const octet *p, size_t n,
-                   const gccipher *mgfc, const octet *k, size_t ksz)
+                     const gccipher *mgfc, const octet *k, size_t ksz)
 {
   gcipher *mgf;
   dhsc *x;
@@ -374,6 +390,66 @@ static void rs_time(retry *rs, struct timeval *tv, const struct timeval *now)
 
 static void rs_reset(retry *rs) { rs->t = 0; }
 
+/* --- @notice_message@ --- *
+ *
+ * Arguments:  @keyexch *kx@ = pointer to key-exchange block
+ *
+ * Returns:    Zero if OK; @-1@ if the public key is in a bad state.
+ *
+ * Use:                Updates the key-exchange state following a received message.
+ *             Specifically, if there's no currently active key-exchange in
+ *             progress, and we're not in the cooling-off period, then
+ *             commence a new one; reset the retry timers; and if we're
+ *             corked then pop the cork so that we can reply.
+ */
+
+static int checkpub(keyexch *kx);
+static void stop(keyexch *kx);
+static void start(keyexch *kx, time_t now);
+
+static int notice_message(keyexch *kx)
+{
+  struct timeval now, tv;
+
+  gettimeofday(&now, 0);
+  rs_reset(&kx->rs);
+  if (kx->f & KXF_CORK) {
+    start(kx, now.tv_sec);
+    rs_time(&kx->rs, &tv, &now);
+    settimer(kx, &tv);
+    a_notify("KXSTART", "?PEER", kx->p, A_END);
+  }
+  if (checkpub(kx)) return (-1);
+  if (!VALIDP(kx, now.tv_sec)) {
+    stop(kx);
+    start(kx, now.tv_sec);
+  }
+  return (0);
+}
+
+/* --- @update_stats_tx@, @update_stats_rx@ --- *
+ *
+ * Arguments:  @keyexch *kx@ = pointer to key-exchange block
+ *             @int ok@ = nonzero if the message was valid (for @rx@)
+ *             @size_t sz@ = size of sent message
+ *
+ * Returns:    ---
+ *
+ * Use:                Records that a key-exchange message was sent to, or received
+ *             from, the peer.
+ */
+
+static void update_stats_tx(keyexch *kx, size_t sz)
+  { stats *st = p_stats(kx->p); st->n_kxout++; st->sz_kxout += sz; }
+
+static void update_stats_rx(keyexch *kx, int ok, size_t sz)
+{
+  stats *st = p_stats(kx->p);
+
+  if (!ok) st->n_reject++;
+  else { st->n_kxin++; st->sz_kxin += sz; }
+}
+
 /*----- Challenge management ----------------------------------------------*/
 
 /* --- Notes on challenge management --- *
@@ -528,7 +604,6 @@ static void kxc_timer(struct timeval *tv, void *v)
 
 static void kxc_answer(keyexch *kx, kxchal *kxc)
 {
-  stats *st = p_stats(kx->p);
   buf *b = p_txstart(kx->p, MSG_KEYEXCH | KX_REPLY);
   const dhgrp *g = kx->kpriv->grp;
   struct timeval tv;
@@ -546,8 +621,7 @@ static void kxc_answer(keyexch *kx, kxchal *kxc)
   /* --- Update the statistics --- */
 
   if (BOK(b)) {
-    st->n_kxout++;
-    st->sz_kxout += BLEN(b);
+    update_stats_tx(kx, BLEN(b));
     p_txend(kx->p);
   }
 
@@ -563,6 +637,148 @@ static void kxc_answer(keyexch *kx, kxchal *kxc)
 
 /*----- Individual message handlers ---------------------------------------*/
 
+static ratelim unauth_limit;
+
+/* --- @dotokenrq@ --- *
+ *
+ * Arguments:  @const addr *a@ = sender's address
+ *             @buf *b@ = buffer containing the packet
+ *
+ * Returns:    ---
+ *
+ * Use:                Processes a token-request message.
+ */
+
+static void dotokenrq(const addr *a, buf *b)
+{
+  uint32 id;
+  kdata *kpriv = 0, *kpub = 0;
+  char *pname;
+  const char *tag;
+  size_t sz;
+  buf bb, bbb;
+
+  /* --- Check if we're in danger of overloading --- */
+
+  if (ratelim_withdraw(&unauth_limit, 1)) goto done;
+
+  /* --- Start building the reply --- */
+
+  buf_init(&bbb, buf_o, sizeof(buf_o));
+  buf_putu8(&bbb, MSG_KEYEXCH | KX_TOKEN);
+
+  /* --- Fetch and copy the challenge string --- */
+
+  if (buf_getbuf16(b, &bb)) goto done;
+  buf_putmem16(&bbb, BBASE(&bb), BSZ(&bb));
+
+  /* --- Make our own challenge for the response --- */
+
+  buf_init(&bb, buf_t, sizeof(buf_t));
+  c_new(0, 0, &bb); assert(BOK(&bb)); buf_putbuf16(&bbb, &bb);
+
+  /* --- Figure out which private key I'm supposed to use --- */
+
+  if (buf_getu32(b, &id)) goto done;
+  if ((kpriv = km_findprivbyid(id)) == 0) goto done;
+
+  /* --- Decrypt the message --- */
+
+  buf_init(&bb, buf_t, sizeof(buf_t));
+  if (ies_decrypt(kpriv, MSG_KEYEXCH | KX_TOKENRQ, b, &bb) || BLEFT(b))
+    goto done;
+
+  /* --- Parse the token request and find the sender's public key --- */
+
+  assert(BOK(&bb)); buf_flip(&bb);
+  if ((pname = buf_getmem16(&bb, &sz)) == 0 || memchr(pname, 0, sz))
+    goto done;
+  assert(sz < sizeof(buf_t) - ((const octet *)pname - buf_t));
+  pname[sz] = 0;
+  if ((tag = strchr(pname, '.')) != 0) tag++;
+  else tag = pname;
+  if ((kpub = km_findpub(tag)) == 0) goto done;
+
+  /* --- Build and encrypt the token --- */
+
+  buf_init(&bb, buf_i, sizeof(buf_i));
+  c_new(pname, sz, &bb);
+  assert(BOK(&bb)); buf_flip(&bb);
+  if (ies_encrypt(kpub, MSG_KEYEXCH | KX_TOKEN, &bb, &bbb)) goto done;
+  assert(BOK(&bbb));
+
+  /* --- Send the response -- or at least give it a try --- */
+
+  p_txaddr(a, BBASE(&bbb), BLEN(&bbb));
+
+  /* --- All done --- */
+
+done:
+  if (kpriv) km_unref(kpriv);
+  if (kpub) km_unref(kpub);
+}
+
+/* --- @dotoken@ --- *
+ *
+ * Arguments:  @keyexch *kx@ = pointer to key exchange block
+ *             @buf *b@ = buffer containing the packet
+ *
+ * Returns:    Zero if OK, nonzero of the packet was rejected.
+ *
+ * Use:                Processes a token message.
+ */
+
+static int dotoken(keyexch *kx, buf *b)
+{
+  buf bb;
+  buf *bbb;
+  const dhgrp *g = kx->kpriv->grp;
+  octet *p;
+  size_t sz;
+
+  /* --- Make sure this is a sensible message to have received --- */
+
+  if (!kx->p->spec.knock) return (-1);
+
+  /* --- First, collect and verify our challenge --- */
+
+  if (buf_getbuf16(b, &bb) || c_check(0, 0, &bb) || BLEFT(&bb)) return (-1);
+
+  /* --- Start building the knock message from here --- */
+
+  bbb = p_txstart(kx->p, MSG_KEYEXCH | KX_KNOCK);
+
+  /* --- Copy the peer's challenge --- */
+
+  if (buf_getbuf16(b, &bb)) return (-1);
+  buf_putmem16(bbb, BBASE(&bb), BSZ(&bb));
+
+  /* --- Add the key indicator --- */
+
+  buf_putu32(bbb, kx->kpub->id);
+
+  /* --- Building the knock payload --- */
+
+  buf_init(&bb, buf_t, sizeof(buf_t));
+  buf_putstr16(&bb, kx->p->spec.knock);
+  sz = BLEN(&bb)%64; if (sz) sz = 64 - sz;
+  if (ies_decrypt(kx->kpriv, MSG_KEYEXCH | KX_TOKEN, b, &bb)) return (-1);
+  p = buf_get(&bb, sz); assert(p); memset(p, 0, sz);
+  assert(BOK(&bb)); buf_flip(&bb);
+  if (ies_encrypt(kx->kpub, MSG_KEYEXCH | KX_KNOCK, &bb, bbb)) return (-1);
+
+  /* --- Finally, the pre-challenge group element --- */
+
+  g->ops->stge(g, bbb, kx->C, DHFMT_VAR);
+
+  /* --- And we're done --- */
+
+  if (BBAD(bbb)) return (-1);
+  update_stats_tx(kx, BLEN(bbb));
+  p_txend(kx->p);
+  return (0);
+}
+
 /* --- @doprechallenge@ --- *
  *
  * Arguments:  @keyexch *kx@ = pointer to key exchange block
@@ -575,7 +791,6 @@ static void kxc_answer(keyexch *kx, kxchal *kxc)
 
 static int doprechallenge(keyexch *kx, buf *b)
 {
-  stats *st = p_stats(kx->p);
   const dhgrp *g = kx->kpriv->grp;
   dhge *C = 0;
   ghash *h;
@@ -604,8 +819,7 @@ static int doprechallenge(keyexch *kx, buf *b)
   hashge(h, g, C);
   sendchallenge(kx, b, C, GH_DONE(h, 0));
   GH_DESTROY(h);
-  st->n_kxout++;
-  st->sz_kxout += BLEN(b);
+  update_stats_tx(kx, BLEN(b));
   p_txend(kx->p);
 
   /* --- Done --- */
@@ -618,6 +832,73 @@ bad:
   return (-1);
 }
 
+/* --- @doknock@ --- *
+ *
+ * Arguments:  @const addr *a@ = sender's address
+ *             @buf *b@ = buffer containing the packet
+ *
+ * Returns:    ---
+ *
+ * Use:                Processes a knock message.
+ */
+
+static void doknock(const addr *a, buf *b)
+{
+  keyexch *kx;
+  peer *p;
+  uint32 id;
+  kdata *kpriv = 0;
+  char *pname;
+  size_t sz, msgsz = BLEN(b);
+  buf bb;
+  int rc;
+
+  /* --- Read and check the challenge --- */
+
+  buf_getbuf16(b, &bb);
+  if (c_check(0, 0, &bb)) goto done;
+
+  /* --- Figure out which private key I'm supposed to use --- */
+
+  if (buf_getu32(b, &id)) goto done;
+  if ((kpriv = km_findprivbyid(id)) == 0) goto done;
+
+  /* --- Decrypt and check the peer's name against the token --- */
+
+  buf_init(&bb, buf_t, sizeof(buf_t));
+  if (ies_decrypt(kpriv, MSG_KEYEXCH | KX_KNOCK, b, &bb)) goto done;
+  assert(BOK(&bb)); buf_flip(&bb);
+  if ((pname = buf_getmem16(&bb, &sz)) == 0 ||
+      memchr(pname, 0, sz) ||
+      c_check(pname, sz, &bb))
+    goto done;
+  assert(sz < sizeof(buf_t) - ((const octet *)pname - buf_t));
+  pname[sz] = 0;
+
+  /* --- If we can't find the peer, then issue a notification --- */
+
+  if ((p = p_find(pname)) == 0) {
+    a_notify("KNOCK", "%s", pname, "?ADDR", a, A_END);
+    goto done;
+  }
+
+  /* --- Update the peer's address --- */
+
+  kx = &p->kx;
+  p_updateaddr(kx->p, a);
+
+  /* --- Now treat the remainder of the message as a pre-challenge --- */
+
+  notice_message(kx);
+  rc = doprechallenge(kx, b);
+  update_stats_rx(kx, !rc, msgsz);
+
+  /* --- All done: clean up --- */
+
+done:
+  if (kpriv) km_unref(kpriv);
+}
+
 /* --- @respond@ --- *
  *
  * Arguments:  @keyexch *kx@ = pointer to key exchange block
@@ -638,8 +919,8 @@ static kxchal *respond(keyexch *kx, unsigned msg, buf *b)
   dhge *C = 0;
   dhge *R = 0;
   dhge *CC = 0;
+  deriveargs a;
   const octet *hc, *ck;
-  size_t x, y, z;
   dhsc *c = 0;
   kxchal *kxc;
   ghash *h = 0;
@@ -753,13 +1034,13 @@ static kxchal *respond(keyexch *kx, unsigned msg, buf *b)
 
     /* --- Create a new symmetric keyset --- */
 
-    buf_init(&bb, buf_o, sizeof(buf_o));
-    g->ops->stge(g, &bb, kx->C, DHFMT_HASH); x = BLEN(&bb);
-    g->ops->stge(g, &bb, kxc->C, DHFMT_HASH); y = BLEN(&bb);
-    g->ops->stge(g, &bb, R, DHFMT_HASH); z = BLEN(&bb);
+    buf_init(&bb, buf_o, sizeof(buf_o)); a.k = BBASE(&bb);
+    g->ops->stge(g, &bb, kx->C, DHFMT_HASH); a.x = BLEN(&bb);
+    g->ops->stge(g, &bb, kxc->C, DHFMT_HASH); a.y = BLEN(&bb);
+    g->ops->stge(g, &bb, R, DHFMT_HASH); a.z = BLEN(&bb);
     assert(BOK(&bb));
 
-    kxc->ks = ks_gen(BBASE(&bb), x, y, z, kx->p);
+    kxc->ks = ks_gen(&a, kx->p);
   }
 
   if (C) g->ops->freege(g, C);
@@ -824,17 +1105,37 @@ static void resend(keyexch *kx)
 {
   kxchal *kxc;
   buf bb;
-  stats *st = p_stats(kx->p);
   struct timeval tv;
   const dhgrp *g = kx->kpriv->grp;
+  octet *p;
+  size_t sz;
   buf *b;
 
   switch (kx->s) {
     case KXS_CHAL:
-      T( trace(T_KEYEXCH, "keyexch: sending prechallenge to `%s'",
-              p_name(kx->p)); )
-      b = p_txstart(kx->p, MSG_KEYEXCH | KX_PRECHAL);
-      g->ops->stge(g, b, kx->C, DHFMT_VAR);
+      if (!kx->p->spec.knock) {
+       T( trace(T_KEYEXCH, "keyexch: sending prechallenge to `%s'",
+                p_name(kx->p)); )
+       b = p_txstart(kx->p, MSG_KEYEXCH | KX_PRECHAL);
+       g->ops->stge(g, b, kx->C, DHFMT_VAR);
+      } else {
+       T( trace(T_KEYEXCH, "keyexch: sending token-request to `%s'",
+                p_name(kx->p)); )
+       b = p_txstart(kx->p, MSG_KEYEXCH | KX_TOKENRQ);
+
+       buf_init(&bb, buf_t, sizeof(buf_t));
+       c_new(0, 0, &bb); assert(BOK(&bb)); buf_putbuf16(b, &bb);
+
+       buf_putu32(b, kx->kpub->id);
+
+       buf_init(&bb, buf_t, sizeof(buf_t));
+       buf_putstr16(&bb, kx->p->spec.knock);
+       sz = BLEN(&bb)%64; if (sz) sz = 64 - sz;
+       p = buf_get(&bb, sz); assert(p); memset(p, 0, sz);
+       assert(BOK(&bb)); buf_flip(&bb);
+       if (ies_encrypt(kx->kpub, MSG_KEYEXCH | KX_TOKENRQ, &bb, b))
+         buf_break(b);
+      }
       break;
     case KXS_COMMIT:
       T( trace(T_KEYEXCH, "keyexch: sending switch request to `%s'",
@@ -864,8 +1165,7 @@ static void resend(keyexch *kx)
   }
 
   if (BOK(b)) {
-    st->n_kxout++;
-    st->sz_kxout += BLEN(b);
+    update_stats_tx(kx, BLEN(b));
     p_txend(kx->p);
   }
 
@@ -1267,69 +1567,48 @@ void kx_start(keyexch *kx, int forcep)
 /* --- @kx_message@ --- *
  *
  * Arguments:  @keyexch *kx@ = pointer to key exchange context
+ *             @const addr *a@ = sender's IP address and port
  *             @unsigned msg@ = the message code
  *             @buf *b@ = pointer to buffer containing the packet
  *
- * Returns:    ---
+ * Returns:    Nonzero if the sender's address was unknown.
  *
  * Use:                Reads a packet containing key exchange messages and handles
  *             it.
  */
 
-void kx_message(keyexch *kx, unsigned msg, buf *b)
+int kx_message(keyexch *kx, const addr *a, unsigned msg, buf *b)
 {
-  struct timeval now, tv;
-  stats *st = p_stats(kx->p);
   size_t sz = BSZ(b);
   int rc;
 
-  gettimeofday(&now, 0);
-  rs_reset(&kx->rs);
-  if (kx->f & KXF_CORK) {
-    start(kx, now.tv_sec);
-    rs_time(&kx->rs, &tv, &now);
-    settimer(kx, &tv);
-    a_notify("KXSTART", "?PEER", kx->p, A_END);
-  }
+  T( trace(T_KEYEXCH, "keyexch: processing %s packet from %c%s%c",
+          msg < KX_NMSG ? pkname[msg] : "unknown",
+          kx ? '`' : '<', kx ? p_name(kx->p) : "nil", kx ? '\'' : '>'); )
 
-  if (checkpub(kx))
-    return;
-
-  if (!VALIDP(kx, now.tv_sec)) {
-    stop(kx);
-    start(kx, now.tv_sec);
+  switch (msg) {
+    case KX_TOKENRQ: dotokenrq(a, b); return (0);
+    case KX_KNOCK: doknock(a, b); return (0);
   }
-  T( trace(T_KEYEXCH, "keyexch: processing %s packet from `%s'",
-          msg < KX_NMSG ? pkname[msg] : "unknown", p_name(kx->p)); )
+
+  if (!kx) return (-1);
+  if (notice_message(kx)) return (0);
 
   switch (msg) {
-    case KX_PRECHAL:
-      rc = doprechallenge(kx, b);
-      break;
-    case KX_CHAL:
-      rc = dochallenge(kx, b);
-      break;
-    case KX_REPLY:
-      rc = doreply(kx, b);
-      break;
-    case KX_SWITCH:
-      rc = doswitch(kx, b);
-      break;
-    case KX_SWITCHOK:
-      rc = doswitchok(kx, b);
-      break;
+    case KX_TOKEN: rc = dotoken(kx, b); break;
+    case KX_PRECHAL: rc = doprechallenge(kx, b); break;
+    case KX_CHAL: rc = dochallenge(kx, b); break;
+    case KX_REPLY: rc = doreply(kx, b); break;
+    case KX_SWITCH: rc = doswitch(kx, b); break;
+    case KX_SWITCHOK: rc = doswitchok(kx, b); break;
     default:
       a_warn("KX", "?PEER", kx->p, "unknown-message", "0x%02x", msg, A_END);
       rc = -1;
       break;
   }
 
-  if (rc)
-    st->n_reject++;
-  else {
-    st->n_kxin++;
-    st->sz_kxin += sz;
-  }
+  update_stats_rx(kx, !rc, sz);
+  return (0);
 }
 
 /* --- @kx_free@ --- *
@@ -1473,7 +1752,7 @@ newkeys:
   }
 }
 
-/* --- @kx_init@ --- *
+/* --- @kx_setup@ --- *
  *
  * Arguments:  @keyexch *kx@ = pointer to key exchange context
  *             @peer *p@ = pointer to peer context
@@ -1487,7 +1766,7 @@ newkeys:
  *             exchange.
  */
 
-int kx_init(keyexch *kx, peer *p, keyset **ks, unsigned f)
+int kx_setup(keyexch *kx, peer *p, keyset **ks, unsigned f)
 {
   if ((kx->kpriv = km_findpriv(p_privtag(p))) == 0) goto fail_0;
   if ((kx->kpub = km_findpub(p_tag(p))) == 0) goto fail_1;
@@ -1518,4 +1797,16 @@ fail_0:
   return (-1);
 }
 
+/* --- @kx_init@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes the key-exchange logic.
+ */
+
+void kx_init(void)
+  { ratelim_init(&unauth_limit, 20, 500); }
+
 /*----- That's all, folks -------------------------------------------------*/
index 3a20be0..33fa7e2 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 /*----- Header files ------------------------------------------------------*/
@@ -146,7 +145,7 @@ typedef struct keyhalf {
   const char *kind;
   int (*load)(key_file *, key *, key_data *,
              const dhops *, kdata *, dstr *, dstr *);
-  const char *kr;
+  char *kr;
   key_file *kf;
   fwatch w;
   sym_table tab;
@@ -276,18 +275,15 @@ static int kh_reopen(keyhalf *kh)
   key_file *kf = CREATE(key_file);
 
   if (key_open(kf, kh->kr, KOPEN_READ, keymoan, kh)) {
-    a_warn("KEYMGMT", "%s-keyring", kh->kind, "%s", kh->kr,
-          "io-error", "?ERRNO", A_END);
     DESTROY(kf);
     return (-1);
-  } else {
-    if (kh->kf) {
-      key_close(kh->kf);
-      DESTROY(kh->kf);
-    }
-    kh->kf = kf;
-    return (0);
   }
+  if (kh->kf) {
+    key_close(kh->kf);
+    DESTROY(kh->kf);
+  }
+  kh->kf = kf;
+  return (0);
 }
 
 /* --- @kh_init@ --- *
@@ -295,22 +291,21 @@ static int kh_reopen(keyhalf *kh)
  * Arguments:  @keyhalf *kh@ = pointer to keyhalf structure to set up
  *             @const char *kr@ = name of the keyring file
  *
- * Returns:    ---
+ * Returns:    Zero on success, @-1@ on error.
  *
  * Use:                Initialize a keyhalf structure, maintaining the private or
  *             public keys.  Intended to be called during initialization:
  *             exits if there's some kind of problem.
  */
 
-static void kh_init(keyhalf *kh, const char *kr)
+static int kh_init(keyhalf *kh, const char *kr)
 {
-  kh->kr = kr;
+  if (kh->kf) return (0);
+  kh->kr = xstrdup(kr);
+  if (kh_reopen(kh)) return (-1);
   fwatch_init(&kh->w, kr);
   sym_create(&kh->tab);
-  kh->kf = 0;
-
-  if (kh_reopen(kh))
-    die(EXIT_FAILURE, "failed to load %s keyring `%s'", kh->kind, kr);
+  return (0);
 }
 
 /* --- @kh_load@ --- *
@@ -378,7 +373,7 @@ founddh:
   }
 
   if (algs_get(&kd->algs, &e, kh->kf, k) ||
-      (kd->k && algs_check(&kd->algs, &e, kd->grp))) {
+      algs_check(&kd->algs, &e, kd->grp)) {
     a_warn("KEYMGMT", "%s-keyring", kh->kind,
           "%s", kh->kr, "key", "%s", t.buf,
           "*%s", e.buf, A_END);
@@ -388,6 +383,7 @@ founddh:
   kd->tag = xstrdup(t.buf);
   kd->ref = 1;
   kd->kn = 0;
+  kd->id = k->id;
   kd->t_exp = k->exp;
 
   IF_TRACING(T_KEYMGMT, {
@@ -398,6 +394,8 @@ founddh:
       if (kd->k)
        trace(T_CRYPTO, "crypto: k = %s", g->ops->scstr(g, kd->k));
       trace(T_CRYPTO, "crypto: K = %s", g->ops->gestr(g, kd->K));
+      trace(T_CRYPTO, "crypto: bulk transform = %s",
+           kd->algs.bulk->ops->name);
       kd->algs.bulk->ops->tracealgs(kd->algs.bulk);
     })
   })
@@ -534,10 +532,33 @@ static int kh_refresh(keyhalf *kh)
   return (changep);
 }
 
+/* --- @kh_clear@ --- *
+ *
+ * Arguments:  @keyhalf *kh@ = pointer to keyhalf structure
+ *
+ * Returns:    ---
+ *
+ * Use:                Clears out the keyhalf's keyring and flushes the cache.
+ */
+
+static void kh_clear(keyhalf *kh)
+{
+  sym_iter i;
+  knode *kn;
+
+  if (!kh->kf) return;
+  for (sym_mkiter(&i, &kh->tab); (kn = sym_next(&i)) != 0; )
+    if (kn->kd) km_unref(kn->kd);
+  sym_destroy(&kh->tab);
+  key_close(kh->kf);
+  xfree(kh->kr);
+  kh->kf = 0;
+}
+
 /*----- Main code ---------------------------------------------------------*/
 
-const char *tag_priv;
-kdata *master;
+char *tag_priv = 0;
+kdata *master = 0;
 
 /* --- @km_init@ --- *
  *
@@ -545,28 +566,37 @@ kdata *master;
  *             @const char *pubkr@ = public keyring file
  *             @const char *ptag@ = default private-key tag
  *
- * Returns:    ---
+ * Returns:    Zero on success, @-1@ on failure.
  *
  * Use:                Initializes the key-management machinery, loading the
  *             keyrings and so on.
  */
 
-void km_init(const char *privkr, const char *pubkr, const char *ptag)
+int km_init(const char *privkr, const char *pubkr, const char *ptag)
 {
   const gchash *const *hh;
+  kdata *kd;
 
   for (hh = ghashtab; *hh; hh++) {
     if ((*hh)->hashsz > MAXHASHSZ) {
-      die(EXIT_FAILURE, "INTERNAL ERROR: %s hash length %lu > MAXHASHSZ %d",
-         (*hh)->name, (unsigned long)(*hh)->hashsz, MAXHASHSZ);
+      a_warn("ABORT", "hash-size-too-large", "hash",
+            "%s", (*hh)->name, "size", "%lu", (*hh)->hashsz,
+            "limit", "%d", MAXHASHSZ, A_END);
+      abort();
     }
   }
 
-  kh_init(&priv, privkr);
-  kh_init(&pub, pubkr);
+  if (kh_init(&priv, privkr) || kh_init(&pub, pubkr))
+    return (-1);
+
+  tag_priv = ptag ? xstrdup(ptag) : 0;
+  kh_refresh(&priv);
+
+  if ((kd = km_findpriv(tag_priv)) == 0) return (-1);
+  if (master) km_unref(master);
+  master = kd;
 
-  tag_priv = ptag;
-  if ((master = km_findpriv(ptag)) == 0) exit(EXIT_FAILURE);
+  return (0);
 }
 
 /* --- @km_reload@ --- *
@@ -597,6 +627,26 @@ int km_reload(void)
   return (changep);
 }
 
+/* --- @km_clear@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Forget the currently loaded keyrings.  The @master@ key will
+ *             be cleared, but other keys already loaded will continue to
+ *             exist until their reference count drops to zero.  Call
+ *             @km_init@ to make everything work again.
+ */
+
+void km_clear(void)
+{
+  kh_clear(&priv);
+  kh_clear(&pub);
+  if (master) { km_unref(master); master = 0; }
+  if (tag_priv) { xfree(tag_priv); tag_priv = 0; }
+}
+
 /* --- @km_findpub@, @km_findpriv@ --- *
  *
  * Arguments:  @const char *tag@ = key tag to load
@@ -617,6 +667,41 @@ kdata *km_findpriv(const char *tag)
   else return (kh_find(&priv, tag ? tag : "tripe-dh", 1));
 }
 
+/* --- @km_findpubbyid@, @km_findprivbyid@ --- *
+ *
+ * Arguments:  @uint32 id@ = key id to load
+ *
+ * Returns:    Pointer to the kdata object if successful, or null on error.
+ *
+ * Use:                Fetches a public or private key from the keyring given its
+ *             numeric id.
+ */
+
+static kdata *findbyid(keyhalf *kh, uint32 id)
+{
+  key *k;
+  kdata *kd;
+
+  k = key_byid(kh->kf, id); if (!k) goto notfound;
+  kd = kh_find(kh, k->tag, 1); if (!kd) goto notfound;
+  if (kd->id != id) { km_unref(kd); goto notfound; }
+  return (kd);
+
+notfound:
+  a_warn("KX", "%s-keyring", kh->kind, "%s", kh->kr,
+        "unknown-key-id", "0x%08lx", (unsigned long)id,
+        A_END);
+  return (0);
+}
+
+kdata *km_findpubbyid(uint32 id) { return (findbyid(&pub, id)); }
+
+kdata *km_findprivbyid(uint32 id)
+{
+  if (id == master->id) { km_ref(master); return (master); }
+  else return findbyid(&priv, id);
+}
+
 /* --- @km_tag@ --- *
  *
  * Arguments:  @kdata *kd@ - pointer to the kdata object
index 1d7817c..a0c4577 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 /*----- Header files ------------------------------------------------------*/
@@ -83,7 +82,7 @@ static int doencrypt(keyset *ks, unsigned ty, buf *b, buf *bb)
 
   IF_TRACING(T_KEYSET, {
     trace(T_KEYSET,
-         "keyset: encrypting packet %lu (type %u) using keyset %u",
+         "keyset: encrypting packet %lu (type 0x%02x) using keyset %u",
          (unsigned long)ks->oseq, ty, ks->seq);
     trace_block(T_CRYPTO, "crypto: plaintext packet", BCUR(b), sz);
   })
@@ -135,7 +134,7 @@ static int dodecrypt(keyset *ks, unsigned ty, buf *b, buf *bb, uint32 *seq)
 
   IF_TRACING(T_KEYSET, {
     trace(T_KEYSET,
-         "keyset: try decrypting packet (type %u) using keyset %u",
+         "keyset: try decrypting packet (type 0x%02x) using keyset %u",
          ty, ks->seq);
     trace_block(T_CRYPTO, "crypto: ciphertext packet", BCUR(b), BLEFT(b));
   })
@@ -170,96 +169,33 @@ void ks_drop(keyset *ks)
   DESTROY(ks);
 }
 
-/* --- @ks_derivekey@ --- *
- *
- * Arguments:  @octet *k@ = pointer to an output buffer of at least
- *                     @MAXHASHSZ@ bytes
- *             @size_t ksz@ = actual size wanted (for tracing)
- *             @const struct rawkey *rk@ = a raw key, as passed into
- *                     @genkeys@
- *             @int dir@ = direction for the key (@DIR_IN@ or @DIR_OUT@)
- *             @const char *what@ = label for the key (input to derivation)
- *
- * Returns:    ---
- *
- * Use:                Derives a session key, for use on incoming or outgoing data.
- *             This function is part of a private protocol between @ks_gen@
- *             and the bulk crypto transform @genkeys@ operation.
- */
-
-struct rawkey {
-  const gchash *hc;
-  const octet *k;
-  size_t x, y, z;
-};
-
-void ks_derivekey(octet *k, size_t ksz, const struct rawkey *rk,
-                 int dir, const char *what)
-{
-  const gchash *hc = rk->hc;
-  ghash *h;
-
-  assert(ksz <= hc->hashsz);
-  assert(hc->hashsz <= MAXHASHSZ);
-  h = GH_INIT(hc);
-  GH_HASH(h, "tripe-", 6); GH_HASH(h, what, strlen(what) + 1);
-  switch (dir) {
-    case DIR_IN:
-      GH_HASH(h, rk->k, rk->x);
-      GH_HASH(h, rk->k + rk->x, rk->y - rk->x);
-      break;
-    case DIR_OUT:
-      GH_HASH(h, rk->k + rk->x, rk->y - rk->x);
-      GH_HASH(h, rk->k, rk->x);
-      break;
-    default:
-      abort();
-  }
-  GH_HASH(h, rk->k + rk->y, rk->z - rk->y);
-  GH_DONE(h, k);
-  GH_DESTROY(h);
-  IF_TRACING(T_KEYSET, { IF_TRACING(T_CRYPTO, {
-    char _buf[32];
-    sprintf(_buf, "crypto: %s key %s", dir ? "incoming" : "outgoing", what);
-    trace_block(T_CRYPTO, _buf, k, ksz);
-  }) })
-}
-
 /* --- @ks_gen@ --- *
  *
- * Arguments:  @const void *k@ = pointer to key material
- *             @size_t x, y, z@ = offsets into key material (see below)
+ * Arguments:  @deriveargs *a@ = key derivation parameters (modified)
  *             @peer *p@ = pointer to peer information
  *
  * Returns:    A pointer to the new keyset.
  *
- * Use:                Derives a new keyset from the given key material.  The
- *             offsets @x@, @y@ and @z@ separate the key material into three
- *             parts.  Between the @k@ and @k + x@ is `my' contribution to
- *             the key material; between @k + x@ and @k + y@ is `your'
- *             contribution; and between @k + y@ and @k + z@ is a shared
- *             value we made together.  These are used to construct two
- *             pairs of symmetric keys.  Each pair consists of an encryption
- *             key and a message authentication key.  One pair is used for
- *             outgoing messages, the other for incoming messages.
+ * Use:                Derives a new keyset from the given key material.  This will
+ *             set the @what@, @f@, and @hc@ members in @*a@; other members
+ *             must be filled in by the caller.
  *
  *             The new key is marked so that it won't be selected for output
  *             by @ksl_encrypt@.  You can still encrypt data with it by
  *             calling @ks_encrypt@ directly.
  */
 
-keyset *ks_gen(const void *k, size_t x, size_t y, size_t z, peer *p)
+keyset *ks_gen(deriveargs *a, peer *p)
 {
   keyset *ks = CREATE(keyset);
   time_t now = time(0);
   const algswitch *algs = &p->kx.kpriv->algs;
-  struct rawkey rk;
   T( static unsigned seq = 0; )
 
   T( trace(T_KEYSET, "keyset: adding new keyset %u", seq); )
 
-  rk.hc = algs->h; rk.k = k; rk.x = x; rk.y = y; rk.z = z;
-  ks->bulk = algs->bulk->ops->genkeys(algs->bulk, &rk);
+  a->what = "tripe-"; a->f = DF_IN | DF_OUT; a->hc = algs->h;
+  ks->bulk = algs->bulk->ops->genkeys(algs->bulk, a);
   ks->bulk->ops = algs->bulk->ops;
 
   T( ks->seq = seq++; )
index 57891e7..dab3bfc 100644 (file)
@@ -9,47 +9,38 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 /*----- Header files ------------------------------------------------------*/
 
 #include "tripe.h"
 
+/*----- Global state ------------------------------------------------------*/
+
+udpsocket udpsock[NADDRFAM];
+
 /*----- Static variables --------------------------------------------------*/
 
 static sym_table byname;
 static addrmap byaddr;
-static sel_file sock;
 static unsigned nmobile;
-
-/*----- Tunnel table ------------------------------------------------------*/
-
-const tunnel_ops *tunnels[] = {
-#ifdef TUN_LINUX
-  &tun_linux,
-#endif
-#ifdef TUN_BSD
-  &tun_bsd,
-#endif
-#ifdef TUN_UNET
-  &tun_unet,
-#endif
-  &tun_slip,
-  0
-}, *tun_default;
+static struct tunnel_node {
+  struct tunnel_node *next;
+  const tunnel_ops *tops;
+} *tunnels, **tunnels_tail = &tunnels;
+const tunnel_ops *dflttun;
 
 /*----- Main code ---------------------------------------------------------*/
 
@@ -167,6 +158,54 @@ static int p_encrypt(peer *p, int ty, buf *bin, buf *bout)
   return (err);
 }
 
+/* --- @p_updateaddr@ --- *
+ *
+ * Arguments:  @peer *p@ = pointer to peer block
+ *             @const addr *a@ = address to associate with this peer
+ *
+ * Returns:    Zero if the address was changed; @+1@ if it was already
+ *             right.
+ *
+ * Use:                Updates our idea of @p@'s address.
+ */
+
+int p_updateaddr(peer *p, const addr *a)
+{
+  peer *q;
+  peer_byaddr *pa, *qa;
+  int ix;
+  unsigned f;
+
+  /* --- Figure out how to proceed --- *
+   *
+   * If this address already belongs to a different peer, then swap the
+   * addresses over.  This doesn't leave the displaced peer in an especially
+   * good state, but it ought to get sorted out soon enough.
+   */
+
+  pa = am_find(&byaddr, a, sizeof(peer_byaddr), &f);
+  if (f && pa->p == p)
+    return (+1);
+  else if (!f) {
+    T( trace(T_PEER, "peer: updating address for `%s'", p_name(p)); )
+    am_remove(&byaddr, p->byaddr);
+    p->byaddr = pa; p->spec.sa = *a; pa->p = p;
+    p->afix = afix(p->spec.sa.sa.sa_family); assert(p->afix >= 0);
+    a_notify("NEWADDR", "?PEER", p, "?ADDR", a, A_END);
+    return (0);
+  } else {
+    q = pa->p; qa = p->byaddr;
+    T( trace(T_PEER, "peer: swapping addresses for `%s' and `%s'",
+            p_name(p), p_name(q)); )
+    q->byaddr = qa; qa->p = q; q->spec.sa = p->spec.sa;
+    p->byaddr = pa; pa->p = p; p->spec.sa = *a;
+    ix = p->afix; p->afix = q->afix; q->afix = ix;
+    a_notify("NEWADDR", "?PEER", p, "?ADDR", a, A_END);
+    a_notify("NEWADDR", "?PEER", q, "?ADDR", &q->spec.sa, A_END);
+    return (0);
+  }
+}
+
 /* --- @p_decrypt@ --- *
  *
  * Arguments:  @peer **pp@ = pointer to peer to decrypt message from
@@ -189,9 +228,7 @@ static int p_decrypt(peer **pp, addr *a, size_t n,
                     int ty, buf *bin, buf *bout)
 {
   peer *p, *q;
-  peer_byaddr *pa, *qa;
   int err = KSERR_DECRYPT;
-  unsigned f;
 
   /* --- If we have a match on the source address then try that first --- */
 
@@ -249,33 +286,11 @@ searched:
     return (-1);
   }
 
-  /* --- We found one that accepted, so update the peer's address --- *
-   *
-   * If we had an initial guess of which peer this packet came from -- i.e.,
-   * @q@ is not null -- then swap the addresses over.  This doesn't leave the
-   * evicted peer in an especially good state, but it ought to get sorted out
-   * soon enough.
-   */
+  /* --- We found one that accepted, so update the peer's address --- */
 
   if (!err) {
     *pp = p;
-    if (!q) {
-      T( trace(T_PEER, "peer: updating address for `%s'", p_name(p)); )
-      pa = am_find(&byaddr, a, sizeof(peer_byaddr), &f); assert(!f);
-      am_remove(&byaddr, p->byaddr);
-      p->byaddr = pa;
-      pa->p = p;
-      p->spec.sa = *a;
-      a_notify("NEWADDR", "?PEER", p, "?ADDR", a, A_END);
-    } else {
-      T( trace(T_PEER, "peer: swapping addresses for `%s' and `%s'",
-              p_name(p), p_name(q)); )
-      pa = p->byaddr; qa = q->byaddr;
-      pa->p = q; q->byaddr = pa; q->spec.sa = p->spec.sa;
-      qa->p = p; p->byaddr = qa; p->spec.sa = *a;
-      a_notify("NEWADDR", "?PEER", p, "?ADDR", a, A_END);
-      a_notify("NEWADDR", "?PEER", q, "?ADDR", &q->spec.sa, A_END);
-    }
+    p_updateaddr(p, a);
   }
 
 match:
@@ -310,6 +325,10 @@ static void p_read(int fd, unsigned mode, void *v)
   ssize_t n;
   int ch;
   buf b, bb;
+#ifndef NTRACE
+  int ix = -1;
+  char name[NI_MAXHOST], svc[NI_MAXSERV];
+#endif
 
   /* --- Read the data --- */
 
@@ -320,19 +339,23 @@ static void p_read(int fd, unsigned mode, void *v)
     a_warn("PEER", "-", "socket-read-error", "?ERRNO", A_END);
     return;
   }
+  IF_TRACING(T_PEER, {
+    ix = afix(a.sa.sa_family);
+    getnameinfo(&a.sa, sz, name, sizeof(name), svc, sizeof(svc),
+               NI_NUMERICHOST | NI_NUMERICSERV);
+  })
 
   /* --- If the packet is a greeting, don't check peers --- */
 
   if (n && buf_i[0] == (MSG_MISC | MISC_GREET)) {
     IF_TRACING(T_PEER, {
-      trace(T_PEER, "peer: greeting received from INET %s %u",
-           inet_ntoa(a.sin.sin_addr),
-           (unsigned)ntohs(a.sin.sin_port));
+      trace(T_PEER, "peer: greeting received from %s %s %s",
+           aftab[ix].name, name, svc);
       trace_block(T_PACKET, "peer: greeting contents", buf_i, n);
     })
     buf_init(&b, buf_i, n);
     buf_getbyte(&b);
-    if (c_check(&b) || BLEFT(&b)) {
+    if (c_check(0, 0, &b) || BLEFT(&b)) {
       a_warn("PEER", "-", "invalid-greeting", A_END);
       return;
     }
@@ -353,11 +376,11 @@ static void p_read(int fd, unsigned mode, void *v)
   IF_TRACING(T_PEER, {
     if (p) {
       trace(T_PEER,
-           "peer: packet received from `%s' from address INET %s %d",
-           p_name(p), inet_ntoa(a.sin.sin_addr), ntohs(a.sin.sin_port));
+           "peer: packet received from `%s' from address %s %s %s",
+           p_name(p), aftab[ix].name, name, svc);
     } else {
-      trace(T_PEER, "peer: packet received from unknown address INET %s %d",
-           inet_ntoa(a.sin.sin_addr), ntohs(a.sin.sin_port));
+      trace(T_PEER, "peer: packet received from unknown address %s %s %s",
+           aftab[ix].name, name, svc);
     }
     trace_block(T_PACKET, "peer: packet contents", buf_i, n);
   })
@@ -393,9 +416,8 @@ static void p_read(int fd, unsigned mode, void *v)
       }
       break;
     case MSG_KEYEXCH:
-      if (!p) goto unexp;
-      p_rxupdstats(p, n);
-      kx_message(&p->kx, ch & MSG_TYPEMASK, &b);
+      if (p) p_rxupdstats(p, n);
+      if (kx_message(p ? &p->kx : 0, &a, ch & MSG_TYPEMASK, &b)) goto unexp;
       break;
     case MSG_MISC:
       switch (ch & MSG_TYPEMASK) {
@@ -435,6 +457,16 @@ static void p_read(int fd, unsigned mode, void *v)
            p_ponged(p, MISC_EPONG, &bb);
          }
          break;
+       case MISC_BYE:
+         buf_init(&bb, buf_t, sizeof(buf_t));
+         if (p_decrypt(&p, &a, n, ch, &b, &bb)) return;
+         if (!(p->spec.f&PSF_EPHEM)) return;
+         if (BOK(&bb)) {
+           buf_flip(&bb);
+           if (BSZ(&bb)) return;
+           p_destroy(p, 0);
+         }
+         break;
       }
       break;
     default:
@@ -469,6 +501,35 @@ buf *p_txstart(peer *p, unsigned msg)
   return (&p->b);
 }
 
+/* --- @p_txaddr@ --- *
+ *
+ * Arguments:  @const addr *a@ = recipient address
+ *             @const void *p@ = pointer to packet to send
+ *             @size_t sz@ = length of packet
+ *
+ * Returns:    Zero if successful, nonzero on error.
+ *
+ * Use:                Sends a packet to an address which (possibly) isn't a current
+ *             peer.
+ */
+
+int p_txaddr(const addr *a, const void *p, size_t sz)
+{
+  socklen_t sasz = addrsz(a);
+  int i;
+
+  if ((i = afix(a->sa.sa_family)) < 0) {
+    a_warn("PEER", "?ADDR", a, "disabled-address-family", A_END);
+    return (-1);
+  }
+  IF_TRACING(T_PEER, trace_block(T_PACKET, "peer: sending packet", p, sz); )
+  if (sendto(udpsock[i].sf.fd, p, sz, 0, &a->sa, sasz) < 0) {
+    a_warn("PEER", "?ADDR", a, "socket-write-error", "?ERRNO", A_END);
+    return (-1);
+  }
+  return (0);
+}
+
 /* --- @p_txend@ --- *
  *
  * Arguments:  @peer *p@ = pointer to peer block
@@ -482,14 +543,16 @@ static void p_setkatimer(peer *);
 
 static int p_dotxend(peer *p)
 {
+  socklen_t sasz = addrsz(&p->spec.sa);
+
   if (!BOK(&p->b)) {
     a_warn("PEER", "?PEER", p, "packet-build-failed", A_END);
     return (0);
   }
   IF_TRACING(T_PEER, trace_block(T_PACKET, "peer: sending packet",
                                 BBASE(&p->b), BLEN(&p->b)); )
-  if (sendto(sock.fd, BBASE(&p->b), BLEN(&p->b),
-            0, &p->spec.sa.sa, p->spec.sasz) < 0) {
+  if (sendto(udpsock[p->afix].sf.fd, BBASE(&p->b), BLEN(&p->b),
+            0, &p->spec.sa.sa, sasz) < 0) {
     a_warn("PEER", "?PEER", p, "socket-write-error", "?ERRNO", A_END);
     return (0);
   } else {
@@ -742,68 +805,226 @@ void p_setifname(peer *p, const char *name)
 
 const addr *p_addr(peer *p) { return (&p->spec.sa); }
 
-/* --- @p_init@ --- *
+/* --- @p_bind@ --- *
  *
- * Arguments:  @struct in_addr addr@ = address to bind to
- *             @unsigned port@ = port number to listen to
+ * Arguments:  @struct addrinfo *ailist@ = addresses to bind to
  *
- * Returns:    ---
+ * Returns:    Zero on success, @-1@ on failure.
  *
- * Use:                Initializes the peer system; creates the socket.
+ * Use:                Binds to the main UDP sockets.
  */
 
-void p_init(struct in_addr addr, unsigned port)
+int p_bind(struct addrinfo *ailist)
 {
-  int fd;
-  struct sockaddr_in sin;
+  int fd = -1;
   int len = PKBUFSZ;
+  int yes = 1;
+  int i;
+  struct addrinfo *ai;
+  unsigned port, lastport = 0;
+  addr a;
+  socklen_t sz;
 
-  /* --- Note on socket buffer sizes --- *
-   *
-   * For some bizarre reason, Linux 2.2 (at least) doubles the socket buffer
-   * sizes I pass to @setsockopt@.  I'm not putting special-case code here
-   * for Linux: BSD (at least TCPv2) does what I tell it rather than second-
-   * guessing me.
-   */
+  for (i = 0; i < NADDRFAM; i++) udpsock[i].sf.fd = -1;
+
+  for (ai = ailist; ai; ai = ai->ai_next) {
+    if ((i = afix(ai->ai_family)) < 0) continue;
+    if (udpsock[i].sf.fd != -1) continue;
+
+    /* --- Note on socket buffer sizes --- *
+     *
+     * For some bizarre reason, Linux 2.2 (at least) doubles the socket
+     * buffer sizes I pass to @setsockopt@.  I'm not putting special-case
+     * code here for Linux: BSD (at least TCPv2) does what I tell it rather
+     * than second-guessing me.
+     */
+
+    if ((fd = socket(ai->ai_family, SOCK_DGRAM, 0)) < 0) {
+      a_warn("PEER", "-", "udp-socket", "%s", aftab[i].name,
+            "create-failed", "?ERRNO", A_END);
+      goto fail;
+    }
+    if (i == AFIX_INET6 &&
+       setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes))) {
+      a_warn("PEER", "-", "udp-socket", "%s", aftab[i].name,
+            "set-v6only-failed", "?ERRNO", A_END);
+      goto fail;
+    }
+    assert(ai->ai_addrlen <= sizeof(a));
+    memcpy(&a, ai->ai_addr, ai->ai_addrlen);
+    if ((port = getport(&a)) == 0 && lastport) setport(&a, lastport);
+    if (bind(fd, &a.sa, addrsz(&a))) {
+      a_warn("PEER", "-", "udp-socket", "%s", aftab[i].name,
+            "bind-failed", "?ERRNO", A_END);
+      goto fail;
+    }
+    if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len)) ||
+       setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &len, sizeof(len))) {
+      a_warn("PEER", "-", "udp-socket", "%s", aftab[i].name,
+            "set-buffers-failed", "?ERRNO", A_END);
+      goto fail;
+    }
+    fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
+    if (port)
+      udpsock[i].port = port;
+    else {
+      sz = sizeof(a);
+      if (getsockname(fd, &a.sa, &sz)) {
+       a_warn("PEER", "-", "udp-socket", "%s", aftab[i].name,
+              "read-local-address-failed", "?ERRNO", A_END);
+       goto fail;
+      }
+      udpsock[i].port = lastport = getport(&a);
+    }
+    T( trace(T_PEER, "peer: created %s socket", aftab[i].name); )
+    sel_initfile(&sel, &udpsock[i].sf, fd, SEL_READ, p_read, 0);
+    sel_addfile(&udpsock[i].sf);
+    fd = -1;
+  }
+
+  return (0);
 
-  if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
-    die(EXIT_FAILURE, "socket creation failed: %s", strerror(errno));
-  BURN(sin);
-  sin.sin_family = AF_INET;
-  sin.sin_addr = addr;
-  sin.sin_port = htons(port);
-  if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)))
-    die(EXIT_FAILURE, "bind failed: %s", strerror(errno));
-  if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len)) ||
-      setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &len, sizeof(len))) {
-    die(EXIT_FAILURE, "failed to set socket buffer sizes: %s",
-       strerror(errno));
+fail:
+  if (fd != -1) close(fd);
+  p_unbind();
+  return (-1);
+}
+
+/* --- @p_unbind@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Unbinds the UDP sockets.  There must not be any active peers,
+ *             and none can be created until the sockets are rebound.
+ */
+
+void p_unbind(void)
+{
+  int i;
+
+#ifndef NDEBUG
+  { peer_iter it; p_mkiter(&it); assert(!p_next(&it)); }
+#endif
+
+  for (i = 0; i < NADDRFAM; i++) {
+    if (udpsock[i].sf.fd == -1) continue;
+    sel_rmfile(&udpsock[i].sf);
+    close(udpsock[i].sf.fd);
+    udpsock[i].sf.fd = -1;
   }
-  fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
-  sel_initfile(&sel, &sock, fd, SEL_READ, p_read, 0);
-  sel_addfile(&sock);
-  T( trace(T_PEER, "peer: created socket"); )
+}
+
+/* --- @p_init@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes the peer system.
+ */
 
+void p_init(void)
+{
   sym_create(&byname);
   am_create(&byaddr);
 }
 
-/* --- @p_port@ --- *
+/* --- @p_addtun@ --- *
+ *
+ * Arguments:  @const tunnel_ops *tops@ = tunnel ops to add
+ *
+ * Returns:    Zero on success, @-1@ on failure.
+ *
+ * Use:                Adds a tunnel class to the list of known classes, if it
+ *             initializes properly.  If there is no current default tunnel,
+ *             then this one is made the default.
+ *
+ *             Does nothing if the tunnel class is already known.  So adding
+ *             a bunch of tunnels takes quadratic time, but there will be
+ *             too few to care about.
+ */
+
+int p_addtun(const tunnel_ops *tops)
+{
+  struct tunnel_node *tn;
+
+  for (tn = tunnels; tn; tn = tn->next)
+    if (tn->tops == tops) return (0);
+  if (tops->init()) return (-1);
+  tn = CREATE(struct tunnel_node);
+  tn->next = 0; tn->tops = tops;
+  *tunnels_tail = tn; tunnels_tail = &tn->next;
+  if (!dflttun) dflttun = tops;
+  return (0);
+}
+
+/* --- @p_setdflttun@ --- *
+ *
+ * Arguments:  @const tunnel_ops *tops@ = tunnel ops to set
+ *
+ * Returns:    ---
+ *
+ * Use:                Sets the default tunnel.  It must already be registered.  The
+ *             old default is forgotten.
+ */
+
+void p_setdflttun(const tunnel_ops *tops)
+  { dflttun = tops; }
+
+/* --- @p_dflttun@ --- *
  *
  * Arguments:  ---
  *
- * Returns:    Port number used for socket.
+ * Returns:    A pointer to the current default tunnel operations, or null
+ *             if no tunnels are defined.
+ */
+
+const tunnel_ops *p_dflttun(void) { return (dflttun); }
+
+/* --- @p_findtun@ --- *
+ *
+ * Arguments:  @const char *name@ = tunnel name
+ *
+ * Returns:    Pointer to the tunnel operations, or null.
+ *
+ * Use:                Finds the operations for a named tunnel class.
  */
 
-unsigned p_port(void)
+const tunnel_ops *p_findtun(const char *name)
 {
-  addr a;
-  socklen_t sz = sizeof(addr);
+  const struct tunnel_node *tn;
+
+  for (tn = tunnels; tn; tn = tn->next)
+    if (mystrieq(tn->tops->name, name) == 0) return (tn->tops);
+  return (0);
+}
+
+/* --- @p_mktuniter@ --- *
+ *
+ * Arguments:  @tuniter *i@ = pointer to iterator to initialize
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a tunnel iterator.
+ */
+
+void p_mktuniter(tun_iter *i) { i->next = tunnels; }
 
-  if (getsockname(sock.fd, &a.sa, &sz))
-    die(EXIT_FAILURE, "couldn't read port number: %s", strerror(errno));
-  assert(a.sa.sa_family == AF_INET);
-  return (ntohs(a.sin.sin_port));
+/* --- @p_nexttun@ --- *
+ *
+ * Arguments:  @tuniter *i@ = pointer to iterator
+ *
+ * Returns:    Pointer to the next tunnel's operations, or null.
+ */
+
+const tunnel_ops *p_nexttun(tun_iter *i)
+{
+  const struct tunnel_node *tn = i->next;
+
+  if (!tn) return (0);
+  else { i->next = tn->next; return (tn->tops); }
 }
 
 /* --- @p_keepalive@ --- *
@@ -873,9 +1094,11 @@ peer *p_create(peerspec *spec)
   p->spec.name = (/*unconst*/ char *)SYM_NAME(p->byname);
   if (spec->tag) p->spec.tag = xstrdup(spec->tag);
   if (spec->privtag) p->spec.privtag = xstrdup(spec->privtag);
+  if (spec->knock) p->spec.knock = xstrdup(spec->knock);
   p->ks = 0;
   p->pings = 0;
   p->ifname = 0;
+  p->afix = afix(p->spec.sa.sa.sa_family); assert(p->afix >= 0);
   memset(&p->st, 0, sizeof(stats));
   p->st.t_start = time(0);
   if (!(tops->flags & TUNF_PRIVOPEN))
@@ -887,7 +1110,8 @@ peer *p_create(peerspec *spec)
   T( trace(T_TUNNEL, "peer: attached interface %s to peer `%s'",
           p->ifname, p_name(p)); )
   p_setkatimer(p);
-  if (kx_init(&p->kx, p, &p->ks, p->spec.f & PSF_KXMASK))
+  iv_addreason();
+  if (kx_setup(&p->kx, p, &p->ks, p->spec.f & PSF_KXMASK))
     goto tidy_4;
   a_notify("ADD",
           "?PEER", p,
@@ -905,6 +1129,7 @@ tidy_4:
   if (spec->t_ka) sel_rmtimer(&p->tka);
   xfree(p->ifname);
   p->t->ops->destroy(p->t);
+  iv_rmreason();
 tidy_3:
   if (fd >= 0) close(fd);
 tidy_2:
@@ -998,17 +1223,28 @@ peer *p_find(const char *name)
 /* --- @p_destroy@ --- *
  *
  * Arguments:  @peer *p@ = pointer to a peer
+ *             @int bye@ = say goodbye to the peer?
  *
  * Returns:    ---
  *
  * Use:                Destroys a peer.
  */
 
-void p_destroy(peer *p)
+void p_destroy(peer *p, int bye)
 {
   ping *pg, *ppg;
+  buf *b, bb;
 
   T( trace(T_PEER, "peer: destroying peer `%s'", p->spec.name); )
+
+  if (bye) {
+    b = p_txstart(p, MSG_MISC | MISC_BYE);
+    buf_init(&bb, buf_t, sizeof(buf_t));
+    assert(BOK(&bb)); buf_flip(&bb);
+    p_encrypt(p, MSG_MISC | MISC_BYE, &bb, b);
+    p_txend(p);
+  }
+
   a_notify("KILL", "%s", p->spec.name, A_END);
   ksl_free(&p->ks);
   kx_free(&p->kx);
@@ -1016,6 +1252,7 @@ void p_destroy(peer *p)
   if (p->ifname) xfree(p->ifname);
   if (p->spec.tag) xfree(p->spec.tag);
   if (p->spec.privtag) xfree(p->spec.privtag);
+  if (p->spec.knock) xfree(p->spec.knock);
   p->t->ops->destroy(p->t);
   if (p->spec.t_ka) sel_rmtimer(&p->tka);
   for (pg = p->pings; pg; pg = ppg) {
@@ -1024,9 +1261,21 @@ void p_destroy(peer *p)
   }
   sym_remove(&byname, p->byname);
   am_remove(&byaddr, p->byaddr);
+  iv_rmreason();
   DESTROY(p);
 }
 
+/* --- @p_destroyall@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Destroys all of the peers, saying goodbye.
+ */
+
+void p_destroyall(void) { FOREACH_PEER(p, { p_destroy(p, 1); }); }
+
 /* --- @p_mkiter@ --- *
  *
  * Arguments:  @peer_iter *i@ = pointer to an iterator
index d7063b6..ffe6404 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 /*----- Header files ------------------------------------------------------*/
@@ -164,13 +163,13 @@ static void reap(int sig, void *p)
  *
  * Arguments:  @int detachp@ = whether to detach the child from its terminal
  *
- * Returns:    ---
+ * Returns:    Zero on success, @-1@ on failure.
  *
  * Use:                Separates off the privileged tunnel-opening service from the
  *             rest of the server.
  */
 
-void ps_split(int detachp)
+int ps_split(int detachp)
 {
   pid_t kid;
   int fd[2];
@@ -178,9 +177,8 @@ void ps_split(int detachp)
   const char *helper;
 
   if (socketpair(PF_UNIX, SOCK_STREAM, 0, fd)) {
-    die(EXIT_FAILURE,
-       "failed to create socket pair for privilege separation: %s",
-       strerror(errno));
+    a_warn("PRIVSEP", "socketpair-create-failed", "?ERRNO", A_END);
+    return (-1);
   }
   helper = getenv("TRIPE_PRIVHELPER");
   if (!helper) helper = PRIVSEP_HELPER;
@@ -202,6 +200,7 @@ void ps_split(int detachp)
   T( trace(T_PRIVSEP, "privsep: forked child successfully"); )
   close(fd[0]);
   pc_fd = fd[1];
+  return (0);
 }
 
 /* --- @ps_quit@ --- *
index f19ce53..703e448 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 /*----- Header files ------------------------------------------------------*/
 
 octet buf_i[PKBUFSZ], buf_o[PKBUFSZ], buf_t[PKBUFSZ], buf_u[PKBUFSZ];
 
-/*----- Main code ---------------------------------------------------------*/
+/*----- Sequence numbers --------------------------------------------------*/
+
+/* --- @seq_reset@ --- *
+ *
+ * Arguments:  @seqwin *s@ = sequence-checking window
+ *
+ * Returns:    ---
+ *
+ * Use:                Resets a sequence number window.
+ */
+
+void seq_reset(seqwin *s) { s->seq = 0; s->win = 0; }
+
+/* --- @seq_check@ --- *
+ *
+ * Arguments:  @seqwin *s@ = sequence-checking window
+ *             @uint32 q@ = sequence number to check
+ *             @const char *service@ = service to report message from
+ *
+ * Returns:    Zero on success, nonzero if the sequence number was bad.
+ *
+ * Use:                Checks a sequence number against the window, updating things
+ *             as necessary.
+ */
+
+int seq_check(seqwin *s, uint32 q, const char *service)
+{
+  uint32 qbit;
+  uint32 n;
+
+  if (q < s->seq) {
+    a_warn(service, "replay", "old-sequence", A_END);
+    return (-1);
+  }
+  if (q >= s->seq + SEQ_WINSZ) {
+    n = q - (s->seq + SEQ_WINSZ - 1);
+    if (n < SEQ_WINSZ)
+      s->win >>= n;
+    else
+      s->win = 0;
+    s->seq += n;
+  }
+  qbit = 1 << (q - s->seq);
+  if (s->win & qbit) {
+    a_warn(service, "replay", "duplicated-sequence", A_END);
+    return (-1);
+  }
+  s->win |= qbit;
+  return (0);
+}
+
+/*----- Rate limiting -----------------------------------------------------*/
+
+/* --- @ratelim_init@ --- *
+ *
+ * Arguments:  @ratelim *r@ = rate-limiting state to fill in
+ *             @unsigned persec@ = credit to accumulate per second
+ *             @unsigned max@ = maximum credit to retain
+ *
+ * Returns:    ---
+ *
+ * Use:                Initialize a rate-limiting state.
+ */
+
+void ratelim_init(ratelim *r, unsigned persec, unsigned max)
+{
+  r->n = r->max = max;
+  r->persec = persec;
+  gettimeofday(&r->when, 0);
+}
+
+/* --- @ratelim_withdraw@ --- *
+ *
+ * Arguments:  @ratelim *r@ = rate-limiting state
+ *             @unsigned n@ = credit to withdraw
+ *
+ * Returns:    Zero if successful; @-1@ if there is unsufficient credit
+ *
+ * Use:                Updates the state with any accumulated credit.  Then, if
+ *             there there are more than @n@ credits available, withdraw @n@
+ *             and return successfully; otherwise, report failure.
+ */
+
+int ratelim_withdraw(ratelim *r, unsigned n)
+{
+  struct timeval now, delta;
+  unsigned long d;
+
+  gettimeofday(&now, 0);
+  TV_SUB(&delta, &now, &r->when);
+  d = (unsigned long)r->persec*delta.tv_sec +
+    (unsigned long)r->persec*delta.tv_usec/MILLION;
+  if (d < r->max - r->n) r->n += d;
+  else r->n = r->max;
+  r->when = now;
+
+  if (n > r->n) return (-1);
+  else { r->n -= n; return (0); }
+}
+
+/*----- Crypto ------------------------------------------------------------*/
+
+/* --- @ies_encrypt@ --- *
+ *
+ * Arguments:  @kdata *kpub@ = recipient's public key
+ *             @unsigned ty@ = message type octet
+ *             @buf *b@ = input message buffer
+ *             @buf *bb@ = output buffer for the ciphertext
+ *
+ * Returns:    On error, returns a @KSERR_...@ code or breaks the buffer;
+ *             on success, returns zero and the buffer is good.
+ *
+ * Use:                Encrypts a message for a recipient, given their public key.
+ *             This does not (by itself) provide forward secrecy or sender
+ *             authenticity.  The ciphertext is self-delimiting (unlike
+ *             @ks_encrypt@).
+ */
+
+int ies_encrypt(kdata *kpub, unsigned ty, buf *b, buf *bb)
+{
+  dhgrp *g = kpub->grp;
+  dhsc *u = g->ops->randsc(g);
+  dhge *U = g->ops->mul(g, u, 0), *Z = g->ops->mul(g, u, kpub->K);
+  bulkalgs *algs = kpub->algs.bulk;
+  octet *len;
+  bulkctx *bulk;
+  deriveargs a;
+  size_t n;
+  buf bk;
+  int rc = 0;
+
+  IF_TRACING(T_CRYPTO, {
+    trace(T_CRYPTO,
+         "crypto: encrypting IES message (type 0x%02x) for recipient `%s'",
+         ty, kpub->tag);
+    trace_block(T_CRYPTO, "crypto: plaintext message", BCUR(b), BLEFT(b));
+  })
+
+  a.hc = kpub->algs.h; a.what = "tripe:ecies-"; a.f = DF_OUT;
+  buf_init(&bk, buf_u, sizeof(buf_u)); a.k = BBASE(&bk);
+  g->ops->stge(g, &bk, U, DHFMT_HASH); a.x = a.y = BLEN(&bk);
+  g->ops->stge(g, &bk, Z, DHFMT_HASH); a.z = BLEN(&bk);
+  assert(BOK(&bk));
+  T( trace_block(T_CRYPTO, "crypto: KEM clue", a.k, a.x);
+     trace_block(T_CRYPTO, "crypto: shared secret", a.k + a.y, a.z - a.y); )
+
+  len = BCUR(bb); buf_get(bb, 2);
+  bulk = algs->ops->genkeys(algs, &a);
+  bulk->ops = algs->ops;
+  g->ops->stge(g, bb, U, DHFMT_VAR); if (BBAD(bb)) goto end;
+  rc = bulk->ops->encrypt(bulk, ty, b, bb, 0);
+  if (rc || BBAD(bb)) goto end;
+  n = BCUR(bb) - len - 2; assert(n <= MASK16); STORE16(len, n);
+
+end:
+  bulk->ops->freectx(bulk);
+  g->ops->freesc(g, u);
+  g->ops->freege(g, U);
+  g->ops->freege(g, Z);
+  return (rc);
+}
+
+/* --- @ies_decrypt@ --- *
+ *
+ * Arguments:  @kdata *kpub@ = private key key
+ *             @unsigned ty@ = message type octet
+ *             @buf *b@ = input ciphertext buffer
+ *             @buf *bb@ = output buffer for the message
+ *
+ * Returns:    On error, returns a @KSERR_...@ code; on success, returns
+ *             zero and the buffer is good.
+ *
+ * Use:                Decrypts a message encrypted using @ies_encrypt@, given our
+ *             private key.
+ */
+
+int ies_decrypt(kdata *kpriv, unsigned ty, buf *b, buf *bb)
+{
+  dhgrp *g = kpriv->grp;
+  bulkalgs *algs = kpriv->algs.bulk;
+  bulkctx *bulk = 0;
+  T( const octet *m; )
+  dhge *U = 0, *Z = 0;
+  deriveargs a;
+  uint32 seq;
+  buf bk, bc;
+  int rc;
+
+  IF_TRACING(T_CRYPTO, {
+    trace(T_CRYPTO,
+         "crypto: decrypting IES message (type 0x%02x) to recipient `%s'",
+         ty, kpriv->tag);
+    trace_block(T_CRYPTO, "crypto: ciphertext message", BCUR(b), BLEFT(b));
+  })
+
+  if (buf_getbuf16(b, &bc) ||
+      (U = g->ops->ldge(g, &bc, DHFMT_VAR)) == 0 ||
+      g->ops->checkge(g, U))
+    { rc = KSERR_MALFORMED; goto end; }
+  Z = g->ops->mul(g, kpriv->k, U);
+
+  a.hc = kpriv->algs.h; a.what = "tripe:ecies-"; a.f = DF_IN;
+  buf_init(&bk, buf_u, sizeof(buf_u)); a.k = BBASE(&bk); a.x = 0;
+  g->ops->stge(g, &bk, U, DHFMT_HASH); a.y = BLEN(&bk);
+  g->ops->stge(g, &bk, Z, DHFMT_HASH); a.z = BLEN(&bk);
+  T( trace_block(T_CRYPTO, "crypto: KEM clue", a.k + a.x, a.y - a.x);
+     trace_block(T_CRYPTO, "crypto: shared secret", a.k + a.y, a.z - a.y); )
+  assert(BOK(&bk));
+
+  bulk = algs->ops->genkeys(algs, &a);
+  bulk->ops = algs->ops;
+  T( m = BCUR(bb); )
+  rc = bulk->ops->decrypt(bulk, ty, &bc, bb, &seq);
+  if (rc) goto end;
+  if (seq) { rc = KSERR_SEQ; goto end; }
+  assert(BOK(bb));
+  T( trace_block(T_CRYPTO, "crypto: decrypted message", m, BCUR(bb) - m); )
+
+end:
+  if (bulk) bulk->ops->freectx(bulk);
+  g->ops->freege(g, U);
+  g->ops->freege(g, Z);
+  return (rc);
+}
+
+/*----- Random odds and sods ----------------------------------------------*/
 
 /* --- @timestr@ --- *
  *
@@ -71,53 +295,76 @@ int mystrieq(const char *x, const char *y)
   }
 }
 
-/* --- @seq_reset@ --- *
+/*----- Address handling --------------------------------------------------*/
+
+const struct addrfam aftab[] = {
+#ifdef HAVE_LIBADNS
+#  define DEF(af, qf) { AF_##af, #af, adns_qf_##qf },
+#else
+#  define DEF(af, qf) { AF_##af, #af },
+#endif
+  ADDRFAM(DEF)
+#undef DEF
+};
+
+/* --- @afix@ --- *
  *
- * Arguments:  @seqwin *s@ = sequence-checking window
+ * Arguments:  @int af@ = an address family code
  *
- * Returns:    ---
+ * Returns:    The index of the address family's record in @aftab@, or @-1@.
+ */
+
+int afix(int af)
+{
+  int i;
+
+  for (i = 0; i < NADDRFAM; i++)
+    if (af == aftab[i].af) return (i);
+  return (-1);
+}
+
+/* --- @addrsz@ --- *
  *
- * Use:                Resets a sequence number window.
+ * Arguments:  @const addr *a@ = a network address
+ *
+ * Returns:    The size of the address, for passing into the sockets API.
  */
 
-void seq_reset(seqwin *s) { s->seq = 0; s->win = 0; }
+socklen_t addrsz(const addr *a)
+{
+  switch (a->sa.sa_family) {
+    case AF_INET: return (sizeof(a->sin));
+    case AF_INET6: return (sizeof(a->sin6));
+    default: abort();
+  }
+}
 
-/* --- @seq_check@ --- *
+/* --- @getport@, @setport@ --- *
  *
- * Arguments:  @seqwin *s@ = sequence-checking window
- *             @uint32 q@ = sequence number to check
- *             @const char *service@ = service to report message from
+ * Arguments:  @addr *a@ = a network address
+ *             @unsigned port@ = port number to set
  *
- * Returns:    Zero on success, nonzero if the sequence number was bad.
+ * Returns:    ---
  *
- * Use:                Checks a sequence number against the window, updating things
- *             as necessary.
+ * Use:                Retrieves or sets the port number in an address structure.
  */
 
-int seq_check(seqwin *s, uint32 q, const char *service)
+unsigned getport(addr *a)
 {
-  uint32 qbit;
-  uint32 n;
-
-  if (q < s->seq) {
-    a_warn(service, "replay", "old-sequence", A_END);
-    return (-1);
-  }
-  if (q >= s->seq + SEQ_WINSZ) {
-    n = q - (s->seq + SEQ_WINSZ - 1);
-    if (n < SEQ_WINSZ)
-      s->win >>= n;
-    else
-      s->win = 0;
-    s->seq += n;
+  switch (a->sa.sa_family) {
+    case AF_INET: return (ntohs(a->sin.sin_port)); break;
+    case AF_INET6: return (ntohs(a->sin6.sin6_port)); break;
+    default: abort();
   }
-  qbit = 1 << (q - s->seq);
-  if (s->win & qbit) {
-    a_warn(service, "replay", "duplicated-sequence", A_END);
-    return (-1);
+}
+
+void setport(addr *a, unsigned port)
+{
+  switch (a->sa.sa_family) {
+    case AF_INET: a->sin.sin_port = htons(port); break;
+    case AF_INET6: a->sin6.sin6_port = htons(port); break;
+    default: abort();
   }
-  s->win |= qbit;
-  return (0);
 }
 
 /*----- That's all, folks -------------------------------------------------*/
diff --git a/server/standalone.c b/server/standalone.c
new file mode 100644 (file)
index 0000000..b6f3487
--- /dev/null
@@ -0,0 +1,327 @@
+/* -*-c-*-
+ *
+ * Initialization for the standalone server
+ *
+ * (c) 2018 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Trivial IP Encryption (TrIPE).
+ *
+ * TrIPE 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.
+ *
+ * TrIPE 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 TrIPE.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "tripe.h"
+
+/*----- Tunnel table ------------------------------------------------------*/
+
+static const tunnel_ops *tunnels[] = {
+#ifdef TUN_LINUX
+  &tun_linux,
+#endif
+#ifdef TUN_BSD
+  &tun_bsd,
+#endif
+#ifdef TUN_UNET
+  &tun_unet,
+#endif
+  &tun_slip,
+};
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @main@ --- *
+ *
+ * Arguments:  @int argc@ = number of command line arguments
+ *             @char *argv[]@ = vector of arguments
+ *
+ * Returns:    Zero if OK, nonzero on error.
+ *
+ * Use:                Main program.  Provides a simple VPN.
+ */
+
+static void usage(FILE *fp)
+{
+  pquis(fp, "Usage: $ [-DF] [-d DIR] [-b ADDR] [-p PORT] [-n TUNNEL]\n\
+       [-U USER] [-G GROUP] [-a SOCKET] [-m MODE] [-T TRACE-OPTS]\n\
+       [-k PRIV-KEYRING] [-K PUB-KEYRING] [-t KEY-TAG]\n");
+}
+
+static void version(FILE *fp) { pquis(fp, "$, version " VERSION "\n"); }
+
+static void help(FILE *fp)
+{
+  version(fp);
+  fputc('\n', fp);
+  usage(fp);
+  fputs("\n\
+Options:\n\
+\n\
+-h, --help             Display this help text.\n\
+-v, --version          Display version number.\n\
+-u, --usage            Display pointless usage message.\n\
+    --tunnels          Display IP tunnel drivers and exit.\n\
+\n\
+-4, --ipv4             Transport over IPv4 only.\n\
+-6, --ipv6             Transport over IPv6 only.\n\
+-D, --daemon           Run in the background.\n\
+-F, --foreground       Quit when stdin reports end-of-file.\n\
+-d, --directory=DIR    Switch to directory DIR [default " CONFIGDIR "].\n\
+-b, --bind-address=ADDR        Bind UDP socket to this IP ADDR.\n\
+-p, --port=PORT                Select UDP port to listen to "
+       "[default " STR(TRIPE_PORT) "].\n\
+-n, --tunnel=TUNNEL    Seelect default tunnel driver.\n\
+-U, --setuid=USER      Set uid to USER after initialization.\n\
+-G, --setgid=GROUP     Set gid to GROUP after initialization.\n\
+-k, --priv-keyring=FILE        Get private key from FILE.\n\
+-K, --pub-keyring=FILE Get public keys from FILE.\n\
+-t, --tag=KEYTAG       Use private key labelled TAG.\n\
+-a, --admin-socket=FILE        Use FILE as the adminstration socket.\n\
+-m, --admin-perms=MODE Permissions to set on admin socket [default 600].\n\
+" T( "\
+-T, --trace=OPTIONS    Turn on tracing options.\n\
+" ) "\
+", fp);
+}
+
+int main(int argc, char *argv[])
+{
+  const char *kr_priv = "keyring", *kr_pub = "keyring.pub";
+  const char *tag_priv = 0;
+  const char *csock = SOCKETDIR "/tripesock";
+  int csockmode = 0600;
+  const char *dir = CONFIGDIR;
+  const char *p;
+  const char *bindhost = 0, *bindsvc = STR(TRIPE_PORT);
+  struct addrinfo aihint = { 0 }, *ailist;
+  const tunnel_ops *dflt = 0;
+  unsigned f = 0;
+  int i;
+  int err;
+  unsigned af;
+  uid_t u = -1;
+  gid_t g = -1;
+
+#define f_bogus 1u
+#define f_daemon 2u
+#define f_foreground 4u
+
+  ego(argv[0]);
+  T( trace_on(stderr, 0); )
+
+  if ((p = getenv("TRIPEDIR")) != 0)
+    dir = p;
+  if ((p = getenv("TRIPESOCK")) != 0)
+    csock = p;
+  aihint.ai_family = AF_UNSPEC;
+
+  for (;;) {
+    static const struct option opts[] = {
+      { "help",                0,              0,      'h' },
+      { "version",     0,              0,      'v' },
+      { "usage",       0,              0,      'u' },
+      { "tunnels",     0,              0,      '0' },
+
+      { "ipv4",                0,              0,      '4' },
+      { "ipv6",                0,              0,      '6' },
+      { "daemon",      0,              0,      'D' },
+      { "foreground",  0,              0,      'F' },
+      { "uid",         OPTF_ARGREQ,    0,      'U' },
+      { "setuid",      OPTF_ARGREQ,    0,      'U' },
+      { "gid",         OPTF_ARGREQ,    0,      'G' },
+      { "setgid",      OPTF_ARGREQ,    0,      'G' },
+      { "bind-address",        OPTF_ARGREQ,    0,      'b' },
+      { "tunnel",      OPTF_ARGREQ,    0,      'n' },
+      { "port",                OPTF_ARGREQ,    0,      'p' },
+      { "directory",   OPTF_ARGREQ,    0,      'd' },
+      { "priv-keyring",        OPTF_ARGREQ,    0,      'k' },
+      { "pub-keyring", OPTF_ARGREQ,    0,      'K' },
+      { "tag",         OPTF_ARGREQ,    0,      't' },
+      { "admin-socket", OPTF_ARGREQ,   0,      'a' },
+      { "admin-perms", OPTF_ARGREQ,    0,      'm' },
+#ifndef NTRACE
+      { "trace",       OPTF_ARGREQ,    0,      'T' },
+#endif
+
+      { 0,             0,              0,      0 }
+    };
+
+    i = mdwopt(argc, argv, "hvu46DFU:G:b:n:p:d:k:K:t:a:m:" T("T:"),
+              opts, 0, 0, 0);
+    if (i < 0)
+      break;
+    switch (i) {
+      case 'h':
+       help(stdout);
+       exit(0);
+      case 'v':
+       version(stdout);
+       exit(0);
+      case 'u':
+       usage(stdout);
+       exit(0);
+
+      case '4':
+       aihint.ai_family = AF_INET;
+       break;
+      case '6':
+       aihint.ai_family = AF_INET6;
+       break;
+      case 'D':
+       f |= f_daemon;
+       break;
+      case 'U':
+       u = u_getuser(optarg, &g);
+       break;
+      case 'G':
+       g = u_getgroup(optarg);
+       break;
+      case 'F':
+       f |= f_foreground;
+       break;
+
+      case 'b':
+       bindhost = optarg;
+       break;
+      case 'p':
+       bindsvc = optarg;
+       break;
+      case 'n': {
+       int i;
+       for (i = 0; i < N(tunnels); i++)
+         if (mystrieq(optarg, tunnels[i]->name))
+           { dflt = tunnels[i]; goto found_tun; }
+       die(EXIT_FAILURE, "unknown tunnel `%s'", optarg);
+      found_tun:;
+      } break;
+      case 'd':
+       dir = optarg;
+       break;
+      case 'k':
+       kr_priv = optarg;
+       break;
+      case 'K':
+       kr_pub = optarg;
+       break;
+      case 'a':
+       csock = optarg;
+       break;
+      case 'm': {
+       char *p;
+       csockmode = strtol(optarg, &p, 8);
+       if (*p) die(EXIT_FAILURE, "bad permissions: `%s'", optarg);
+      } break;
+      case 't':
+       tag_priv = optarg;
+       break;
+#ifndef NTRACE
+      case 'T':
+       tr_flags = traceopt(tr_opts, optarg, tr_flags, 0);
+       trace_level(tr_flags);
+       break;
+#endif
+      case '0': {
+       int i;
+       for (i = 0; i < N(tunnels); i++)
+         puts(tunnels[i]->name);
+       exit(0);
+      } break;
+      default:
+       f |= f_bogus;
+       break;
+    }
+  }
+
+  if (optind < argc || (f & f_bogus)) {
+    usage(stderr);
+    exit(EXIT_FAILURE);
+  }
+  if (!(~f & (f_daemon | f_foreground)))
+    die(EXIT_FAILURE, "foreground operation for a daemon is silly");
+
+  aihint.ai_protocol = IPPROTO_UDP;
+  aihint.ai_socktype = SOCK_DGRAM;
+  aihint.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
+  if ((err = getaddrinfo(bindhost, bindsvc, &aihint, &ailist)) != 0) {
+    die(EXIT_FAILURE, "couldn't resolve hostname %c%s%c, port `%s': %s",
+       bindhost ? '`' : '<',
+       bindhost ? bindhost : "nil",
+       bindhost ? '\'' : '>',
+       bindsvc, gai_strerror(err));
+  }
+
+  if (chdir(dir)) {
+    die(EXIT_FAILURE, "can't set current directory to `%s': %s",
+       dir, strerror(errno));
+  }
+
+  rand_noisesrc(RAND_GLOBAL, &noise_source);
+  rand_seed(RAND_GLOBAL, MAXHASHSZ);
+
+  lp_init();
+
+  if (!(f & f_daemon)) {
+    af = AF_WARN;
+#ifndef NTRACE
+    af |= AF_TRACE;
+#endif
+    if (f & f_foreground)
+      af |= AF_FOREGROUND;
+    a_create(STDIN_FILENO, STDOUT_FILENO, af);
+    a_switcherr();
+  }
+
+  p_init();
+  for (i = 0; i < N(tunnels); i++)
+    if (p_addtun(tunnels[i])) exit(EXIT_FAILURE);
+  if (dflt) p_setdflttun(dflt);
+  if (p_bind(ailist)) exit(EXIT_FAILURE);
+  freeaddrinfo(ailist);
+
+  for (i = 0; tunnels[i]; i++) {
+    if (tunnels[i]->flags&TUNF_PRIVOPEN) {
+      if (ps_split(f & f_daemon)) exit(EXIT_FAILURE);
+      break;
+    }
+  }
+
+  if (a_init()) exit(EXIT_FAILURE);
+  a_signals();
+  if (a_listen(csock, u, g, csockmode)) exit(EXIT_FAILURE);
+  u_setugid(u, g);
+  if (km_init(kr_priv, kr_pub, tag_priv)) exit(EXIT_FAILURE);
+  kx_init();
+  if (f & f_daemon) {
+    if (daemonize()) {
+      a_warn("SERVER", "daemon-error", "?ERRNO", A_END);
+      exit(EXIT_FAILURE);
+    }
+    a_daemon();
+    a_switcherr();
+  }
+
+  lp_run();
+
+  p_destroyall();
+  p_unbind();
+  a_unlisten();
+  km_clear();
+  ps_quit();
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/server/test.c b/server/test.c
new file mode 100644 (file)
index 0000000..7e25d50
--- /dev/null
@@ -0,0 +1,176 @@
+/* -*-c-*-
+ *
+ * Various unit-level tests
+ *
+ * (c) 2017 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Trivial IP Encryption (TrIPE).
+ *
+ * TrIPE 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.
+ *
+ * TrIPE 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 TrIPE.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "tripe.h"
+
+/*----- Data structures ---------------------------------------------------*/
+
+/*----- Global variables --------------------------------------------------*/
+
+sel_state sel;
+
+/*----- Static variables --------------------------------------------------*/
+
+static char **args;
+
+/*----- Main code ---------------------------------------------------------*/
+
+static void usage(FILE *fp)
+{
+  pquis(fp,
+"Usage: $ [-k FILE] [-t KEYTAG] [-T TRACE-OPTS] COMMAND [ARGS...]\n");
+}
+
+static void version(FILE *fp)
+  { pquis(fp, "$, TrIPE version " VERSION "\n"); }
+
+static void help(FILE *fp)
+{
+  version(fp); fputc('\n', fp);
+  usage(fp); fputc('\n', fp);
+  fputs("\
+Options in full:\n\
+\n\
+-h, --help             Show this help text.\n\
+-v, --version          Show version number.\n\
+-u, --usage            Show brief usage message.\n\
+\n\
+-k, --keyring=FILE     Get keys from FILE.\n\
+-t, --tag=KEYTAG       Use KEYTAG as master private key.\n\
+-T, --trace=TRACE-OPTS Turn on tracing options.\n\
+\n\
+Commands:\n\
+\n\
+ies-encrypt TY MESSAGE\n\
+ies-decrypt TY CIPHERTEXT\n\
+", fp);
+}
+
+static uint32 parseu32(const char *p)
+{
+  int e = errno;
+  unsigned long i;
+  char *q;
+
+  errno = 0;
+  i = strtoul(p, &q, 0);
+  while (*q && isspace((unsigned char)*q)) q++;
+  if (errno || *q || i > 0xffffffffu) die(2, "bad 32-bit integer `%s'", p);
+  errno = e;
+  return (i);
+}
+
+static const char *getarg(void)
+  { if (!*args) die(2, "missing argument"); else return (*args++); }
+
+static void lastarg(void)
+  { if (*args) die(2, "unexpected argument `%s'", *args); }
+
+int main(int argc, char *argv[])
+{
+  const char *kr = "keyring";
+  const char *tag = "tripe";
+  const char *arg;
+  codec *b64;
+  int i, err;
+  uint32 ty;
+  buf b, bb;
+  dstr d = DSTR_INIT;
+  unsigned f = 0;
+#define f_bogus 1u
+
+  ego(argv[0]);
+  for (;;) {
+    static const struct option opts[] = {
+      { "help",                0,              0,      'h' },
+      { "version",     0,              0,      'v' },
+      { "usage",       0,              0,      'u' },
+      { "keyring",     OPTF_ARGREQ,    0,      'k' },
+      { "tag",         OPTF_ARGREQ,    0,      't' },
+      { "trace",       OPTF_ARGREQ,    0,      'T' },
+      { 0,             0,              0,      0 }
+    };
+
+    i = mdwopt(argc, argv, "hvuk:t:T:", opts, 0, 0, 0); if (i < 0) break;
+    switch (i) {
+      case 'h': help(stdout); exit(0);
+      case 'v': version(stdout); exit(0);
+      case 'u': usage(stdout); exit(0);
+      case 'k': kr = optarg; break;
+      case 't': tag = optarg; break;
+      case 'T':
+       tr_flags = traceopt(tr_opts, optarg, tr_flags, 0);
+       break;
+      default: f |= f_bogus; break;
+    }
+  }
+  if (f&f_bogus) { usage(stderr); exit(2); }
+  args = argv + optind;
+
+  km_init(kr, kr, tag);
+  if (!master) die(3, "failed to load the master key");
+  T( trace_on(stderr, tr_flags);
+     master->grp->ops->tracegrp(master->grp);
+     master->algs.bulk->ops->tracealgs(master->algs.bulk); )
+
+  arg = getarg();
+  if (strcmp(arg, "ies-encrypt") == 0) {
+    arg = getarg(); ty = parseu32(arg);
+    arg = getarg(); buf_init(&b, (/*unconst*/ octet *)arg, strlen(arg));
+    buf_init(&bb, buf_t, sizeof(buf_t));
+    lastarg();
+    if ((err = ies_encrypt(master, ty, &b, &bb)) != 0)
+      die(3, "ies_encrypt failed: err = %d", err);
+    b64 = base64_class.encoder(0, "\n", 72);
+    if ((err = b64->ops->code(b64, BBASE(&bb), BLEN(&bb), &d)) != 0 ||
+       (err = b64->ops->code(b64, 0, 0, &d)) != 0)
+      die(3, "base64 encoding failed: %s", codec_strerror(err));
+    b64->ops->destroy(b64);
+    DPUTC(&d, '\n');
+    fwrite(d.buf, 1, d.len, stdout);
+    dstr_destroy(&d);
+  } else if (strcmp(arg, "ies-decrypt") == 0) {
+    arg = getarg(); ty = parseu32(arg);
+    arg = getarg();
+    b64 = base64_class.decoder(CDCF_IGNSPC | CDCF_IGNNEWL);
+    if ((err = b64->ops->code(b64, arg, strlen(arg), &d)) != 0 ||
+       (err = b64->ops->code(b64, 0, 0, &d)) != 0)
+      die(3, "base64 decoding failed: %s", codec_strerror(err));
+    b64->ops->destroy(b64);
+    lastarg();
+    buf_init(&b, d.buf, d.len); buf_init(&bb, buf_t, sizeof(buf_t));
+    if ((err = ies_decrypt(master, ty, &b, &bb)) != 0)
+      die(3, "ies_decrypt failed: err = %d", err);
+    fwrite(BBASE(&bb), 1, BLEN(&bb), stdout);
+    dstr_destroy(&d);
+  } else
+    die(2, "unknown command `%s'", arg);
+
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
index f3f7c2e..5f8377a 100644 (file)
@@ -9,19 +9,18 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 m4_define([nl], [
 ])
@@ -35,13 +34,17 @@ m4_define([SETUPDIR], [
 ## Running standard programs with useful options.
 m4_define([TRIPE],
   [env TRIPE_PRIVHELPER=$abs_top_builddir/priv/tripe-privhelper \
-     $abs_top_builddir/server/tripe -F -d. -aadmin -p0 -b127.0.0.1 -talice \
+     $abs_top_builddir/server/tripe -F -d. -aadmin -p0 -b127.0.0.1 \
        ${TRIPE_TEST_TRACEOPTS+-T$TRIPE_TEST_TRACEOPTS}])
 m4_define([TRIPECTL], [$abs_top_builddir/client/tripectl -d. -aadmin])
 m4_define([USLIP], [$abs_top_builddir/uslip/tripe-uslip])
-m4_define([PKSTREAM],
-  [$abs_top_builddir/pkstream/pkstream -b127.0.0.1 -p127.0.0.1])
 m4_define([MITM], [$abs_top_builddir/proxy/tripe-mitm])
+m4_define([BULKTEST],
+  [$abs_top_builddir/server/tripe-test \
+     ${TRIPE_TEST_TRACEOPTS+-T$TRIPE_TEST_TRACEOPTS}])
+
+## Pause for a bit.
+m4_define([SLEEP], [sleep 0.2])
 
 ## WITH_STRACE(tag, cmd)
 ##
@@ -405,16 +408,23 @@ export TRIPE_SLIPIF=USLIP
 
 for p in alice bob carol; do (mkdir $p; cd $p; SETUPDIR([alpha])); done
 
-## WITH_PKSTREAM(adir, aport, bdir, bport, body)
-m4_define([WITH_PKSTREAM], [
-  echo >&2 "pkstream: $1 <--> :$2 <-pkstream-> :$4 <--> $3"
-  PKSTREAM -l$4 127.0.0.1:$4 127.0.0.1:$(cat $3/port)& pkstream_$3_$1=$!
-  sleep 1
-  PKSTREAM -c127.0.0.1:$4 127.0.0.1:$2 127.0.0.1:$(cat $1/port)&
-  pkstream_$1_$3=$!
-  set +x
+## WITH_MITM(adir, aport, bdir, bport, body)
+m4_define([WITH_MITM], [
+  aspec="$2" bspec="$4"
+  case $aspec in =*) aport="?$1/$3.mitm" ;; *) aport=$aspec ;; esac
+  case $bspec in =*) bport="?$3/$1.mitm" ;; *) bport=$bspec ;; esac
+  MITM -k$1/keyring.pub \
+       peer:$1:$aport:127.0.0.1:$(cat $1/port) \
+       peer:$3:$bport:127.0.0.1:$(cat $3/port) \
+       filt:send& mitmpid_$1_$3=$!
+  SLEEP
+  case $aspec in =*) aport=$(cat ${aport#\?}); eval ${aspec#=}=\$aport ;; esac
+  case $bspec in =*) bport=$(cat ${bport#\?}); eval ${bspec#=}=\$bport ;; esac
+  echo >&2 "mitm: $1 <--> :$aport <-mitm-> :$bport <--> $3"
+  trap 'kill $mitmpid_$1_$3; exit 127' EXIT INT QUIT TERM HUP
+  SLEEP
   $5
-  kill $pkstream_$3_$1 $pkstream_$1_$3
+  kill $mitmpid_$1_$3; trap - EXIT INT QUIT TERM HUP
 ])
 
 WITH_3TRIPES([alice], [bob], [carol], [-nslip],
@@ -422,35 +432,38 @@ WITH_3TRIPES([alice], [bob], [carol], [-nslip],
 
   ## We need an indirection layer between the two peers so that we can
   ## simulate the effects of NAT remapping.  The nearest thing we have to
-  ## this is pkstream, so we may as well use that.
+  ## this is the mitm proxy, so we may as well use that.
   ##
-  ## alice <--> :5311 <-pkstream-> :5312 <--> bob
-  ## alice <--> :5321 <-pkstream-> :5322 <--> carol
+  ## alice <--> :5311 <-mitm-> :5312 <--> bob
+  ## alice <--> :5321 <-mitm-> :5322 <--> carol
 
-  WITH_PKSTREAM([alice], [5311], [bob], [5312], [
-    ESTABLISH([alice], [alice], [], [bob], [bob], [-mobile], [5312], [5311])
+  WITH_MITM([alice], [=bob_from_alice], [bob], [=alice_from_bob], [
+    ESTABLISH([alice], [alice], [],
+             [bob], [bob], [-mobile],
+             [$alice_from_bob], [$bob_from_alice])
   ])
 
-  WITH_PKSTREAM([alice], [5319], [bob], [5312], [
+  WITH_MITM([alice], [=new_bob_from_alice], [bob], [$alice_from_bob], [
     COMMS_EPING([bob], [bob], [alice], [alice])
     COMMS_SLIP([bob], [bob], [alice], [alice])
   ])
 
-  WITH_PKSTREAM([alice], [5321], [carol], [5322], [
-    ESTABLISH([alice], [alice], [], [carol], [carol], [-mobile],
-       [5322], [5321])
+  WITH_MITM([alice], [=carol_from_alice], [carol], [=alice_from_carol], [
+    ESTABLISH([alice], [alice], [],
+             [carol], [carol], [-mobile],
+             [$alice_from_carol], [$carol_from_alice])
   ])
 
-  WITH_PKSTREAM([alice], [5311], [bob], [5312], [
-  WITH_PKSTREAM([alice], [5321], [carol], [5322], [
+  WITH_MITM([alice], [$bob_from_alice], [bob], [$alice_from_bob], [
+  WITH_MITM([alice], [$carol_from_alice], [carol], [$alice_from_carol], [
     COMMS_EPING([bob], [bob], [alice], [alice])
     COMMS_EPING([carol], [carol], [alice], [alice])
     COMMS_SLIP([bob], [bob], [alice], [alice])
     COMMS_SLIP([carol], [carol], [alice], [alice])
   ])])
 
-  WITH_PKSTREAM([alice], [5321], [bob], [5312], [
-  WITH_PKSTREAM([alice], [5311], [carol], [5322], [
+  WITH_MITM([alice], [$carol_from_alice], [bob], [$alice_from_bob], [
+  WITH_MITM([alice], [$bob_from_alice], [carol], [$alice_from_carol], [
     COMMS_EPING([bob], [bob], [alice], [alice])
     COMMS_EPING([carol], [carol], [alice], [alice])
     COMMS_SLIP([bob], [bob], [alice], [alice])
@@ -473,15 +486,16 @@ for i in alice bob; do (mkdir $i; cd $i; SETUPDIR([beta])); done
 WITH_2TRIPES([alice], [bob], [-nslip], [-talice], [-tbob], [
 
   ## Set up the evil proxy.
-  alicemitm=24516 bobmitm=14016
   mknod pipe-mitmpid p
   WITH_STRACE([mitm],
              [sh -c 'echo $$ >pipe-mitmpid; exec "$@"' - \
               MITM -kalice/keyring.pub >mitm.out 2>mitm.err \
-                peer:alice:$alicemitm:127.0.0.1:$(cat alice/port) \
-                peer:bob:$bobmitm:127.0.0.1:$(cat bob/port) \
+                peer:alice:\?alice.mitm:127.0.0.1:$(cat alice/port) \
+                peer:bob:\?bob.mitm:127.0.0.1:$(cat bob/port) \
                 filt:drop:5 filt:send])&
   read mitmpid <pipe-mitmpid
+  SLEEP
+  alicemitm=$(cat alice.mitm) bobmitm=$(cat bob.mitm)
   trap 'kill $mitmpid; exit 127' EXIT INT QUIT TERM HUP
   exec 3>&-
 
@@ -753,4 +767,87 @@ WITH_TRIPE(, [
 
 AT_CLEANUP
 
+###--------------------------------------------------------------------------
+### Knock and bye.
+
+AT_SETUP([server knock])
+AT_KEYWORDS([knock])
+export TRIPE_SLIPIF=USLIP
+
+for i in alice bob; do (mkdir $i; cd $i; SETUPDIR([gamma])); done
+
+WITH_2TRIPES([alice], [bob], [-nslip], [-talice], [-tbob], [
+  WITH_MITM([alice], [=bob_from_alice], [bob], [=alice_from_bob], [
+
+    COPROCESSES([wait-knock], [
+      echo WATCH +n
+      while read line; do
+       set x $line; shift
+       echo >&2 ">>> $line"
+       case "$1:$2:$3" in
+         OK::) ;;
+         NOTE:KNOCK:bob) shift 3; echo "$*" >knock-addr; break ;;
+         NOTE:* | TRACE:* | WARN:*) ;;
+         *) exit 63 ;;
+       esac
+      done
+    ], [
+      TRIPECTL -dalice
+    ])& waiter=$!
+
+    AT_CHECK([TRIPECTL -dbob ADD -knock bob -ephemeral alice INET 127.0.0.1 $alice_from_bob])
+
+    wait $waiter; waitrc=$?
+    AT_CHECK([echo $waitrc],, [0[]nl])
+    AT_CHECK_UNQUOTED([cat knock-addr],, [INET 127.0.0.1 $bob_from_alice[]nl])
+
+    AWAIT_KXDONE([alice], [alice], [bob], [bob], [
+      AT_CHECK([TRIPECTL -dalice ADD -ephemeral bob INET 127.0.0.1 $bob_from_alice])
+    ])
+
+    COMMS_EPING([alice], [alice], [bob], [bob])
+    COMMS_SLIP([alice], [alice], [bob], [bob])
+  ])
+
+  WITH_MITM([alice], [=new_bob_from_alice], [bob], [$alice_from_bob], [
+    AWAIT_KXDONE([alice], [alice], [bob], [bob], [
+      AT_CHECK([TRIPECTL -dalice FORCEKX bob])
+      AT_CHECK([TRIPECTL -dbob FORCEKX alice])
+    ])
+
+    AT_CHECK([TRIPECTL -dbob KILL alice])
+    AT_CHECK([TRIPECTL -dalice LIST],, [])
+  ])
+])
+
+AT_CLEANUP
+
+###--------------------------------------------------------------------------
+### Key-exchange ad bulk crypto round trip.
+
+AT_SETUP([server roundtrip])
+AT_KEYWORDS([roundtrip])
+
+while read label genalg paramalg spec; do
+  paramopts=${spec%--*} attrs=${spec#*--}
+  key -kkeyring.$label add -a$paramalg -eforever $paramopts -tparam tripe-param $attrs
+  key -kkeyring.$label add -a$genalg -pparam -eforever -talice tripe
+  key -kkeyring.$label extract -f-secret keyring.$label-pub
+  { sh -c "echo $$"; date; } >msg
+  AT_CHECK([BULKTEST -kkeyring.$label -talice \
+    ies-encrypt 99 "$(cat msg)nl"], [0], [stdout], [stderr])
+  cp msg expout; mv stdout ct
+  AT_CHECK([BULKTEST -kkeyring.$label -talice \
+    ies-decrypt 99 "$(cat ct)"], [0], [expout], [stderr])
+done <<EOF
+vanilla dh dh-param -Ccatacomb-ll-256-3072 --
+suite-b ec ec-param -Cnist-p256 -- kx-group=ec bulk=iiv cipher=rijndael-counter hash=sha256
+djb x25519 empty -- kx-group=x25519 bulk=naclbox cipher=salsa20
+fancy x448 empty -- kx-group=x448 bulk=aead cipher=chacha20-poly1305 hash=shake256 mgf=shake256-xof
+weird x25519 empty -- kx-group=x25519 bulk=aead cipher=blowfish-ocb3 hash=sha256 tagsz=48
+terrible ec ec-param -Csecp112r1 -- kx-group=ec bulk=aead cipher=des-ccm mac=aead/16
+EOF
+
+AT_CLEANUP
+
 ###----- That's all, folks --------------------------------------------------
index e15a66a..50933a8 100644 (file)
@@ -9,19 +9,18 @@
 .\"
 .\" This file is part of Trivial IP Encryption (TrIPE).
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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.
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\" along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 .
 .\"--------------------------------------------------------------------------
 .so ../common/defs.man \" @@@PRE@@@
@@ -252,21 +251,50 @@ the meanings of the subsequent tokens depend on the address family.
 Address family tokens are not case-sensitive on input; on output, they
 are always in upper-case.
 .PP
-At present, only one address family is understood.
+The following address families are recognized.
+.TP
+.BI "ANY " address " \fR[" port \fR]
+An address and port number for any supported address family.  On output,
+.B tripe
+never uses this form.  On input, the
+.I address
+is examined: if it is a numeric address for some recognized address
+family, then it is interpreted as such; otherwise it is looked up using
+the DNS (in the background).  The background resolver's address-sorting
+rules apply, and
+.B tripe
+simply takes the first address in the returned list which is of a
+supported address family.  Symbolic port numbers are permitted; if
+omitted, the default port 4070 is used.
 .TP
 .BI "INET " address " \fR[" port \fR]
 An Internet socket, naming an IPv4 address and UDP port.  On output, the
-address is always in numeric dotted-quad form, and the port is given as
-a plain number.  On input, DNS hostnames and symbolic port names are
-permitted; if omitted, the default port 4070 is used.  Name resolution
-does not block the main server, but will block the requesting client,
-unless the command is run in the background.
+.I address
+is always in numeric dotted-quad form, and the
+.I port
+is given as a plain decimal number.  On input, DNS hostnames and
+symbolic port names are permitted; if omitted, the default port 4070 is
+used.
+.TP
+.BI "INET6 " address " \fR[" port \fR]
+An Internet socket, naming an IPv6 address and UDP port.  On output, the
+.I address
+is always in numeric hex-and-colons form, and the
+.I port
+is given as a plain decimal number.  On input, DNS hostnames and
+symbolic port names may be permitted, depending on how
+.B tripe
+was compiled; if omitted, the default port 4070 is used.
 .PP
 If, on input, no recognized address family token is found, the following
 tokens are assumed to represent an
-.B INET
+.B ANY
 address.  Addresses output by the server always have an address family
-token.
+token, and do not use
+.BR ANY .
+.PP
+Name resolution never blocks the main server, but will block the
+requesting client, unless the command is run in the background.
 .SS "Key-value output"
 Some commands (e.g.,
 .B STATS
@@ -333,6 +361,21 @@ Run the command in the background, using the given
 Don't send an immediate challenge to the peer; instead, wait until it
 sends us something before responding.
 .TP
+.B "\-ephemeral"
+The association with the peer is not intended to persist indefinitely.
+When a peer is killed, or the
+.BR tripe (8)
+daemon is shut down, a
+.B bye
+packet is to the peer(s).  If a peer marked as ephemeral sends us a
+.B bye
+packet then it is killed (but in this case no further
+.B bye
+packet is sent).  A
+.B bye
+packet from a peer which isn't marked as ephemeral leaves the peer alone
+in the hope that the connection can be reestablished.
+.TP
 .BI "\-keepalive " time
 Send a no-op packet if we've not sent a packet to the peer in the last
 .I time
@@ -354,6 +397,26 @@ Use the public key
 to authenticate the peer.  The default is to use the key tagged
 .IR peer .
 .TP
+.BI "\-knock \fR[" prefix .\fR] tag
+Send the string
+.RI [ prefix\fB. ] tag
+in
+.B token-rq
+and
+.B knock
+messages to the peer during key-exchange.  The string as a whole should
+name the local machine to the peer, and
+.I tag
+should name its public key.  When such messages are received from a
+currently unknown peer,
+.BR tripe (8)
+emits a
+.B KNOCK
+notification stating the peer's (claimed) name and address.  The server
+will already have verified that the sender is using the peer's private
+key by this point.  Prior to version 1.6.0, this option used to imply
+.BR \-ephemeral .
+.TP
 .B "\-mobile"
 The peer is a mobile device, and is likely to change address rapidly.
 If a packet arrives from an unknown address, the server's usual response
@@ -362,7 +425,8 @@ peers, however, it will attempt to decrypt the packet using their keys,
 and if one succeeds, the server will update its idea of the peer's
 address and emit an
 .B NEWADDR
-notification.
+notification.  Prior to version 1.6.0, this option used to imply
+.BR \-ephemeral .
 .TP
 .BI "\-priv " tag
 Use the private key
@@ -472,12 +536,16 @@ tunnel interface.  If
 is the MTU of the path to the peer, then the tunnel MTU should be
 .IP
 .I MTU
-\- 29 \-
+\-
+.I header-length
+\- 9 \-
 .I bulk-overhead
 .PP
-allowing 20 bytes of IP header, 8 bytes of UDP header, a packet type
-octet, and the bulk-crypto transform overhead (which includes the
-sequence number).
+allowing
+.I header-length
+= 20 (IPv4) or 40 (IPv6) bytes of IP header, 8 bytes of UDP header, a
+packet type octet, and the bulk-crypto transform overhead (which
+includes the sequence number).
 .RE
 .SP
 .BI "BGCANCEL " tag
@@ -573,6 +641,16 @@ The tunnel driver used for this peer.
 The keepalive interval, in seconds, or zero if no keepalives are to be
 sent.
 .TP
+.B knock
+If present, the string sent to the peer to set up the association; see
+the
+.B \-knock
+option to
+.BR ADD ,
+and the
+.B KNOCK
+notification.
+.TP
 .B key
 The (short) key tag being used for the peer, as passed to the
 .B ADD
@@ -595,6 +673,30 @@ since there is no fixed tag used under these circumstances.
 .B current-private-key
 The full key tag of the private key currently being used for this
 association.  This may change during the life of the association.
+.TP
+.B corked
+Either
+.B t
+or
+.B nil
+depending on whether or not (respectively) key-exchange is waiting for
+the peer to initiate.
+.TP
+.B mobile
+Either
+.B t
+or
+.B nil
+depending on whether or not (respectively) the peer is expected to
+change its address unpredictably.
+.TP
+.B ephemeral
+Either
+.B t
+or
+.B nil
+depending on whether the association with the peer is expected to be
+temporary or persistent (respectively).
 .RE
 .SP
 .BI "PING \fR[" options "\fR] " peer
@@ -648,12 +750,18 @@ given, seconds are assumed.
 .RE
 .SP
 .B "PORT"
+.RI [ family ]
 Emits an
 .B INFO
 line containing just the number of the UDP port used by the
 .B tripe
-server.  If you've allowed your server to allocate a port dynamically,
-this is how to find out which one it chose.
+server, for the given address
+.I family
+(or one chosen arbitrarily if omitted -- though
+.B tripe
+tries to use the same port number consistently so this is not a likely
+problem in practice).  If you've allowed your server to allocate a port
+dynamically, this is how to find out which one it chose.
 .SP
 .B "RELOAD"
 Instructs the server to recheck its keyring files.  The server checks
@@ -695,6 +803,13 @@ This is useful if firewalling decisions are made based on interface
 names: a setup script for a particular peer can change the name, and
 then update the server's records so that they're accurate.
 .SP
+.BI "STATS " peer
+Emits a number of
+.B INFO
+lines, each containing one or more statistics in the form
+.IB name = value \fR.
+The statistics-gathering is experimental and subject to change.
+.SP
 .BI "SVCCLAIM " service " " version
 Attempts to claim the named
 .IR service ,
@@ -797,13 +912,6 @@ of the service is available before submitting the job.
 .RE
 .\"-opts
 .SP
-.BI "STATS " peer
-Emits a number of
-.B INFO
-lines, each containing one or more statistics in the form
-.IB name = value \fR.
-The statistics-gathering is experimental and subject to change.
-.SP
 .BR "TRACE " [\fIoptions\fP]
 Selects trace outputs: see
 .B "Trace lists"
@@ -943,6 +1051,10 @@ server is already running as a daemon.
 (For commands accepting socket addresses.)  The address couldn't be
 understood.
 .SP
+.BI "bad-base64 " message
+(For commands accepting Base64-encoded input.)  The Base64-encoded
+string was invalid.
+.SP
 .BI "bad-syntax " cmd " " message
 (For any command.)  The command couldn't be understood: e.g., the number
 of arguments was wrong.
@@ -975,6 +1087,15 @@ An unknown watch option was requested.
 An error occurred during the attempt to become a daemon, as reported by
 .IR message .
 .SP
+.BI "disabled-address-family " afam
+(For
+.B ADD
+and
+.BR PORT .)
+The address family
+.I afam
+is supported, but was disabled using command-line arguments.
+.SP
 .BI "invalid-port " number
 (For
 .BR ADD .)
@@ -1011,6 +1132,17 @@ There is already a peer named
 The attempt to send a ping packet failed, probably due to lack of
 encryption keys.
 .SP
+.B "provider-failed"
+(For
+.BR SVCSUBMIT .)
+The service provider disconnected without sending back a final reply to
+the job.
+.SP
+.B "provider-overloaded"
+(For
+.BR SVCSUBMIT .)
+The service provider has too many jobs queued up for it already.
+.SP
 .BI "resolve-error " hostname
 (For
 .BR ADD .)
@@ -1049,6 +1181,13 @@ is available, which does not meet the stated requirements.
 .I tag
 is already the tag of an outstanding job.
 .SP
+.BI "unknown-address-family " afam
+(For
+.BR PORT .)
+The address family
+.I afam
+is unrecognized.
+.SP
 .BI "unknown-command " token
 The command
 .I token
@@ -1083,7 +1222,7 @@ The port name
 .I port
 couldn't be found in
 .BR /etc/services .
-.TP
+.SP
 .BI "unknown-service " service
 (For
 .BR SVCENSURE ,
@@ -1094,7 +1233,7 @@ and
 The token
 .I service
 is not recognized as the name of a client-provided service.
-.TP
+.SP
 .BI "unknown-tag " tag
 (For
 .BR BGCANCEL .)
@@ -1102,6 +1241,13 @@ The given
 .I tag
 is not the tag for any outstanding background job.  It may have just
 finished.
+.SP
+.BI "unknown-tunnel " tun
+(For
+.BR ADD .)
+The given
+.I tun
+is not the name of any known tunnel driver.
 .
 .\"--------------------------------------------------------------------------
 .SH "NOTIFICATIONS"
@@ -1131,6 +1277,12 @@ The peer
 .I peer
 has been killed.
 .SP
+.BI "KNOCK " peer " " address
+The currently unknown
+.I peer
+is attempting to connect from
+.IR address .
+.SP
 .BI "KXDONE " peer
 Key exchange with
 .I peer
@@ -1207,6 +1359,16 @@ core in its configuration directory.
 .BI "ABORT repeated-select-errors"
 The main event loop is repeatedly failing.  If the server doesn't quit,
 it will probably waste all available CPU doing nothing.
+.SP
+.BI "ABORT hash-size-too-large hash " name " size " sz " limit " max
+An internal inconsistency: the hash function
+.I name
+produces a
+.IR sz -byte
+hash, but the server has been compiled to assume that no hash function
+returns more than
+.I max
+bytes.
 .SS "ADMIN warnings"
 These indicate a problem with the administration socket interface.
 .SP
@@ -1217,6 +1379,59 @@ client.
 .BI "ADMIN client-write-error " ecode " " message
 There was an error sending data to a client.  The connection to the
 client has been closed.
+.SP
+.BI "ADMIN admin-socket " path " already-in-use"
+The server failed to create the Unix-domain socket object in the
+filesystem, because there's already a socket there, and some other
+process is actively listening for incoming connections.
+.SP
+.BI "ADMIN admin-socket " path " bind-failed " ecode " " message
+The server failed to create the Unix-domain socket object in the
+filesystem for an unusual reason.  (The usual reason is
+.BR EADDRINUSE ,
+but this is handled specially.)
+.SP
+.BI "ADMIN admin-socket " path " chmod-failed " ecode " " message
+The server failed to set the correct permissions of the Unix-domain
+socket object.
+.SP
+.BI "ADMIN admin-socket " path " chown-failed " ecode " " message
+The server failed to set the correct ownership of the Unix-domain socket
+object.
+.SP
+.BI "ADMIN admin-socket " path " create-failed " ecode " " message
+The server failed to create its administration socket.  This is usually
+because some system resource is unavailable.
+.SP
+.BI "ADMIN admin-socket " path " listen-failed " ecode " " message
+The server failed to arrange to receive incoming connections on its
+Unix-domain socket.
+.SP
+.BI "ADMIN admin-socket " path " name-too-long"
+The server can't create its administration socket, because the chosen
+pathname
+.I path
+is too long.  There is, for historical reasons, a rather tight limit on
+the length of name permitted for Unix-domain sockets, usually around 108
+bytes.
+.SP
+.BI "ADMIN admin-socket " path " stat-failed " ecode " " message
+The server failed to create the Unix-domain socket object in the
+filesystem, because there's already something there, but the server
+couldn't discover what.
+.SP
+.BI "ADMIN admin-socket " path " too-many-retries"
+The server failed to create the Unix-domain socket object in the
+filesystem.  This error indicates that another process is also
+repeatedly trying to create a Unix-domain socket at the same
+.IR path ,
+and then failing to actually listen for connections on it, but the
+server always loses the applicable race for some reason.  This situation
+merits investigation.
+.SP
+.BI "ADMIN adns-init-failed " ecode " " message
+The server failed to initialize the ADNS asynchronous DNS-resolution
+library.
 .SS "CHAL warnings"
 These indicate errors in challenges, either in the
 .B CHECKCHAL
@@ -1253,9 +1468,6 @@ and the second token is the filename of the keyring.  Frequently a key
 tag may be given next, preceded by the token
 .BR key .
 .SP
-.BI "KEYMGMT private-keyring " file " key " tag " incorrect-public-key"
-The private key doesn't record the correct corresponding public key.
-.SP
 .BI "KEYMGMT public-keyring " file " key " tag " algorithm-mismatch"
 A peer's public key doesn't request the same algorithms as our private
 key.
@@ -1271,13 +1483,21 @@ The key attributes contain
 .I str
 where a MAC tag length was expected.  The key was generated wrongly.
 .SP
+.BI "KEYMGMT private-keyring " file " key " tag " incorrect-public-key"
+The private key doesn't record the correct corresponding public key.
+.SP
+.BI "KEYMGMT " which "-keyring " file " io-error " ecode " " message
+A system error occurred while opening or reading the keyring file.
+.SP
 .BI "KEYMGMT private-keyring " file " key " tag " changed-group"
 The private keyring has been changed, but the new private key can't be
 used because it uses a different group for Diffie\(enHellman key
 exchange.
 .SP
-.BI "KEYMGMT " which "-keyring " file " io-error " ecode " " message
-A system error occurred while opening or reading the keyring file.
+.BI "KEYMGMT " which "-keyring " file " key " tag " no-hmac-for-hash " hash
+No message authentication code was given explicitly, and there's no
+implementation of HMAC for the selected hash function
+.IR hash .
 .SP
 .BI "KEYMGMT " which "-keyring " file " key " tag " unknown-bulk-transform " bulk
 The key specifies the use of an unknown bulk-crypto transform
@@ -1322,10 +1542,51 @@ The key specifies the use of an unknown serialization format
 for hashing group elements.  Maybe the key was generated wrongly, or
 maybe the version of Catacomb installed is too old.
 .SP
-.BI "KEYMGMT " which "-keyring " file " key " tag " no-hmac-for-hash " hash
-No message authentication code was given explicitly, and there's no
-implementation of HMAC for the selected hash function
-.IR hash .
+.BI "KEYMGMT " which "-keyring " file " key " tag " unsuitable-aead-cipher " cipher "no-aad"
+The key specifies the use of an authenticated encryption scheme
+.I cipher
+which does not support the processing of additional authenticated data.
+The most prominent examples of such schemes are the
+.IB cipher -naclbox
+collection, where
+.I cipher
+is
+.BR salsa20 ,
+.BR salsa20/12 ,
+.BR salsa20/8 ,
+.BR chacha20 ,
+.BR chacha12 ,
+or
+.BR chacha8 ;
+use the
+.B naclbox
+bulk transform rather than
+.B aead
+for these
+(or switch to the IETF
+.IB cipher -poly1305
+schemes instead).
+.SP
+.BI "KEYMGMT " which "-keyring " file " key " tag " unsuitable-aead-cipher " cipher "nonce-too-small"
+The key specifies the use of an authenticated encryption scheme
+.I cipher
+which doesn't even allow a 5-byte (40-bit) nonce.  Catacomb doesn't
+implement any such limited AE schemes: you must be doing something
+strange.
+.SP
+.BI "KEYMGMT " which "-keyring " file " key " tag " unsuitable-aead-cipher " cipher "nonce-too-large"
+The key specifies the use of an authenticated encryption scheme
+.I cipher
+which doesn't support any nonce size smaller than 64 bytes (512 bits).
+Catacomb doesn't implement any such extravagant AE schemes: you must be
+doing something strange.
+.SP
+.BI "KEYMGMT " which "-keyring " file " key " tag " unsuitable-aead-cipher " cipher "nonempty-ciphertext-for-empty-message"
+The key specifies the use of an authenticated encryption scheme
+.I cipher
+which produces ciphertext output even when given a completely empty
+message.  Catacomb doesn't implement any such unhelpful AE schemes: you
+must be doing something strange.
 .SP
 .BI "KEYMGMT " which "-keyring " file " key " tag " " alg " " name " no-key-size " hashsz
 The
@@ -1347,6 +1608,11 @@ A key named
 .I tag
 couldn't be found in the keyring.
 .SP
+.BI "KEYMGMT " which "-keyring " file " unknown-key-id 0x" keyid
+A key with the given
+.I keyid
+(in hex) was requested but not found.
+.SP
 .BI "KEYMGMT " which "-keyring " file " line " line " " message
 The contents of the keyring file are invalid.  There may well be a bug
 in the
@@ -1368,8 +1634,11 @@ is one of the tokens
 .BR challenge ,
 .BR reply ,
 .BR switch-rq ,
-or
 .BR switch-ok .
+.BR token-rq ,
+.BR token ,
+or
+.BR knock .
 .SP
 .BI "KX " peer " algorithms-mismatch local-private-key " privtag " peer-public-key " pubtag
 The algorithms specified in the peer's public key
@@ -1484,6 +1753,32 @@ An error occurred trying to read an incoming packet.
 An error occurred attempting to send a network packet.  We lost that
 one.
 .SP
+.BI "PEER " address\fR... " disabled-address-family"
+An attempt was made to send a packet to an address for which support was
+switched off by command-line options.
+.SP
+.BI "PEER " address\fR... " socket-write-error " ecode " " message
+An error occurred attempting to send a network packet.  We lost that
+one.
+.SP
+.BI "PEER \- udp-socket " address-family " bind-failed " ecode " " message
+The server failed to associate a UDP socket with a local address.
+.SP
+.BI "PEER \- udp-socket " address-family " create-failed " ecode " " message
+The server failed to create a UDP socket for the
+.IR address-family .
+.SP
+.BI "PEER \- udp-socket " address-family " read-local-address-failed " ecode " " message
+The server failed to discover the local address for one of its own UDP
+sockets.
+.SP
+.BI "PEER \- udp-socket " address-family " set-buffers-failed " ecode " " message
+The server failed to configure appropriate buffer sizes on a UDP socket.
+.SP
+.BI "PEER \- udp-socket INET6 set-v6only-failed " ecode " " message
+The server failed to configure an IPv6 socket not to try to collect IPv4
+traffic too.
+.SP
 .BI "PEER " peer " unexpected-encrypted-ping 0x" id
 The peer sent an encrypted ping response whose id doesn't match any
 outstanding ping.  Maybe it was delayed for longer than the server was
@@ -1501,6 +1796,55 @@ The peer (apparently) sent a transport ping response whose id doesn't
 match any outstanding ping.  Maybe it was delayed for longer than the
 server was willing to wait, or maybe the peer has gone mad; or maybe
 there are bad people trying to confuse you.
+.SS "PRIVSEP warnings"
+These indicate problems with the privilege-separation helper process.
+(The server tries to drop its privileges when it starts up, leaving a
+privileged helper process behind which will create and hand over tunnel
+descriptors on request, but hopefully not do anything else especially
+dangerous.  Tunnel descriptors are not completely safe, but this is
+probably better than nothing.)
+.SP
+.BI "PRIVSEP child-exited " rc
+The helper process exited normally with status
+.IR rc .
+Status 0 means that it thought the server didn't want it any more; 1
+means that it was invoked incorrectly; 127 means that some system call
+failed.
+.SP
+.BI "PRIVSEP child-killed " sig
+The helper process was killed by signal number
+.IR sig .
+.SP
+.BI "PRIVSEP child-died " status
+The helper process died in some unexpected way;
+.I status is the raw status code returned by
+.BR waitpid (2),
+because the server didn't understand how to decode it.
+.SP
+.BI "PRIVSEP helper-died"
+A tunnel driver requires a tunnel descriptor from the helper, but the
+helper isn't running so this won't work.
+.SP
+.BI "PRIVSEP helper-read-error " ecode " " message
+The server failed to read a response from the helper process.
+.SP
+.BI "PRIVSEP helper-short-read"
+The helper process didn't send back enough data, and has likely crashed.
+.SP
+.BI "PRIVSEP helper-write-error " ecode " " message
+The server failed to send a message to the helper process.
+.SP
+.BI "PRIVSEP no-fd-from-helper"
+The helper process sent back a positive response, but didn't include the
+requested tunnel descriptor.
+.SP
+.BI "PRIVSEP socketpair-create-failed " ecode " " message
+The server couldn't create the socketpair it's supposed to use to
+communicate with the helper process.
+.SP
+.BI "PRIVSEP unknown-response-code"
+The helper process sent back an incomprehensible reply.  It's probably
+very confused and may crash.
 .SS "SERVER warnings"
 These indicate problems concerning the server process as a whole.
 .SP
@@ -1524,6 +1868,9 @@ A client of the administration interface issued a
 .B QUIT
 command.
 .SP
+.BI "SERVER daemon-error " ecode " " message
+The server failed to become a daemon during initialization.
+.SP
 .BI "SERVER quit foreground-eof"
 The server is running in foreground mode (the
 .B \-F
@@ -1532,6 +1879,10 @@ option), and encountered end-of-file on standard input.
 .BI "SERVER select-error " ecode " " message
 An error occurred in the server's main event loop.  This is bad: if it
 happens too many times, the server will abort.
+.SP
+.BI "SERVER waitpid-error " ecode " " message
+The server was informed that one of its child processes had exited, but
+couldn't retrieve the child's status.
 .SS "SYMM warnings"
 These are concerned with the symmetric encryption and decryption
 process.
@@ -1575,6 +1926,11 @@ Writing from the tunnel device failed.
 The SLIP driver encountered a escaped byte it wasn't expecting to see.
 The erroneous packet will be ignored.
 .SP
+.BI "TUN \- slip bad-interface-list"
+The interface list, in the
+.B TRIPE_SLIPIF
+environment variable, is malformed.
+.SP
 .BI "TUN " ifname " slip eof"
 The SLIP driver encountered end-of-file on its input descriptor.
 Pending data is discarded, and no attempt is made to read any more data
index 23272ea..4fe770f 100644 (file)
@@ -9,22 +9,21 @@
 .\"
 .\" This file is part of Trivial IP Encryption (TrIPE).
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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.
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\" along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 .
 .\"--------------------------------------------------------------------------
-.so ../defs.man.in \"@@@PRE@@@
+.so ../common/defs.man \"@@@PRE@@@
 .
 .\"--------------------------------------------------------------------------
 .TH tripe-service 7tripe "8 January 2007" "Straylight/Edgeware" "TrIPE: Trivial IP Encryption"
index 3997e1d..96d8896 100644 (file)
@@ -9,19 +9,18 @@
 .\"
 .\" This file is part of Trivial IP Encryption (TrIPE).
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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.
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\" along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 .
 .\"--------------------------------------------------------------------------
 .so ../common/defs.man \" @@@PRE@@@
@@ -38,7 +37,7 @@ tripe \- a simple VPN daemon
 .SH "SYNOPSIS"
 .
 .B tripe
-.RB [ \-DF ]
+.RB [ \-46DF ]
 .RB [ \-d
 .IR dir ]
 .RB [ \-b
@@ -111,7 +110,7 @@ environment variable (or
 .B "\*(/c"
 if the variable is unset) as the current directory.
 .hP 2.
-It acquires a UDP socket with an arbitrary kernel-selected port number.
+It acquires a UDP socket.  The default port is 4070
 It will use this socket to send and receive all communications with its
 peer servers.  The port chosen may be discovered by means of the
 .B PORT
@@ -166,6 +165,15 @@ Writes to standard output a list of the configured tunnel drivers, one
 per line, and exits with status 0.  This is intended for the use of the
 start-up script, so that it can check that it will actually work.
 .TP
+.B "\-4, \-\-ipv4"
+Use only IPv4 addresses.  The server will resolve names only to IPv4
+addresses, and not attempt to create IPv6 sockets.
+.TP
+.B "\-6, \-\-ipv6"
+Use only IPv6 addresses.  The server will resolve names only to IPv6
+addresses, and not attempt to create IPv4 sockets.  Note that v6-mapped
+IPv4 addresses won't work either.
+.TP
 .B "\-D, \-\-daemon"
 Dissociates from its terminal and starts running in the background after
 completing the initialization procedure described above.  If running as
@@ -206,7 +214,11 @@ to tunnel through the VPN.
 .TP
 .BI "\-p, \-\-port=" port
 Use the specified UDP port for all communications with peers, rather
-than an arbitarary kernel-assigned port.
+than the default port 4070.  If this is zero, the kernel will assign a
+free port, which can be determined using the
+.B PORT
+administration command (see
+.BR tripe-admin (5)).
 .TP
 .BI "\-n, \-\-tunnel=" tunnel
 Use the specified tunnel driver for new peers by default.
@@ -429,9 +441,9 @@ overridden by setting attributes on your private key, as follows.
 Names the bulk-crypto transform to use.  See below.
 .TP
 .B blkc
-Names a block cipher, used by some bulk-crypto transforms (e.g.,
+Names a blockcipher, used by some bulk-crypto transforms (e.g.,
 .BR iiv ).
-The default is to use the block cipher underlying the chosen
+The default is to use the blockcipher underlying the chosen
 .BR cipher ,
 if any.
 .TP
@@ -478,7 +490,7 @@ random and included explicitly in the cryptogram.
 .TP
 .B iiv
 A newer `implicit-IV' transform.  Rather than having an explicit random
-IV, the IV is computed from the sequence number using a block cipher.
+IV, the IV is computed from the sequence number using a blockcipher.
 This has two advantages over the
 .B v0
 transform.  Firstly, it adds less overhead to encrypted messages
@@ -488,6 +500,36 @@ doesn't need the (possibly slow) random number generator, and (b) it
 closes a kleptographic channel, over which a compromised implementation
 could leak secret information to a third party.
 .TP
+.B aead
+A transform based on an all-in-one `authenticated encryption with
+additional data' scheme.  The scheme is named in the
+.B cipher
+attribute; the default is
+.BR rijndael-ocb3 .
+If the
+.B mac
+attribute is given, it must be either
+.B aead
+or
+.BR aead/ \c
+.IR tagsz ,
+where
+.I tagsz
+is the desired tag length in bits; alternatively, the tag length can be
+set in the
+.B tagsz
+attribute.  The chosen AEAD scheme must accept at least a 64-bit nonce
+(this rules out OCB3 and CCM with 64-bit blockciphers); it mustn't
+require an absurdly large nonce size (none of the schemes implemented in
+Catacomb present a problem here, but it bears mentioning); it must
+actually support additional header data (which rules out the
+.B naclbox
+schemes, but see the
+.B naclbox
+transform below); and it must produce an empty ciphertext when
+encrypting an empty message (again, all of Catacomb's schemes meet this
+requirement).
+.TP
 .B naclbox
 A transform based on the NaCl
 .B crypto_secretbox
index 1e6d406..e4e4e6a 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 /*----- Header files ------------------------------------------------------*/
@@ -37,7 +36,11 @@ sel_state sel;
 static sel_timer it;
 #define T_INTERVAL MIN(1)
 
-/*----- Main code ---------------------------------------------------------*/
+static unsigned iv_nreasons = 0;
+static struct timeval iv_next = { 0, 0 };
+static int lpdone = 0;
+
+/*----- The interval timer ------------------------------------------------*/
 
 /* --- @interval@ --- *
  *
@@ -51,279 +54,110 @@ static sel_timer it;
 
 static void interval(struct timeval *tv, void *v)
 {
-  struct timeval tvv;
   T( trace(T_PEER, "peer: interval timer"); )
+  iv_next = *tv;
   rand_seed(RAND_GLOBAL, MAXHASHSZ);
   p_interval();
-  tvv = *tv;
-  tvv.tv_sec += T_INTERVAL;
-  sel_addtimer(&sel, &it, &tvv, interval, v);
+  iv_next.tv_sec += T_INTERVAL;
+  sel_addtimer(&sel, &it, &iv_next, interval, v);
 }
 
-/* --- @main@ --- *
+/* --- @iv_addreason@ --- *
  *
- * Arguments:  @int argc@ = number of command line arguments
- *             @char *argv[]@ = vector of arguments
+ * Arguments:  ---
  *
- * Returns:    Zero if OK, nonzero on error.
+ * Returns:    ---
  *
- * Use:                Main program.  Provides a simple VPN.
+ * Use:                Adds an `interval timer reason'; if there are no others, the
+ *             interval timer is engaged.
  */
 
-static void usage(FILE *fp)
+void iv_addreason(void)
 {
-  pquis(fp, "Usage: $ [-DF] [-d DIR] [-b ADDR] [-p PORT] [-n TUNNEL]\n\
-       [-U USER] [-G GROUP] [-a SOCKET] [-m MODE] [-T TRACE-OPTS]\n\
-       [-k PRIV-KEYRING] [-K PUB-KEYRING] [-t KEY-TAG]\n");
+  struct timeval tv;
+
+  if (!iv_nreasons) {
+    gettimeofday(&tv, 0);
+    if (TV_CMP(&tv, >=, &iv_next)) interval(&tv, 0);
+    else sel_addtimer(&sel, &it, &iv_next, interval, 0);
+  }
+  iv_nreasons++;
 }
 
-static void version(FILE *fp) { pquis(fp, "$, version " VERSION "\n"); }
+/* --- @iv_rmreason@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Removes an interval timer reason; if there are none left, the
+ *             interval timer is disengaged.
+ */
 
-static void help(FILE *fp)
+void iv_rmreason(void)
 {
-  version(fp);
-  fputc('\n', fp);
-  usage(fp);
-  fputs("\n\
-Options:\n\
-\n\
--h, --help             Display this help text.\n\
--v, --version          Display version number.\n\
--u, --usage            Display pointless usage message.\n\
-    --tunnels          Display IP tunnel drivers and exit.\n\
-\n\
--D, --daemon           Run in the background.\n\
--F, --foreground       Quit when stdin reports end-of-file.\n\
--d, --directory=DIR    Switch to directory DIR [default " CONFIGDIR "].\n\
--b, --bind-address=ADDR        Bind UDP socket to this IP ADDR.\n\
--p, --port=PORT                Select UDP port to listen to "
-       "[default " STR(TRIPE_PORT) "].\n\
--n, --tunnel=TUNNEL    Seelect default tunnel driver.\n\
--U, --setuid=USER      Set uid to USER after initialization.\n\
--G, --setgid=GROUP     Set gid to GROUP after initialization.\n\
--k, --priv-keyring=FILE        Get private key from FILE.\n\
--K, --pub-keyring=FILE Get public keys from FILE.\n\
--t, --tag=KEYTAG       Use private key labelled TAG.\n\
--a, --admin-socket=FILE        Use FILE as the adminstration socket.\n\
--m, --admin-perms=MODE Permissions to set on admin socket [default 600].\n\
-" T( "\
--T, --trace=OPTIONS    Turn on tracing options.\n\
-" ) "\
-", fp);
+  assert(iv_nreasons); iv_nreasons--;
+  if (!iv_nreasons) sel_rmtimer(&it);
 }
 
-int main(int argc, char *argv[])
-{
-  const char *kr_priv = "keyring", *kr_pub = "keyring.pub";
-  const char *tag_priv = 0;
-  const char *csock = SOCKETDIR "/tripesock";
-  int csockmode = 0600;
-  const char *dir = CONFIGDIR;
-  const char *p;
-  unsigned port = TRIPE_PORT;
-  struct in_addr baddr = { INADDR_ANY };
-  unsigned f = 0;
-  int i;
-  int selerr = 0;
-  unsigned af;
-  struct timeval tv;
-  uid_t u = -1;
-  gid_t g = -1;
-
-#define f_bogus 1u
-#define f_daemon 2u
-#define f_foreground 4u
+/*----- The main loop -----------------------------------------------------*/
 
-  ego(argv[0]);
-  T( trace_on(stderr, 0); )
-
-  if ((p = getenv("TRIPEDIR")) != 0)
-    dir = p;
-  if ((p = getenv("TRIPESOCK")) != 0)
-    csock = p;
-  tun_default = tunnels[0];
-
-  for (;;) {
-    static const struct option opts[] = {
-      { "help",                0,              0,      'h' },
-      { "version",     0,              0,      'v' },
-      { "usage",       0,              0,      'u' },
-      { "tunnels",     0,              0,      '0' },
-
-      { "daemon",      0,              0,      'D' },
-      { "foreground",  0,              0,      'F' },
-      { "uid",         OPTF_ARGREQ,    0,      'U' },
-      { "setuid",      OPTF_ARGREQ,    0,      'U' },
-      { "gid",         OPTF_ARGREQ,    0,      'G' },
-      { "setgid",      OPTF_ARGREQ,    0,      'G' },
-      { "bind-address",        OPTF_ARGREQ,    0,      'b' },
-      { "tunnel",      OPTF_ARGREQ,    0,      'n' },
-      { "port",                OPTF_ARGREQ,    0,      'p' },
-      { "directory",   OPTF_ARGREQ,    0,      'd' },
-      { "priv-keyring",        OPTF_ARGREQ,    0,      'k' },
-      { "pub-keyring", OPTF_ARGREQ,    0,      'K' },
-      { "tag",         OPTF_ARGREQ,    0,      't' },
-      { "admin-socket", OPTF_ARGREQ,   0,      'a' },
-      { "admin-perms", OPTF_ARGREQ,    0,      'm' },
-#ifndef NTRACE
-      { "trace",       OPTF_ARGREQ,    0,      'T' },
-#endif
-
-      { 0,             0,              0,      0 }
-    };
-
-    i = mdwopt(argc, argv, "hvuDFU:G:b:n:p:d:k:K:t:a:m:" T("T:"),
-              opts, 0, 0, 0);
-    if (i < 0)
-      break;
-    switch (i) {
-      case 'h':
-       help(stdout);
-       exit(0);
-      case 'v':
-       version(stdout);
-       exit(0);
-      case 'u':
-       usage(stdout);
-       exit(0);
-
-      case 'D':
-       f |= f_daemon;
-       break;
-      case 'U':
-       u = u_getuser(optarg, &g);
-       break;
-      case 'G':
-       g = u_getgroup(optarg);
-       break;
-      case 'F':
-       f |= f_foreground;
-       break;
+/* --- @lp_init@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes the main loop.  Most importantly, this sets up
+ *             the select multiplexor that everything else hooks onto.
+ */
 
-      case 'b': {
-       struct hostent *h = gethostbyname(optarg);
-       if (!h)
-         die(EXIT_FAILURE, "unknown host name `%s'", optarg);
-       memcpy(&baddr, h->h_addr, sizeof(struct in_addr));
-      } break;
-      case 'p': {
-       char *p;
-       unsigned long i = strtoul(optarg, &p, 0);
-       if (*p) {
-         struct servent *s = getservbyname(optarg, "udp");
-         if (!s)
-           die(EXIT_FAILURE, "unknown service name `%s'", optarg);
-         i = ntohs(s->s_port);
-       }
-       if (i >= 65536)
-         die(EXIT_FAILURE, "bad port number %lu", i);
-       port = i;
-      } break;
-      case 'n': {
-       int i;
-       for (i = 0;; i++) {
-         if (!tunnels[i])
-           die(EXIT_FAILURE, "unknown tunnel `%s'", optarg);
-         if (mystrieq(optarg, tunnels[i]->name))
-           break;
-       }
-       tun_default = tunnels[i];
-      } break;
-      case 'd':
-       dir = optarg;
-       break;
-      case 'k':
-       kr_priv = optarg;
-       break;
-      case 'K':
-       kr_pub = optarg;
-       break;
-      case 'a':
-       csock = optarg;
-       break;
-      case 'm': {
-       char *p;
-       csockmode = strtol(optarg, &p, 8);
-       if (*p) die(EXIT_FAILURE, "bad permissions: `%s'", optarg);
-      } break;
-      case 't':
-       tag_priv = optarg;
-       break;
-#ifndef NTRACE
-      case 'T':
-       tr_flags = traceopt(tr_opts, optarg, tr_flags, 0);
-       trace_level(tr_flags);
-       break;
-#endif
-      case '0': {
-       int i;
-       for (i = 0; tunnels[i]; i++)
-         puts(tunnels[i]->name);
-       exit(0);
-      } break;
-      default:
-       f |= f_bogus;
-       break;
-    }
-  }
+void lp_init(void)
+{
+  gettimeofday(&iv_next, 0); iv_next.tv_sec += T_INTERVAL;
+  signal(SIGPIPE, SIG_IGN);
+  sel_init(&sel);
+  sig_init(&sel);
+}
 
-  if (optind < argc || (f & f_bogus)) {
-    usage(stderr);
-    exit(EXIT_FAILURE);
-  }
-  if (!(~f & (f_daemon | f_foreground)))
-    die(EXIT_FAILURE, "foreground operation for a daemon is silly");
+/* --- @lp_end@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Requests an exit from the main loop.
+ */
 
-  if (chdir(dir)) {
-    die(EXIT_FAILURE, "can't set current directory to `%s': %s",
-       dir, strerror(errno));
-  }
+void lp_end(void) { lpdone = 1; }
 
-  sel_init(&sel);
-  sig_init(&sel);
-  rand_noisesrc(RAND_GLOBAL, &noise_source);
-  rand_seed(RAND_GLOBAL, MAXHASHSZ);
-  signal(SIGPIPE, SIG_IGN);
-  for (i = 0; tunnels[i]; i++)
-    tunnels[i]->init();
-  p_init(baddr, port);
-  if (!(f & f_daemon)) {
-    af = AF_WARN;
-#ifndef NTRACE
-    af |= AF_TRACE;
-#endif
-    if (f & f_foreground)
-      af |= AF_FOREGROUND;
-    a_create(STDIN_FILENO, STDOUT_FILENO, af);
-  }
-  ps_split(f & f_daemon);
-  a_init(csock, u, g, csockmode);
-  u_setugid(u, g);
-  km_init(kr_priv, kr_pub, tag_priv);
-  if (f & f_daemon) {
-    if (daemonize())
-      die(EXIT_FAILURE, "couldn't become a daemon: %s", strerror(errno));
-    a_daemon();
-  }
+/* --- @lp_run@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    Zero on successful termination; @-1@ if things went wrong.
+ *
+ * Use:                Cranks the main loop until it should be cranked no more.
+ */
 
-  tv.tv_sec = time(0) + T_INTERVAL;
-  tv.tv_usec = 0;
-  sel_addtimer(&sel, &it, &tv, interval, 0);
+int lp_run(void)
+{
+  int nerr = 0;
 
   for (;;) {
     a_preselect();
-    if (!sel_select(&sel))
-      selerr = 0;
+    if (lpdone) break;
+    if (!sel_select(&sel)) nerr = 0;
     else if (errno != EINTR && errno != EAGAIN) {
       a_warn("SERVER", "select-error", "?ERRNO", A_END);
-      selerr++;
-      if (selerr > 8) {
+      nerr++;
+      if (nerr > 8) {
        a_warn("ABORT", "repeated-select-errors", A_END);
        abort();
       }
     }
   }
-
+  lpdone = 0;
   return (0);
 }
 
index 56cd0b4..ea980c6 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 #ifndef TRIPE_H
 #include <pwd.h>
 #include <grp.h>
 
+#ifdef HAVE_LIBADNS
+#  define ADNS_FEATURE_MANYAF
+#  include <adns.h>
+#endif
+
 #include <mLib/alloc.h>
 #include <mLib/arena.h>
 #include <mLib/base64.h>
-#include <mLib/bres.h>
+#ifndef HAVE_LIBADNS
+#  include <mLib/bres.h>
+#endif
+#include <mLib/codec.h>
 #include <mLib/daemonize.h>
 #include <mLib/dstr.h>
 #include <mLib/env.h>
 #include <catacomb/ct.h>
 
 #include <catacomb/chacha.h>
+#include <catacomb/gaead.h>
 #include <catacomb/gcipher.h>
 #include <catacomb/gmac.h>
 #include <catacomb/grand.h>
+#include <catacomb/latinpoly.h>
 #include <catacomb/key.h>
 #include <catacomb/paranoia.h>
 #include <catacomb/poly1305.h>
@@ -181,6 +190,16 @@ enum {
   DHFMT_VAR                   /* Variable-width-format, mostly a bad idea */
 };
 
+typedef struct deriveargs {
+  const char *what;                    /* Operation name (hashed) */
+  unsigned f;                          /* Flags */
+#define DF_IN 1u                       /*   Make incoming key */
+#define DF_OUT 2u                      /*   Make outgoing key */
+  const gchash *hc;                    /* Hash class */
+  const octet *k;                      /* Pointer to contributions */
+  size_t x, y, z;                      /* Markers in contributions */
+} deriveargs;
+
 typedef struct bulkalgs {
   const struct bulkops *ops;
 } bulkalgs;
@@ -194,15 +213,13 @@ typedef struct bulkchal {
   size_t tagsz;
 } bulkchal;
 
-struct rawkey;
-
 typedef struct dhops {
   const char *name;
 
   int (*ldpriv)(key_file */*kf*/, key */*k*/, key_data */*d*/,
                kdata */*kd*/, dstr */*t*/, dstr */*e*/);
        /* Load a private key from @d@, storing the data in @kd@.  The key's
-        * file and key object are in @kf@ and @k, mostly in case its
+        * file and key object are in @kf@ and @k@, mostly in case its
         * attributes are interesting; the key tag is in @t@; errors are
         * reported by writing tokens to @e@ and returning nonzero.
         */
@@ -210,7 +227,7 @@ typedef struct dhops {
   int (*ldpub)(key_file */*kf*/, key */*k*/, key_data */*d*/,
               kdata */*kd*/, dstr */*t*/, dstr */*e*/);
        /* Load a public key from @d@, storing the data in @kd@.  The key's
-        * file and key object are in @kf@ and @k, mostly in case its
+        * file and key object are in @kf@ and @k@, mostly in case its
         * attributes are interesting; the key tag is in @t@; errors are
         * reported by writing tokens to @e@ and returning nonzero.
         */
@@ -325,9 +342,17 @@ typedef struct bulkops {
         * after which the keys must no longer be used.
         */
 
-  bulkctx *(*genkeys)(const bulkalgs */*a*/, const struct rawkey */*rk*/);
+  bulkctx *(*genkeys)(const bulkalgs */*a*/, const deriveargs */*a*/);
        /* Generate session keys and construct and return an appropriate
-        * context for using them, by calling @ks_derive@.
+        * context for using them.  The offsets @a->x@, @a->y@ and @a->z@
+        * separate the key material into three parts.  Between @a->k@ and
+        * @a->k + a->x@ is `my' contribution to the key material; between
+        * @a->k + a->x@ and @a->k + a->y@ is `your' contribution; and
+        * between @a->k + a->y@ and @a->k + a->z@ is a shared value we made
+        * together.  These are used to construct (up to) two collections of
+        * symmetric keys: one for outgoing messages, the other for incoming
+        * messages.  If @a->x == 0@ (or @a->y == a->x@) then my (or your)
+        * contribution is omitted.
         */
 
   bulkchal *(*genchal)(const bulkalgs */*a*/);
@@ -360,15 +385,16 @@ typedef struct bulkops {
        /* Release a bulk encryption context and the resources it holds. */
 
   int (*chaltag)(bulkchal */*bc*/, const void */*m*/, size_t /*msz*/,
-                void */*t*/);
-       /* Calculate a tag for the challenge in @m@, @msz@, and write it to
-        * @t@.  Return @-1@ on error, zero on success.
+                uint32 /*seq*/, void */*t*/);
+       /* Calculate a tag for the challenge in @m@, @msz@, with the sequence
+        * number @seq@, and write it to @t@.  Return @-1@ on error, zero on
+        * success.
         */
 
   int (*chalvrf)(bulkchal */*bc*/, const void */*m*/, size_t /*msz*/,
-                const void */*t*/);
-       /* Check the tag @t@ on @m@, @msz@: return zero if the tag is OK,
-        * nonzero if it's bad.
+                uint32 /*seq*/, const void */*t*/);
+       /* Check the tag @t@ on @m@, @msz@ and @seq@: return zero if the tag
+        * is OK, nonzero if it's bad.
         */
 
   void (*freechal)(bulkchal */*bc*/);
@@ -385,6 +411,7 @@ struct algswitch {
 struct kdata {
   unsigned ref;                                /* Reference counter */
   struct knode *kn;                    /* Pointer to cache entry */
+  uint32 id;                           /* The underlying key's id */
   char *tag;                           /* Full tag name of the key */
   dhgrp *grp;                          /* The group we work in */
   dhsc *k;                             /* The private key (or null) */
@@ -410,6 +437,27 @@ extern const bulkops bulktab[];
 
 /*----- Data structures ---------------------------------------------------*/
 
+/* --- The address-family table --- */
+
+#define ADDRFAM(_)                                                     \
+  _(INET,      want_ipv4)                                              \
+  _(INET6,     want_ipv6)
+
+enum {
+#define ENUM(af, qf) AFIX_##af,
+  ADDRFAM(ENUM)
+#undef ENUM
+  NADDRFAM
+};
+
+extern const struct addrfam {
+  int af;
+  const char *name;
+#ifdef HAVE_LIBADNS
+  adns_queryflags qf;
+#endif
+} aftab[NADDRFAM];
+
 /* --- Socket addresses --- *
  *
  * A magic union of supported socket addresses.
@@ -418,6 +466,7 @@ extern const bulkops bulktab[];
 typedef union addr {
   struct sockaddr sa;
   struct sockaddr_in sin;
+  struct sockaddr_in6 sin6;
 } addr;
 
 /* --- Mapping keyed on addresses --- */
@@ -551,7 +600,7 @@ typedef struct tunnel_ops {
   const char *name;                    /* Name of this tunnel driver */
   unsigned flags;                      /* Various interesting flags */
 #define TUNF_PRIVOPEN 1u               /*   Need helper to open file */
-  void (*init)(void);                  /* Initializes the system */
+  int (*init)(void);                   /* Initializes the system */
   tunnel *(*create)(struct peer */*p*/, int /*fd*/, char **/*ifn*/);
                                        /* Initializes a new tunnel */
   void (*setifname)(tunnel */*t*/, const char */*ifn*/);
@@ -564,6 +613,10 @@ typedef struct tunnel_ops {
 struct tunnel { const tunnel_ops *ops; };
 #endif
 
+typedef struct tun_iter {
+  const struct tunnel_node *next;
+} tun_iter;
+
 /* --- Peer statistics --- *
  *
  * Contains various interesting and not-so-interesting statistics about a
@@ -592,13 +645,14 @@ typedef struct peerspec {
   char *name;                          /* Peer's name */
   char *privtag;                       /* Private key tag */
   char *tag;                           /* Public key tag */
+  char *knock;                         /* Knock string, or null */
   const tunnel_ops *tops;              /* Tunnel operations */
   unsigned long t_ka;                  /* Keep alive interval */
   addr sa;                             /* Socket address to speak to */
-  size_t sasz;                         /* Socket address size */
   unsigned f;                          /* Flags for the peer */
 #define PSF_KXMASK 255u                        /*   Key-exchange flags to set */
 #define PSF_MOBILE 256u                        /*   Address may change rapidly */
+#define PSF_EPHEM 512u                 /*   Association is ephemeral */
 } peerspec;
 
 typedef struct peer_byname {
@@ -616,6 +670,7 @@ typedef struct peer {
   peer_byaddr *byaddr;                 /* Lookup-by-address block */
   struct ping *pings;                  /* Pings we're waiting for */
   peerspec spec;                       /* Specifications for this peer */
+  int afix;                            /* Index of address family */
   tunnel *t;                           /* Tunnel for local packets */
   char *ifname;                                /* Interface name for tunnel */
   keyset *ks;                          /* List head for keysets */
@@ -627,6 +682,11 @@ typedef struct peer {
 
 typedef struct peer_iter { sym_iter i; } peer_iter;
 
+typedef struct udpsocket {
+  sel_file sf;                         /* Selector for the socket */
+  unsigned port;                       /* Chosen port number */
+} udpsocket;
+
 typedef struct ping {
   struct ping *next, *prev;            /* Links to next and previous */
   peer *p;                             /* Peer so we can free it */
@@ -672,9 +732,14 @@ typedef struct admin_bgop {
 typedef struct admin_resop {
   admin_bgop bg;                       /* Background operation header */
   char *addr;                          /* Hostname to be resolved */
+#ifdef HAVE_LIBADNS
+  adns_query q;
+#else
   bres_client r;                       /* Background resolver task */
+#endif
   sel_timer t;                         /* Timer for resolver */
   addr sa;                             /* Socket address */
+  unsigned port;                       /* Port number chosen */
   size_t sasz;                         /* Socket address size */
   void (*func)(struct admin_resop *, int); /* Handler */
 } admin_resop;
@@ -742,7 +807,7 @@ struct admin {
 #define AF_NOTE 4u                     /* Catch notifications */
 #define AF_WARN 8u                     /* Catch warning messages */
 #ifndef NTRACE
-  #define AF_TRACE 16u                 /* Catch tracing */
+#  define AF_TRACE 16u                 /* Catch tracing */
 #endif
 #define AF_FOREGROUND 32u              /* Quit server when client closes */
 
@@ -756,10 +821,9 @@ struct admin {
 
 extern sel_state sel;                  /* Global I/O event state */
 extern octet buf_i[PKBUFSZ], buf_o[PKBUFSZ], buf_t[PKBUFSZ], buf_u[PKBUFSZ];
-extern const tunnel_ops *tunnels[];    /* Table of tunnels (0-term) */
-extern const tunnel_ops *tun_default;  /* Default tunnel to use */
+extern udpsocket udpsock[NADDRFAM];    /* The master UDP sockets */
 extern kdata *master;                  /* Default private key */
-extern const char *tag_priv;           /* Default private key tag */
+extern char *tag_priv;                 /* Default private key tag */
 
 #ifndef NTRACE
 extern const trace_opt tr_opts[];      /* Trace options array */
@@ -779,14 +843,14 @@ extern unsigned tr_flags;         /* Trace options flags */
  *             @const char *pubkr@ = public keyring file
  *             @const char *ptag@ = default private-key tag
  *
- * Returns:    ---
+ * Returns:    Zero on success, @-1@ on failure.
  *
  * Use:                Initializes the key-management machinery, loading the
  *             keyrings and so on.
  */
 
-extern void km_init(const char */*privkr*/, const char */*pubkr*/,
-                   const char */*ptag*/);
+extern int km_init(const char */*privkr*/, const char */*pubkr*/,
+                  const char */*ptag*/);
 
 /* --- @km_reload@ --- *
  *
@@ -799,6 +863,20 @@ extern void km_init(const char */*privkr*/, const char */*pubkr*/,
 
 extern int km_reload(void);
 
+/* --- @km_clear@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Forget the currently loaded keyrings.  The @master@ key will
+ *             be cleared, but other keys already loaded will continue to
+ *             exist until their reference count drops to zero.  Call
+ *             @km_init@ to make everything work again.
+ */
+
+extern void km_clear(void);
+
 /* --- @km_findpub@, @km_findpriv@ --- *
  *
  * Arguments:  @const char *tag@ = key tag to load
@@ -811,6 +889,19 @@ extern int km_reload(void);
 extern kdata *km_findpub(const char */*tag*/);
 extern kdata *km_findpriv(const char */*tag*/);
 
+/* --- @km_findpubbyid@, @km_findprivbyid@ --- *
+ *
+ * Arguments:  @uint32 id@ = key id to load
+ *
+ * Returns:    Pointer to the kdata object if successful, or null on error.
+ *
+ * Use:                Fetches a public or private key from the keyring given its
+ *             numeric id.
+ */
+
+extern kdata *km_findpubbyid(uint32 /*id*/);
+extern kdata *km_findprivbyid(uint32 /*id*/);
+
 /* --- @km_samealgsp@ --- *
  *
  * Arguments:  @const kdata *kdx, *kdy@ = two key data objects
@@ -873,16 +964,18 @@ extern void kx_start(keyexch */*kx*/, int /*forcep*/);
 /* --- @kx_message@ --- *
  *
  * Arguments:  @keyexch *kx@ = pointer to key exchange context
+ *             @const addr *a@ = sender's IP address and port
  *             @unsigned msg@ = the message code
  *             @buf *b@ = pointer to buffer containing the packet
  *
- * Returns:    ---
+ * Returns:    Nonzero if the sender's address was unknown.
  *
  * Use:                Reads a packet containing key exchange messages and handles
  *             it.
  */
 
-extern void kx_message(keyexch */*kx*/, unsigned /*msg*/, buf */*b*/);
+extern int kx_message(keyexch */*kx*/, const addr */*a*/,
+                     unsigned /*msg*/, buf */*b*/);
 
 /* --- @kx_free@ --- *
  *
@@ -909,7 +1002,7 @@ extern void kx_free(keyexch */*kx*/);
 
 extern void kx_newkeys(keyexch */*kx*/);
 
-/* --- @kx_init@ --- *
+/* --- @kx_setup@ --- *
  *
  * Arguments:  @keyexch *kx@ = pointer to key exchange context
  *             @peer *p@ = pointer to peer context
@@ -923,70 +1016,51 @@ extern void kx_newkeys(keyexch */*kx*/);
  *             exchange.
  */
 
-extern int kx_init(keyexch */*kx*/, peer */*p*/,
-                  keyset **/*ks*/, unsigned /*f*/);
-
-/*----- Keysets and symmetric cryptography --------------------------------*/
+extern int kx_setup(keyexch */*kx*/, peer */*p*/,
+                   keyset **/*ks*/, unsigned /*f*/);
 
-/* --- @ks_drop@ --- *
+/* --- @kx_init@ --- *
  *
- * Arguments:  @keyset *ks@ = pointer to a keyset
+ * Arguments:  ---
  *
  * Returns:    ---
  *
- * Use:                Decrements a keyset's reference counter.  If the counter hits
- *             zero, the keyset is freed.
+ * Use:                Initializes the key-exchange logic.
  */
 
-extern void ks_drop(keyset */*ks*/);
+extern void kx_init(void);
 
-/* --- @ks_derivekey@ --- *
+/*----- Keysets and symmetric cryptography --------------------------------*/
+
+/* --- @ks_drop@ --- *
  *
- * Arguments:  @octet *k@ = pointer to an output buffer of at least
- *                     @MAXHASHSZ@ bytes
- *             @size_t ksz@ = actual size wanted (for tracing)
- *             @const struct rawkey *rk@ = a raw key, as passed into
- *                     @genkeys@
- *             @int dir@ = direction for the key (@DIR_IN@ or @DIR_OUT@)
- *             @const char *what@ = label for the key (input to derivation)
+ * Arguments:  @keyset *ks@ = pointer to a keyset
  *
  * Returns:    ---
  *
- * Use:                Derives a session key, for use on incoming or outgoing data.
- *             This function is part of a private protocol between @ks_gen@
- *             and the bulk crypto transform @genkeys@ operation.
+ * Use:                Decrements a keyset's reference counter.  If the counter hits
+ *             zero, the keyset is freed.
  */
 
-extern void ks_derivekey(octet */*k*/, size_t /*ksz*/,
-                        const struct rawkey */*rk*/,
-                        int /*dir*/, const char */*what*/);
+extern void ks_drop(keyset */*ks*/);
 
 /* --- @ks_gen@ --- *
  *
- * Arguments:  @const void *k@ = pointer to key material
- *             @size_t x, y, z@ = offsets into key material (see below)
+ * Arguments:  @deriveargs *a@ = key derivation parameters (modified)
  *             @peer *p@ = pointer to peer information
  *
  * Returns:    A pointer to the new keyset.
  *
- * Use:                Derives a new keyset from the given key material.  The
- *             offsets @x@, @y@ and @z@ separate the key material into three
- *             parts.  Between the @k@ and @k + x@ is `my' contribution to
- *             the key material; between @k + x@ and @k + y@ is `your'
- *             contribution; and between @k + y@ and @k + z@ is a shared
- *             value we made together.  These are used to construct two
- *             pairs of symmetric keys.  Each pair consists of an encryption
- *             key and a message authentication key.  One pair is used for
- *             outgoing messages, the other for incoming messages.
+ * Use:                Derives a new keyset from the given key material.  This will
+ *             set the @what@, @f@, and @hc@ members in @*a@; other members
+ *             must be filled in by the caller.
  *
  *             The new key is marked so that it won't be selected for output
  *             by @ksl_encrypt@.  You can still encrypt data with it by
  *             calling @ks_encrypt@ directly.
  */
 
-extern keyset *ks_gen(const void */*k*/,
-                     size_t /*x*/, size_t /*y*/, size_t /*z*/,
-                     peer */*p*/);
+extern keyset *ks_gen(deriveargs */*a*/, peer */*p*/);
 
 /* --- @ks_activate@ --- *
  *
@@ -1125,25 +1199,29 @@ extern int ksl_decrypt(keyset **/*ksroot*/, unsigned /*ty*/,
 
 /* --- @c_new@ --- *
  *
- * Arguments:  @buf *b@ = where to put the challenge
+ * Arguments:  @const void *m@ = pointer to associated message, or null
+ *             @size_t msz@ = length of associated message
+ *             @buf *b@ = where to put the challenge
  *
  * Returns:    Zero if OK, nonzero on error.
  *
  * Use:                Issues a new challenge.
  */
 
-extern int c_new(buf */*b*/);
+extern int c_new(const void */*m*/, size_t /*msz*/, buf */*b*/);
 
 /* --- @c_check@ --- *
  *
- * Arguments:  @buf *b@ = where to find the challenge
+ * Arguments:  @const void *m@ = pointer to associated message, or null
+ *             @size_t msz@ = length of associated message
+ *             @buf *b@ = where to find the challenge
  *
  * Returns:    Zero if OK, nonzero if it didn't work.
  *
  * Use:                Checks a challenge.  On failure, the buffer is broken.
  */
 
-extern int c_check(buf */*b*/);
+extern int c_check(const void */*m*/, size_t /*msz*/, buf */*b*/);
 
 /*----- Administration interface ------------------------------------------*/
 
@@ -1173,7 +1251,9 @@ extern int c_check(buf */*b*/);
  *
  *               * "?PEER" PEER -- peer's name
  *
- *               * "?ERRNO" ERRNO -- system error code
+ *               * "?ERR" CODE -- system error code
+ *
+ *               * "?ERRNO" -- system error code from @errno@
  *
  *               * "[!]..." ... -- @dstr_putf@-like string as single token
  */
@@ -1237,22 +1317,14 @@ extern void EXECL_LIKE(0) a_notify(const char */*fmt*/, ...);
  *
  * Returns:    ---
  *
- * Use:                Creates a new admin connection.
+ * Use:                Creates a new admin connection.  It's safe to call this
+ *             before @a_init@ -- and, indeed, this makes sense if you also
+ *             call @a_switcherr@ to report initialization errors through
+ *             the administration machinery.
  */
 
 extern void a_create(int /*fd_in*/, int /*fd_out*/, unsigned /*f*/);
 
-/* --- @a_quit@ --- *
- *
- * Arguments:  ---
- *
- * Returns:    ---
- *
- * Use:                Shuts things down nicely.
- */
-
-extern void a_quit(void);
-
 /* --- @a_preselect@ --- *
  *
  * Arguments:  ---
@@ -1277,6 +1349,61 @@ extern void a_preselect(void);
 
 extern void a_daemon(void);
 
+/* --- @a_listen@ --- *
+ *
+ * Arguments:  @const char *name@ = socket name to create
+ *             @uid_t u@ = user to own the socket
+ *             @gid_t g@ = group to own the socket
+ *             @mode_t m@ = permissions to set on the socket
+ *
+ * Returns:    Zero on success, @-1@ on failure.
+ *
+ * Use:                Creates the admin listening socket.
+ */
+
+extern int a_listen(const char */*sock*/,
+                   uid_t /*u*/, gid_t /*g*/, mode_t /*m*/);
+
+/* --- @a_unlisten@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Stops listening to the administration socket and removes it.
+ */
+
+extern void a_unlisten(void);
+
+/* --- @a_switcherr@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Arrange to report warnings, trace messages, etc. to
+ *             administration clients rather than the standard-error stream.
+ *
+ *             Obviously this makes no sense unless there is at least one
+ *             client established.  Calling @a_listen@ won't help with this,
+ *             because the earliest a new client can connect is during the
+ *             first select-loop iteration, which is too late: some initial
+ *             client must have been added manually using @a_create@.
+ */
+
+extern void a_switcherr(void);
+
+/* --- @a_signals@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Establishes handlers for the obvious signals.
+ */
+
+extern void a_signals(void);
+
 /* --- @a_init@ --- *
  *
  * Arguments:  @const char *sock@ = socket name to create
@@ -1284,13 +1411,12 @@ extern void a_daemon(void);
  *             @gid_t g@ = group to own the socket
  *             @mode_t m@ = permissions to set on the socket
  *
- * Returns:    ---
+ * Returns:    Zero on success, @-1@ on failure.
  *
  * Use:                Creates the admin listening socket.
  */
 
-extern void a_init(const char */*sock*/,
-                  uid_t /*u*/, gid_t /*g*/, mode_t /*m*/);
+extern int a_init(void);
 
 /*----- Mapping with addresses as keys ------------------------------------*/
 
@@ -1390,13 +1516,13 @@ extern int ps_tunfd(const tunnel_ops */*tops*/, char **/*ifn*/);
  *
  * Arguments:  @int detachp@ = whether to detach the child from its terminal
  *
- * Returns:    ---
+ * Returns:    Zero on success, @-1@ on failure.
  *
  * Use:                Separates off the privileged tunnel-opening service from the
  *             rest of the server.
  */
 
-extern void ps_split(int /*detachp*/);
+extern int ps_split(int /*detachp*/);
 
 /* --- @ps_quit@ --- *
  *
@@ -1411,6 +1537,19 @@ extern void ps_quit(void);
 
 /*----- Peer management ---------------------------------------------------*/
 
+/* --- @p_updateaddr@ --- *
+ *
+ * Arguments:  @peer *p@ = pointer to peer block
+ *             @const addr *a@ = address to associate with this peer
+ *
+ * Returns:    Zero if the address was changed; @+1@ if it was already
+ *             right.
+ *
+ * Use:                Updates our idea of @p@'s address.
+ */
+
+extern int p_updateaddr(peer */*p*/, const addr */*a*/);
+
 /* --- @p_txstart@ --- *
  *
  * Arguments:  @peer *p@ = pointer to peer block
@@ -1424,6 +1563,20 @@ extern void ps_quit(void);
 
 extern buf *p_txstart(peer */*p*/, unsigned /*msg*/);
 
+/* --- @p_txaddr@ --- *
+ *
+ * Arguments:  @const addr *a@ = recipient address
+ *             @const void *p@ = pointer to packet to send
+ *             @size_t sz@ = length of packet
+ *
+ * Returns:    Zero if successful, nonzero on error.
+ *
+ * Use:                Sends a packet to an address which (possibly) isn't a current
+ *             peer.
+ */
+
+extern int p_txaddr(const addr */*a*/, const void */*p*/, size_t /*sz*/);
+
 /* --- @p_txend@ --- *
  *
  * Arguments:  @peer *p@ = pointer to peer block
@@ -1552,26 +1705,123 @@ extern void p_setifname(peer */*p*/, const char */*name*/);
 
 extern const addr *p_addr(peer */*p*/);
 
+/* --- @p_bind@ --- *
+ *
+ * Arguments:  @struct addrinfo *ailist@ = addresses to bind to
+ *
+ * Returns:    Zero on success, @-1@ on failure.
+ *
+ * Use:                Binds to the main UDP sockets.
+ */
+
+extern int p_bind(struct addrinfo */*ailist*/);
+
+/* --- @p_unbind@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Unbinds the UDP sockets.  There must not be any active peers,
+ *             and none can be created until the sockets are rebound.
+ */
+
+extern void p_unbind(void);
+
 /* --- @p_init@ --- *
  *
- * Arguments:  @struct in_addr addr@ = address to bind to
- *             @unsigned port@ = port number to listen to
+ * Arguments:  ---
  *
  * Returns:    ---
  *
- * Use:                Initializes the peer system; creates the socket.
+ * Use:                Initializes the peer system.
  */
 
-extern void p_init(struct in_addr /*addr*/, unsigned /*port*/);
+extern void p_init(void);
 
-/* --- @p_port@ --- *
+/* --- @p_addtun@ --- *
+ *
+ * Arguments:  @const tunnel_ops *tops@ = tunnel ops to add
+ *
+ * Returns:    Zero on success, @-1@ on failure.
+ *
+ * Use:                Adds a tunnel class to the list of known classes, if it
+ *             initializes properly.  If there is no current default tunnel,
+ *             then this one is made the default.
+ *
+ *             Does nothing if the tunnel class is already known.  So adding
+ *             a bunch of tunnels takes quadratic time, but there will be
+ *             too few to care about.
+ */
+
+extern int p_addtun(const tunnel_ops */*tops*/);
+
+/* --- @p_setdflttun@ --- *
+ *
+ * Arguments:  @const tunnel_ops *tops@ = tunnel ops to set
+ *
+ * Returns:    ---
+ *
+ * Use:                Sets the default tunnel.  It must already be registered.  The
+ *             old default is forgotten.
+ */
+
+extern void p_setdflttun(const tunnel_ops */*tops*/);
+
+/* --- @p_dflttun@ --- *
  *
  * Arguments:  ---
  *
- * Returns:    Port number used for socket.
+ * Returns:    A pointer to the current default tunnel operations, or null
+ *             if no tunnels are defined.
+ */
+
+extern const tunnel_ops *p_dflttun(void);
+
+/* --- @p_findtun@ --- *
+ *
+ * Arguments:  @const char *name@ = tunnel name
+ *
+ * Returns:    Pointer to the tunnel operations, or null.
+ *
+ * Use:                Finds the operations for a named tunnel class.
  */
 
-unsigned p_port(void);
+extern const tunnel_ops *p_findtun(const char */*name*/);
+
+/* --- @p_mktuniter@ --- *
+ *
+ * Arguments:  @tuniter *i@ = pointer to iterator to initialize
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a tunnel iterator.
+ */
+
+extern void p_mktuniter(tun_iter */*i*/);
+
+/* --- @p_nexttun@ --- *
+ *
+ * Arguments:  @tuniter *i@ = pointer to iterator
+ *
+ * Returns:    Pointer to the next tunnel's operations, or null.
+ */
+
+extern const tunnel_ops *p_nexttun(tun_iter */*i*/);
+
+/* --- @FOREACH_TUN@ --- *
+ *
+ * Arguments:  @tops@ = name to bind to each tunnel
+ *             @stuff@ = thing to do for each item
+ *
+ * Use:                Does something for each known tunnel class.
+ */
+
+#define FOREACH_TUN(tops, stuff) do {                                  \
+  tun_iter i_;                                                         \
+  const tunnel_ops *tops;                                              \
+  for (p_mktuniter(&i_); (tops = p_nexttun(&i_)) != 0; ) stuff;                \
+} while (0)
 
 /* --- @p_create@ --- *
  *
@@ -1648,13 +1898,25 @@ extern peer *p_find(const char */*name*/);
 /* --- @p_destroy@ --- *
  *
  * Arguments:  @peer *p@ = pointer to a peer
+ *             @int bye@ = say goodbye to the peer?
  *
  * Returns:    ---
  *
  * Use:                Destroys a peer.
  */
 
-extern void p_destroy(peer */*p*/);
+extern void p_destroy(peer */*p*/, int /*bye*/);
+
+/* --- @p_destroyall@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Destroys all of the peers, saying goodbye.
+ */
+
+extern void p_destroyall(void);
 
 /* --- @FOREACH_PEER@ --- *
  *
@@ -1692,6 +1954,68 @@ extern void p_mkiter(peer_iter */*i*/);
 
 extern peer *p_next(peer_iter */*i*/);
 
+/*----- The interval timer ------------------------------------------------*/
+
+/* --- @iv_addreason@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Adds an `interval timer reason'; if there are no others, the
+ *             interval timer is engaged.
+ */
+
+extern void iv_addreason(void);
+
+/* --- @iv_rmreason@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Removes an interval timer reason; if there are none left, the
+ *             interval timer is disengaged.
+ */
+
+extern void iv_rmreason(void);
+
+/*----- The main loop -----------------------------------------------------*/
+
+/* --- @lp_init@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes the main loop.  Most importantly, this sets up
+ *             the select multiplexor that everything else hooks onto.
+ */
+
+extern void lp_init(void);
+
+/* --- @lp_end@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Requests an exit from the main loop.
+ */
+
+extern void lp_end(void);
+
+/* --- @lp_run@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    Zero on successful termination; @-1@ if things went wrong.
+ *
+ * Use:                Cranks the main loop until it should be cranked no more.
+ */
+
+extern int lp_run(void);
+
 /*----- Tunnel drivers ----------------------------------------------------*/
 
 #ifdef TUN_LINUX
@@ -1731,6 +2055,37 @@ extern const char *timestr(time_t /*t*/);
 
 extern int mystrieq(const char */*x*/, const char */*y*/);
 
+/* --- @afix@ --- *
+ *
+ * Arguments:  @int af@ = an address family code
+ *
+ * Returns:    The index of the address family's record in @aftab@, or @-1@.
+ */
+
+extern int afix(int af);
+
+/* --- @addrsz@ --- *
+ *
+ * Arguments:  @const addr *a@ = a network address
+ *
+ * Returns:    The size of the address, for passing into the sockets API.
+ */
+
+extern socklen_t addrsz(const addr */*a*/);
+
+/* --- @getport@, @setport@ --- *
+ *
+ * Arguments:  @addr *a@ = a network address
+ *             @unsigned port@ = port number to set
+ *
+ * Returns:    ---
+ *
+ * Use:                Retrieves or sets the port number in an address structure.
+ */
+
+extern unsigned getport(addr */*a*/);
+extern void setport(addr */*a*/, unsigned /*port*/);
+
 /* --- @seq_reset@ --- *
  *
  * Arguments:  @seqwin *s@ = sequence-checking window
@@ -1756,6 +2111,75 @@ extern void seq_reset(seqwin */*s*/);
 
 extern int seq_check(seqwin */*s*/, uint32 /*q*/, const char */*service*/);
 
+typedef struct ratelim {
+  unsigned n, max, persec;
+  struct timeval when;
+} ratelim;
+
+/* --- @ratelim_init@ --- *
+ *
+ * Arguments:  @ratelim *r@ = rate-limiting state to fill in
+ *             @unsigned persec@ = credit to accumulate per second
+ *             @unsigned max@ = maximum credit to retain
+ *
+ * Returns:    ---
+ *
+ * Use:                Initialize a rate-limiting state.
+ */
+
+extern void ratelim_init(ratelim */*r*/,
+                        unsigned /*persec*/, unsigned /*max*/);
+
+/* --- @ratelim_withdraw@ --- *
+ *
+ * Arguments:  @ratelim *r@ = rate-limiting state
+ *             @unsigned n@ = credit to withdraw
+ *
+ * Returns:    Zero if successful; @-1@ if there is unsufficient credit
+ *
+ * Use:                Updates the state with any accumulated credit.  Then, if
+ *             there there are more than @n@ credits available, withdraw @n@
+ *             and return successfully; otherwise, report failure.
+ */
+
+extern int ratelim_withdraw(ratelim */*r*/, unsigned /*n*/);
+
+/* --- @ies_encrypt@ --- *
+ *
+ * Arguments:  @kdata *kpub@ = recipient's public key
+ *             @unsigned ty@ = message type octet
+ *             @buf *b@ = input message buffer
+ *             @buf *bb@ = output buffer for the ciphertext
+ *
+ * Returns:    On error, returns a @KSERR_...@ code or breaks the buffer;
+ *             on success, returns zero and the buffer is good.
+ *
+ * Use:                Encrypts a message for a recipient, given their public key.
+ *             This does not (by itself) provide forward secrecy or sender
+ *             authenticity.  The ciphertext is self-delimiting (unlike
+ *             @ks_encrypt@).
+ */
+
+extern int ies_encrypt(kdata */*kpub*/, unsigned /*ty*/,
+                      buf */*b*/, buf */*bb*/);
+
+/* --- @ies_decrypt@ --- *
+ *
+ * Arguments:  @kdata *kpub@ = private key key
+ *             @unsigned ty@ = message type octet
+ *             @buf *b@ = input ciphertext buffer
+ *             @buf *bb@ = output buffer for the message
+ *
+ * Returns:    On error, returns a @KSERR_...@ code; on success, returns
+ *             zero and the buffer is good.
+ *
+ * Use:                Decrypts a message encrypted using @ies_encrypt@, given our
+ *             private key.
+ */
+
+extern int ies_decrypt(kdata */*kpriv*/, unsigned /*ty*/,
+                      buf */*b*/, buf */*bb*/);
+
 /*----- That's all, folks -------------------------------------------------*/
 
 #ifdef __cplusplus
index 85e9cd8..29f42d5 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 /*----- Header files ------------------------------------------------------*/
@@ -180,13 +179,13 @@ static void t_read(int fd, unsigned mode, void *v)
  *
  * Arguments:  ---
  *
- * Returns:    ---
+ * Returns:    Zero on success, @-1@ on failure.
  *
  * Use:                Initializes the tunneling system.  Maybe this will require
  *             opening file descriptors or something.
  */
 
-static void t_init(void)
+static int t_init(void)
 {
   char *p, *q;
   dstr d = DSTR_INIT;
@@ -195,7 +194,7 @@ static void t_init(void)
   size_t n;
 
   if ((p = getenv("TRIPE_SLIPIF")) == 0)
-    return;
+    return (0);
 
   /* --- Build the list of available interfaces --- */
 
@@ -241,10 +240,11 @@ static void t_init(void)
       break;
     p++;
   }
-  return;
+  return (0);
 
 whine:
-  moan("bad slip interface list");
+  a_warn("TUN", "-", "slip", "bad-interface-list", A_END);
+  return (-1);
 }
 
 /* --- @t_broken@ --- *
index 2983767..4a9643e 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 /*----- Header files ------------------------------------------------------*/
@@ -75,13 +74,13 @@ static void t_read(int fd, unsigned mode, void *v)
  *
  * Arguments:  ---
  *
- * Returns:    ---
+ * Returns:    Zero on success, @-1@ on failure.
  *
  * Use:                Initializes the tunneling system.  Maybe this will require
  *             opening file descriptors or something.
  */
 
-static void t_init(void) { return; }
+static int t_init(void) { return (0); }
 
 /* --- @t_create@ --- *
  *
index c9f98fe..2b73ebe 100644 (file)
@@ -9,19 +9,18 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 include $(top_srcdir)/vars.am
 
index 468a5c6..1e4e2d2 100644 (file)
@@ -9,22 +9,21 @@
 .\"
 .\" This file is part of Trivial IP Encryption (TrIPE).
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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.
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\" along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 .
 .\"--------------------------------------------------------------------------
-.so ../defs.man.in \"@@@PRE@@@
+.so ../common/defs.man \"@@@PRE@@@
 .
 .\"--------------------------------------------------------------------------
 .TH connect 8tripe "11 December 2007" "Straylight/Edgeware" "TrIPE: Trivial IP Encryption"
@@ -247,6 +246,33 @@ becomes
 .BR A_CIPHER_BLKSZ .
 .
 .SS "Dynamic connection"
+The
+.B connect
+service supports two kinds of dynamic connection.
+.PP
+The new kind of dynamic association uses the built-in
+.B knock
+protocol.  If the peer's database record assigns a value to the
+.B knock
+key, then the new connection protocol is used: this value is sent to the
+peer during key-exchange, which should (if the peer is properly
+configured) automatically establish the other end of the connection.
+The string should have the form
+.RI [ prefix\fB. ] tag ,
+where the whole string names this host as it is known by the remote
+host, and the
+.I tag
+names this host's public key.  The passive server receives this string,
+verifies that the sender has access to the claimed private key, and
+emits a
+.B KNOCK
+notification which
+.B connect
+notices, causing it to establish the passive peer.  While the internals
+are somewhat complex, configuration is pretty easy.
+.PP
+The older kind of dynamic association is rather more complicated to set
+up, and involves running shell commands, and probably configuring SSH.
 If a peer's database record assigns a value to the
 .B connect
 key, then the
@@ -295,7 +321,8 @@ key is invoked as a Bourne shell command.  This ought to result in a
 .B KILL
 command being issued to the peer's server.
 .PP
-In detail, the protocol for passive connection works as follows.
+In detail, the protocol for old-style dynamic connection works as
+follows.
 .hP 1.
 The active peer
 .BR ADD s
@@ -440,6 +467,7 @@ The service will submit the command
 .RB [ \-priv
 .IR tag ]
 .RB [ \-mobile ]
+.RB [ \-ephemeral ]
 .RB [ \-tunnel
 .IR driver ]
 .I address
@@ -506,6 +534,19 @@ to the
 .B tunnel
 key.
 .hP \*o
+The option
+.B \-ephemeral
+is provided if the peer's database record assigns the
+.B ephemeral
+key one of the values
+.BR t ,
+.BR true ,
+.BR y ,
+.BR yes,
+or
+.BR on ;
+or if it is absent.
+.hP \*o
 The
 .I address
 is the value assigned to the
@@ -524,7 +565,7 @@ provided about each peer, in the form of subsequent tokens.  Clients
 should be prepared to ignore such tokens.)
 .SP
 .BI "info " peer
-Lists the database record for the named
+Lists the database record and additional information about the named
 .IR peer .
 For each key/value pair, a line
 .RS
@@ -533,6 +574,67 @@ For each key/value pair, a line
 .IB key = value
 .PP
 is output.  The key/value pairs are output in an arbitrary order.
+.PP
+In addition to the fields of the peer's database record, the following
+additional keys are defined.
+.TP
+.B failures
+The number of failed pings in the current or most recent batch, in
+decimal.
+.TP
+.B last-ping
+The round-trip time of the most recent ping in milliseconds, in the form
+.IB nn.n ms\fR,
+or
+.B timeout
+if the most recent ping timed out,
+or
+.B \-
+if no pings have yet completed.
+.TP
+.B max-ping
+The maximum successful ping time so far in milliseconds, in the form
+.IB nn.n ms\fR,
+or
+.B \-
+if no pings have yet succeeded.
+.TP
+.B mean-ping
+The average successful ping time so far in milliseconds, in the form
+.IB nn.n ms\fR,
+or
+.B \-
+if no pings have yet succeeded.
+.TP
+.B min-ping
+The minimum successful ping time so far in milliseconds, in the form
+.IB nn.n ms\fR,
+or
+.B \-
+if no pings have yet succeeded.
+.TP
+.B n-lost
+The number of pings which have been declared timed out so far, in
+decimal.
+.TP
+.B n-ping
+The number of successful pings so far, in decimal.
+.TP
+.B sd-ping
+The standard deviation of ping times so far in milliseconds, in the form
+.IB nn.n ms\fR,
+or
+.B \-
+if no pings have yet succeeded.
+.TP
+.B state
+One of the strings:
+.B idle
+if the peer has responded to a ping recently, and we are waiting for the
+.B every
+delay before we try again; or
+.B check
+if we are currently waiting for a ping to return.
 .RE
 .SP
 .BI "kick " peer
@@ -597,6 +699,17 @@ seconds are assumed.
 .\"-opts
 .RE
 .SP
+.BI "sabotage " peer
+Sabotage the
+.I peer
+so that
+.B connect
+thinks that it can't respond to pings.  This will usually provoke a
+reconnection attempt.  Use
+.B kick
+instead unless you're trying to test
+.BR connect .
+.SP
 .BI "userpeer " user
 Output a single
 .B INFO
@@ -653,6 +766,29 @@ command reported
 .B FAIL
 .IR error ...
 .SP
+.BI "USER connect knock-active-peer " name
+The server reported a valid
+.B knock
+message from a peer calling itself
+.I name
+but this is not a passive peer.
+.SP
+.BI "USER connect knock-unknown-peer " name
+The server reported a valid
+.B knock
+message from a peer calling itself
+.I name
+but no such peer is defined in the database.
+.SP
+.BI "USER connect knock-tag-mismatch peer " name " public-key-tag " tag
+The server reported a valid
+.B knock
+message from a peer calling itself
+.I name
+but this name doesn't match that peer's recorded public-key tag, which
+is
+.IR tag .
+.SP
 .BI "USER connect ping-ok " peer
 A reply was received to a
 .B PING
index 78da325..268ad5f 100644 (file)
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 VERSION = '@VERSION@'
 
@@ -34,9 +33,11 @@ import tripe as T
 import os as OS
 import signal as SIG
 import errno as E
+from math import sqrt
 import cdb as CDB
 import mLib as M
 import re as RX
+import sys as SYS
 from time import time
 import subprocess as PROC
 
@@ -295,12 +296,15 @@ class Peer (object):
     a FILTER function is given then apply it to the information from the
     database before returning it.
     """
-    attr = me.__dict__.get(key, default)
-    if attr is _magic:
-      raise T.TripeJobError('malformed-peer', me.name, 'missing-key', key)
-    elif filter is not None:
-      attr = filter(attr)
-    return attr
+    try:
+      attr = me.__dict__[key]
+    except KeyError:
+      if default is _magic:
+        raise T.TripeJobError('malformed-peer', me.name, 'missing-key', key)
+      return default
+    else:
+      if filter is not None: attr = filter(attr)
+      return attr
 
   def has(me, key):
     """
@@ -396,6 +400,13 @@ class PingPeer (object):
     me.seq = _pingseq
     _pingseq += 1
     me._failures = 0
+    me._sabotage = False
+    me._last = '-'
+    me._nping = 0
+    me._nlost = 0
+    me._sigma_t = 0
+    me._sigma_t2 = 0
+    me._min = me._max = '-'
     if pingnow:
       me._timer = None
       me._ping()
@@ -414,6 +425,7 @@ class PingPeer (object):
     me._timeout = peer.get('timeout', filter = T.timespec, default = 10)
     me._retries = peer.get('retries', filter = int, default = 5)
     me._connectp = peer.has('connect')
+    me._knockp = peer.has('knock')
     return me
 
   def _ping(me):
@@ -429,14 +441,18 @@ class PingPeer (object):
        me._peer]))
 
   def _reconnect(me):
-    peer = Peer(me._peer)
-    if me._connectp:
-      S.warn('connect', 'reconnecting', me._peer)
-      S.forcekx(me._peer)
-      T.spawn(run_connect, peer, peer.get('connect'))
-      me._timer = M.SelTimer(time() + me._every, me._time)
-    else:
-      S.kill(me._peer)
+    try:
+      peer = Peer(me._peer)
+      if me._connectp or me._knockp:
+        S.warn('connect', 'reconnecting', me._peer)
+        S.forcekx(me._peer)
+        if me._connectp: T.spawn(run_connect, peer, peer.get('connect'))
+        me._timer = M.SelTimer(time() + me._every, me._time)
+        me._sabotage = False
+      else:
+        S.kill(me._peer)
+    except TripeError, e:
+      if e.args[0] == 'unknown-peer': me._pinger.kill(me._peer)
 
   def event(me, code, stuff):
     """
@@ -455,28 +471,63 @@ class PingPeer (object):
       me._ping()
     elif code == 'FAIL':
       S.notify('connect', 'ping-failed', me._peer, *stuff)
-      if not stuff:
-        pass
-      elif stuff[0] == 'unknown-peer':
-        me._pinger.kill(me._peer)
-      elif stuff[0] == 'ping-send-failed':
-        me._reconnect()
+      if not stuff: pass
+      elif stuff[0] == 'unknown-peer': me._pinger.kill(me._peer)
+      elif stuff[0] == 'ping-send-failed': me._reconnect()
     elif code == 'INFO':
-      if stuff[0] == 'ping-ok':
-        if me._failures > 0:
-          S.warn('connect', 'ping-ok', me._peer)
+      outcome = stuff[0]
+      if outcome == 'ping-ok' and me._sabotage:
+        outcome = 'ping-timeout'
+      if outcome == 'ping-ok':
+        if me._failures > 0: S.warn('connect', 'ping-ok', me._peer)
+        t = float(stuff[1])
+        me._last = '%.1fms' % t
+        me._sigma_t += t
+        me._sigma_t2 += t*t
+        me._nping += 1
+        if me._min == '-' or t < me._min: me._min = t
+        if me._max == '-' or t > me._max: me._max = t
         me._timer = M.SelTimer(time() + me._every, me._time)
-      elif stuff[0] == 'ping-timeout':
+      elif outcome == 'ping-timeout':
         me._failures += 1
+        me._nlost += 1
         S.warn('connect', 'ping-timeout', me._peer,
                'attempt', str(me._failures), 'of', str(me._retries))
         if me._failures < me._retries:
           me._ping()
+          me._last = 'timeout'
         else:
           me._reconnect()
-      elif stuff[0] == 'ping-peer-died':
+          me._last = 'reconnect'
+      elif outcome == 'ping-peer-died':
         me._pinger.kill(me._peer)
 
+  def sabotage(me):
+    """Sabotage the peer, for testing purposes."""
+    me._sabotage = True
+    if me._timer: me._timer.kill()
+    T.defer(me._time)
+
+  def info(me):
+    if not me._nping:
+      mean = sd = '-'
+    else:
+      mean = me._sigma_t/me._nping
+      sd = sqrt(me._sigma_t2/me._nping - mean*mean)
+    n = me._nping + me._nlost
+    if not n: pclost = '-'
+    else: pclost = '%d' % ((100*me._nlost + n//2)//n)
+    return { 'last-ping': me._last,
+             'mean-ping': '%.1fms' % mean,
+             'sd-ping': '%.1fms' % sd,
+             'n-ping': '%d' % me._nping,
+             'n-lost': '%d' % me._nlost,
+             'percent-lost': pclost,
+             'min-ping': '%.1fms' % me._min,
+             'max-ping': '%.1fms' % me._max,
+             'state': me._timer and 'idle' or 'check',
+             'failures': me._failures }
+
   @T._callback
   def _time(me):
     """
@@ -517,7 +568,9 @@ class Pinger (T.Coroutine):
     while True:
       (peer, seq), code, stuff = me._q.get()
       if peer in me._peers and seq == me._peers[peer].seq:
-        me._peers[peer].event(code, stuff)
+        try: me._peers[peer].event(code, stuff)
+        except Exception, e:
+          SYS.excepthook(*SYS.exc_info())
 
   def add(me, peer, pingnow):
     """
@@ -529,7 +582,8 @@ class Pinger (T.Coroutine):
 
   def kill(me, peername):
     """Remove PEER from the peers being watched by the Pinger."""
-    del me._peers[peername]
+    try: del me._peers[peername]
+    except KeyError: pass
     return me
 
   def rescan(me, startup):
@@ -586,6 +640,10 @@ class Pinger (T.Coroutine):
     """
     return me._peers.keys()
 
+  def find(me, name):
+    """Return the PingPeer with the given name."""
+    return me._peers[name]
+
 ###--------------------------------------------------------------------------
 ### New connections.
 
@@ -659,23 +717,26 @@ def disownpeer(peer):
     T.Coroutine(run_ifupdown, name = 'ifdown %s' % peer.name) \
         .switch('ifdown', peer)
 
-def addpeer(peer, addr):
+def addpeer(peer, addr, ephemp):
   """
   Process a connect request from a new peer PEER on address ADDR.
 
-  Any existing peer with this name is disconnected from the server.
+  Any existing peer with this name is disconnected from the server.  EPHEMP
+  is the default ephemeral-ness state for the new peer.
   """
   if peer.name in S.list():
     S.kill(peer.name)
   try:
-    booltrue = ['t', 'true', 'y', 'yes', 'on']
     S.add(peer.name,
-          tunnel = peer.get('tunnel', None),
-          keepalive = peer.get('keepalive', None),
-          key = peer.get('key', None),
-          priv = peer.get('priv', None),
-          mobile = peer.get('mobile', 'nil') in booltrue,
-          cork = peer.get('cork', 'nil') in booltrue,
+          tunnel = peer.get('tunnel', default = None),
+          keepalive = peer.get('keepalive', default = None),
+          key = peer.get('key', default = None),
+          priv = peer.get('priv', default = None),
+          mobile = peer.get('mobile', filter = boolean, default = False),
+          knock = peer.get('knock', default = None),
+          cork = peer.get('cork', filter = boolean, default = False),
+          ephemeral = peer.get('ephemeral', filter = boolean,
+                               default = ephemp),
           *addr)
   except T.TripeError, exc:
     raise T.TripeJobError(*exc.args)
@@ -704,6 +765,23 @@ def notify(_, code, *rest):
     try: cr = chalmap[chal]
     except KeyError: pass
     else: cr.switch(rest[1:])
+  elif code == 'KNOCK':
+    try: p = Peer(rest[0])
+    except KeyError:
+      S.warn(['connect', 'knock-unknown-peer', rest[0]])
+      return
+    if p.get('peer') != 'PASSIVE':
+      S.warn(['connect', 'knock-active-peer', p.name])
+      return
+    dot = p.name.find('.')
+    if dot >= 0: kname = p.name[dot + 1:]
+    else: kname = p.name
+    ktag = p.get('key', p.name)
+    if kname != ktag:
+      S.warn(['connect', 'knock-tag-mismatch',
+              'peer', pname, 'public-key-tag', ktag])
+      return
+    T.spawn(addpeer, p, rest[1:], True)
 
 ###--------------------------------------------------------------------------
 ### Command implementation.
@@ -712,11 +790,13 @@ def cmd_kick(name):
   """
   kick NAME: Force a new connection attempt for the NAMEd peer.
   """
-  if name not in pinger.adopted():
-    raise T.TripeJobError('peer-not-adopted', name)
+  try: pp = pinger.find(name)
+  except KeyError: raise T.TripeJobError('peer-not-adopted', name)
   try: peer = Peer(name)
   except KeyError: raise T.TripeJobError('unknown-peer', name)
-  T.spawn(run_connect, peer, peer.get('connect'))
+  conn = peer.get('connect', None)
+  if conn: T.spawn(run_connect, peer, peer.get('connect'))
+  else: T.spawn(lambda p: S.forcekx(p.name), peer)
 
 def cmd_adopted():
   """
@@ -736,7 +816,7 @@ def cmd_active(name):
   addr = peer.get('peer')
   if addr == 'PASSIVE':
     raise T.TripeJobError('passive-peer', name)
-  addpeer(peer, M.split(addr, quotep = True)[0])
+  addpeer(peer, M.split(addr, quotep = True)[0], True)
 
 def cmd_listactive():
   """
@@ -753,10 +833,16 @@ def cmd_info(name):
   """
   try: peer = Peer(name)
   except KeyError: raise T.TripeJobError('unknown-peer', name)
-  items = list(peer.list())
+  d = {}
+  try: pp = pinger.find(name)
+  except KeyError: pass
+  else: d.update(pp.info())
+  items = list(peer.list()) + d.keys()
   items.sort()
   for i in items:
-    T.svcinfo('%s=%s' % (i, peer.get(i).replace('\n', ' ')))
+    try: v = d[i]
+    except KeyError: v = peer.get(i)
+    T.svcinfo('%s=%s' % (i, v.replace('\n', ' ')))
 
 def cmd_userpeer(user):
   """
@@ -792,10 +878,18 @@ def cmd_passive(*args):
     addr = cr.parent.switch()
     if addr is None:
       raise T.TripeJobError('connect-timeout')
-    addpeer(peer, addr)
+    addpeer(peer, addr, True)
   finally:
     del chalmap[chal]
 
+def cmd_sabotage(name):
+  """
+  sabotage NAME: Sabotage the NAMEd peer so that we think it can't be pinged.
+  """
+  try: pp = pinger.find(name)
+  except KeyError: raise T.TripeJobError('unknown-peer', name)
+  pp.sabotage()
+
 ###--------------------------------------------------------------------------
 ### Start up.
 
@@ -820,7 +914,7 @@ def setup():
     for name in M.split(autos)[0]:
       try:
         peer = Peer(name, cdb)
-        addpeer(peer, M.split(peer.get('peer'), quotep = True)[0])
+        addpeer(peer, M.split(peer.get('peer'), quotep = True)[0], False)
       except T.TripeJobError, err:
         S.warn('connect', 'auto-add-failed', name, *err.args)
 
@@ -879,11 +973,13 @@ service_info = [('connect', T.VERSION, {
   'active': (1, 1, 'PEER', cmd_active),
   'info': (1, 1, 'PEER', cmd_info),
   'list-active': (0, 0, '', cmd_listactive),
-  'userpeer': (1, 1, 'USER', cmd_userpeer)
+  'userpeer': (1, 1, 'USER', cmd_userpeer),
+  'sabotage': (1, 1, 'PEER', cmd_sabotage)
 })]
 
 if __name__ == '__main__':
   opts = parse_options()
+  OS.environ['TRIPESOCK'] = opts.tripesock
   T.runservices(opts.tripesock, service_info,
                 init = init, setup = setup,
                 daemon = opts.daemon)
index 316639b..b38f668 100644 (file)
@@ -9,19 +9,18 @@
 .\"
 .\" This file is part of Trivial IP Encryption (TrIPE).
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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.
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\" along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 .
 .\"--------------------------------------------------------------------------
 .so ../common/defs.man \"@@@PRE@@@
@@ -56,7 +55,9 @@ conntrack \- tripe service to start/stop peers depending on external connectivit
 The
 .B conntrack
 service watches D-Bus network management services like
-.BR NetworkManager (8)
+.BR NetworkManager (8),
+.BR ConnMan
+.RB ( connmand (8)),
 and Nokia's
 .BR ICd ,
 bringing peers up and down automatically.  It's designed to be useful on
@@ -84,22 +85,24 @@ followed by peer definitions, each of which looks like this:
 .B =
 .RI [ remote-addr ]
 .IB network / mask
+\&...
 .PP
 This means that the peer
 .I tag
-should be selected if the host's current IP address is within the
-network indicated by
+should be selected if the host's current IP address is within one of the
+networks indicated by
 .IB network / mask \fR.  
-Here,
+Here, a
 .I network
-is an IP address in dotted-quad form, and
+is an IPv4 or IPv6 address in dotted-quad form, and
 .I mask
-is a netmask, either in dotted-quad form, or as a number of 1-bits.
-Only one peer in each group may be connected at any given time; if a
-change is needed, any existing peer in the group is killed before
-connecting the new one.  If no match is found in a particular group,
-then no peers in the group are connected.  Strange and unhelpful things
-will happen if you put the same peer in several different groups.
+is a netmask, either in dotted-quad form (for IPv4), or as a prefix
+length (i.e., the number of initial 1-bits).  Only one peer in each
+group may be connected at any given time; if a change is needed, any
+existing peer in the group is killed before connecting the new one.  If
+no match is found in a particular group, then no peers in the group are
+connected.  Strange and unhelpful things will happen if you put the same
+peer in several different groups.
 .PP
 The tags
 .B down
@@ -112,36 +115,29 @@ is useful for detecting a `home' network, where a VPN is unnecessary
 The notion of `current IP address' is somewhat vague.  The
 .B conntrack
 service calculates it as the source address that the host would put on
-an IP packet sent to an arbitrarily chosen remote address.  The default
-remote address is 1.2.3.4 (which is unlikely ever to be assigned); this
-should determine an IP address on the network interface closest to the
-default gateway.  You can influence this process in two ways.  Firstly,
-you can change the default remote address used by adding a line
+an IP packet sent to a particular remote address; note that this is
+entirely hypothetical, and no actual packets are transmitted.  The
+default remote addresses are 1.2.3.4 (for IPv4, which is unlikely ever
+to be assigned), and 2001::1 (for IPv6); this should determine an IP
+address on the network interface closest to the default gateway.  You
+can influence this process in two ways.  Firstly, you can change the
+default remote address used by adding one or more lines
 .IP
 .B "test-addr ="
 .I remote-addr
+\&...
 .PP
 before the first peer group section.  Secondly, you can specify a
 particular
 .I remote-addr
 to use when checking whether a particular peer is applicable.
 .PP
-The peer definitions can be in any order.  They are checked
-most-specific first, and searching stops as soon as a match is found.
-Therefore a default definition can be added as
-.IP
-.I tag
-.B =
-.B 0/0
-.PP
-without fear of overriding any more specific definitions.  For avoidance
-of doubt, one peer definition is
-.I more specific
-than another if either the former has a specified
-.I remote-addr
-and the latter has not, or the former is wholly contained within the
-latter.  (Overlapping definitions are not recommended, and will be
-processed in an arbitrary order.)
+The peer definitions in each group are checked in the order given, and
+searching stops as soon as a match is found.  (In older versions of
+.BR conntrack ,
+definitions were processed according to a most-specific-first order, but
+that doesn't provide an ordering between IPv4 and IPv6 networks, which
+is important; so this has been changed.)
 .PP
 Peers are connected using the
 .BR connect (8)
@@ -218,22 +214,30 @@ is to be brought down, or no matching peer was found.  The
 is one of the following.
 .RS
 .TP
-.B "nm initially-connected"
-NetworkManager was detected on startup, and has an active network
-connection.
-.TP
-.B "nm initially-disconnected"
-NetworkManager was detected on startup, and has no active network
-connection.
+.BI "connman initially-" state
+ConnMan was detected on startup, and is in the given
+.I state
+\(en see below.
 .TP
-.B "nm connected"
-NetworkManager has acquired an active network connection.
+.BI "connman " state
+ConnMan has transitioned to
+.IR state .
+The possible states are:
+.B offline
+(the network is turned off by user request);
+.B idle
+(no network interfaces are active);
+.B ready
+(an interface is up but not fully configured); and
+.B online
+(an interface is up and configured).
 .TP
-.B "nm disconnected"
-NetworkManager has lost its active network connection.
+.BI "icd connected " iap
+Maemo ICd has acquired an active network connection, identified by
+.IR iap .
 .TP
-.B "nm default-connection-change"
-NetworkManager has changed its default route.
+.B "icd idle"
+Maemo ICd has lost its active network connection.
 .TP
 .BI "icd initially-connected " iap
 Maemo ICd was detected on startup, and has an active network connection
@@ -243,13 +247,6 @@ identified by
 .B "icd initially-disconnected"
 Maemo ICd was detected on startup, and has no active network connection.
 .TP
-.BI "icd connected " iap
-Maemo ICd has acquired an active network connection, identified by
-.IR iap .
-.TP
-.B "icd idle"
-Maemo ICd has lost its active network connection.
-.TP
 .B interval-timer
 A change was detected during
 .BR conntrack 's
@@ -263,6 +260,23 @@ The connection status was changed manually, using the
 or
 .B down
 service command.
+.TP
+.B "nm connected"
+NetworkManager has acquired an active network connection.
+.TP
+.B "nm default-connection-change"
+NetworkManager has changed its default route.
+.TP
+.B "nm disconnected"
+NetworkManager has lost its active network connection.
+.TP
+.B "nm initially-connected"
+NetworkManager was detected on startup, and has an active network
+connection.
+.TP
+.B "nm initially-disconnected"
+NetworkManager was detected on startup, and has no active network
+connection.
 .RE
 .
 .\"--------------------------------------------------------------------------
index 6ada45d..28e4b0b 100644 (file)
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 VERSION = '@VERSION@'
 
@@ -37,11 +36,13 @@ import socket as S
 import mLib as M
 import tripe as T
 import dbus as D
+import re as RX
 for i in ['mainloop', 'mainloop.glib']:
   __import__('dbus.%s' % i)
 try: from gi.repository import GLib as G
 except ImportError: import gobject as G
 from struct import pack, unpack
+from cStringIO import StringIO
 
 SM = T.svcmgr
 ##__import__('rmcr').__debug = True
@@ -54,46 +55,139 @@ class struct (object):
   def __init__(me, **kw):
     me.__dict__.update(kw)
 
-def toposort(cmp, things):
-  """
-  Generate the THINGS in an order consistent with a given partial order.
-
-  The function CMP(X, Y) should return true if X must precede Y, and false if
-  it doesn't care.  If X and Y are equal then it should return false.
+def loadb(s):
+  n = 0
+  for ch in s: n = 256*n + ord(ch)
+  return n
 
-  The THINGS may be any finite iterable; it is converted to a list
-  internally.
-  """
+def storeb(n, wd = None):
+  if wd is None: wd = n.bit_length()
+  s = StringIO()
+  for i in xrange((wd - 1)&-8, -8, -8): s.write(chr((n >> i)&0xff))
+  return s.getvalue()
 
-  ## Make sure we can index the THINGS, and prepare an ordering table.
-  ## What's going on?  The THINGS might not have a helpful equality
-  ## predicate, so it's easier to work with indices.  The ordering table will
-  ## remember which THINGS (by index) are considered greater than other
-  ## things.
-  things = list(things)
-  n = len(things)
-  order = [{} for i in xrange(n)]
-  rorder = [{} for i in xrange(n)]
-  for i in xrange(n):
-    for j in xrange(n):
-      if i != j and cmp(things[i], things[j]):
-        order[j][i] = True
-        rorder[i][j] = True
-
-  ## Now we can do the sort.
-  out = []
-  while True:
-    done = True
-    for i in xrange(n):
-      if order[i] is not None:
-        done = False
-        if len(order[i]) == 0:
-          for j in rorder[i]:
-            del order[j][i]
-          yield things[i]
-          order[i] = None
-    if done:
-      break
+###--------------------------------------------------------------------------
+### Address manipulation.
+###
+### I think this is the most demanding application, in terms of address
+### hacking, in the entire TrIPE suite.  At least we don't have to do it in
+### C.
+
+class BaseAddress (object):
+  def __init__(me, addrstr, maskstr = None):
+    me._setaddr(addrstr)
+    if maskstr is None:
+      me.mask = -1
+    elif maskstr.isdigit():
+      me.mask = (1 << me.NBITS) - (1 << me.NBITS - int(maskstr))
+    else:
+      me._setmask(maskstr)
+    if me.addr&~me.mask:
+      raise ValueError('network contains bits set beyond mask')
+  def _addrstr_to_int(me, addrstr):
+    try: return loadb(S.inet_pton(me.AF, addrstr))
+    except S.error: raise ValueError('bad address syntax')
+  def _int_to_addrstr(me, n):
+    return S.inet_ntop(me.AF, storeb(me.addr, me.NBITS))
+  def _setmask(me, maskstr):
+    raise ValueError('only prefix masked supported')
+  def _maskstr(me):
+    raise ValueError('only prefix masked supported')
+  def sockaddr(me, port = 0):
+    if me.mask != -1: raise ValueError('not a simple address')
+    return me._sockaddr(port)
+  def __str__(me):
+    addrstr = me._addrstr()
+    if me.mask == -1:
+      return addrstr
+    else:
+      inv = me.mask ^ ((1 << me.NBITS) - 1)
+      if (inv&(inv + 1)) == 0:
+        return '%s/%d' % (addrstr, me.NBITS - inv.bit_length())
+      else:
+        return '%s/%s' % (addrstr, me._maskstr())
+  def withinp(me, net):
+    if type(net) != type(me): return False
+    if (me.mask&net.mask) != net.mask: return False
+    if (me.addr ^ net.addr)&net.mask: return False
+    return me._withinp(net)
+  def eq(me, other):
+    if type(me) != type(other): return False
+    if me.mask != other.mask: return False
+    if me.addr != other.addr: return False
+    return me._eq(other)
+  def _withinp(me, net):
+    return True
+  def _eq(me, other):
+    return True
+
+class InetAddress (BaseAddress):
+  AF = S.AF_INET
+  AFNAME = 'IPv4'
+  NBITS = 32
+  def _addrstr_to_int(me, addrstr):
+    try: return loadb(S.inet_aton(addrstr))
+    except S.error: raise ValueError('bad address syntax')
+  def _setaddr(me, addrstr):
+    me.addr = me._addrstr_to_int(addrstr)
+  def _setmask(me, maskstr):
+    me.mask = me._addrstr_to_int(maskstr)
+  def _addrstr(me):
+    return me._int_to_addrstr(me.addr)
+  def _maskstr(me):
+    return me._int_to_addrstr(me.mask)
+  def _sockaddr(me, port = 0):
+    return (me._addrstr(), port)
+  @classmethod
+  def from_sockaddr(cls, sa):
+    addr, port = (lambda a, p: (a, p))(*sa)
+    return cls(addr), port
+
+class Inet6Address (BaseAddress):
+  AF = S.AF_INET6
+  AFNAME = 'IPv6'
+  NBITS = 128
+  def _setaddr(me, addrstr):
+    pc = addrstr.find('%')
+    if pc == -1:
+      me.addr = me._addrstr_to_int(addrstr)
+      me.scope = 0
+    else:
+      me.addr = me._addrstr_to_int(addrstr[:pc])
+      ais = S.getaddrinfo(addrstr, 0, S.AF_INET6, S.SOCK_DGRAM, 0,
+                          S.AI_NUMERICHOST | S.AI_NUMERICSERV)
+      me.scope = ais[0][4][3]
+  def _addrstr(me):
+    addrstr = me._int_to_addrstr(me.addr)
+    if me.scope == 0:
+      return addrstr
+    else:
+      name, _ = S.getnameinfo((addrstr, 0, 0, me.scope),
+                              S.NI_NUMERICHOST | S.NI_NUMERICSERV)
+      return name
+  def _sockaddr(me, port = 0):
+    return (me._addrstr(), port, 0, me.scope)
+  @classmethod
+  def from_sockaddr(cls, sa):
+    addr, port, _, scope = (lambda a, p, f = 0, s = 0: (a, p, f, s))(*sa)
+    me = cls(addr)
+    me.scope = scope
+    return me, port
+  def _withinp(me, net):
+    return net.scope == 0 or me.scope == net.scope
+  def _eq(me, other):
+    return me.scope == other.scope
+
+def parse_address(addrstr, maskstr = None):
+  if addrstr.find(':') >= 0: return Inet6Address(addrstr, maskstr)
+  else: return InetAddress(addrstr, maskstr)
+
+def parse_net(netstr):
+  try: sl = netstr.index('/')
+  except ValueError: raise ValueError('missing mask')
+  return parse_address(netstr[:sl], netstr[sl + 1:])
+
+def straddr(a): return a is None and '#<none>' or str(a)
 
 ###--------------------------------------------------------------------------
 ### Parse the configuration file.
@@ -103,18 +197,32 @@ def toposort(cmp, things):
 ## this service are largely going to be satellite notes, I don't think
 ## scalability's going to be a problem.
 
+TESTADDRS = [InetAddress('1.2.3.4'), Inet6Address('2001::1')]
+
+CONFSYNTAX = [
+  ('COMMENT', RX.compile(r'^\s*($|[;#])')),
+  ('GRPHDR', RX.compile(r'^\s*\[(.*)\]\s*$')),
+  ('ASSGN', RX.compile(r'\s*([\w.-]+)\s*[:=]\s*(|\S|\S.*\S)\s*$'))]
+
+class ConfigError (Exception):
+  def __init__(me, file, lno, msg):
+    me.file = file
+    me.lno = lno
+    me.msg = msg
+  def __str__(me):
+    return '%s:%d: %s' % (me.file, me.lno, me.msg)
+
 class Config (object):
   """
   Represents a configuration file.
 
   The most interesting thing is probably the `groups' slot, which stores a
   list of pairs (NAME, PATTERNS); the NAME is a string, and the PATTERNS a
-  list of (TAG, PEER, ADDR, MASK) triples.  The implication is that there
-  should be precisely one peer with a name matching NAME-*, and that it
-  should be NAME-TAG, where (TAG, PEER, ADDR, MASK) is the first triple such
-  that the host's primary IP address (if PEER is None -- or the IP address it
-  would use for communicating with PEER) is within the network defined by
-  ADDR/MASK.
+  list of (TAG, PEER, NETS) triples.  The implication is that there should be
+  precisely one peer from the set, and that it should be named TAG, where
+  (TAG, PEER, NETS) is the first triple such that the host's primary IP
+  address (if PEER is None -- or the IP address it would use for
+  communicating with PEER) is within one of the networks defined by NETS.
   """
 
   def __init__(me, file):
@@ -137,86 +245,136 @@ class Config (object):
     Internal function to update the configuration from the underlying file.
     """
 
-    ## Read the configuration.  We have no need of the fancy substitutions,
-    ## so turn them all off.
-    cp = RawConfigParser()
-    cp.read(me._file)
     if T._debug: print '# reread config'
 
-    ## Save the test address.  Make sure it's vaguely sensible.  The default
-    ## is probably good for most cases, in fact, since that address isn't
-    ## actually in use.  Note that we never send packets to the test address;
-    ## we just use it to discover routing information.
-    if cp.has_option('DEFAULT', 'test-addr'):
-      testaddr = cp.get('DEFAULT', 'test-addr')
-      S.inet_aton(testaddr)
-    else:
-      testaddr = '1.2.3.4'
-
-    ## Scan the configuration file and build the groups structure.
-    groups = []
-    for sec in cp.sections():
-      pats = []
-      for tag in cp.options(sec):
-        spec = cp.get(sec, tag).split()
-
-        ## Parse the entry into peer and network.
-        if len(spec) == 1:
-          peer = None
-          net = spec[0]
-        else:
-          peer, net = spec
-
-        ## Syntax of a net is ADDRESS/MASK, where ADDRESS is a dotted-quad,
-        ## and MASK is either a dotted-quad or a single integer N indicating
-        ## a mask with N leading ones followed by trailing zeroes.
-        slash = net.index('/')
-        addr, = unpack('>L', S.inet_aton(net[:slash]))
-        if net.find('.', slash + 1) >= 0:
-          mask, = unpack('>L', S.inet_aton(net[:slash]))
+    ## Initial state.
+    testaddrs = {}
+    groups = {}
+    grpname = None
+    grplist = []
+
+    ## Open the file and start reading.
+    with open(me._file) as f:
+      lno = 0
+      for line in f:
+        lno += 1
+        for tag, rx in CONFSYNTAX:
+          m = rx.match(line)
+          if m: break
         else:
-          n = int(net[slash + 1:], 10)
-          mask = (1 << 32) - (1 << 32 - n)
-        pats.append((tag, peer, addr & mask, mask))
-
-      ## Annoyingly, RawConfigParser doesn't preserve the order of options.
-      ## In order to make things vaguely sane, we topologically sort the
-      ## patterns so that more specific patterns are checked first.
-      pats = list(toposort(lambda (t, p, a, m), (tt, pp, aa, mm): \
-                             (p and not pp) or \
-                             (p == pp and m == (m | mm) and aa == (a & mm)),
-                           pats))
-      groups.append((sec, pats))
+          raise ConfigError(me._file, lno, 'failed to parse line: %r' % line)
+
+        if tag == 'COMMENT':
+          ## A comment.  Ignore it and hope it goes away.
+
+          continue
+
+        elif tag == 'GRPHDR':
+          ## A group header.  Flush the old group and start a new one.
+          newname = m.group(1)
+
+          if grpname is not None: groups[grpname] = grplist
+          if newname in groups:
+            raise ConfigError(me._file, lno,
+                              "duplicate group name `%s'" % newname)
+          grpname = newname
+          grplist = []
+
+        elif tag == 'ASSGN':
+           ## An assignment.  Deal with it.
+          name, value = m.group(1), m.group(2)
+
+          if grpname is None:
+            ## We're outside of any group, so this is a global configuration
+            ## tweak.
+
+            if name == 'test-addr':
+              for astr in value.split():
+                try:
+                  a = parse_address(astr)
+                except Exception, e:
+                  raise ConfigError(me._file, lno,
+                                    "invalid IP address `%s': %s" %
+                                    (astr, e))
+                if a.AF in testaddrs:
+                  raise ConfigError(me._file, lno,
+                                    'duplicate %s test-address' % a.AFNAME)
+                testaddrs[a.AF] = a
+            else:
+              raise ConfigError(me._file, lno,
+                                "unknown global option `%s'" % name)
+
+          else:
+            ## Parse a pattern and add it to the group.
+            spec = value.split()
+            i = 0
+
+            ## Check for an explicit target address.
+            if i >= len(spec) or spec[i].find('/') >= 0:
+              peer = None
+              af = None
+            else:
+              try:
+                peer = parse_address(spec[i])
+              except Exception, e:
+                raise ConfigError(me._file, lno,
+                                  "invalid IP address `%s': %s" %
+                                  (spec[i], e))
+              af = peer.AF
+              i += 1
+
+            ## Parse the list of local networks.
+            nets = []
+            while i < len(spec):
+              try:
+                net = parse_net(spec[i])
+              except Exception, e:
+                raise ConfigError(me._file, lno,
+                                  "invalid IP network `%s': %s" %
+                                  (spec[i], e))
+              else:
+                nets.append(net)
+              i += 1
+            if not nets:
+              raise ConfigError(me._file, lno, 'no networks defined')
+
+            ## Make sure that the addresses are consistent.
+            for net in nets:
+              if af is None:
+                af = net.AF
+              elif net.AF != af:
+                raise ConfigError(me._file, lno,
+                                  "net %s doesn't match" % net)
+
+            ## Add this entry to the list.
+            grplist.append((name, peer, nets))
+
+    ## Fill in the default test addresses if necessary.
+    for a in TESTADDRS: testaddrs.setdefault(a.AF, a)
 
     ## Done.
-    me.testaddr = testaddr
+    if grpname is not None: groups[grpname] = grplist
+    me.testaddrs = testaddrs
     me.groups = groups
 
 ### This will be a configuration file.
 CF = None
 
-def straddr(a): return a is None and '#<none>' or S.inet_ntoa(pack('>L', a))
-def strmask(m):
-  for i in xrange(33):
-    if m == 0xffffffff ^ ((1 << (32 - i)) - 1): return i
-  return straddr(m)
-
 def cmd_showconfig():
-  T.svcinfo('test-addr=%s' % CF.testaddr)
+  T.svcinfo('test-addr=%s' %
+            ' '.join(str(a)
+                     for a in sorted(CF.testaddrs.itervalues(),
+                                     key = lambda a: a.AFNAME)))
 def cmd_showgroups():
-  for sec, pats in CF.groups:
-    T.svcinfo(sec)
+  for g in sorted(CF.groups.iterkeys()):
+    T.svcinfo(g)
 def cmd_showgroup(g):
-  for s, p in CF.groups:
-    if s == g:
-      pats = p
-      break
-  else:
-    raise T.TripeJobError, 'unknown-group', g
-  for t, p, a, m in pats:
+  try: pats = CF.groups[g]
+  except KeyError: raise T.TripeJobError('unknown-group', g)
+  for t, p, nn in pats:
     T.svcinfo('peer', t,
-              'target', p or '(default)',
-              'net', '%s/%s' % (straddr(a), strmask(m)))
+              'target', p and str(p) or '(default)',
+              'net', ' '.join(map(str, nn)))
 
 ###--------------------------------------------------------------------------
 ### Responding to a network up/down event.
@@ -225,24 +383,54 @@ def localaddr(peer):
   """
   Return the local IP address used for talking to PEER.
   """
-  sk = S.socket(S.AF_INET, S.SOCK_DGRAM)
+  sk = S.socket(peer.AF, S.SOCK_DGRAM)
   try:
     try:
-      sk.connect((peer, 1))
-      addr, _ = sk.getsockname()
-      addr, = unpack('>L', S.inet_aton(addr))
-      return addr
+      sk.connect(peer.sockaddr(1))
+      addr = sk.getsockname()
+      return type(peer).from_sockaddr(addr)[0]
     except S.error:
       return None
   finally:
     sk.close()
 
 _kick = T.Queue()
+_delay = None
+
+def cancel_delay():
+  global _delay
+  if _delay is not None:
+    if T._debug: print '# cancel delayed kick'
+    G.source_remove(_delay)
+    _delay = None
+
+def netupdown(upness, reason):
+  """
+  Add or kill peers according to whether the network is up or down.
+
+  UPNESS is true if the network is up, or false if it's down.
+  """
+
+  _kick.put((upness, reason))
+
+def delay_netupdown(upness, reason):
+  global _delay
+  cancel_delay()
+  def _func():
+    global _delay
+    if T._debug: print '# delayed %s: %s' % (upness, reason)
+    _delay = None
+    netupdown(upness, reason)
+    return False
+  if T._debug: print '# delaying %s: %s' % (upness, reason)
+  _delay = G.timeout_add(2000, _func)
+
 def kickpeers():
   while True:
     upness, reason = _kick.get()
     if T._debug: print '# kickpeers %s: %s' % (upness, reason)
     select = []
+    cancel_delay()
 
     ## Make sure the configuration file is up-to-date.  Don't worry if we
     ## can't do anything useful.
@@ -255,53 +443,52 @@ def kickpeers():
     ## Find the current list of peers.
     peers = SM.list()
 
-    ## Work out the primary IP address.
+    ## Work out the primary IP addresses.
+    locals = {}
     if upness:
-      addr = localaddr(CF.testaddr)
-      if addr is None:
-        upness = False
-    else:
-      addr = None
+      for af, remote in CF.testaddrs.iteritems():
+        local = localaddr(remote)
+        if local is not None: locals[af] = local
+      if not locals: upness = False
     if not T._debug: pass
-    elif addr: print '#   local address = %s' % straddr(addr)
-    else: print '#   offline'
+    elif not locals: print '#   offline'
+    else:
+      for local in locals.itervalues():
+        print '#   local %s address = %s' % (local.AFNAME, local)
 
     ## Now decide what to do.
     changes = []
-    for g, pp in CF.groups:
+    for g, pp in CF.groups.iteritems():
       if T._debug: print '#   check group %s' % g
 
       ## Find out which peer in the group ought to be active.
-      ip = None
-      map = {}
+      statemap = {}
       want = None
-      for t, p, a, m in pp:
-        if p is None or not upness:
-          ipq = addr
-        else:
-          ipq = localaddr(p)
+      matchp = False
+      for t, p, nn in pp:
+        af = nn[0].AF
+        if p is None or not upness: ip = locals.get(af)
+        else: ip = localaddr(p)
         if T._debug:
-          info = 'peer=%s; target=%s; net=%s/%s; local=%s' % (
-            t, p or '(default)', straddr(a), strmask(m), straddr(ipq))
-        if upness and ip is None and \
-              ipq is not None and (ipq & m) == a:
+          info = 'peer = %s; target = %s; nets = %s; local = %s' % (
+            t, p or '(default)', ', '.join(map(str, nn)), straddr(ip))
+        if upness and not matchp and \
+           ip is not None and any(ip.withinp(n) for n in nn):
           if T._debug: print '#     %s: SELECTED' % info
-          map[t] = 'up'
+          statemap[t] = 'up'
           select.append('%s=%s' % (g, t))
-          if t == 'down' or t.startswith('down/'):
-            want = None
-          else:
-            want = t
-          ip = ipq
+          if t == 'down' or t.startswith('down/'): want = None
+          else: want = t
+          matchp = True
         else:
-          map[t] = 'down'
+          statemap[t] = 'down'
           if T._debug: print '#     %s: skipped' % info
 
       ## Shut down the wrong ones.
       found = False
-      if T._debug: print '#   peer-map = %r' % map
+      if T._debug: print '#   peer-map = %r' % statemap
       for p in peers:
-        what = map.get(p, 'leave')
+        what = statemap.get(p, 'leave')
         if what == 'up':
           found = True
           if T._debug: print '#   peer %s: already up' % p
@@ -322,7 +509,7 @@ def kickpeers():
       if want is not None and not found:
         def _(want = want):
           try:
-            SM.svcsubmit('connect', 'active', want)
+            list(SM.svcsubmit('connect', 'active', want))
           except T.TripeError, exc:
             SM.warn('conntrack', 'connect-failed', want, *exc.args)
         if T._debug: print '#   peer %s: bring up' % want
@@ -333,18 +520,11 @@ def kickpeers():
       SM.notify('conntrack', upness and 'up' or 'down', *select + reason)
       for c in changes: c()
 
-def netupdown(upness, reason):
-  """
-  Add or kill peers according to whether the network is up or down.
-
-  UPNESS is true if the network is up, or false if it's down.
-  """
-
-  _kick.put((upness, reason))
-
 ###--------------------------------------------------------------------------
 ### NetworkManager monitor.
 
+DBPROPS_IFACE = 'org.freedesktop.DBus.Properties'
+
 NM_NAME = 'org.freedesktop.NetworkManager'
 NM_PATH = '/org/freedesktop/NetworkManager'
 NM_IFACE = NM_NAME
@@ -376,13 +556,13 @@ class NetworkManagerMonitor (object):
   def attach(me, bus):
     try:
       nm = bus.get_object(NM_NAME, NM_PATH)
-      state = nm.Get(NM_IFACE, 'State')
+      state = nm.Get(NM_IFACE, 'State', dbus_interface = DBPROPS_IFACE)
       if state in NM_CONNSTATES:
         netupdown(True, ['nm', 'initially-connected'])
       else:
         netupdown(False, ['nm', 'initially-disconnected'])
-    except D.DBusException:
-      pass
+    except D.DBusException, e:
+      if T._debug: print '# exception attaching to network-manager: %s' % e
     bus.add_signal_receiver(me._nm_state, 'StateChanged',
                             NM_IFACE, NM_NAME, NM_PATH)
     bus.add_signal_receiver(me._nm_connchange, 'PropertiesChanged',
@@ -390,13 +570,13 @@ class NetworkManagerMonitor (object):
 
   def _nm_state(me, state):
     if state in NM_CONNSTATES:
-      netupdown(True, ['nm', 'connected'])
+      delay_netupdown(True, ['nm', 'connected'])
     else:
-      netupdown(False, ['nm', 'disconnected'])
+      delay_netupdown(False, ['nm', 'disconnected'])
 
   def _nm_connchange(me, props):
-    if props.get('Default', False):
-      netupdown(True, ['nm', 'default-connection-change'])
+    if props.get('Default', False) or props.get('Default6', False):
+      delay_netupdown(True, ['nm', 'default-connection-change'])
 
 ##--------------------------------------------------------------------------
 ### Connman monitor.
@@ -421,14 +601,14 @@ class ConnManMonitor (object):
       props = cm.GetProperties(dbus_interface = CM_IFACE)
       state = props['State']
       netupdown(state == 'online', ['connman', 'initially-%s' % state])
-    except D.DBusException:
-      pass
+    except D.DBusException, e:
+      if T._debug: print '# exception attaching to connman: %s' % e
     bus.add_signal_receiver(me._cm_state, 'PropertyChanged',
                             CM_IFACE, CM_NAME, CM_PATH)
 
   def _cm_state(me, prop, value):
     if prop != 'State': return
-    netupdown(value == 'online', ['connman', value])
+    delay_netupdown(value == 'online', ['connman', value])
 
 ###--------------------------------------------------------------------------
 ### Maemo monitor.
@@ -460,7 +640,8 @@ class MaemoICdMonitor (object):
       except D.DBusException:
         me._iap = None
         netupdown(False, ['icd', 'initially-disconnected'])
-    except D.DBusException:
+    except D.DBusException, e:
+      if T._debug: print '# exception attaching to icd: %s' % e
       me._iap = None
     bus.add_signal_receiver(me._icd_state, 'status_changed', ICD_IFACE,
                             ICD_NAME, ICD_PATH)
@@ -468,10 +649,10 @@ class MaemoICdMonitor (object):
   def _icd_state(me, iap, ty, state, hunoz):
     if state == 'CONNECTED':
       me._iap = iap
-      netupdown(True, ['icd', 'connected', iap])
+      delay_netupdown(True, ['icd', 'connected', iap])
     elif state == 'IDLE' and iap == me._iap:
       me._iap = None
-      netupdown(False, ['icd', 'idle'])
+      delay_netupdown(False, ['icd', 'idle'])
 
 ###--------------------------------------------------------------------------
 ### D-Bus connection tracking.
@@ -583,8 +764,9 @@ def init():
   DBM.addmon(NetworkManagerMonitor())
   DBM.addmon(ConnManMonitor())
   DBM.addmon(MaemoICdMonitor())
-  G.timeout_add_seconds(30, lambda: (netupdown(True, ['interval-timer'])
-                                     or True))
+  G.timeout_add_seconds(30, lambda: (_delay is not None or
+                                     netupdown(True, ['interval-timer']) or
+                                     True))
 
 def parse_options():
   """
index a661214..6385d13 100644 (file)
@@ -9,22 +9,21 @@
 .\"
 .\" This file is part of Trivial IP Encryption (TrIPE).
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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.
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\" along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 .
 .\"--------------------------------------------------------------------------
-.so ../defs.man.in \"@@@PRE@@@
+.so ../common/defs.man \"@@@PRE@@@
 .
 .\"--------------------------------------------------------------------------
 .TH tripe-ifup 8tripe "20 December 2008" "Straylight/Edgeware" "TrIPE: Trivial IP Encryption"
index 80a315b..63b5b2d 100644 (file)
@@ -46,11 +46,16 @@ fi
 peer=$1 ifname=$2 family=$3; shift 3
 
 ## Parse the address family.
+case "$family" in
+  INET) ipsz=20 ;;
+  INET6) ipsz=40 ;;
+  *) echo >&2 "$0: unknown address family $family"; exit 1 ;;
+esac
 case "$family,$#" in
-  INET,1) addr=$1 port=4070 ;;
-  INET,2) addr=$1 port=$2 ;;
-  INET,*) echo >&2 "$0: bad INET address"; exit 1 ;;
-  *)      echo >&2 "$0: unknown address family $family"; exit 1 ;;
+  INET,1 | INET6,1) addr=$1 port=4070 ;;
+  INET,2 | INET6,2) addr=$1 port=$2 ;;
+  INET,* | INET6,*) echo >&2 "$0: bad $family address"; exit 1 ;;
+  *) echo >&2 "$0: unknown address family $family"; exit 1 ;;
 esac
 
 ###--------------------------------------------------------------------------
@@ -134,7 +139,7 @@ case $haveaddr4,$haveaddr6 in
        mtu=$P_MTU;;
       *)
        pathmtu=$(pathmtu "$addr")
-       mtu=$(expr "$pathmtu" - 29 - $A_BULK_OVERHEAD)
+       mtu=$(( $pathmtu - $ipsz - 9 - $A_BULK_OVERHEAD ))
        ;;
     esac
     try ip link set dev "$ifname" up mtu "$mtu"
index 09387aa..910bf63 100644 (file)
@@ -9,23 +9,23 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 include autotest.am
 
 autotest_TESTS          =
+TEST_ARGS               = -j4
 
 ###--------------------------------------------------------------------------
 ### Test directories.
@@ -40,6 +40,8 @@ autotest_TESTS                += $(top_srcdir)/keys/tests.at
 ### Test files.
 
 ## Keyring files.
-EXTRA_DIST             += keyring-alpha keyring-beta keyring-beta-new
+EXTRA_DIST             += keyring-alpha
+EXTRA_DIST             += keyring-beta keyring-beta-new
+EXTRA_DIST             += keyring-gamma
 
 ###----- That's all, folks --------------------------------------------------
index 12b6902..18d9175 100644 (file)
@@ -9,19 +9,18 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 ###--------------------------------------------------------------------------
 ### Configuration snippets.
index 321bc09..f594e21 100644 (file)
@@ -1,5 +1,5 @@
-6f825e20:tripe-x25519:bob struct:[pub=binary,public:r+C4uNU0b06HsBunjoYEuzrij92jZi81/jPaQWjMAG4=,private=struct:[priv=binary,private,burn:jxcgIQSi6Otuvfl0Xd8JXL3p5mNFyuMKxCm4e1Zu2Us=]] forever forever hash=sha256&cipher=chacha20&bulk=naclbox
-03349b54:tripe-x25519:bob-new struct:[pub=binary,public:T1Xkv+kA2inWUsoF1HMhQV9l7FFUVAuqJaIfSIbo/wY=,private=struct:[priv=binary,private,burn:yYvE82HsJlk3wOSi1gXJL0laeo1f+Af4u+fjIzU7Hoo=]] forever forever hash=sha256&cipher=chacha20&bulk=naclbox
-abff7e38:tripe-x25519-param string,shared:%3cempty%3e forever forever hash=sha256&cipher=chacha20&bulk=naclbox
-fbf10a4e:tripe-x25519:alice struct:[pub=binary,public:n27vXTI/1lyxTlx3iDPAYfuhuRrGTc5/ohJfFsvQRl8=,private=struct:[priv=binary,private,burn:9OsXq+xPsDeAujcy6+tLBTyfXuqBYhrKazWzR/6cH5w=]] forever forever hash=sha256&cipher=chacha20&bulk=naclbox
-4c758f1e:tripe-x25519:carol struct:[pub=binary,public:yi7XVssUkQePU/1ybS6XkwrqvXqWaN04XboBj/r+jmA=,private=struct:[priv=binary,private,burn:++CCD35eVRUPsML4B4rT4n+QiUpBcQbdO5+WF82b/Bk=]] forever forever hash=sha256&cipher=chacha20&bulk=naclbox
+6f825e20:tripe:bob struct:[pub=binary,public:r+C4uNU0b06HsBunjoYEuzrij92jZi81/jPaQWjMAG4=,private=struct:[priv=binary,private,burn:jxcgIQSi6Otuvfl0Xd8JXL3p5mNFyuMKxCm4e1Zu2Us=]] forever forever kx-group=x25519&hash=sha256&cipher=chacha20&bulk=naclbox
+03349b54:tripe:bob-new struct:[pub=binary,public:T1Xkv+kA2inWUsoF1HMhQV9l7FFUVAuqJaIfSIbo/wY=,private=struct:[priv=binary,private,burn:yYvE82HsJlk3wOSi1gXJL0laeo1f+Af4u+fjIzU7Hoo=]] forever forever kx-group=x25519&hash=sha256&cipher=chacha20&bulk=naclbox
+abff7e38:tripe-param string,shared:%3cempty%3e forever forever kx-group=x25519&hash=sha256&cipher=chacha20&bulk=naclbox
+fbf10a4e:tripe:alice struct:[pub=binary,public:n27vXTI/1lyxTlx3iDPAYfuhuRrGTc5/ohJfFsvQRl8=,private=struct:[priv=binary,private,burn:9OsXq+xPsDeAujcy6+tLBTyfXuqBYhrKazWzR/6cH5w=]] forever forever kx-group=x25519&hash=sha256&cipher=chacha20&bulk=naclbox
+4c758f1e:tripe:carol struct:[pub=binary,public:yi7XVssUkQePU/1ybS6XkwrqvXqWaN04XboBj/r+jmA=,private=struct:[priv=binary,private,burn:++CCD35eVRUPsML4B4rT4n+QiUpBcQbdO5+WF82b/Bk=]] forever forever kx-group=x25519&hash=sha256&cipher=chacha20&bulk=naclbox
index 1285dfb..534d91c 100644 (file)
@@ -1,4 +1,4 @@
-bafb2876:tripe-dh:carol struct:[p=integer,shared:69775951038073580217048751187698556149910661999359201823421066000439190288124938297116840422332973903349265313226189724474672148172906743149961449018143681316055777549225333684417216672046201528908637006946721694566251047975893301628540057123962444434130461052652526277961662241061299058137499738575071867183,g=integer,shared:24031880137812767104513348688448999056754549625670167617628008814539123937598464069755252337842705625768270002772600104095116713027947418314704258588569425678035630653993833866258334547988915311887541947589468379164053328339235797437698686107421712042993209960764868024283667188725341401853595169622553906137,private=struct:[x=integer,private,burn:73991155902179945261508509191547148355158375351],y=integer,public:37069178228377811166030071731849765678259693396714688962600218701483091198810506754964630489911246536883135042437408082007532504680074395403119352076064967233299326035447428716504321984168387644976048560331078815373900584178897334717028451802614990292165323963715739372593761778142664403111989271476234631785,q=integer,shared:741802303617786660769426556982216255271020758647] forever forever -
-b9839a99:tripe-dh:bob struct:[p=integer,shared:69775951038073580217048751187698556149910661999359201823421066000439190288124938297116840422332973903349265313226189724474672148172906743149961449018143681316055777549225333684417216672046201528908637006946721694566251047975893301628540057123962444434130461052652526277961662241061299058137499738575071867183,g=integer,shared:24031880137812767104513348688448999056754549625670167617628008814539123937598464069755252337842705625768270002772600104095116713027947418314704258588569425678035630653993833866258334547988915311887541947589468379164053328339235797437698686107421712042993209960764868024283667188725341401853595169622553906137,private=struct:[x=integer,private,burn:375609211293082370476422549702736644094563285051],y=integer,public:52887463659985761430091133772124690768677781799280413773044912238296204331699417469091296610220239433325238224933647139929492958281289467149794491179434949190055446809143849149086322387583189724300256430823699284964722221389756296695948072813949287172770178259455456837453570760294758645159814018680210877172,q=integer,shared:741802303617786660769426556982216255271020758647] forever forever -
-e41ac92a:tripe-dh:alice struct:[p=integer,shared:69775951038073580217048751187698556149910661999359201823421066000439190288124938297116840422332973903349265313226189724474672148172906743149961449018143681316055777549225333684417216672046201528908637006946721694566251047975893301628540057123962444434130461052652526277961662241061299058137499738575071867183,g=integer,shared:24031880137812767104513348688448999056754549625670167617628008814539123937598464069755252337842705625768270002772600104095116713027947418314704258588569425678035630653993833866258334547988915311887541947589468379164053328339235797437698686107421712042993209960764868024283667188725341401853595169622553906137,private=struct:[x=integer,private,burn:688117919585761159182535704066379347003922575359],y=integer,public:11968810422097828040138356493810753667079777301137513503524758653663206529933596206587409211665755594436008067034048682156890432817887506008283388939899943548428563491314269113784275369524966123025279618224821883359350680218152442710235178411332384857718603980372482853673813823181754321480786169027071242150,q=integer,shared:741802303617786660769426556982216255271020758647] forever forever -
-9f3a179b:tripe-dh-param struct:[p=integer,shared:69775951038073580217048751187698556149910661999359201823421066000439190288124938297116840422332973903349265313226189724474672148172906743149961449018143681316055777549225333684417216672046201528908637006946721694566251047975893301628540057123962444434130461052652526277961662241061299058137499738575071867183,g=integer,shared:24031880137812767104513348688448999056754549625670167617628008814539123937598464069755252337842705625768270002772600104095116713027947418314704258588569425678035630653993833866258334547988915311887541947589468379164053328339235797437698686107421712042993209960764868024283667188725341401853595169622553906137,q=integer,shared:741802303617786660769426556982216255271020758647] forever forever -
+bafb2876:tripe:carol struct:[p=integer,shared:69775951038073580217048751187698556149910661999359201823421066000439190288124938297116840422332973903349265313226189724474672148172906743149961449018143681316055777549225333684417216672046201528908637006946721694566251047975893301628540057123962444434130461052652526277961662241061299058137499738575071867183,g=integer,shared:24031880137812767104513348688448999056754549625670167617628008814539123937598464069755252337842705625768270002772600104095116713027947418314704258588569425678035630653993833866258334547988915311887541947589468379164053328339235797437698686107421712042993209960764868024283667188725341401853595169622553906137,private=struct:[x=integer,private,burn:73991155902179945261508509191547148355158375351],y=integer,public:37069178228377811166030071731849765678259693396714688962600218701483091198810506754964630489911246536883135042437408082007532504680074395403119352076064967233299326035447428716504321984168387644976048560331078815373900584178897334717028451802614990292165323963715739372593761778142664403111989271476234631785,q=integer,shared:741802303617786660769426556982216255271020758647] forever forever kx-group=dh
+b9839a99:tripe:bob struct:[p=integer,shared:69775951038073580217048751187698556149910661999359201823421066000439190288124938297116840422332973903349265313226189724474672148172906743149961449018143681316055777549225333684417216672046201528908637006946721694566251047975893301628540057123962444434130461052652526277961662241061299058137499738575071867183,g=integer,shared:24031880137812767104513348688448999056754549625670167617628008814539123937598464069755252337842705625768270002772600104095116713027947418314704258588569425678035630653993833866258334547988915311887541947589468379164053328339235797437698686107421712042993209960764868024283667188725341401853595169622553906137,private=struct:[x=integer,private,burn:375609211293082370476422549702736644094563285051],y=integer,public:52887463659985761430091133772124690768677781799280413773044912238296204331699417469091296610220239433325238224933647139929492958281289467149794491179434949190055446809143849149086322387583189724300256430823699284964722221389756296695948072813949287172770178259455456837453570760294758645159814018680210877172,q=integer,shared:741802303617786660769426556982216255271020758647] forever forever kx-group=dh
+e41ac92a:tripe:alice struct:[p=integer,shared:69775951038073580217048751187698556149910661999359201823421066000439190288124938297116840422332973903349265313226189724474672148172906743149961449018143681316055777549225333684417216672046201528908637006946721694566251047975893301628540057123962444434130461052652526277961662241061299058137499738575071867183,g=integer,shared:24031880137812767104513348688448999056754549625670167617628008814539123937598464069755252337842705625768270002772600104095116713027947418314704258588569425678035630653993833866258334547988915311887541947589468379164053328339235797437698686107421712042993209960764868024283667188725341401853595169622553906137,private=struct:[x=integer,private,burn:688117919585761159182535704066379347003922575359],y=integer,public:11968810422097828040138356493810753667079777301137513503524758653663206529933596206587409211665755594436008067034048682156890432817887506008283388939899943548428563491314269113784275369524966123025279618224821883359350680218152442710235178411332384857718603980372482853673813823181754321480786169027071242150,q=integer,shared:741802303617786660769426556982216255271020758647] forever forever kx-group=dh
+9f3a179b:tripe-param struct:[p=integer,shared:69775951038073580217048751187698556149910661999359201823421066000439190288124938297116840422332973903349265313226189724474672148172906743149961449018143681316055777549225333684417216672046201528908637006946721694566251047975893301628540057123962444434130461052652526277961662241061299058137499738575071867183,g=integer,shared:24031880137812767104513348688448999056754549625670167617628008814539123937598464069755252337842705625768270002772600104095116713027947418314704258588569425678035630653993833866258334547988915311887541947589468379164053328339235797437698686107421712042993209960764868024283667188725341401853595169622553906137,q=integer,shared:741802303617786660769426556982216255271020758647] forever forever kx-group=dh
index ff32d9e..13c052c 100644 (file)
@@ -1,4 +1,4 @@
-000f1070:tripe-ec-param struct:[curve=string,shared:secp160r1] forever forever bulk=iiv&serialization=constlen
-63803f04:tripe-ec:carol struct:[p=ec,public:0x42a11d34a1e19a69222a67c6ec29ff1d421cb021,0xc7d35a6dc9c330a09ee8e30680397b8bf51c29de,private=struct:[x=integer,private,burn:964893088925854502228477969935127891578649410999],curve=string,shared:secp160r1] forever forever bulk=iiv&serialization=constlen
-4045911b:tripe-ec:bob struct:[p=ec,public:0xcfc0b74fe1c4f30ced726bb70fbe4592ed8456ba,0x351d4dcbc200ccd3f3b6f4ecc112ecbf2043402d,private=struct:[x=integer,private,burn:333869955870933965311262832506583263321304092611],curve=string,shared:secp160r1] forever forever bulk=iiv&serialization=constlen
-e1a55f0e:tripe-ec:alice struct:[p=ec,public:0xce3bdc518066042b19083e2d27b0ded1915cb6b9,0xadef0e2006072f7bf038ef608e039a47e5767070,private=struct:[x=integer,private,burn:184161721501402669384082492238940360070520035996],curve=string,shared:secp160r1] forever forever bulk=iiv&serialization=constlen
+000f1070:tripe-param struct:[curve=string,shared:secp160r1] forever forever kx-group=ec&bulk=iiv&serialization=constlen
+63803f04:tripe:carol struct:[p=ec,public:0x42a11d34a1e19a69222a67c6ec29ff1d421cb021,0xc7d35a6dc9c330a09ee8e30680397b8bf51c29de,private=struct:[x=integer,private,burn:964893088925854502228477969935127891578649410999],curve=string,shared:secp160r1] forever forever kx-group=ec&bulk=iiv&serialization=constlen
+4045911b:tripe:bob struct:[p=ec,public:0xcfc0b74fe1c4f30ced726bb70fbe4592ed8456ba,0x351d4dcbc200ccd3f3b6f4ecc112ecbf2043402d,private=struct:[x=integer,private,burn:333869955870933965311262832506583263321304092611],curve=string,shared:secp160r1] forever forever kx-group=ec&bulk=iiv&serialization=constlen
+e1a55f0e:tripe:alice struct:[p=ec,public:0xce3bdc518066042b19083e2d27b0ded1915cb6b9,0xadef0e2006072f7bf038ef608e039a47e5767070,private=struct:[x=integer,private,burn:184161721501402669384082492238940360070520035996],curve=string,shared:secp160r1] forever forever kx-group=ec&bulk=iiv&serialization=constlen
diff --git a/t/keyring-gamma b/t/keyring-gamma
new file mode 100644 (file)
index 0000000..8121094
--- /dev/null
@@ -0,0 +1,4 @@
+ad1a94b3:tripe-param:param string,shared:%2e forever forever kx-group=x448&cipher=twofish-ocb3&bulk=aead
+5a4c1e54:tripe:carol struct:[pub=binary,public:N3xC4xLieyQ5p758JjKuhEwAYr4Nq98H2kFWtI6OLk13b7NJmSi4GW3aHGfYdKGhcxR+rpHwVm0=,private=struct:[priv=binary,private,burn:rlXbkcoD7+iqckBj1MAXkSjoU4b6wOQq+xjQda1kD3m5/MTVDm/dFFPLtGdANGtDkiDwVFO/DBs=]] forever forever kx-group=x448&cipher=twofish-ocb3&bulk=aead
+69941be8:tripe:alice struct:[pub=binary,public:1pQP3OAelcLeFY5GHD5k9NBI7Y7a8t5N/LuY0VEd3rKBKBpX08zMMBtp3Okdveqfjgp7tzRHVY4=,private=struct:[priv=binary,private,burn:kYKn5Qskt1B4JKvAY48mP5i+l/HiRuQQwFL8p6oeZXd3gYCWRUJRHFieuA5sCU6fuzst+3DHAc0=]] forever forever kx-group=x448&cipher=twofish-ocb3&bulk=aead
+a8e18348:tripe:bob struct:[pub=binary,public:krXa8TLVt7496UeDHm30NaNuNKxTrAnt2PvLZ5DjzcI16cbjS5WGdDd2jsycfnr7+5CylvsUlkE=,private=struct:[priv=binary,private,burn:XAN4vEA4zC6K9OgeidUwAQIOEcq+YxNo59NlxKJlEb14dAAy6RPmaBobDxaeNuxplKvazq728ZU=]] forever forever kx-group=x448&cipher=twofish-ocb3&bulk=aead
index cfdfc2e..1a57931 100644 (file)
@@ -9,19 +9,18 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 include $(top_srcdir)/vars.am
 
index e0c75de..0f4bc56 100644 (file)
@@ -4,24 +4,23 @@
 .\"
 .\" (c) 2008 Straylight/Edgeware.
 .\"
-
+.
 .\"----- Licensing notice ---------------------------------------------------
 .\"
 .\" This file is part of Trivial IP Encryption (TrIPE).
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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.
 .\"
-.\" TrIPE 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.
+.\" TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\" along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 .
 .\"--------------------------------------------------------------------------
 .so ../common/defs.man \" @@@PRE@@@
index 0ed9e70..629da12 100644 (file)
@@ -9,19 +9,18 @@
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE 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.
+ * TrIPE 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.
  *
- * TrIPE 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.
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 /*----- Header files ------------------------------------------------------*/
@@ -606,6 +605,7 @@ static void slipif(void)
 
   sig_init(&sel);
   sig_add(&term, SIGTERM, slip_term, &fd);
+  sig_add(&term, SIGHUP, slip_term, &fd);
   sig_add(&term, SIGINT, slip_term, &fd);
 
   initqueue(&q_in);
@@ -621,7 +621,7 @@ static void slipif(void)
   /* --- Main loop --- */
 
   while (reasons) {
-    if (sel_select(&sel))
+    if (sel_select(&sel) && errno != EINTR)
       die(EXIT_FAILURE, "select: %s", strerror(errno));
   }
 
diff --git a/vars.am b/vars.am
index fc00b87..6ef92b0 100644 (file)
--- a/vars.am
+++ b/vars.am
@@ -9,19 +9,18 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 ###--------------------------------------------------------------------------
 ### Initial values of common variables.
@@ -47,7 +46,7 @@ AM_CPPFLAGS           += $(TRIPE_INCLUDES)
 ### Miscellanous useful definitions.
 
 ## Libraries of common code.
-libtripe                = $(top_builddir)/common/libtripe.a
+libcommon               = $(top_builddir)/common/libcommon.a
 libpriv                         = $(top_builddir)/priv/libpriv.a
 
 ###--------------------------------------------------------------------------
@@ -66,9 +65,9 @@ SUBSTITUTIONS = \
        PACKAGE=$(PACKAGE) VERSION=$(VERSION) \
        PYTHON=$(PYTHON)
 
-V_SUBST                         = $(V_SUBST_$(V))
-V_SUBST_                = $(V_SUBST_$(AM_DEFAULT_VERBOSITY))
-V_SUBST_0               = @echo "  SUBST  $@";
+V_SUBST                         = $(V_SUBST_@AM_V@)
+V_SUBST_                = $(V_SUBST_@AM_DEFAULT_V@)
+V_SUBST_0               = @echo "  SUBST    $@";
 SUBST                   = $(V_SUBST)$(confsubst)
 
 ###--------------------------------------------------------------------------
@@ -82,9 +81,9 @@ SUFFIXES              += .8tripe .8.in
 mandefs                         = $(top_srcdir)/common/defs.man
 makesummary             = $(top_srcdir)/common/make-summary
 
-V_MAN                   = $(V_MAN_$(V))
-V_MAN_                  = $(V_MAN_$(AM_DEFAULT_VERBOSITY))
-V_MAN_0                         = @echo "  MAN    $@";
+V_MAN                   = $(V_MAN_@AM_V@)
+V_MAN_                  = $(V_MAN_@AM_DEFAULT_V@)
+V_MAN_0                         = @echo "  MAN      $@";
 
 .1.in.1 .1.in.1tripe .5.in.5tripe .7.in.7tripe .8.in.8tripe:
        $(V_MAN)
index 6342178..c1af307 100644 (file)
@@ -9,31 +9,25 @@
 ###
 ### This file is part of Trivial IP Encryption (TrIPE).
 ###
-### TrIPE 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.
+### TrIPE 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.
 ###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
 
 include $(top_srcdir)/vars.am
 
 ###--------------------------------------------------------------------------
 ### Wireshark plugin.
 
-AM_CFLAGS                      += $(WIRESHARK_CFLAGS)
-LIBS                            =
-
-wireshark_plugin_LTLIBRARIES    = tripe.la
-
-tripe_la_LDFLAGS                = -module -avoid-version
-tripe_la_SOURCES                = packet-tripe.c
+wireshark_plugin_DATA   = tripe.lua
+EXTRA_DIST             += tripe.lua
 
 ###----- That's all, folks --------------------------------------------------
index 1bd4c45..8e5cdc6 100644 (file)
Binary files a/wireshark/cap and b/wireshark/cap differ
diff --git a/wireshark/cap.dh b/wireshark/cap.dh
new file mode 100644 (file)
index 0000000..fcf40e6
Binary files /dev/null and b/wireshark/cap.dh differ
diff --git a/wireshark/cap.ec b/wireshark/cap.ec
new file mode 100644 (file)
index 0000000..a9ecf1d
Binary files /dev/null and b/wireshark/cap.ec differ
diff --git a/wireshark/cap.knock b/wireshark/cap.knock
new file mode 100644 (file)
index 0000000..2ca3537
Binary files /dev/null and b/wireshark/cap.knock differ
diff --git a/wireshark/cap.x25519 b/wireshark/cap.x25519
new file mode 100644 (file)
index 0000000..6b65c11
Binary files /dev/null and b/wireshark/cap.x25519 differ
diff --git a/wireshark/cap.x448 b/wireshark/cap.x448
new file mode 100644 (file)
index 0000000..0dd5d94
Binary files /dev/null and b/wireshark/cap.x448 differ
diff --git a/wireshark/capture-session b/wireshark/capture-session
new file mode 100755 (executable)
index 0000000..4b5d81a
--- /dev/null
@@ -0,0 +1,77 @@
+#! /bin/sh -e
+###
+### A simple script for capturing TrIPE sessions, for testing the Wireshark
+### dissector.
+
+ty=${1?ty} param=${2-$ty}
+tripe=${TRIPE-tripe}
+rm -rf captmp
+mkdir captmp
+cd captmp
+
+cp ../keyring .
+for i in alice bob; do
+  key add -eforever -a$ty -t$i -pparam-$param tripe
+done
+cp keyring keyring.pub
+
+for i in alice bob; do
+  mkfifo $i.in $i.out
+  TRIPE_SLIPIF=/usr/bin/tripe-uslip \
+    $tripe -d. -as.$i -F -nslip -t$i -p0 <$i.in >$i.out 2>$i.err&
+done
+exec 3>alice.in 4<alice.out; alice_in=3 alice_out=4
+exec 5>bob.in 6<bob.out; bob_in=5 bob_out=6
+
+docmd () {
+  who=$1; shift
+  eval in=\$${who}_in out=\$${who}_out
+  echo "$*" >&$in
+  while read tag tail; do
+    : "$tag $tail"
+    case $tag in
+      INFO) echo $tail ;;
+      FAIL) echo >&2 "command \`$*' failed: $tail"; exit 10 ;;
+      OK) break ;;
+    esac
+  done <&$out
+}
+
+await () {
+  who=$1
+  eval out=\$${who}_out
+  while read tag kind rest; do
+    : "$tag $kind $rest"
+    case $tag,$kind in
+      NOTE,KXDONE) break ;;
+    esac
+  done <&$out
+}
+
+docmd alice watch n-tw
+docmd bob watch n-tw
+
+p_alice=$(docmd alice port)
+p_bob=$(docmd bob port)
+
+tshark -ilo -f"udp port $p_alice or udp port $p_bob" \
+       -w../cap.$param& shark=$!
+pause
+
+docmd alice add -cork bob 127.0.0.1 $p_bob
+c=$(docmd bob getchal)
+docmd alice greet bob $c
+docmd bob add alice 127.0.0.1 $p_alice
+await alice& walice=$!
+await bob& wbob=$!
+wait $walice
+wait $wbob
+
+echo ping | tripe-uslip -p bob
+x=$(tripe-uslip -g alice)
+
+pause
+
+exec 3>&- 4>&- 5>&- 6>&-
+kill $shark
+wait
diff --git a/wireshark/keyring b/wireshark/keyring
new file mode 100644 (file)
index 0000000..84b6133
--- /dev/null
@@ -0,0 +1,4 @@
+86249683:tripe-param:param-ec struct:[curve=string,shared:nist%2dp256] forever forever hash=sha256&kx-group=ec&cipher=rijndael-cbc&bulk=iiv
+fae23925:tripe-param:param-x25519 string,shared:%2e forever forever hash=sha256&kx-group=x25519&cipher=salsa20&bulk=naclbox
+a0fafc6b:tripe-param:param-dh struct:[p=integer,shared:635937223373484887991140560420669529960468634418212194527199243018802509220923645480563049852948379631872315326364181219863391536284352632127476573990043945919830000350264391397346335414535975554209931971547284463207275833747975949070870172306582775948778222246682185331862354083029804303222690541851195763223409045953191584611635790362191424339883737168342809190665629632289528654983812904611647818546832860081714363504512892885600580448186423533085059295328609139522690627823102925150246378111221377628009667319528150747320084312110336288028683700603719073769314245952464113622780032073817850603131463761017417455806035107984621889773217138911836476354442521466189648565925624512100087534918369360007697883847802211797655614684090408428715299537509886614640500137409891381649298835563011151796284848165977247286439820050405175697064122759870661294968492275655767983096727976056361878300352234381528837008128941375285251387,g=integer,shared:293025651734044707050351995205142657657141955131408425452312245943331353163428637443799381119408990271553007202915223601376791534187122912295000993316207089139736477148501917083144416322685925261624358336253378688806309805096283386698409139605996997308008266491782991490291726095895573563845937760385377670846794792328581579165150512160132493375389995617711312966986681865792948443127326586291576561351005982500369077653940283008076332825908419690031303502192009860824429031526378047535596212801179578879818504990773143072529887215206211778546705082334239130823956177702608427222251531070457604183947814624087379251368161182770323100074409926426428526795865215674068539423364010352920515052672366811052717283396264950182005583498061313136436293898863077084485762289185337928869086731032780517039420605615746259170222257438835029958473402842467194409561307509136616954789698181252705567549441508247668319257495617674881793988,q=integer,shared:89271282791461757245617785540129155142212959423277129581512553253419075634703] forever forever hash=sha256&kx-group=dh&cipher=twofish-cbc&bulk=v0
+8f879aef:tripe-param:param-x448 string,shared:%2e forever forever mgf=shake256-xof&hash=shake256&kx-group=x448&cipher=chacha20&bulk=naclbox
diff --git a/wireshark/packet-tripe.c b/wireshark/packet-tripe.c
deleted file mode 100644 (file)
index 28bfc83..0000000
+++ /dev/null
@@ -1,479 +0,0 @@
-/* -*-c-*-
- *
- * TrIPE protocol dissector for Wireshark
- *
- * (c) 2003 Straylight/Edgeware
- */
-
-/*----- Licensing notice --------------------------------------------------*
- *
- * This file is part of Trivial IP Encryption (TrIPE).
- *
- * TrIPE 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.
- *
- * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- */
-
-/*----- Header files ------------------------------------------------------*/
-
-#include "config.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <netinet/in.h>
-
-#include <glib.h>
-#include <gmodule.h>
-#include <wireshark/config.h>
-#include <wireshark/epan/packet.h>
-#include <wireshark/epan/prefs.h>
-
-#include "protocol.h"
-
-/*----- Static variables --------------------------------------------------*/
-
-static int proto_tripe = -1;
-
-static guint hashsz = 20, tagsz = 10, ivsz = 8;
-
-typedef struct hfmp {
-  int hf, hf_len, hf_val, tt;
-} hfmp;
-typedef struct hfge {
-  int hf, hf_len, hf_val, hfx_len, hfx_val, hfy_len, hfy_val, tt;
-} hfge;
-
-static int hf_tripe_cat = -1;
-static int hf_tripe_packet_type = -1;
-static int hf_tripe_ct = -1;
-static int hf_tripe_ct_seq = -1;
-static int hf_tripe_ct_iv = -1;
-static int hf_tripe_ct_ct = -1;
-static int hf_tripe_ct_tag = -1;
-static int hf_tripe_misc_type = -1;
-static int hf_tripe_misc_payload = -1;
-static int hf_tripe_kx_type = -1;
-static hfge hf_tripe_kx_mychal = { -1, -1, -1, -1, -1, -1, -1, -1 };
-static int hf_tripe_kx_mycookie = -1;
-static int hf_tripe_kx_yourcookie = -1;
-static hfmp hf_tripe_kx_check = { -1, -1, -1, -1 };
-static int hf_tripe_huh = -1;
-
-static int tt_tripe = -1;
-static int tt_tripe_ct = -1;
-
-G_MODULE_EXPORT const gchar version[] = VERSION;
-
-/*----- Main code ---------------------------------------------------------*/
-
-static void prefcb(void) { }
-
-static gint gethash(proto_tree *tt, int hf, tvbuff_t *b, gint off)
-{
-  proto_tree_add_item(tt, hf, b, off, hashsz, FALSE);
-  return (off + hashsz);
-}
-
-static gint getmp(proto_tree *tt, const hfmp *hf, tvbuff_t *b, gint off)
-{
-  guint16 len = tvb_get_ntohs(b, off);
-  proto_item *ti = proto_tree_add_item(tt, hf->hf, b, off, len + 2, FALSE);
-  tt = proto_item_add_subtree(ti, hf->tt);
-  proto_tree_add_item(tt, hf->hf_len, b, off, 2, FALSE);
-  proto_tree_add_item(tt, hf->hf_val, b, off + 2, len, FALSE);
-  return (off + 2 + len);
-}
-
-static gint getge(proto_tree *tt, const hfge *hf, tvbuff_t *b, gint off)
-{
-  guint16 len = tvb_get_ntohs(b, off), len2;
-  guint r;
-  proto_item *ti;
-  r = tvb_length_remaining(b, off);
-  if (r < 4 + len ||
-      (len2 = tvb_get_ntohs(b, off + 2 + len), r < 4 + len + len2)) {
-    ti = proto_tree_add_item(tt, hf->hf, b, off, len + 2, FALSE);
-    tt = proto_item_add_subtree(ti, hf->tt);
-    proto_tree_add_item(tt, hf->hf_len, b, off, 2, FALSE);
-    proto_tree_add_item(tt, hf->hf_val, b, off + 2, len, FALSE);
-    r = off + len + 2;
-  } else {
-    ti = proto_tree_add_item(tt, hf->hf, b, off, len + len2 + 4, FALSE);
-    tt = proto_item_add_subtree(ti, hf->tt);
-    proto_tree_add_item(tt, hf->hfx_len, b, off, 2, FALSE);
-    proto_tree_add_item(tt, hf->hfx_val, b, off + 2, len, FALSE);
-    proto_tree_add_item(tt, hf->hfy_len, b, off + 2 + len, 2, FALSE);
-    proto_tree_add_item(tt, hf->hfy_val, b, off + 4 + len, len2, FALSE);
-    r = off + len + len2 + 4;
-  }
-  return (r);
-}
-
-static void dissect_tripe(tvbuff_t *b, packet_info *p, proto_tree *t)
-{
-  proto_item *ti;
-  proto_tree *tt;
-  guint8 ty;
-  gint off = 0;
-  guint32 seq;
-
-  /* --- Initialize the summary cells --- */
-
-  col_set_str(p->cinfo, COL_PROTOCOL, "TrIPE");
-  col_clear(p->cinfo, COL_INFO);
-  ty = tvb_get_guint8(b, 0);
-  col_clear(p->cinfo, COL_INFO);
-  switch (ty & MSG_CATMASK) {
-    case MSG_PACKET:
-      switch (ty & MSG_TYPEMASK) {
-       case 0:
-         col_set_str(p->cinfo, COL_INFO, "Packet data");
-         break;
-       default:
-         col_add_fstr(p->cinfo, COL_INFO,
-                      "Packet data, unknown type code %u",
-                      ty & MSG_TYPEMASK);
-         break;
-      }
-      break;
-    case MSG_KEYEXCH:
-      switch (ty & MSG_TYPEMASK) {
-       case KX_PRECHAL:
-         col_set_str(p->cinfo, COL_INFO, "Key exchange, prechallenge");
-         break;
-       case KX_CHAL:
-         col_set_str(p->cinfo, COL_INFO, "Key exchange, challenge");
-         break;
-       case KX_REPLY:
-         col_set_str(p->cinfo, COL_INFO, "Key exchange, reply");
-         break;
-       case KX_SWITCH:
-         col_set_str(p->cinfo, COL_INFO, "Key exchange, switch request");
-         break;
-       case KX_SWITCHOK:
-         col_set_str(p->cinfo, COL_INFO, "Key exchange, switch response");
-         break;
-       default:
-         col_add_fstr(p->cinfo, COL_INFO,
-                      "Key exchange, unknown type code %u",
-                      ty & MSG_TYPEMASK);
-         break;
-      }
-      break;
-    case MSG_MISC:
-      switch (ty & MSG_TYPEMASK) {
-       case MISC_NOP:
-         col_set_str(p->cinfo, COL_INFO, "Miscellaneous, no-operation");
-         break;
-       case MISC_PING:
-         col_set_str(p->cinfo, COL_INFO, "Miscellaneous, transport ping");
-         break;
-       case MISC_PONG:
-         col_set_str(p->cinfo, COL_INFO,
-                     "Miscellaneous, transport ping reply");
-         break;
-       case MISC_EPING:
-         col_set_str(p->cinfo, COL_INFO, "Miscellaneous, encrypted ping");
-         break;
-       case MISC_EPONG:
-         col_set_str(p->cinfo, COL_INFO,
-                     "Miscellaneous, encrypted ping reply");
-         break;
-       case MISC_GREET:
-         col_set_str(p->cinfo, COL_INFO,
-                     "Miscellaneous, greeting");
-         break;
-       default:
-         col_add_fstr(p->cinfo, COL_INFO,
-                      "Miscellaneous, unknown type code %u",
-                      ty & MSG_TYPEMASK);
-         break;
-      }
-      break;
-    default:
-      col_add_fstr(p->cinfo, COL_INFO,
-                  "Unknown category code %u, unknown type code %u",
-                  ty & MSG_CATMASK, ty & MSG_TYPEMASK);
-      break;
-  }
-
-  /* --- Fill in the tree --- */
-
-  if (t) {
-    ti = proto_tree_add_item(t, proto_tripe, b, 0, -1, FALSE);
-    tt = proto_item_add_subtree(ti, tt_tripe);
-
-    proto_tree_add_item(tt, hf_tripe_cat, b, 0, 1, FALSE);
-
-    off = 1;
-    switch (ty & MSG_CATMASK) {
-      case MSG_PACKET:
-       proto_tree_add_item(tt, hf_tripe_packet_type, b, 0, 1, FALSE);
-       switch (ty & MSG_TYPEMASK) {
-         case 0:
-           goto ct;
-         default:
-           goto huh;
-       }
-       break;
-      case MSG_KEYEXCH:
-       proto_tree_add_item(tt, hf_tripe_kx_type, b, 0, 1, FALSE);
-       switch (ty & MSG_TYPEMASK) {
-         case KX_PRECHAL:
-           off = getge(tt, &hf_tripe_kx_mychal, b, off);
-           goto tail;
-         case KX_CHAL:
-           off = getge(tt, &hf_tripe_kx_mychal, b, off);
-           off = gethash(tt, hf_tripe_kx_yourcookie, b, off);
-           off = getmp(tt, &hf_tripe_kx_check, b, off);
-           goto tail;
-         case KX_REPLY:
-           off = getge(tt, &hf_tripe_kx_mychal, b, off);
-           off = gethash(tt, hf_tripe_kx_yourcookie, b, off);
-           off = getmp(tt, &hf_tripe_kx_check, b, off);
-           goto ct;
-         case KX_SWITCH:
-           off = gethash(tt, hf_tripe_kx_mycookie, b, off);
-           off = gethash(tt, hf_tripe_kx_yourcookie, b, off);
-           goto ct;
-         case KX_SWITCHOK:
-           goto ct;
-         default:
-           goto huh;
-       }
-       break;
-      case MSG_MISC:
-       proto_tree_add_item(tt, hf_tripe_misc_type, b, 0, 1, FALSE);
-       switch (ty & MSG_TYPEMASK) {
-         case MISC_NOP:
-         case MISC_PING:
-         case MISC_PONG:
-         case MISC_GREET:
-           proto_tree_add_item(tt, hf_tripe_misc_payload,
-                               b, off, -1, FALSE);
-           goto done;
-         case MISC_EPING:
-         case MISC_EPONG:
-           goto ct;
-         default:
-           goto huh;
-       }
-       break;
-      default:
-       goto huh;
-    }
-  tail:
-    if (tvb_offset_exists(b, off))
-      goto huh;
-    goto done;
-  huh:
-    proto_tree_add_item(tt, hf_tripe_huh, b, off, -1, FALSE);
-    goto done;
-  ct:
-    ti = proto_tree_add_item(tt, hf_tripe_ct, b, off, -1, FALSE);
-    seq = tvb_get_ntohl(b, off + tagsz);
-    proto_item_set_text(ti, "Encrypted ciphertext (sequence number %lu)",
-                       (unsigned long)seq);
-    tt = proto_item_add_subtree(ti, tt_tripe_ct);
-    if (tagsz) {
-      proto_tree_add_item(tt, hf_tripe_ct_tag, b, off, tagsz, FALSE);
-      off += tagsz;
-    }
-    proto_tree_add_item(tt, hf_tripe_ct_seq, b, off, 4, FALSE);
-    off += 4;
-    if (ivsz) {
-      proto_tree_add_item(tt, hf_tripe_ct_iv, b, off, ivsz, FALSE);
-      off += ivsz;
-    }
-    proto_tree_add_item(ti, hf_tripe_ct_ct, b, off, -1, FALSE);
-    goto done;
-  done:;
-  }
-}
-
-void proto_register_tripe(void)
-{
-  module_t *mod;
-
-  static value_string vs_kxtype[] = {
-    { KX_PRECHAL,      "KX_PRECHAL (prechallenge)" },
-    { KX_CHAL,         "KX_CHAL (challenge)" },
-    { KX_REPLY,                "KX_REPLY (reply)" },
-    { KX_SWITCH,       "KX_SWITCH (switch request)" },
-    { KX_SWITCHOK,     "KX_SWITCHOK (switch response)" },
-    { 0,               0 }
-  };
-
-  static hf_register_info hfs[] = {
-    { &hf_tripe_cat, {
-      "Message category", "tripe.cat",
-      FT_UINT8, BASE_HEX, 0, MSG_CATMASK
-    } },
-    { &hf_tripe_packet_type, {
-      "Packet message type", "tripe.packet.type",
-      FT_UINT8, BASE_HEX, 0, MSG_TYPEMASK,
-      "This is the TrIPE packet type subcode."
-    } },
-    { &hf_tripe_ct, {
-      "Encrypted ciphertext", "tripe.ct",
-      FT_BYTES, BASE_NONE, 0, 0,
-      "This is an encrypted message."
-    } },
-    { &hf_tripe_ct_seq, {
-      "Ciphertext sequence number", "tripe.ct.seq",
-      FT_UINT32, BASE_DEC, 0, 0,
-      "This is the unique sequence number for the ciphertext."
-    } },
-    { &hf_tripe_ct_iv, {
-      "Ciphertext initialization vector", "tripe.ct.iv",
-      FT_BYTES, BASE_NONE, 0, 0,
-      "This is the initialization vector used for the actual encryption."
-    } },
-    { &hf_tripe_ct_ct, {
-      "Actual encrypted data", "tripe.ct.ct",
-      FT_BYTES, BASE_NONE, 0, 0,
-      "This is the encrypted message.  Reading it ought to be hard."
-    } },
-    { &hf_tripe_ct_tag, {
-      "Message authentication code", "tripe.ct.tag",
-      FT_BYTES, BASE_NONE, 0, 0,
-      "This is the message authentication code tag for the ciphertext."
-    } },
-    { &hf_tripe_misc_type, {
-      "Miscellaneous message type", "tripe.misc.type",
-      FT_UINT8, BASE_HEX, 0, MSG_TYPEMASK,
-      "This is the TrIPE miscellaneous message type subcode."
-    } },
-    { &hf_tripe_misc_payload, {
-      "Miscellaneous message payload", "tripe.misc.payload",
-      FT_BYTES, BASE_NONE, 0, 0,
-      "This is the miscellaneous message payload."
-    } },
-    { &hf_tripe_kx_type, {
-      "Key-exchange message type", "tripe.kx.type",
-      FT_UINT8, BASE_HEX, vs_kxtype, MSG_TYPEMASK,
-      "This is the TrIPE key-exchange type subcode."
-    } },
-    { &hf_tripe_kx_mychal.hf, {
-      "Sender's challenge data", "tripe.kx.mychal",
-      FT_BYTES, BASE_NONE, 0, 0,
-      "This is the sender's challenge."
-    } },
-    { &hf_tripe_kx_mychal.hf_len, {
-      "Challenge length", "tripe.kx.mychal.len",
-      FT_UINT16, BASE_DEC, 0, 0,
-      "This is the length of the sender's challenge."
-    } },
-    { &hf_tripe_kx_mychal.hf_val, {
-      "Challenge", "tripe.kx.mychal.val",
-      FT_BYTES, BASE_NONE, 0, 0,
-      "This is the value of the sender's challenge."
-    } },
-    { &hf_tripe_kx_mychal.hfx_len, {
-      "Challenge x length", "tripe.kx.mychal.x.len",
-      FT_UINT16, BASE_DEC, 0, 0,
-      "This is the length of the sender's challenge x-coordinate."
-    } },
-    { &hf_tripe_kx_mychal.hfx_val, {
-      "Challenge x value", "tripe.kx.mychal.x.val",
-      FT_BYTES, BASE_NONE, 0, 0,
-      "This is the value of the sender's challenge x-coordinate."
-    } },
-    { &hf_tripe_kx_mychal.hfy_len, {
-      "Challenge y length", "tripe.kx.mychal.y.len",
-      FT_UINT16, BASE_DEC, 0, 0,
-      "This is the length of the sender's challenge x-coordinate."
-    } },
-    { &hf_tripe_kx_mychal.hfy_val, {
-      "Challenge y value", "tripe.kx.mychal.y.val",
-      FT_BYTES, BASE_NONE, 0, 0,
-      "This is the value of the sender's challenge x-coordinate."
-    } },
-    { &hf_tripe_kx_mycookie, {
-      "Sender's hashed cookie", "tripe.kx.mycookie",
-      FT_BYTES, BASE_NONE, 0, 0,
-      "This is the hash of the sender's challenge."
-    } },
-    { &hf_tripe_kx_yourcookie, {
-      "Recipient's hashed cookie", "tripe.kx.yourcookie",
-      FT_BYTES, BASE_NONE, 0, 0,
-      "This is the hash of the recipient's challenge."
-    } },
-    { &hf_tripe_kx_check.hf, {
-      "Challenge check-value", "tripe.kx.check",
-      FT_BYTES, BASE_NONE, 0, 0,
-      "This is an encrypted check-value which proves that the sender "
-      "knows the answer to the challenge, and that it is therefore honest."
-    } },
-    { &hf_tripe_kx_check.hf_len, {
-      "Check-value length", "tripe.kx.check.len",
-      FT_UINT16, BASE_DEC, 0, 0,
-      "This is the length of the encrypted check-value."
-    } },
-    { &hf_tripe_kx_check.hf_val, {
-      "Check-value data", "tripe.kx.check.val",
-      FT_BYTES, BASE_NONE, 0, 0,
-      "This is the actual encrypted check-value."
-    } },
-    { &hf_tripe_huh, {
-      "Unknown data", "tripe.huh",
-      FT_BYTES, BASE_NONE, 0, 0,
-      "I don't know what's meant to appear here."
-    } },
-  };
-
-  static gint *tts[] = {
-    &tt_tripe,
-    &tt_tripe_ct,
-    &hf_tripe_kx_mychal.tt,
-    &hf_tripe_kx_check.tt,
-  };
-
-  proto_tripe = proto_register_protocol("TrIPE", "TrIPE", "tripe");
-  proto_register_field_array(proto_tripe, hfs, array_length(hfs));
-  proto_register_subtree_array(tts, array_length(tts));
-
-  mod = prefs_register_protocol(proto_tripe, prefcb);
-  prefs_register_uint_preference(mod, "hashsz", "Hash length",
-                                "hash function output length (in octets)",
-                                10, &hashsz);
-  prefs_register_uint_preference(mod, "tagsz", "MAC tag length",
-                                "MAC tag length (in octets)", 10, &tagsz);
-  prefs_register_uint_preference(mod, "ivsz", "IV length",
-                                "block cipher initialization vector length"
-                                " (in octets)",
-                                10, &ivsz);
-}
-
-void proto_reg_handoff_tripe(void)
-{
-  dissector_handle_t dh;
-
-  dh = create_dissector_handle(dissect_tripe, proto_tripe);
-  dissector_add_uint("udp.port", TRIPE_PORT, dh);
-}
-
-G_MODULE_EXPORT void plugin_reg_handoff(void)
-{
-  proto_reg_handoff_tripe();
-}
-
-G_MODULE_EXPORT void plugin_register(void)
-{
-  if (proto_tripe == -1)
-    proto_register_tripe();
-}
-
-/*----- That's all, folks -------------------------------------------------*/
diff --git a/wireshark/tripe.lua b/wireshark/tripe.lua
new file mode 100644 (file)
index 0000000..a3544e9
--- /dev/null
@@ -0,0 +1,666 @@
+--- -*-lua-*-
+---
+--- Wireshark protocol dissector for TrIPE
+---
+--- (c) 2017 Straylight/Edgeware
+---
+
+-------- Licensing notice ---------------------------------------------------
+---
+--- This file is part of Trivial IP Encryption (TrIPE).
+---
+--- TrIPE 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.
+---
+--- TrIPE 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 TrIPE.  If not, see <https://www.gnu.org/licenses/>.
+
+local tripe = Proto("tripe", "TrIPE VPN")
+
+-----------------------------------------------------------------------------
+--- Configuration handling.
+
+local CONFIG = {
+  -- Information about the configuration variables.  This table, when it's
+  -- set up, maps the internal names, which are used to refer to
+  -- configuration variables in the rest of this code, to a little structure:
+  --
+  --   * `var' names the variable, and is the usual key for lookups;
+  --
+  --   * `name' is the label used in the dialogue box;
+  --
+  --   * `type' is the type of variable, currently either `enum' or `int';
+  --
+  --   * `descr' is a longer (but generally fairly useless) description for
+  --    use in a tooltip;
+  --
+  --   * `allowed' is a sequence of allowed values for an `enum' variable;
+  --    and
+  --
+  --   * `min' and `max' are the limits on permitted values for an `int'
+  --    variable (and may be omitted).
+  --
+  -- More slots are added at runtime:
+  --
+  --   * `map' is a table mapping string values to their integer indices, as
+  --     stored in Wireshark's preferences database.
+  --
+  -- Initially, though, the table is given as a sequence, so that the
+  -- preferences can be populated in a consistent (and approximately logical)
+  -- order.
+
+  { var = "bulk", name = "Bulk transform",
+    type = "enum", allowed = { "v0", "iiv", "naclbox", "aead" },
+    descr = "Bulk cryptographic transform", default = "v0" },
+  { var = "hashsz", name = "Hash length", type = "int", min = 0,
+    descr = "Hash length (bytes)", default = 20 },
+  { var = "tagsz", name = "Tag length", type = "int", min = 0,
+    descr = "Authentication tag length (bytes)", default = 10 },
+  { var = "ivsz", name = "IV length", type = "int", min = 0,
+    descr = "Initialization vector length (bytes)", default = 8 },
+  { var = "kx", name = "Key-exchange group",
+    type = "enum", allowed = { "dh", "ec", "x25519", "x448" },
+    descr = "Key-exchange group type", default = "dh" },
+  { var = "scsz", name = "Scalar length", type = "int", min = 1,
+    descr = "Scalar field-element length (bytes)", default = 32 },
+}
+
+local C = { } -- The working values of the configuration variables.
+
+local function set_config(k, v)
+  -- Set configuration variable K to the value V.
+  --
+  -- K is a string naming the variable to set.  V is the new value, which may
+  -- be a string or a number.
+  --
+  -- For `int' variables, V is converted to a number if necessary, and then
+  -- checked against the permitted bounds.
+  --
+  -- For `enum' variables, things are more complicated.  If V is a string,
+  -- it's checked against the permitted values.  If V is a number, it's
+  -- converted back into the corresponding string.
+
+  local info = CONFIG[k]
+
+  if info == nil then error("unknown config key `" .. k .. "'") end
+
+  if info.type == "enum" then
+    if type(v) == "number" then
+      local t = info.allowed[v]
+      if t == nil then
+       error(string.format("bad index %d for `%s'", n, k))
+      end
+      v = t
+    else
+      if info.map[v] == nil then
+       error(string.format("bad value `%s' for `%s'", v, k))
+      end
+    end
+
+  elseif info.type == "int" then
+    local n = tonumber(v)
+    if n == nil then error("bad number `" .. v .. "'") end
+    if n ~= math.floor(n) then
+      error("value `" .. v .. "' is not an integer")
+    end
+    if (info.min ~= nil and n < info.min) or
+       (info.max ~= nil and n > info.max)
+    then
+      error(string.format("value %d out of range for `%s'", n, k))
+    end
+    v = n
+  end
+
+  C[k] = v
+end
+
+-- Set up the configuration information.  Configure preferences objects on
+-- the dissector.  For `enum' variables, build the `map' slots.
+for i, v in ipairs(CONFIG) do
+  local k = v.var
+  CONFIG[k] = v
+  if v.type == "enum" then
+    local tab = { }
+    v.map = { }
+    for i, t in pairs(v.allowed) do
+      v.map[t] = i
+      tab[i] = { i, t, i }
+    end
+    tripe.prefs[k] = Pref.enum(v.name, v.map[v.default], v.descr, tab)
+  elseif v.type == "int" then
+    tripe.prefs[k] = Pref.uint(v.name, v.default, v.descr)
+  end
+end
+
+local function prefs_changed()
+  -- Notice that the preferences have been changed and update `C'.
+
+  for k, _ in pairs(CONFIG) do
+    if type(k) == "string" then set_config(k, tripe.prefs[k]) end
+  end
+end
+tripe.prefs_changed = prefs_changed
+
+-- Populate the configuration table from the stored preferences or their
+-- default values.
+prefs_changed()
+
+-- Now work through arguments passed in on the command line.  Annoyingly,
+-- while one can set preferences on the Wireshark command line, these are
+-- done /before/ Lua scripts are loaded, so the silly thing thinks the
+-- preference slots don't exist.  So we have to do it a different way.
+for _, arg in ipairs({...}) do
+  local k, v = arg:match("(.+)=(.+)")
+  if k == nil or v == nil then error("bad option syntax `" .. arg .. "'") end
+  se_config(k, v)
+end
+
+-----------------------------------------------------------------------------
+--- Protocol dissection primitives.
+
+local PF = { } -- The table of protocol fields, filled in later.
+
+-- 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.  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_wtf(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["tripe.wtf"], buf(pos, sz - pos)) end
+  return sz
+end
+
+-- Dissect a ciphertext of some particular kind.
+local dissect_ct = { }
+function dissect_ct.aead(buf, tree, pos, sz)
+  tree:add(PF["tripe.ciphertext.tag"], buf(pos, C.tagsz)); pos = pos + C.tagsz
+  tree:add(PF["tripe.ciphertext.seq"], buf(pos, 4)); pos = pos + 4
+  tree:add(PF["tripe.ciphertext.body"], buf(pos, sz - pos))
+end
+function dissect_ct.naclbox(buf, tree, pos, sz)
+  tree:add(PF["tripe.ciphertext.tag"], buf(pos, 16)); pos = pos + 16
+  tree:add(PF["tripe.ciphertext.seq"], buf(pos, 4)); pos = pos + 4
+  tree:add(PF["tripe.ciphertext.body"], buf(pos, sz - pos))
+end
+function dissect_ct.iiv(buf, tree, pos, sz)
+  tree:add(PF["tripe.ciphertext.tag"], buf(pos, C.tagsz)); pos = pos + C.tagsz
+  tree:add(PF["tripe.ciphertext.seq"], buf(pos, 4)); pos = pos + 4
+  tree:add(PF["tripe.ciphertext.body"], buf(pos, sz - pos))
+end
+function dissect_ct.v0(buf, tree, pos, sz)
+  tree:add(PF["tripe.ciphertext.tag"], buf(pos, C.tagsz)); pos = pos + C.tagsz
+  tree:add(PF["tripe.ciphertext.seq"], buf(pos, 4)); pos = pos + 4
+  tree:add(PF["tripe.ciphertext.iv"], buf(pos, C.ivsz)); pos = pos + C.ivsz
+  tree:add(PF["tripe.ciphertext.body"], buf(pos, sz - pos))
+end
+
+local function dissect_ciphertext(buf, tree, label, pos, sz)
+  -- Dissect a ciphertext, making the whole thing be a little subtree with
+  -- the given LABEL.
+
+  local t = tree:add(PF[label], buf(pos, sz - pos))
+  dissect_ct[C.bulk](buf, t, pos, sz)
+  return sz
+end
+
+local function dissect_packet(buf, tree, pos, sz)
+  return dissect_ciphertext(buf, tree, "tripe.packet.payload", pos, sz)
+end
+
+-- Dissect a group element of some particular kind.
+local dissect_ge = { }
+function dissect_ge.dh(buf, tree, pos, sz)
+  tree:add(PF["tripe.dh.len"], buf(pos, 2))
+  xsz = buf(pos, 2):uint(); pos = pos + 2
+  tree:add(PF["tripe.dh.x"], buf(pos, xsz)); pos = pos + xsz
+  return pos
+end
+function dissect_ge.ec(buf, tree, pos, sz)
+  tree:add(PF["tripe.ec.xlen"], buf(pos, 2))
+  xsz = buf(pos, 2):uint(); pos = pos + 2
+  tree:add(PF["tripe.ec.x"], buf(pos, xsz)); pos = pos + xsz
+  tree:add(PF["tripe.ec.ylen"], buf(pos, 2))
+  ysz = buf(pos, 2):uint(); pos = pos + 2
+  tree:add(PF["tripe.ec.y"], buf(pos, ysz)); pos = pos + ysz
+  return pos
+end
+function dissect_ge.x25519(buf, tree, pos, sz)
+  tree:add(PF["tripe.x25519.x"], buf(pos, 32))
+  return pos + 32
+end
+function dissect_ge.x448(buf, tree, pos, sz)
+  tree:add(PF["tripe.x448.x"], buf(pos, 56))
+  return pos + 56
+end
+
+local function dissect_my_challenge(buf, tree, pos, sz)
+  -- We don't know how long the group element is going to be.  We can set the
+  -- length later, but (at least in older versions) it doesn't work so well
+  -- to increase the length, so make it large to start out, and shrink it
+  -- later.
+  local t = tree:add(PF["tripe.keyexch.mychal"], buf(pos, sz - pos))
+  local q = dissect_ge[C.kx](buf, t, pos, sz)
+  t:set_len(q - pos)
+  return q
+end
+
+local function dissect_my_cookie(buf, tree, pos, sz)
+  tree:add(PF["tripe.keyexch.mycookie"], buf(pos, C.hashsz))
+  return pos + C.hashsz
+end
+
+local function dissect_your_cookie(buf, tree, pos, sz)
+  tree:add(PF["tripe.keyexch.yourcookie"], buf(pos, C.hashsz))
+  return pos + C.hashsz
+end
+
+local kx_scsz = { x25519 = 32, x448 = 56 } -- Hardwired scalar sizes.
+local function dissect_check(buf, tree, pos, sz)
+  local scsz = kx_scsz[C.kx] or C.scsz
+  tree:add(PF["tripe.keyexch.check"], buf(pos, scsz))
+  return pos + scsz
+end
+
+local function dissect_reply(buf, tree, pos, sz)
+  return dissect_ciphertext(buf, tree, "tripe.keyexch.reply", pos, sz)
+end
+
+local function dissect_switch(buf, tree, pos, sz)
+  return dissect_ciphertext(buf, tree, "tripe.keyexch.switch", pos, sz)
+end
+
+local function dissect_switchok(buf, tree, pos, sz)
+  return dissect_ciphertext(buf, tree, "tripe.keyexch.switchok", pos, sz)
+end
+
+local function dissect_misc_payload(buf, tree, pos, sz)
+  tree:add(PF["tripe.misc.payload"], buf(pos, sz - pos))
+  return sz
+end
+
+local function dissect_misc_ciphertext(buf, tree, pos, sz)
+  return dissect_ciphertext(buf, tree, "tripe.misc.ciphertext", pos, sz)
+end
+
+local function dissect_chal(buf, tree, label, pos, sz)
+  local len = buf(pos, 2):uint()
+  local t = tree:add(PF[label], buf(pos, len + 2))
+  t:add(PF["tripe.chal.len"], buf(pos, 2)); pos = pos + 2
+  t:add(PF["tripe.chal.sequence"], buf(pos, 4)); pos = pos + 4; len = len - 4
+  t:add(PF["tripe.chal.tag"], buf(pos, len))
+  return pos + len
+end
+
+local function dissect_my_chal(buf, tree, pos, sz)
+  return dissect_chal(buf, tree, "tripe.knock.mychal", pos, sz)
+end
+
+local function dissect_your_chal(buf, tree, pos, sz)
+  return dissect_chal(buf, tree, "tripe.knock.yourchal", pos, sz)
+end
+
+local function dissect_keyid(buf, tree, pos, sz)
+  tree:add(PF["tripe.knock.keyid"], buf(pos, 4))
+  return pos + 4
+end
+
+local function dissect_ies(buf, tree, pos, sz)
+  local len = buf(pos, 2):uint()
+  local lim = pos + len + 2
+  local t = tree:add(PF["tripe.knock.ies"], buf(pos, len + 2))
+  t:add(PF["tripe.ies.len"], buf(pos, 2)); pos = pos + 2
+  pos = dissect_ge[C.kx](buf, t, pos, sz)
+  return dissect_ciphertext(buf, t, "tripe.ies.ciphertext", pos, lim)
+end
+
+-----------------------------------------------------------------------------
+--- The protocol information table.
+
+local PKTINFO = {
+  -- This is the main table which describes the protocol.  The top level maps
+  -- category codes to structures:
+  --
+  --   * `label' is the category code's symbolic name;
+  --
+  --   * `subtype' is the field name for the subtype code;
+  --
+  --   * `info' is a prefix for the information column display; and
+  --
+  --   * `sub' is a table describing the individual subtypes.
+  --
+  -- The subtype table similarly maps subtype codes to structures:
+  --
+  --   * `label' is the subtype code's symbolic name;
+  --
+  --   * `info' is the suffix for the information column display; and
+  --
+  --   * `dissect' is a sequence of primitive dissectors to run in order to
+  --     parse the rest of the packet.
+
+  [0] = {
+    label = "MSG_PACKET", subtype = "tripe.packet.type",
+    info = "Packet data",
+    sub = {
+      [0] = { label = "PACKET_IP", info = "encapsulated IP datagram",
+             dissect = { dissect_packet} }
+    }
+  },
+
+  [1] = {
+    label = "MSG_KEYEXCH", subtype = "tripe.keyexch.type",
+    info = "Key exchange",
+    sub = {
+      [0] = { label = "KX_PRECHAL", info = "pre-challenge",
+             dissect = { dissect_my_challenge,
+                         dissect_wtf } },
+      [1] = { label = "KX_CHAL", info = "challenge",
+             dissect = { dissect_my_challenge,
+                         dissect_your_cookie,
+                         dissect_check,
+                         dissect_wtf } },
+      [2] = { label = "KX_REPLY", info = "reply",
+             dissect = { dissect_my_challenge,
+                         dissect_your_cookie,
+                         dissect_check,
+                         dissect_reply } },
+      [3] = { label = "KX_SWITCH", info = "switch",
+             dissect = { dissect_my_cookie,
+                         dissect_your_cookie,
+                         dissect_switch } },
+      [4] = { label = "KX_SWITCHOK", info = "switch-ok",
+             dissect = { dissect_switchok } },
+      [5] = { label = "KX_TOKENRQ", info = "token-rq",
+             dissect = { dissect_my_chal,
+                         dissect_keyid,
+                         dissect_ies } },
+      [6] = { label = "KX_TOKEN", info = "token",
+             dissect = { dissect_your_chal,
+                         dissect_my_chal,
+                         dissect_ies } },
+      [7] = { label = "KX_KNOCK", info = "knock",
+             dissect = { dissect_your_chal,
+                         dissect_keyid,
+                         dissect_ies,
+                         dissect_my_challenge } }
+    }
+  },
+
+  [2] = {
+    label = "MSG_MISC", subtype = "tripe.misc.type",
+    info = "Miscellaneous",
+    sub = {
+      [0] = { label = "MISC_NOP", info = "no-operation (keepalive)",
+             dissect = { dissect_misc_payload } },
+      [1] = { label = "MISC_PING", info = "transport-level ping",
+             dissect = { dissect_misc_payload } },
+      [2] = { label = "MISC_PONG", info = "transport-level ping reply",
+             dissect = { dissect_misc_payload } },
+      [3] = { label = "MISC_EPING", info = "crypto-level ping",
+             dissect = { dissect_misc_ciphertext } },
+      [4] = { label = "MISC_EPONG", info = "crypto-level ping reply",
+             dissect = { dissect_misc_ciphertext } },
+      [5] = { label = "MISC_GREET", info = "greeting",
+             dissect = { dissect_misc_payload } },
+      [6] = { label = "MISC_BYE", info = "disconnect notification",
+             dissect = { dissect_misc_ciphertext } },
+    }
+  }
+}
+
+do
+  -- Work through the master table and build `cattab' and `subtab' tables,
+  -- mapping category and subtype codes to their symbolic names for
+  -- presentation.  The `subtab' is a two-level table, needing two layers of
+  -- indexing.
+  local cattab = { }
+  local subtab = { }
+  for i, v in pairs(PKTINFO) do
+    cattab[i] = v.label
+    if v.sub ~= nil then
+      subtab[i] = { }
+      for j, w in pairs(v.sub) do
+       subtab[i][j] = w.label
+      end
+    end
+  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; and
+    --
+    --   * `tab' names a mapping table used to convert numerical values to
+    --     symbolic names.
+
+    ["tripe.type"] = {
+      name = "Message type", type = ftypes.UINT8, base = base.HEX
+    },
+    ["tripe.cat"] = {
+      name = "Message category", type = ftypes.UINT8, base = base.DEC,
+      mask = 0xf0, tab = cattab
+    },
+    ["tripe.packet.type"] = {
+      name = "Packet subcode", type = ftypes.UINT8, base = base.DEC,
+      mask = 0x0f, tab = subtab[0]
+    },
+    ["tripe.packet.payload"] = {
+      name = "Encrypted packet", type = ftypes.NONE
+    },
+    ["tripe.knock.keyid"] = {
+      name = "Short key indicator", type = ftypes.UINT32, base = base.HEX
+    },
+    ["tripe.knock.mychal"] = {
+      name = "Sender's one-time challenge", type = ftypes.NONE
+    },
+    ["tripe.knock.yourchal"] = {
+      name = "Recipient's one-time challenge", type = ftypes.NONE
+    },
+    ["tripe.chal.len"] = {
+      name = "Challenge length", type = ftypes.UINT16, base = base.DEC
+    },
+    ["tripe.chal.sequence"] = {
+      name = "Challenge sequence number",
+      type = ftypes.UINT32, base = base.DEC
+    },
+    ["tripe.chal.tag"] = {
+      name = "Challenge tag", type = ftypes.BYTES, base = base.SPACE
+    },
+    ["tripe.knock.ies"] = {
+      name = "Encrypted message", type = ftypes.NONE
+    },
+    ["tripe.ies.len"] = {
+      name = "Encrypted message length",
+      type = ftypes.UINT16, base = base.DEC
+    },
+    ["tripe.ies.clue"] = {
+      name = "Encrypted message KEM clue",
+      type = ftypes.BYTES, base = base.SPACE
+    },
+    ["tripe.ies.ciphertext"] = {
+      name = "Encrypted message ciphertext",
+      type = ftypes.BYTES, base = base.SPACE
+    },
+    ["tripe.keyexch.type"] = {
+      name = "Key-exchange subcode", type = ftypes.UINT8, base = base.DEC,
+      mask = 0x0f, tab = subtab[1]
+    },
+    ["tripe.keyexch.mychal"] = {
+      name = "Sender's challenge R = r P", type = ftypes.NONE
+    },
+    ["tripe.keyexch.mycookie"] = {
+      name = "Hash of recipient's challenge = H(R, ...)",
+      type = ftypes.BYTES, base = base.SPACE
+    },
+    ["tripe.keyexch.yourcookie"] = {
+      name = "Hash of sender's challenge = H(R', ...)",
+      type = ftypes.BYTES, base = base.SPACE
+    },
+    ["tripe.keyexch.reply"] = {
+      name = "Encrypted reply = k R'", type = ftypes.NONE
+    },
+    ["tripe.keyexch.switch"] = {
+      name = "Encrypted reply and switch request = k R', H(...)",
+      type = ftypes.NONE
+    },
+    ["tripe.keyexch.switchok"] = {
+      name = "Encrypted switch confirmation = H(...)", type = ftypes.NONE
+    },
+    ["tripe.misc.type"] = {
+      name = "Miscellenaous subcode", type = ftypes.UINT8, base = base.DEC,
+      mask = 0x0f, tab = subtab[2]
+    },
+    ["tripe.misc.payload"] = {
+      name = "Miscellaneous payload",
+      type = ftypes.BYTES, base = base.SPACE
+    },
+    ["tripe.misc.ciphertext"] = {
+      name = "Miscellaneous encrypted payload", type = ftypes.NONE
+    },
+    ["tripe.wtf"] = {
+      name = "Unexpected trailing data",
+      type = ftypes.BYTES, base = base.SPACE
+    },
+    ["tripe.keyexch.check"] = {
+      name = "Sender's challenge check value = r XOR H(r K', ...)",
+      type = ftypes.BYTES, base = base.SPACE
+    },
+    ["tripe.ciphertext.seq"] = {
+      name = "Sequence number", type = ftypes.UINT32, base = base.DEC
+    },
+    ["tripe.ciphertext.iv"] = {
+      name = "Initialization vector", type = ftypes.BYTES, base = base.SPACE
+    },
+    ["tripe.ciphertext.tag"] = {
+      name = "Authentication tag", type = ftypes.BYTES, base = base.SPACE
+    },
+    ["tripe.ciphertext.body"] = {
+      name = "Encrypted data", type = ftypes.BYTES, base = base.SPACE
+    },
+    ["tripe.dh.len"] = {
+      name = "DH group element length",
+      type = ftypes.UINT16, base = base.DEC
+    },
+    ["tripe.dh.x"] = {
+      name = "DH group element value",
+      type = ftypes.BYTES, base = base.SPACE
+    },
+    ["tripe.ec.xlen"] = {
+      name = "Elliptic curve x-coordinate length",
+      type = ftypes.UINT16, base = base.DEC
+    },
+    ["tripe.ec.x"] = {
+      name = "Elliptic curve x-coordinate value",
+      type = ftypes.BYTES, base = base.SPACE
+    },
+    ["tripe.ec.ylen"] = {
+      name = "Elliptic curve y-coordinate length",
+      type = ftypes.UINT16, base = base.DEC
+    },
+    ["tripe.ec.y"] = {
+      name = "Elliptic curve y-coordinate value",
+      type = ftypes.BYTES, base = base.SPACE
+    },
+    ["tripe.x25519.x"] = {
+      name = "X25519 x-coordinate",
+      type = ftypes.BYTES, base = base.SPACE
+    },
+    ["tripe.x448.x"] = {
+      name = "X448 x-coordinate",
+      type = ftypes.BYTES, base = base.SPACE
+    },
+  }
+
+  -- 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).
+  -- probe for this easily
+  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 and not use_none_p then
+      ty = ftypes.BYTES
+      b = base.SPACE
+    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
+  tripe.fields = PF
+end
+
+-----------------------------------------------------------------------------
+--- The main dissector.
+
+function tripe.dissector(buf, pinfo, tree)
+
+  -- Fill in the obvious stuff.
+  pinfo.cols.protocol = "TrIPE"
+
+  local sz = buf:reported_length_remaining()
+  local sub = tree:add(tripe, buf(0, sz), "TrIPE packet")
+  local p = 1
+
+  -- Decode the packet type octet.
+  local tycode = buf(0, 1):uint()
+  local ty = sub:add(PF["tripe.type"], buf(0, 1))
+  ty:add(PF["tripe.cat"], buf(0, 1))
+  local cat = bit.rshift(bit.band(tycode, 0xf0), 4)
+  local subty = bit.band(tycode, 0x0f)
+  local info = PKTINFO[cat]
+
+  -- Dispatch using the master protocol table.
+  if info == nil then
+    pinfo.cols.info = string.format("Unknown category code %u, " ..
+                                     "unknown type code %u",
+                                   cat, subty)
+  else
+    ty:add(PF[info.subtype], buf(0, 1))
+    local subinfo = info.sub[subty]
+    if subinfo == nil then
+      pinfo.cols.info = string.format("%s, unknown type code %u",
+                                     info.info, subty)
+    else
+      pinfo.cols.info = string.format("%s, %s", info.info, subinfo.info)
+      p = 1
+      for _, d in ipairs(subinfo.dissect) do p = d(buf, sub, p, sz) end
+    end
+  end
+
+  -- Return the final position we reached.
+  return p
+end
+
+-- We're done.  Register the dissector.
+DissectorTable.get("udp.port"):add(4070, tripe)
+
+-------- That's all, folks --------------------------------------------------