=== modified file 'DBUS-API' --- DBUS-API 2019-02-10 04:20:26 +0000 +++ DBUS-API 2018-02-08 10:23:55 +0000 @@ -27,9 +27,9 @@ Removes a client ** Signals: -*** ClientNotFound(s: KeyID, s: Address) - A client connected from Address using KeyID, but was - rejected because it was not found in the server. The key ID +*** ClientNotFound(s: Fingerprint, s: Address) + A client connected from Address using Fingerprint, but was + rejected because it was not found in the server. The fingerprint is represented as a string of hexadecimal digits. The address is an IPv4 or IPv6 address in its normal string format. @@ -66,7 +66,6 @@ | Expires (f) | s | Read | N/A | | ExtendedTimeout (a) | t | Read/Write | extended_timeout | | Fingerprint | s | Read | fingerprint | - | KeyID | s | Read | key_id | | Host | s | Read/Write | host | | Interval (a) | t | Read/Write | interval | | LastApprovalRequest (g) | s | Read | N/A | @@ -131,8 +130,8 @@ * Copyright - Copyright © 2010-2019 Teddy Hogeborn - Copyright © 2010-2019 Björn Påhlsson + Copyright © 2010-2018 Teddy Hogeborn + Copyright © 2010-2018 Björn Påhlsson ** License: === modified file 'INSTALL' --- INSTALL 2019-02-09 23:23:26 +0000 +++ INSTALL 2016-07-03 03:32:28 +0000 @@ -39,7 +39,6 @@ *** Mandos Server + GnuTLS 3.3 https://www.gnutls.org/ - (but not 3.6.0 or later, until 3.6.6, which works) + Avahi 0.6.16 http://www.avahi.org/ + Python 2.7 https://www.python.org/ + dbus-python 0.82.4 https://dbus.freedesktop.org/doc/dbus-python/ @@ -61,7 +60,6 @@ + initramfs-tools 0.85i https://tracker.debian.org/pkg/initramfs-tools + GnuTLS 3.3 https://www.gnutls.org/ - (but not 3.6.0 or later, until 3.6.6 which works) + Avahi 0.6.16 http://www.avahi.org/ + GnuPG 1.4.9 https://www.gnupg.org/ + GPGME 1.1.6 https://www.gnupg.org/related_software/gpgme/ @@ -71,7 +69,7 @@ + OpenSSH http://www.openssh.com/ Package names: - initramfs-tools libgnutls-dev gnutls-bin libavahi-core-dev gnupg + initramfs-tools libgnutls-dev libavahi-core-dev gnupg libgpgme11-dev pkg-config ssh * Installing the Mandos server @@ -125,9 +123,7 @@ # /usr/lib/mandos/plugins.d/mandos-client \ --pubkey=/etc/keys/mandos/pubkey.txt \ - --seckey=/etc/keys/mandos/seckey.txt \ - --tls-privkey=/etc/keys/mandos/tls-privkey.pem \ - --tls-pubkey=/etc/keys/mandos/tls-pubkey.pem; echo + --seckey=/etc/keys/mandos/seckey.txt; echo This command should retrieve the password from the server, decrypt it, and output it to standard output. === modified file 'Makefile' --- Makefile 2019-04-09 19:33:36 +0000 +++ Makefile 2018-01-29 19:43:17 +0000 @@ -40,7 +40,7 @@ OPTIMIZE:=-Os -fno-strict-aliasing LANGUAGE:=-std=gnu11 htmldir:=man -version:=1.8.3 +version:=1.7.16 SED:=sed USER:=$(firstword $(subst :, ,$(shell getent passwd _mandos || getent passwd nobody || echo 65534))) @@ -89,7 +89,8 @@ # Do not change these two CFLAGS+=$(WARN) $(DEBUG) $(FORTIFY) $(SANITIZE) $(COVERAGE) \ - $(OPTIMIZE) $(LANGUAGE) -DVERSION='"$(version)"' + $(OPTIMIZE) $(LANGUAGE) $(GNUTLS_CFLAGS) $(AVAHI_CFLAGS) \ + $(GPGME_CFLAGS) -DVERSION='"$(version)"' LDFLAGS+=-Xlinker --as-needed $(COVERAGE) $(LINK_FORTIFY) $(foreach flag,$(LINK_FORTIFY_LD),-Xlinker $(flag)) # Commands to format a DocBook document into a manual page @@ -252,13 +253,8 @@ --expression='s/\(mandos_\)[0-9.]\+\(\.orig\.tar\.gz\)/\1$(version)\2/' \ $@) -# Need to add the GnuTLS, Avahi and GPGME libraries, and can't use -# -fsanitize=leak because GnuTLS and GPGME both leak memory. plugins.d/mandos-client: plugins.d/mandos-client.c - $(CC) $(filter-out -fsanitize=leak,$(CFLAGS)) $(strip\ - ) $(GNUTLS_CFLAGS) $(AVAHI_CFLAGS) $(GPGME_CFLAGS) $(strip\ - ) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) $^ $(strip\ - ) -lrt $(GNUTLS_LIBS) $(AVAHI_LIBS) $(strip\ + $(LINK.c) $^ -lrt $(GNUTLS_LIBS) $(AVAHI_LIBS) $(strip\ ) $(GPGME_LIBS) $(LOADLIBES) $(LDLIBS) -o $@ plugin-helpers/mandos-client-iprouteadddel: plugin-helpers/mandos-client-iprouteadddel.c @@ -284,7 +280,7 @@ ./mandos-ctl --check # Run the client with a local config and key -run-client: all keydir/seckey.txt keydir/pubkey.txt keydir/tls-privkey.pem keydir/tls-pubkey.pem +run-client: all keydir/seckey.txt keydir/pubkey.txt @echo "###################################################################" @echo "# The following error messages are harmless and can be safely #" @echo "# ignored: #" @@ -303,12 +299,12 @@ ./plugin-runner --plugin-dir=plugins.d \ --plugin-helper-dir=plugin-helpers \ --config-file=plugin-runner.conf \ - --options-for=mandos-client:--seckey=keydir/seckey.txt,--pubkey=keydir/pubkey.txt,--tls-privkey=keydir/tls-privkey.pem,--tls-pubkey=keydir/tls-pubkey.pem,--network-hook-dir=network-hooks.d \ + --options-for=mandos-client:--seckey=keydir/seckey.txt,--pubkey=keydir/pubkey.txt,--network-hook-dir=network-hooks.d \ --env-for=mandos-client:GNOME_KEYRING_CONTROL= \ $(CLIENTARGS) # Used by run-client -keydir/seckey.txt keydir/pubkey.txt keydir/tls-privkey.pem keydir/tls-pubkey.pem: mandos-keygen +keydir/seckey.txt keydir/pubkey.txt: mandos-keygen install --directory keydir ./mandos-keygen --dir keydir --force @@ -321,7 +317,7 @@ confdir/mandos.conf: mandos.conf install --directory confdir install --mode=u=rw,go=r $^ $@ -confdir/clients.conf: clients.conf keydir/seckey.txt keydir/tls-pubkey.pem +confdir/clients.conf: clients.conf keydir/seckey.txt install --directory confdir install --mode=u=rw $< $@ # Add a client password @@ -396,8 +392,6 @@ "$(CONFDIR)/network-hooks.d" install --mode=u=rwx,go=rx \ --target-directory=$(LIBDIR)/mandos plugin-runner - install --mode=u=rwx,go=rx \ - --target-directory=$(LIBDIR)/mandos mandos-to-cryptroot-unlock install --mode=u=rwx,go=rx --target-directory=$(PREFIX)/sbin \ mandos-keygen install --mode=u=rwx,go=rx \ @@ -423,14 +417,10 @@ plugin-helpers/mandos-client-iprouteadddel install initramfs-tools-hook \ $(INITRAMFSTOOLS)/hooks/mandos - install --mode=u=rw,go=r initramfs-tools-conf \ - $(INITRAMFSTOOLS)/conf.d/mandos-conf - install --mode=u=rw,go=r initramfs-tools-conf-hook \ - $(INITRAMFSTOOLS)/conf-hooks.d/zz-mandos + install --mode=u=rw,go=r initramfs-tools-hook-conf \ + $(INITRAMFSTOOLS)/conf-hooks.d/mandos install initramfs-tools-script \ $(INITRAMFSTOOLS)/scripts/init-premount/mandos - install initramfs-tools-script-stop \ - $(INITRAMFSTOOLS)/scripts/local-premount/mandos install --mode=u=rw,go=r plugin-runner.conf $(CONFDIR) gzip --best --to-stdout mandos-keygen.8 \ > $(MANDIR)/man8/mandos-keygen.8.gz @@ -510,8 +500,7 @@ -rmdir $(CONFDIR) purge-client: uninstall-client - -shred --remove $(KEYDIR)/seckey.txt $(KEYDIR)/tls-privkey.pem + -shred --remove $(KEYDIR)/seckey.txt -rm --force $(CONFDIR)/plugin-runner.conf \ - $(KEYDIR)/pubkey.txt $(KEYDIR)/seckey.txt \ - $(KEYDIR)/tls-pubkey.txt $(KEYDIR)/tls-privkey.txt + $(KEYDIR)/pubkey.txt $(KEYDIR)/seckey.txt -rmdir $(KEYDIR) $(CONFDIR)/plugins.d $(CONFDIR) === modified file 'NEWS' --- NEWS 2019-02-11 06:31:42 +0000 +++ NEWS 2017-08-20 19:12:58 +0000 @@ -1,72 +1,6 @@ This NEWS file records noteworthy changes, very tersely. See the manual for detailed information. -Version 1.8.3 (2019-02-11) -* No user-visible changes. - -Version 1.8.2 (2019-02-10) -* Client -** In mandos-keygen, ignore failures to remove files in some cases. - -Version 1.8.1 (2019-02-10) -* Client -** Only generate TLS keys using GnuTLS' certtool, of sufficient - version. Key generation of TLS keys will not happen until a - version of GnuTLS is installed with support for raw public keys. -** Remove any bad keys created by 1.8.0 and openssl. -* Server -** On installation, edit clients.conf and remove the same bad key ID - which was erroneously reported by all 1.8.0 clients. Also do not - trust this key ID in the server. - -Version 1.8.0 (2019-02-10) -* Client -** Use new TLS keys for server communication and identification. - With GnuTLS 3.6 or later, OpenPGP keys are no longer supported. - The client can now use the new "raw public keys" (RFC 7250) API - instead, using GnuTLS 3.6.6. Please note: This *requires* new key - IDs to be added to server's client.conf file. -** New --tls-privkey and --tls-pubkey options to load TLS key files. - If GnuTLS is too old, these options do nothing. -* Server -** Supports either old or new GnuTLS. - The server now supports using GnuTLS 3.6.6 and clients connecting - with "raw public keys" as identification. The server will read - both fingerprints and key IDs from clients.conf file, and will use - either one or the other, depending on what is supported by GnuTLS - on the system. Please note: both are *not* supported at once; if - one type is supported by GnuTLS, all values of the other type from - clients.conf are ignored. - -Version 1.7.20 (2018-08-19) -* Client -** Fix: Adapt to the Debian cryptsetup package 2.0.3 or later. - Important: in that version or later, the plugins "askpass-fifo", - "password-prompt", and "plymouth" will no longer be run, since they - would conflict with what cryptsetup is doing. Other plugins, such - as mandos-client and any user-supplied plugins, will still run. -** Better error message if failing to decrypt secret data -** Check for (and report) any key import failure from GPGME -** Better error message if self-signature verification fails -** Set system clock if not set; required by GnuPG for key import -** When debugging plugin-runner, it will now show starting plugins - -Version 1.7.19 (2018-02-22) -* Client -** Do not print "unlink(...): No such file or directory". -** Bug fixes: Fix file descriptor leaks. -** Bug fix: Don't use leak sanitizer with leaking libraries. - -Version 1.7.18 (2018-02-12) -* Client -** Bug fix: Revert faulty fix for a nonexistent bug in the - plugin-runner - -Version 1.7.17 (2018-02-10) -* Client -** Bug fix: Fix a memory leak in the plugin-runner -** Bug fix: Fix memory leaks in the plymouth plugin - Version 1.7.16 (2017-08-20) * Client ** Bug fix: ignore "resumedev" entries in initramfs' cryptroot file === modified file 'TODO' --- TODO 2019-03-17 16:48:56 +0000 +++ TODO 2017-02-22 21:45:35 +0000 @@ -14,7 +14,6 @@ ** TODO [#C] Make start_mandos_communication() take "struct server". ** TODO [#C] --interfaces=regex,eth*,noregex (bridge-utils-interfaces(5)) ** TODO [#A] Detect partial writes to stdout and exit with EX_TEMPFAIL -** TODO [#B] Use reallocarray() with GNU LibC 2.29 or later. * splashy ** TODO [#B] use scandir(3) instead of readdir(3) @@ -34,7 +33,6 @@ * plymouth ** TODO [#A] Detect partial writes to stdout and exit with EX_TEMPFAIL -** TODO [#B] Use reallocarray() with GNU LibC 2.29 or later. * TODO [#B] passdev @@ -86,6 +84,7 @@ *** TODO [#C] In Python 3.3, use shlex.quote() instead of re.escape() * mandos-ctl +*** Handle "no D-Bus server" and/or "no Mandos server found" better ** TODO Remove old string_to_delta format :2: * TODO mandos-dispatch @@ -120,5 +119,16 @@ * [[http://www.undeadly.org/cgi?action=article&sid=20110530221728][OpenBSD]] +* TODO Use raw public keys (RFC 7250) for TLS communications :2: +** Support for this is planned for GnuTLS version 3.6 + https://gitlab.com/gnutls/gnutls/issues/26 +** Rationale +*** The client key is used both for communication and encryption + Using raw keys in GnuTLS instead uses separate keys for + communication and password decryption. +*** GnuTLS 3.5.9 has deprecated the OpenPGP functions + The functions are still available, but deprecated: + https://gitlab.com/gnutls/gnutls/issues/102 + #+STARTUP: showall === modified file 'clients.conf' --- clients.conf 2019-02-09 23:34:15 +0000 +++ clients.conf 2012-06-23 00:58:49 +0000 @@ -38,9 +38,6 @@ ;# Example client ;[foo] ; -;# TLS public key ID -;key_id = f33fcbed11ed5e03073f6a55b86ffe92af0e24c045fb6e3b40547b3dc0c030ed -; ;# OpenPGP key fingerprint ;fingerprint = 7788 2722 5BA7 DE53 9C5A 7CFA 59CF F7CD BD9A 5920 ; @@ -71,14 +68,11 @@ ;#### ;# Another example client, named "bar". ;[bar] -;# The key ID is not space or case sensitive -;key_id = F33FCBED11ED5E03073F6A55B86FFE92 AF0E24C045FB6E3B40547B3DC0C030ED -; ;# The fingerprint is not space or case sensitive ;fingerprint = 3e393aeaefb84c7e89e2f547b3a107558fca3a27 ; ;# If "secret" is not specified, a file can be read for the data. -;secfile = /etc/keys/mandos/bar-secret.bin +;secfile = /etc/mandos/bar-secret.bin ; ;# An IP address for host is also fine, if the checker accepts it. ;host = 192.0.2.3 === modified file 'common.ent' --- common.ent 2019-02-11 06:31:42 +0000 +++ common.ent 2017-08-20 19:12:58 +0000 @@ -1,3 +1,3 @@ - + === modified file 'debian/changelog' --- debian/changelog 2019-02-11 11:52:07 +0000 +++ debian/changelog 2017-08-20 19:12:58 +0000 @@ -1,143 +1,3 @@ -mandos (1.8.3-2) unstable; urgency=medium - - * debian/rules (override_dh_shlibdeps-arch): New; conditionally edit - debian/control before running dh_shlibdeps. - - -- Teddy Hogeborn Mon, 11 Feb 2019 12:49:57 +0100 - -mandos (1.8.3-1) unstable; urgency=medium - - * New upstream release. - * debian/watch: Make the ".orig" file name suffix non-optional; - otherwise uscan thinks that ".orig" is part of the version number. - * debian/control (Build-Depends): Changed GnuTLS dependencies; move - 3.6.6 alternative to first in list, and remove dependencies on the - virtual package "gnutls-dev", since we need the version restrictions. - (Package: mandos/Depends): Remove dependency on libgnutls28-dev - package. - (Package: mandos/Suggests): New; set to "libc6-dev, c-compiler". (Used - to find value of "SO_BINDTODEVICE"). - (Package: mandos-client/Depends): Don't depend on openssl anymore; - instead depend on either a gnutls-bin (>= 3.6.6) (in which case TLS - key generation will work), or on libgnutls30 (<< 3.6.0) (in which case - TLS key generation will not be needed). - - -- Teddy Hogeborn Mon, 11 Feb 2019 07:30:32 +0100 - -mandos (1.8.2-1) unstable; urgency=medium - - * New upstream release. - * debian/mandos-client.postinst (create_keys): Ignore failure to remove - bad keys. - - -- Teddy Hogeborn Sun, 10 Feb 2019 11:44:56 +0100 - -mandos (1.8.1-1) unstable; urgency=high - - * New upstream release. - * debian/mandos-client.postinst (create_keys): Remove any bad keys - created by 1.8.0-1. Only create TLS keys if certtool succeeds. - * debian/mandos.postinst (configure): Remove any bad keys from - clients.conf, and inform the user if any were found. - * debian/mandos.templates (mandos/removed_bad_key_ids): New message. - - -- Teddy Hogeborn Sun, 10 Feb 2019 10:00:21 +0100 - -mandos (1.8.0-1) unstable; urgency=medium - - * New upstream release. - * Fix "(tries to) use GnuTLS OpenPGP support" by using raw public keys - when available (Closes: #879538) - * Fix "mandos : Depends: libgnutls30 (< 3.6.0) but 3.6.5-2 is to be - installed" by now also allowing GnuTLS >= 3.6.6 (Closes: #916673) - * debian/control (Standards-Version): Update to "4.3.0". - (Package: mandos-client/Depends): Change from "cryptsetup" to - "cryptsetup (<< 2:2.0.3-1) | cryptsetup-initramfs". Add "debconf (>= - 1.5.5) | debconf-2.0". - (Source: mandos/Build-Depends): Also allow libgnutls30 (>= 3.6.6). - (Package: mandos/Depends): - '' - and add debconf (>= 1.5.5) | - debconf-2.0". - (Package: mandos/Description): Alter description to match new design. - (Package: mandos-client/Description): - '' - - (Package: mandos-client/Depends): Move "gnutls-bin | openssl" to here - from "Recommends". - * debian/mandos-client.README.Debian: Add --tls-privkey and --tls-pubkey - options to test command. - * debian/mandos-client.postinst (create_key): Renamed to "create_keys" - - all callers changed - and also create TLS key files. Show notice if - new TLS key files were created. - * debian/mandos-client.postrm (purge): Also remove TLS key files. - * debian/mandos-client.lintian-overrides: Override warnings. - * debian/mandos-client.templates: New. - * debian/mandos.lintian-overrides: Override warnings. - * debian/mandos.postinst (configure): If GnuTLS 3.6.6 or later is - detected, show an important notice (once) about the new key_id option - required in clients.conf. - * debian/mandos.templates: New. - * debian/copyright: Update copyright year to 2019. - - -- Teddy Hogeborn Sun, 10 Feb 2019 05:52:49 +0100 - -mandos (1.7.20-1) unstable; urgency=medium - - * New upstream release. - * Fix "[tethys] mandos-client: Mandos client fails while booting but - works from chroot into unpacked initramfs" by setting system clock if - necessary (Closes: #894495) - * Fix "initramfs boot script assumes internal cryptsetup implementation - details and is now broken" by only using documented - interfaces (Closes: #904899) - * debian/mandos-client.dirs: Add - "usr/share/initramfs-tools/scripts/local-premount" and - "usr/share/initramfs-tools/conf.d", and remove - "usr/share/initramfs-tools/conf-hooks.d". - * debian/control (mandos-client/Depends): Add "(>= 0.99)" to dependency - on "initramfs-tools". - * debian/control (Source: mandos/Rules-Requires-Root): New; set to - "binary-targets". - (Standards-Version): Update to "4.2.0". - - -- Teddy Hogeborn Sun, 19 Aug 2018 22:14:04 +0200 - -mandos (1.7.19-1) unstable; urgency=medium - - * New upstream release. - * Fix "fails with "LeakSanitizer has encountered a fatal error"" by not - using LeakSanitizer in affected binary (Closes: #886595) - - -- Teddy Hogeborn Thu, 22 Feb 2018 19:47:59 +0100 - -mandos (1.7.18-1) unstable; urgency=medium - - * New upstream release. - - -- Teddy Hogeborn Mon, 12 Feb 2018 16:00:11 +0100 - -mandos (1.7.17-1) unstable; urgency=medium - - * New upstream release. - * Fix "fails with "LeakSanitizer has encountered a fatal error"" - by fixing memory leak in plugin-runner (Closes: #886595) - * debian/control (Build-Depends): Also depend on "libgnutls28-dev (<< - 3.6.0) | libgnutls30 (<< 3.6.0)". - (Package: mandos/Depends): - '' - - * debian/compat: Change to "10". - * debian/watch (version): Change to "4". - (opts/pgpsigurlmangle): Remove. - (opts/pgpmode): New; set to "auto". - (URL): Change to "https://ftp.recompile.se/pub/@PACKAGE@/@PACKAGE@ - @ANY_VERSION@(?:\.orig)?@ARCHIVE_EXT@". - * debian/copyright: Update copyright year to 2018. - * debian/rules: Support the "noopt" and "parallel" DEB_BUILD_OPTIONS. - (override_dh_fixperms-arch): Use the DEB_HOST_MULTIARCH - variable directly instead of shelling out to "dpkg-architecture". - * debian/control (Standards-Version): Update to "4.1.3". - (Build-Depends): Change version of debhelper dependency to ">= 10". - * debian/mandos.lintian-overrides - (init.d-script-needs-depends-on-lsb-base): Change line number to "46". - - -- Teddy Hogeborn Sat, 10 Feb 2018 19:09:50 +0100 - mandos (1.7.16-1) unstable; urgency=medium * New upstream release. === modified file 'debian/control' --- debian/control 2019-02-11 06:14:29 +0000 +++ debian/control 2018-01-29 19:26:53 +0000 @@ -4,29 +4,26 @@ Maintainer: Mandos Maintainers Uploaders: Teddy Hogeborn , Björn Påhlsson -Build-Depends: debhelper (>= 10), docbook-xml, docbook-xsl, +Build-Depends: debhelper (>= 9), docbook-xml, docbook-xsl, libavahi-core-dev, libgpgme-dev | libgpgme11-dev, - libgnutls28-dev (>= 3.3.0), - libgnutls28-dev (>= 3.6.6) | libgnutls28-dev (<< 3.6.0), + libgnutls28-dev (>= 3.3.0) | gnutls-dev (>= 3.3.0), + libgnutls28-dev (<< 3.6.0) | libgnutls30 (<< 3.6.0), xsltproc, pkg-config, libnl-route-3-dev Build-Depends-Indep: systemd, python (>= 2.7), python (<< 3), python-dbus, python-gi -Standards-Version: 4.3.0 +Standards-Version: 4.0.1 Vcs-Bzr: https://ftp.recompile.se/pub/mandos/trunk Vcs-Browser: https://bzr.recompile.se/loggerhead/mandos/trunk/files Homepage: https://www.recompile.se/mandos -Rules-Requires-Root: binary-targets Package: mandos Architecture: all Depends: ${misc:Depends}, python (>= 2.7), python (<< 3), - libgnutls30 (>= 3.3.0), - libgnutls30 (>= 3.6.6) | libgnutls30 (<< 3.6.0), + libgnutls28-dev (>= 3.3.0) | libgnutls30 (>= 3.3.0), + libgnutls28-dev (<< 3.6.0) | libgnutls30 (<< 3.6.0), python-dbus, python-gi, avahi-daemon, adduser, python-urwid, - gnupg2 | gnupg, systemd-sysv | lsb-base (>= 3.0-6), - debconf (>= 1.5.5) | debconf-2.0 + gnupg2 | gnupg, systemd-sysv | lsb-base (>= 3.0-6) Recommends: ssh-client | fping -Suggests: libc6-dev | libc-dev, c-compiler Description: server giving encrypted passwords to Mandos clients This is the server part of the Mandos system, which allows computers to have encrypted root file systems and at the @@ -35,21 +32,18 @@ The computers run a small client program in the initial RAM disk environment which will communicate with a server over a network. All network communication is encrypted using TLS. - The clients are identified by the server using a TLS public + The clients are identified by the server using an OpenPGP key; each client has one unique to it. The server sends the clients an encrypted password. The encrypted password is - decrypted by the clients using an OpenPGP key, and the + decrypted by the clients using the same OpenPGP key, and the password is then used to unlock the root file system, whereupon the computers can continue booting normally. Package: mandos-client Architecture: linux-any -Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, - cryptsetup (<< 2:2.0.3-1) | cryptsetup-initramfs, - initramfs-tools (>= 0.99), dpkg-dev (>=1.16.0), - gnutls-bin (>= 3.6.6) | libgnutls30 (<< 3.6.0), - debconf (>= 1.5.5) | debconf-2.0 -Recommends: ssh +Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, cryptsetup, + initramfs-tools, dpkg-dev (>=1.16.0) +Recommends: ssh, gnutls-bin | openssl Breaks: dropbear (<= 0.53.1-1) Enhances: cryptsetup Description: do unattended reboots with an encrypted root file system @@ -60,9 +54,9 @@ The computers run a small client program in the initial RAM disk environment which will communicate with a server over a network. All network communication is encrypted using TLS. - The clients are identified by the server using a TLS public + The clients are identified by the server using an OpenPGP key; each client has one unique to it. The server sends the clients an encrypted password. The encrypted password is - decrypted by the clients using an OpenPGP key, and the + decrypted by the clients using the same OpenPGP key, and the password is then used to unlock the root file system, whereupon the computers can continue booting normally. === modified file 'debian/copyright' --- debian/copyright 2019-02-10 04:20:26 +0000 +++ debian/copyright 2018-02-08 10:23:55 +0000 @@ -4,8 +4,8 @@ Source: Files: * -Copyright: Copyright © 2008-2019 Teddy Hogeborn - Copyright © 2008-2019 Björn Påhlsson +Copyright: Copyright © 2008-2018 Teddy Hogeborn + Copyright © 2008-2018 Björn Påhlsson License: GPL-3+ This file is part of Mandos. . === modified file 'debian/mandos-client.README.Debian' --- debian/mandos-client.README.Debian 2019-02-09 23:23:26 +0000 +++ debian/mandos-client.README.Debian 2016-06-21 19:47:08 +0000 @@ -25,9 +25,7 @@ /usr/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH \ )/mandos/plugins.d/mandos-client \ --pubkey=/etc/keys/mandos/pubkey.txt \ - --seckey=/etc/keys/mandos/seckey.txt \ - --tls-privkey=/etc/keys/mandos/tls-privkey.pem \ - --tls-pubkey=/etc/keys/mandos/tls-pubkey.pem; echo + --seckey=/etc/keys/mandos/seckey.txt; echo This command should retrieve the password from the server, decrypt it, and output it to standard output. There it can be verified to @@ -108,4 +106,4 @@ policy or other reasons, simply replace the existing dhparams.pem file and update the initital RAM disk image. - -- Teddy Hogeborn , Sat, 9 Feb 2019 15:08:04 +0100 + -- Teddy Hogeborn , Tue, 21 Jun 2016 21:43:49 +0200 === modified file 'debian/mandos-client.dirs' --- debian/mandos-client.dirs 2019-04-09 19:33:36 +0000 +++ debian/mandos-client.dirs 2009-02-07 04:50:39 +0000 @@ -1,7 +1,5 @@ usr/share/man/man8 usr/sbin usr/share/initramfs-tools/hooks -usr/share/initramfs-tools/conf.d usr/share/initramfs-tools/conf-hooks.d usr/share/initramfs-tools/scripts/init-premount -usr/share/initramfs-tools/scripts/local-premount === modified file 'debian/mandos-client.lintian-overrides' --- debian/mandos-client.lintian-overrides 2019-02-10 03:50:20 +0000 +++ debian/mandos-client.lintian-overrides 2016-03-19 04:21:00 +0000 @@ -30,14 +30,3 @@ mandos-client binary: non-standard-dir-perm etc/mandos/plugins.d/ 0700 != 0755 # Likewise for plugin-helpers directory mandos-client binary: non-standard-dir-perm etc/mandos/plugin-helpers/ 0700 != 0755 - -# The debconf templates is only used for displaying information -# detected in the postinst, not for saving answers to questions, so we -# don't need a .config file. -mandos-client binary: no-debconf-config - -# The notice displayed from the postinst script really is critical -mandos-client binary: postinst-uses-db-input - -# It is a really long line -mandos-client binary: manpage-has-errors-from-man usr/share/man/man8/plugin-runner.8mandos.gz *: warning *: can't break line === modified file 'debian/mandos-client.postinst' --- debian/mandos-client.postinst 2019-02-10 10:39:26 +0000 +++ debian/mandos-client.postinst 2016-10-09 22:35:41 +0000 @@ -15,8 +15,6 @@ # If prerm fails during replacement due to conflict: # abort-remove in-favour -. /usr/share/debconf/confmodule - set -e # Update the initial RAM file system image @@ -52,79 +50,14 @@ fi } -# Create client key pairs -create_keys(){ - # If the OpenPGP key files do not exist, generate all keys using - # mandos-keygen - if ! [ -r /etc/keys/mandos/pubkey.txt \ - -a -r /etc/keys/mandos/seckey.txt ]; then - mandos-keygen - gpg-connect-agent KILLAGENT /bye || : - return 0 - fi - - # Remove any bad TLS keys by 1.8.0-1 - if dpkg --compare-versions "$2" eq "1.8.0-1" \ - || dpkg --compare-versions "$2" eq "1.8.0-1~bpo9+1"; then - # Is the key bad? - if ! certtool --password='' \ - --load-privkey=/etc/keys/mandos/tls-privkey.pem \ - --outfile=/dev/null --pubkey-info --no-text \ - 2>/dev/null; then - shred --remove -- /etc/keys/mandos/tls-privkey.pem \ - 2>/dev/null || : - rm --force -- /etc/keys/mandos/tls-pubkey.pem - fi - fi - - # If the TLS keys already exists, do nothing - if [ -r /etc/keys/mandos/tls-privkey.pem \ - -a -r /etc/keys/mandos/tls-pubkey.pem ]; then - return 0 - fi - - # Try to create the TLS keys - - TLS_PRIVKEYTMP="`mktemp -t mandos-client-privkey.XXXXXXXXXX`" - - if certtool --generate-privkey --password='' \ - --outfile "$TLS_PRIVKEYTMP" --sec-param ultra \ - --key-type=ed25519 --pkcs8 --no-text 2>/dev/null; then - - local umask=$(umask) - umask 077 - cp --archive "$TLS_PRIVKEYTMP" /etc/keys/mandos/tls-privkey.pem - shred --remove -- "$TLS_PRIVKEYTMP" 2>/dev/null || : - - # First try certtool from GnuTLS - if ! certtool --password='' \ - --load-privkey=/etc/keys/mandos/tls-privkey.pem \ - --outfile=/etc/keys/mandos/tls-pubkey.pem --pubkey-info \ - --no-text 2>/dev/null; then - # Otherwise try OpenSSL - if ! openssl pkey -in /etc/keys/mandos/tls-privkey.pem \ - -out /etc/keys/mandos/tls-pubkey.pem -pubout; then - rm --force /etc/keys/mandos/tls-pubkey.pem - # None of the commands succeded; give up - umask $umask - return 1 - fi - fi - umask $umask - - key_id=$(mandos-keygen --passfile=/dev/null \ - | grep --regexp="^key_id[ =]") - - db_version 2.0 - db_fset mandos-client/key_id seen false - db_reset mandos-client/key_id - db_subst mandos-client/key_id key_id $key_id - db_input critical mandos-client/key_id || true - db_go - db_stop - else - shred --remove -- "$TLS_PRIVKEYTMP" 2>/dev/null || : - fi +# Create client key pair +create_key(){ + if [ -r /etc/keys/mandos/pubkey.txt \ + -a -r /etc/keys/mandos/seckey.txt ]; then + return 0 + fi + mandos-keygen + gpg-connect-agent KILLAGENT /bye || : } create_dh_params(){ @@ -155,7 +88,7 @@ case "$1" in configure) add_mandos_user "$@" - create_keys "$@" + create_key "$@" create_dh_params "$@" || : update_initramfs "$@" if dpkg --compare-versions "$2" lt-nl "1.7.10-1"; then === modified file 'debian/mandos-client.postrm' --- debian/mandos-client.postrm 2019-02-09 23:23:26 +0000 +++ debian/mandos-client.postrm 2015-07-27 09:23:56 +0000 @@ -44,8 +44,6 @@ rm --force /etc/mandos/plugin-runner.conf \ /etc/keys/mandos/pubkey.txt \ /etc/keys/mandos/seckey.txt \ - /etc/keys/mandos/tls-privkey.pem \ - /etc/keys/mandos/tls-pubkey.pem \ /etc/keys/mandos/dhparams.pem 2>/dev/null update_initramfs ;; === removed file 'debian/mandos-client.templates' --- debian/mandos-client.templates 2019-02-10 03:50:20 +0000 +++ debian/mandos-client.templates 1970-01-01 00:00:00 +0000 @@ -1,10 +0,0 @@ -Template: mandos-client/key_id -Type: note -Description: New client option "${key_id}" is REQUIRED on server - A new "key_id" client option is REQUIRED in the server's clients.conf file, otherwise this computer most likely will not reboot unattended. This option: - . - ${key_id} - . - must be added (all on one line!) on the Mandos server host, in the file /etc/mandos/clients.conf, right before the "fingerprint" option for this Mandos client. You must edit that file on that server and add this option. - . - With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP keys as TLS session keys. A new TLS key pair has been generated and will be used as identification, but the key ID of the public key needs to be added to the server, since this will now be used to identify the client to the server. === modified file 'debian/mandos.lintian-overrides' --- debian/mandos.lintian-overrides 2019-02-10 03:50:20 +0000 +++ debian/mandos.lintian-overrides 2017-02-21 18:23:54 +0000 @@ -2,12 +2,4 @@ # it, so it must be kept unreadable for non-root users. # mandos binary: non-standard-file-perm etc/mandos/clients.conf 0600 != 0644 -mandos: init.d-script-needs-depends-on-lsb-base etc/init.d/mandos (line 46) - -# The debconf templates is only used for displaying information -# detected in the postinst, not for saving answers to questions, so we -# don't need a .config file. -mandos binary: no-debconf-config - -# The notice displayed from the postinst script really is critical -mandos binary: postinst-uses-db-input +mandos: init.d-script-needs-depends-on-lsb-base etc/init.d/mandos (line 49) === modified file 'debian/mandos.postinst' --- debian/mandos.postinst 2019-02-10 08:41:14 +0000 +++ debian/mandos.postinst 2016-03-19 03:48:56 +0000 @@ -15,8 +15,6 @@ # If prerm fails during replacement due to conflict: # abort-remove in-favour -. /usr/share/debconf/confmodule - set -e case "$1" in @@ -55,33 +53,6 @@ chown _mandos:_mandos /var/lib/mandos chmod u=rwx,go= /var/lib/mandos fi - - if dpkg --compare-versions "$2" eq "1.8.0-1" \ - || dpkg --compare-versions "$2" eq "1.8.0-1~bpo9+1"; then - if grep --quiet --regexp='^[[:space:]]*key_id[[:space:]]*=[[:space:]]*[Ee]3[Bb]0[Cc]44298[Ff][Cc]1[Cc]149[Aa][Ff][Bb][Ff]4[Cc]8996[Ff][Bb]92427[Aa][Ee]41[Ee]4649[Bb]934[Cc][Aa]495991[Bb]7852[Bb]855[[:space:]]*$' /etc/mandos/clients.conf; then - sed --in-place \ - --expression='/^[[:space:]]*key_id[[:space:]]*=[[:space:]]*[Ee]3[Bb]0[Cc]44298[Ff][Cc]1[Cc]149[Aa][Ff][Bb][Ff]4[Cc]8996[Ff][Bb]92427[Aa][Ee]41[Ee]4649[Bb]934[Cc][Aa]495991[Bb]7852[Bb]855[[:space:]]*$/d' \ - /etc/mandos/clients.conf - invoke-rc.d mandos restart - db_version 2.0 - db_fset mandos/removed_bad_key_ids seen false - db_reset mandos/removed_bad_key_ids - db_input critical mandos/removed_bad_key_ids || true - db_go - db_stop - fi - fi - - gnutls_version=$(dpkg-query --showformat='${Version}' \ - --show libgnutls30 \ - 2>/dev/null || :) - if [ -n "$gnutls_version" ] \ - && dpkg --compare-versions $gnutls_version ge 3.6.6; then - db_version 2.0 - db_input critical mandos/key_id || true - db_go - db_stop - fi ;; abort-upgrade|abort-deconfigure|abort-remove) === removed file 'debian/mandos.templates' --- debian/mandos.templates 2019-02-10 08:41:14 +0000 +++ debian/mandos.templates 1970-01-01 00:00:00 +0000 @@ -1,19 +0,0 @@ -Template: mandos/key_id -Type: note -Description: New client option "key_id" is REQUIRED on server - A new "key_id" client option is REQUIRED in the clients.conf file, otherwise the client most likely will not reboot unattended. This option: - . - key_id = - . - must be added in the file /etc/mandos/clients.conf, right before the "fingerprint" option, for each Mandos client. You must edit that file and add this option for all clients. To see the correct key ID for each client, run this command (on each client): - . - mandos-keygen -F/dev/null|grep ^key_id - . - Note: the client must all also be using GnuTLS 3.6.6 or later; the server cannot serve passwords for both old and new clients! - . - Rationale: With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP keys as TLS session keys. A new TLS key pair will be generated on each client and will be used as identification, but the key ID of the public key needs to be added to this server, since this will now be used to identify the client to the server. - -Template: mandos/removed_bad_key_ids -Type: note -Description: Bad key IDs have been removed from clients.conf - Bad key IDs, which were reported by a bug in Mandos client 1.8.0, have been removed from /etc/mandos/clients.conf === modified file 'debian/rules' --- debian/rules 2019-02-13 08:45:09 +0000 +++ debian/rules 2016-06-23 19:46:41 +0000 @@ -1,14 +1,4 @@ #!/usr/bin/make -f - -ifeq (,$(filter noopt,$(DEB_BUILD_OPTIONS))) - MAKEFLAGS += OPTIMIZE=-O0 -endif - -ifneq (,$(filter parallel=%,$(DEB_BUILD_OPTIONS))) - NUMJOBS = $(patsubst parallel=%,%,$(filter parallel=%,$(DEB_BUILD_OPTIONS))) - MAKEFLAGS += -j$(NUMJOBS) -endif - %: dh $@ @@ -33,8 +23,8 @@ dh_fixperms --exclude etc/keys/mandos \ --exclude etc/mandos/plugins.d \ --exclude etc/mandos/plugin-helpers \ - --exclude usr/lib/$(DEB_HOST_MULTIARCH)/mandos/plugins.d \ - --exclude usr/lib/$(DEB_HOST_MULTIARCH)/mandos/plugin-helpers \ + --exclude usr/lib/$(shell dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null)/mandos/plugins.d \ + --exclude usr/lib/$(shell dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null)/mandos/plugin-helpers \ --exclude usr/share/doc/mandos-client/examples/network-hooks.d chmod --recursive g-w -- \ "$(CURDIR)/debian/mandos-client/usr/share/doc/mandos-client/examples/network-hooks.d" @@ -43,19 +33,3 @@ dh_fixperms --exclude etc/mandos/clients.conf override_dh_auto_test-arch: ; - -#bpo## dpkg-shlibdeps sees the "libgnutls28-dev (>= 3.6.6) | -#bpo## libgnutls28-dev (<< 3.6.0)," in the build-dependencies not as two -#bpo## alternatives, but as an absolute dependency on libgnutls30 >= 3.6.6. -#bpo## So we have to do this ugly hack to hide this build dependency if we -#bpo## compiled with libgnutls30 << 3.6.0. -#bpo#override_dh_shlibdeps-arch: -#bpo# -gnutls_version=$$(dpkg-query --showformat='$${Version}' \ -#bpo# --show libgnutls30); \ -#bpo# dpkg --compare-versions $$gnutls_version lt 3.6.0 \ -#bpo# && { cp --archive debian/control debian/control.orig; sed --expression='s/libgnutls28-dev (>= 3\.6\.6) |//' debian/control; } -#bpo# dh_shlibdeps -#bpo# -gnutls_version=$$(dpkg-query --showformat='$${Version}' \ -#bpo# --show libgnutls30); \ -#bpo# dpkg --compare-versions $$gnutls_version lt 3.6.0 \ -#bpo# && mv debian/control.orig debian/control === modified file 'debian/watch' --- debian/watch 2019-02-11 05:15:24 +0000 +++ debian/watch 2018-02-08 10:02:51 +0000 @@ -1,3 +1,3 @@ version=4 opts=pgpmode=auto \ - https://ftp.recompile.se/pub/@PACKAGE@/@PACKAGE@@ANY_VERSION@\.orig@ARCHIVE_EXT@ + https://ftp.recompile.se/pub/@PACKAGE@/@PACKAGE@@ANY_VERSION@(?:\.orig)?@ARCHIVE_EXT@ === modified file 'init.d-mandos' --- init.d-mandos 2018-02-10 13:23:58 +0000 +++ init.d-mandos 2016-03-13 00:37:02 +0000 @@ -11,6 +11,9 @@ # Author: Teddy Hogeborn # Author: Björn Påhlsson +# +# Please remove the "Author" lines above and replace them +# with your own name if you copy and modify this script. # Do NOT "set -e" === removed file 'initramfs-tools-conf' --- initramfs-tools-conf 2018-08-19 14:06:55 +0000 +++ initramfs-tools-conf 1970-01-01 00:00:00 +0000 @@ -1,17 +0,0 @@ -# -*- shell-script -*- - -# Since the initramfs image will contain key files, we need to -# restrict permissions on it by setting UMASK here. -# -# The proper place to set UMASK is (according to -# /etc/cryptsetup-initramfs/conf-hook), in -# /etc/initramfs-tools/initramfs.conf, which we shouldn't edit. The -# corresponding directory for drop-in files from packages is -# /usr/share/initramfs-tools/conf.d, and this file will be installed -# there as "mandos-conf". -# -# This setting of UMASK will have unfortunate unintended side effects -# on the files *inside* the initramfs, but these are later fixed by -# "initramfs-tools-hook", installed as -# "/usr/share/initramfs-tools/hooks/mandos". -UMASK=0027 === removed file 'initramfs-tools-conf-hook' --- initramfs-tools-conf-hook 2019-04-09 19:33:36 +0000 +++ initramfs-tools-conf-hook 1970-01-01 00:00:00 +0000 @@ -1,14 +0,0 @@ -# -*- shell-script -*- - -# The UMASK is set by the file "initramfs-tools-conf" (which is copied -# to /usr/share/initramfs-tools/conf.d/mandos-conf on installation) -# since there, as described therein, is the proper place to do that. -# However, it is possible for other packages to override the UMASK in -# any file in /usr/share/initramfs-tools/conf-hooks.d. Therefore, -# this file ("initramfs-tools-conf-hook") will be installed as -# "zz-mandos" in that directory to make sure UMASK is set correctly. - -# For more information on the effects of setting UMASK, see the -# aforementioned /usr/share/initramfs-tools/conf.d/mandos-conf file. - -UMASK=0027 === modified file 'initramfs-tools-hook' --- initramfs-tools-hook 2018-08-19 14:06:55 +0000 +++ initramfs-tools-hook 2017-02-21 22:15:43 +0000 @@ -80,8 +80,6 @@ --mode=u=rwx "${DESTDIR}${PLUGINDIR}" \ "${DESTDIR}${PLUGINHELPERDIR}" -copy_exec "$libdir"/mandos/mandos-to-cryptroot-unlock "${MANDOSDIR}" - # Copy the Mandos plugin runner copy_exec "$libdir"/mandos/plugin-runner "${MANDOSDIR}" @@ -252,8 +250,8 @@ # initrd; it is intended to affect the initrd.img file itself, since # it now contains secret key files. There is, however, no other way # to set the permission of the initrd.img file without a race -# condition. This umask is set by "initramfs-tools-conf", installed -# as "/usr/share/initramfs-tools/conf.d/mandos-conf".) +# condition. This umask is set by "initramfs-tools-hook-conf", +# installed as "/usr/share/initramfs-tools/conf-hooks.d/mandos".) # for full in "${MANDOSDIR}" "${CONFDIR}"; do while [ "$full" != "/" ]; do === added file 'initramfs-tools-hook-conf' --- initramfs-tools-hook-conf 1970-01-01 00:00:00 +0000 +++ initramfs-tools-hook-conf 2009-05-17 00:50:09 +0000 @@ -0,0 +1,13 @@ +# -*- shell-script -*- + +# if mkinitramfs is started by mkinitramfs-kpkg, mkinitramfs-kpkg has +# already touched the initrd file with umask 022 before we had a +# chance to affect it. We cannot allow a readable initrd file, +# therefore we must fix this now. +if [ -e "${outfile}" ] \ + && [ `stat --format=%s "${outfile}"` -eq 0 ]; then + rm "${outfile}" + (umask 027; touch "${outfile}") +fi + +UMASK=027 === modified file 'initramfs-tools-script' --- initramfs-tools-script 2018-08-19 01:35:11 +0000 +++ initramfs-tools-script 2017-08-20 14:41:20 +0000 @@ -51,6 +51,9 @@ chmod a=rwxt /tmp +test -r /conf/conf.d/cryptroot +test -w /conf/conf.d + # Get DEVICE from /conf/initramfs.conf and other files . /conf/initramfs.conf for conf in /conf/conf.d/*; do @@ -102,80 +105,74 @@ fi fi -if [ -r /conf/conf.d/cryptroot ]; then - test -w /conf/conf.d - - # Do not replace cryptroot file unless we need to. - replace_cryptroot=no - - # Our keyscript - mandos=/lib/mandos/plugin-runner - test -x "$mandos" - - # parse /conf/conf.d/cryptroot. Format: - # target=sda2_crypt,source=/dev/sda2,rootdev,key=none,keyscript=/foo/bar/baz - # Is the root device specially marked? - changeall=yes - while read -r options; do - case "$options" in - rootdev,*|*,rootdev,*|*,rootdev) - # If the root device is specially marked, don't change all - # lines in crypttab by default. - changeall=no +# Do not replace cryptroot file unless we need to. +replace_cryptroot=no + +# Our keyscript +mandos=/lib/mandos/plugin-runner +test -x "$mandos" + +# parse /conf/conf.d/cryptroot. Format: +# target=sda2_crypt,source=/dev/sda2,rootdev,key=none,keyscript=/foo/bar/baz +# Is the root device specially marked? +changeall=yes +while read -r options; do + case "$options" in + rootdev,*|*,rootdev,*|*,rootdev) + # If the root device is specially marked, don't change all + # lines in crypttab by default. + changeall=no + ;; + esac +done < /conf/conf.d/cryptroot + +exec 3>/conf/conf.d/cryptroot.mandos +while read -r options; do + newopts="" + keyscript="" + changethis="$changeall" + # Split option line on commas + old_ifs="$IFS" + IFS="$IFS," + for opt in $options; do + # Find the keyscript option, if any + case "$opt" in + keyscript=*) + keyscript="${opt#keyscript=}" + newopts="$newopts,$opt" + ;; + "") : ;; + # Always use Mandos on the root device, if marked + rootdev) + changethis=yes + newopts="$newopts,$opt" + ;; + # Don't use Mandos on resume device, if marked + resumedev) + changethis=no + newopts="$newopts,$opt" + ;; + *) + newopts="$newopts,$opt" ;; esac - done < /conf/conf.d/cryptroot - - exec 3>/conf/conf.d/cryptroot.mandos - while read -r options; do - newopts="" - keyscript="" - changethis="$changeall" - # Split option line on commas - old_ifs="$IFS" - IFS="$IFS," - for opt in $options; do - # Find the keyscript option, if any - case "$opt" in - keyscript=*) - keyscript="${opt#keyscript=}" - newopts="$newopts,$opt" - ;; - "") : ;; - # Always use Mandos on the root device, if marked - rootdev) - changethis=yes - newopts="$newopts,$opt" - ;; - # Don't use Mandos on resume device, if marked - resumedev) - changethis=no - newopts="$newopts,$opt" - ;; - *) - newopts="$newopts,$opt" - ;; - esac - done - IFS="$old_ifs" - unset old_ifs - # If there was no keyscript option, add one. - if [ "$changethis" = yes ] && [ -z "$keyscript" ]; then - replace_cryptroot=yes - newopts="$newopts,keyscript=$mandos" - fi - newopts="${newopts#,}" - echo "$newopts" >&3 - done < /conf/conf.d/cryptroot - exec 3>&- - - # If we need to, replace the old cryptroot file with the new file. - if [ "$replace_cryptroot" = yes ]; then - mv /conf/conf.d/cryptroot /conf/conf.d/cryptroot.mandos-old - mv /conf/conf.d/cryptroot.mandos /conf/conf.d/cryptroot - else - rm -f /conf/conf.d/cryptroot.mandos + done + IFS="$old_ifs" + unset old_ifs + # If there was no keyscript option, add one. + if [ "$changethis" = yes ] && [ -z "$keyscript" ]; then + replace_cryptroot=yes + newopts="$newopts,keyscript=$mandos" fi -elif [ -x /usr/bin/cryptroot-unlock ]; then - setsid /lib/mandos/mandos-to-cryptroot-unlock & + newopts="${newopts#,}" + echo "$newopts" >&3 +done < /conf/conf.d/cryptroot +exec 3>&- + +# If we need to, replace the old cryptroot file with the new file. +if [ "$replace_cryptroot" = yes ]; then + mv /conf/conf.d/cryptroot /conf/conf.d/cryptroot.mandos-old + mv /conf/conf.d/cryptroot.mandos /conf/conf.d/cryptroot +else + rm /conf/conf.d/cryptroot.mandos fi === removed file 'initramfs-tools-script-stop' --- initramfs-tools-script-stop 2018-08-19 14:58:40 +0000 +++ initramfs-tools-script-stop 1970-01-01 00:00:00 +0000 @@ -1,65 +0,0 @@ -#!/bin/sh -e -# -# Script to wait for plugin-runner to exit before continuing boot -# -# Copyright © 2018 Teddy Hogeborn -# Copyright © 2018 Björn Påhlsson -# -# This file is part of Mandos. -# -# Mandos 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. -# -# Mandos 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 Mandos. If not, see . -# -# Contact the authors at . -# -# This script will run in the initrd environment at boot and remove -# the file keeping the dummy plugin running, forcing plugin-runner to -# exit if it is still running. - -# This script should be installed as -# "/usr/share/initramfs-tools/scripts/local-premount/mandos" which will -# eventually be "/scripts/local-premount/mandos" in the initrd.img -# file. - -PREREQ="" -prereqs() -{ - echo "$PREREQ" -} - -case $1 in -prereqs) - prereqs - exit 0 - ;; -esac - -. /scripts/functions - -pid=$(cat /run/mandos-plugin-runner.pid 2>/dev/null) - -# If the dummy plugin is running, removing this file should force the -# dummy plugin to exit successfully, thereby making plugin-runner shut -# down all its other plugins and then exit itself. -rm -f /run/mandos-keep-running >/dev/null 2>&1 - -# Wait for exit of plugin-runner, if still running -if [ -n "$pid" ]; then - while :; do - case "$(readlink /proc/"$pid"/exe 2>/dev/null)" in - */plugin-runner) sleep 1;; - *) break;; - esac - done - rm -f /run/mandos-plugin-runner.pid >/dev/null 2>&1 -fi === modified file 'intro.xml' --- intro.xml 2019-03-30 17:02:33 +0000 +++ intro.xml 2018-02-08 10:23:55 +0000 @@ -1,7 +1,7 @@ + %common; ]> @@ -38,7 +38,6 @@ 2016 2017 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -68,10 +67,10 @@ The computers run a small client program in the initial RAM disk environment which will communicate with a server over a network. All network communication is encrypted using TLS. The clients - are identified by the server using a TLS public key; each client + are identified by the server using an OpenPGP key; each client has one unique to it. The server sends the clients an encrypted password. The encrypted password is decrypted by the clients - using a separate OpenPGP key, and the password is then used to + using the same OpenPGP key, and the password is then used to unlock the root file system, whereupon the computers can continue booting normally. @@ -144,7 +143,7 @@ long, and will no longer give out the encrypted key. The timing here is the only real weak point, and the method, frequency and timeout of the server’s checking can be adjusted to any desired - level of paranoia. + level of paranoia (The encrypted keys on the Mandos server is on its normal file @@ -201,8 +200,8 @@ No. The server only gives out the passwords to clients which have in the TLS handshake proven that - they do indeed hold the private key corresponding to that - client. + they do indeed hold the OpenPGP private key corresponding to + that client. === modified file 'mandos' --- mandos 2019-03-23 14:59:54 +0000 +++ mandos 2018-02-08 10:23:55 +0000 @@ -11,8 +11,8 @@ # "AvahiService" class, and some lines in "main". # # Everything else is -# Copyright © 2008-2019 Teddy Hogeborn -# Copyright © 2008-2019 Björn Påhlsson +# Copyright © 2008-2018 Teddy Hogeborn +# Copyright © 2008-2018 Björn Påhlsson # # This file is part of Mandos. # @@ -115,7 +115,7 @@ if sys.version_info.major == 2: str = unicode -version = "1.8.3" +version = "1.7.16" stored_state_file = "clients.pickle" logger = logging.getLogger() @@ -275,8 +275,9 @@ # Pretend that we have an Avahi module -class avahi(object): - """This isn't so much a class as it is a module-like namespace.""" +class Avahi(object): + """This isn't so much a class as it is a module-like namespace. + It is instantiated once, and simulates having an Avahi module.""" IF_UNSPEC = -1 # avahi-common/address.h PROTO_UNSPEC = -1 # avahi-common/address.h PROTO_INET = 0 # avahi-common/address.h @@ -286,8 +287,7 @@ DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server" DBUS_PATH_SERVER = "/" - @staticmethod - def string_array_to_txt_array(t): + def string_array_to_txt_array(self, t): return dbus.Array((dbus.ByteArray(s.encode("utf-8")) for s in t), signature="ay") ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h @@ -298,6 +298,7 @@ SERVER_RUNNING = 2 # avahi-common/defs.h SERVER_COLLISION = 3 # avahi-common/defs.h SERVER_FAILURE = 4 # avahi-common/defs.h +avahi = Avahi() class AvahiError(Exception): @@ -495,7 +496,8 @@ class AvahiServiceToSyslog(AvahiService): def rename(self, *args, **kwargs): """Add the new name to the syslog messages""" - ret = super(AvahiServiceToSyslog, self).rename(*args, **kwargs) + ret = super(AvahiServiceToSyslog, self).rename(self, *args, + **kwargs) syslogger.setFormatter(logging.Formatter( 'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s' .format(self.name))) @@ -503,14 +505,23 @@ # Pretend that we have a GnuTLS module -class gnutls(object): - """This isn't so much a class as it is a module-like namespace.""" +class GnuTLS(object): + """This isn't so much a class as it is a module-like namespace. + It is instantiated once, and simulates having a GnuTLS module.""" library = ctypes.util.find_library("gnutls") if library is None: library = ctypes.util.find_library("gnutls-deb0") _library = ctypes.cdll.LoadLibrary(library) del library + _need_version = b"3.3.0" + + def __init__(self): + # Need to use "self" here, since this method is called before + # the assignment to the "gnutls" global variable happens. + if self.check_version(self._need_version) is None: + raise self.Error("Needs GnuTLS {} or later" + .format(self._need_version)) # Unless otherwise indicated, the constants and types below are # all from the gnutls/gnutls.h C header file. @@ -520,16 +531,10 @@ E_INTERRUPTED = -52 E_AGAIN = -28 CRT_OPENPGP = 2 - CRT_RAWPK = 3 CLIENT = 2 SHUT_RDWR = 0 CRD_CERTIFICATE = 1 E_NO_CERTIFICATE_FOUND = -49 - X509_FMT_DER = 0 - NO_TICKETS = 1<<10 - ENABLE_RAWPK = 1<<18 - CTYPE_PEERS = 3 - KEYID_USE_SHA256 = 1 # gnutls/x509.h OPENPGP_FMT_RAW = 0 # gnutls/openpgp.h # Types @@ -558,14 +563,18 @@ # Exceptions class Error(Exception): + # We need to use the class name "GnuTLS" here, since this + # exception might be raised from within GnuTLS.__init__, + # which is called before the assignment to the "gnutls" + # global variable has happened. def __init__(self, message=None, code=None, args=()): # Default usage is by a message string, but if a return # code is passed, convert it to a string with # gnutls.strerror() self.code = code if message is None and code is not None: - message = gnutls.strerror(code) - return super(gnutls.Error, self).__init__( + message = GnuTLS.strerror(code) + return super(GnuTLS.Error, self).__init__( message, *args) class CertificateSecurityError(Error): @@ -585,13 +594,7 @@ class ClientSession(object): def __init__(self, socket, credentials=None): self._c_object = gnutls.session_t() - gnutls_flags = gnutls.CLIENT - if gnutls.check_version("3.5.6"): - gnutls_flags |= gnutls.NO_TICKETS - if gnutls.has_rawpk: - gnutls_flags |= gnutls.ENABLE_RAWPK - gnutls.init(ctypes.byref(self._c_object), gnutls_flags) - del gnutls_flags + gnutls.init(ctypes.byref(self._c_object), gnutls.CLIENT) gnutls.set_default_priority(self._c_object) gnutls.transport_set_ptr(self._c_object, socket.fileno()) gnutls.handshake_set_private_extensions(self._c_object, @@ -729,78 +732,39 @@ check_version.argtypes = [ctypes.c_char_p] check_version.restype = ctypes.c_char_p - _need_version = b"3.3.0" - if check_version(_need_version) is None: - raise self.Error("Needs GnuTLS {} or later" - .format(_need_version)) - - _tls_rawpk_version = b"3.6.6" - has_rawpk = bool(check_version(_tls_rawpk_version)) - - if has_rawpk: - # Types - class pubkey_st(ctypes.Structure): - _fields = [] - pubkey_t = ctypes.POINTER(pubkey_st) - - x509_crt_fmt_t = ctypes.c_int - - # All the function declarations below are from gnutls/abstract.h - pubkey_init = _library.gnutls_pubkey_init - pubkey_init.argtypes = [ctypes.POINTER(pubkey_t)] - pubkey_init.restype = _error_code - - pubkey_import = _library.gnutls_pubkey_import - pubkey_import.argtypes = [pubkey_t, ctypes.POINTER(datum_t), - x509_crt_fmt_t] - pubkey_import.restype = _error_code - - pubkey_get_key_id = _library.gnutls_pubkey_get_key_id - pubkey_get_key_id.argtypes = [pubkey_t, ctypes.c_int, - ctypes.POINTER(ctypes.c_ubyte), - ctypes.POINTER(ctypes.c_size_t)] - pubkey_get_key_id.restype = _error_code - - pubkey_deinit = _library.gnutls_pubkey_deinit - pubkey_deinit.argtypes = [pubkey_t] - pubkey_deinit.restype = None - else: - # All the function declarations below are from gnutls/openpgp.h - - openpgp_crt_init = _library.gnutls_openpgp_crt_init - openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)] - openpgp_crt_init.restype = _error_code - - openpgp_crt_import = _library.gnutls_openpgp_crt_import - openpgp_crt_import.argtypes = [openpgp_crt_t, - ctypes.POINTER(datum_t), - openpgp_crt_fmt_t] - openpgp_crt_import.restype = _error_code - - openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self - openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint, - ctypes.POINTER(ctypes.c_uint)] - openpgp_crt_verify_self.restype = _error_code - - openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit - openpgp_crt_deinit.argtypes = [openpgp_crt_t] - openpgp_crt_deinit.restype = None - - openpgp_crt_get_fingerprint = ( - _library.gnutls_openpgp_crt_get_fingerprint) - openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t, - ctypes.c_void_p, - ctypes.POINTER( - ctypes.c_size_t)] - openpgp_crt_get_fingerprint.restype = _error_code - - if check_version("3.6.4"): - certificate_type_get2 = _library.gnutls_certificate_type_get2 - certificate_type_get2.argtypes = [session_t, ctypes.c_int] - certificate_type_get2.restype = _error_code + # All the function declarations below are from gnutls/openpgp.h + + openpgp_crt_init = _library.gnutls_openpgp_crt_init + openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)] + openpgp_crt_init.restype = _error_code + + openpgp_crt_import = _library.gnutls_openpgp_crt_import + openpgp_crt_import.argtypes = [openpgp_crt_t, + ctypes.POINTER(datum_t), + openpgp_crt_fmt_t] + openpgp_crt_import.restype = _error_code + + openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self + openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint, + ctypes.POINTER(ctypes.c_uint)] + openpgp_crt_verify_self.restype = _error_code + + openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit + openpgp_crt_deinit.argtypes = [openpgp_crt_t] + openpgp_crt_deinit.restype = None + + openpgp_crt_get_fingerprint = ( + _library.gnutls_openpgp_crt_get_fingerprint) + openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t, + ctypes.c_void_p, + ctypes.POINTER( + ctypes.c_size_t)] + openpgp_crt_get_fingerprint.restype = _error_code # Remove non-public functions del _error_code, _retry_on_error +# Create the global "gnutls" object, simulating a module +gnutls = GnuTLS() def call_pipe(connection, # : multiprocessing.Connection @@ -837,9 +801,7 @@ disable_initiator_tag: a GLib event source tag, or None enabled: bool() fingerprint: string (40 or 32 hexadecimal digits); used to - uniquely identify an OpenPGP client - key_id: string (64 hexadecimal digits); used to uniquely identify - a client using raw public keys + uniquely identify the client host: string; available for use by the checker command interval: datetime.timedelta(); How often to start a new checker last_approval_request: datetime.datetime(); (UTC) or None @@ -863,7 +825,7 @@ """ runtime_expansions = ("approval_delay", "approval_duration", - "created", "enabled", "expires", "key_id", + "created", "enabled", "expires", "fingerprint", "host", "interval", "last_approval_request", "last_checked_ok", "last_enabled", "name", "timeout") @@ -899,11 +861,9 @@ client["enabled"] = config.getboolean(client_name, "enabled") - # Uppercase and remove spaces from key_id and fingerprint - # for later comparison purposes with return value from the - # key_id() and fingerprint() functions - client["key_id"] = (section.get("key_id", "").upper() - .replace(" ", "")) + # Uppercase and remove spaces from fingerprint for later + # comparison purposes with return value from the + # fingerprint() function client["fingerprint"] = (section["fingerprint"].upper() .replace(" ", "")) if "secret" in section: @@ -953,7 +913,6 @@ self.expires = None logger.debug("Creating client %r", self.name) - logger.debug(" Key ID: %s", self.key_id) logger.debug(" Fingerprint: %s", self.fingerprint) self.created = settings.get("created", datetime.datetime.utcnow()) @@ -2041,13 +2000,6 @@ def Name_dbus_property(self): return dbus.String(self.name) - # KeyID - property - @dbus_annotations( - {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"}) - @dbus_service_property(_interface, signature="s", access="read") - def KeyID_dbus_property(self): - return dbus.String(self.key_id) - # Fingerprint - property @dbus_annotations( {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"}) @@ -2209,11 +2161,11 @@ class ProxyClient(object): - def __init__(self, child_pipe, key_id, fpr, address): + def __init__(self, child_pipe, fpr, address): self._pipe = child_pipe - self._pipe.send(('init', key_id, fpr, address)) + self._pipe.send(('init', fpr, address)) if not self._pipe.recv(): - raise KeyError(key_id or fpr) + raise KeyError(fpr) def __getattribute__(self, name): if name == '_pipe': @@ -2286,28 +2238,16 @@ approval_required = False try: - if gnutls.has_rawpk: - fpr = "" - try: - key_id = self.key_id( - self.peer_certificate(session)) - except (TypeError, gnutls.Error) as error: - logger.warning("Bad certificate: %s", error) - return - logger.debug("Key ID: %s", key_id) - - else: - key_id = "" - try: - fpr = self.fingerprint( - self.peer_certificate(session)) - except (TypeError, gnutls.Error) as error: - logger.warning("Bad certificate: %s", error) - return - logger.debug("Fingerprint: %s", fpr) - - try: - client = ProxyClient(child_pipe, key_id, fpr, + try: + fpr = self.fingerprint( + self.peer_certificate(session)) + except (TypeError, gnutls.Error) as error: + logger.warning("Bad certificate: %s", error) + return + logger.debug("Fingerprint: %s", fpr) + + try: + client = ProxyClient(child_pipe, fpr, self.client_address) except KeyError: return @@ -2390,20 +2330,10 @@ @staticmethod def peer_certificate(session): - "Return the peer's certificate as a bytestring" - try: - cert_type = gnutls.certificate_type_get2(session._c_object, - gnutls.CTYPE_PEERS) - except AttributeError: - cert_type = gnutls.certificate_type_get(session._c_object) - if gnutls.has_rawpk: - valid_cert_types = frozenset((gnutls.CRT_RAWPK,)) - else: - valid_cert_types = frozenset((gnutls.CRT_OPENPGP,)) - # If not a valid certificate type... - if cert_type not in valid_cert_types: - logger.info("Cert type %r not in %r", cert_type, - valid_cert_types) + "Return the peer's OpenPGP certificate as a bytestring" + # If not an OpenPGP certificate... + if (gnutls.certificate_type_get(session._c_object) + != gnutls.CRT_OPENPGP): # ...return invalid data return b"" list_size = ctypes.c_uint(1) @@ -2417,40 +2347,6 @@ return ctypes.string_at(cert.data, cert.size) @staticmethod - def key_id(certificate): - "Convert a certificate bytestring to a hexdigit key ID" - # New GnuTLS "datum" with the public key - datum = gnutls.datum_t( - ctypes.cast(ctypes.c_char_p(certificate), - ctypes.POINTER(ctypes.c_ubyte)), - ctypes.c_uint(len(certificate))) - # XXX all these need to be created in the gnutls "module" - # New empty GnuTLS certificate - pubkey = gnutls.pubkey_t() - gnutls.pubkey_init(ctypes.byref(pubkey)) - # Import the raw public key into the certificate - gnutls.pubkey_import(pubkey, - ctypes.byref(datum), - gnutls.X509_FMT_DER) - # New buffer for the key ID - buf = ctypes.create_string_buffer(32) - buf_len = ctypes.c_size_t(len(buf)) - # Get the key ID from the raw public key into the buffer - gnutls.pubkey_get_key_id(pubkey, - gnutls.KEYID_USE_SHA256, - ctypes.cast(ctypes.byref(buf), - ctypes.POINTER(ctypes.c_ubyte)), - ctypes.byref(buf_len)) - # Deinit the certificate - gnutls.pubkey_deinit(pubkey) - - # Convert the buffer to a Python bytestring - key_id = ctypes.string_at(buf, buf_len.value) - # Convert the bytestring to hexadecimal notation - hex_key_id = binascii.hexlify(key_id).upper() - return hex_key_id - - @staticmethod def fingerprint(openpgp): "Convert an OpenPGP bytestring to a hexdigit fingerprint" # New GnuTLS "datum" with the OpenPGP public key @@ -2470,8 +2366,7 @@ ctypes.byref(crtverify)) if crtverify.value != 0: gnutls.openpgp_crt_deinit(crt) - raise gnutls.CertificateSecurityError(code - =crtverify.value) + raise gnutls.CertificateSecurityError("Verify failed") # New buffer for the fingerprint buf = ctypes.create_string_buffer(20) buf_len = ctypes.c_size_t() @@ -2684,25 +2579,19 @@ command = request[0] if command == 'init': - key_id = request[1].decode("ascii") - fpr = request[2].decode("ascii") - address = request[3] + fpr = request[1].decode("ascii") + address = request[2] for c in self.clients.values(): - if key_id == "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855": - continue - if key_id and c.key_id == key_id: - client = c - break - if fpr and c.fingerprint == fpr: + if c.fingerprint == fpr: client = c break else: - logger.info("Client not found for key ID: %s, address" - ": %s", key_id or fpr, address) + logger.info("Client not found for fingerprint: %s, ad" + "dress: %s", fpr, address) if self.use_dbus: # Emit D-Bus signal - mandos_dbus_service.ClientNotFound(key_id or fpr, + mandos_dbus_service.ClientNotFound(fpr, address[0]) parent_pipe.send(False) return False @@ -2971,17 +2860,13 @@ sys.exit(os.EX_OK if fail_count == 0 else 1) # Default values for config file for server-global settings - if gnutls.has_rawpk: - priority = ("SECURE128:!CTYPE-X.509:+CTYPE-RAWPK:!RSA" - ":!VERS-ALL:+VERS-TLS1.3:%PROFILE_ULTRA") - else: - priority = ("SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA" - ":+SIGN-DSA-SHA256") server_defaults = {"interface": "", "address": "", "port": "", "debug": "False", - "priority": priority, + "priority": + "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA" + ":+SIGN-DSA-SHA256", "servicename": "Mandos", "use_dbus": "True", "use_ipv6": "True", @@ -2992,7 +2877,6 @@ "foreground": "False", "zeroconf": "True", } - del priority # Parse config file for server-global settings server_config = configparser.SafeConfigParser(server_defaults) @@ -3242,10 +3126,6 @@ for k in ("name", "host"): if isinstance(value[k], bytes): value[k] = value[k].decode("utf-8") - if not value.has_key("key_id"): - value["key_id"] = "" - elif not value.has_key("fingerprint"): - value["fingerprint"] = "" # old_client_settings # .keys() old_client_settings = { @@ -3388,7 +3268,7 @@ pass @dbus.service.signal(_interface, signature="ss") - def ClientNotFound(self, key_id, address): + def ClientNotFound(self, fingerprint, address): "D-Bus signal" pass === modified file 'mandos-clients.conf.xml' --- mandos-clients.conf.xml 2019-02-10 04:20:26 +0000 +++ mandos-clients.conf.xml 2018-02-08 10:23:55 +0000 @@ -3,7 +3,7 @@ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ /etc/mandos/clients.conf"> - + %common; ]> @@ -43,7 +43,6 @@ 2016 2017 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -187,9 +186,9 @@ >-- %%(host)s. Note that mandos-keygen, when generating output to be inserted into this file, normally looks for an SSH - server on the Mandos client, and, if it finds one, outputs + server on the Mandos client, and, if it find one, outputs a option to check for the - client’s SSH key fingerprint – this is more secure against + client’s key fingerprint – this is more secure against spoofing. @@ -240,22 +239,6 @@ - - - - This option is optional. - - - This option sets the certificate key ID that identifies - the public key that clients authenticate themselves with - through TLS. The string needs to be in hexadecimal form, - but spaces or upper/lower case are not significant. - - - - - @@ -331,9 +314,9 @@ If present, this option must be set to a string of base64-encoded binary data. It will be decoded and sent - to the client matching the above - or . This should, of course, - be OpenPGP encrypted data, decryptable only by the client. + to the client matching the above + . This should, of course, be + OpenPGP encrypted data, decryptable only by the client. The program mandos-keygen8 can, using its @@ -434,7 +417,6 @@ created, enabled, expires, - key_id, fingerprint, host, interval, @@ -497,7 +479,6 @@ # Client "foo" [foo] -key_id = 788cd77115cd0bb7b2d5e0ae8496f6b48149d5e712c652076b1fd2d957ef7c1f fingerprint = 7788 2722 5BA7 DE53 9C5A 7CFA 59CF F7CD BD9A 5920 secret = hQIOA6QdEjBs2L/HEAf/TCyrDe5Xnm9esa+Pb/vWF9CUqfn4srzVgSu234 @@ -520,7 +501,6 @@ # Client "bar" [bar] -key_id = F90C7A81D72D1EA69A51031A91FF8885F36C8B46D155C8C58709A4C99AE9E361 fingerprint = 3e393aeaefb84c7e89e2f547b3a107558fca3a27 secfile = /etc/mandos/bar-secret timeout = PT15M === modified file 'mandos-ctl' --- mandos-ctl 2019-04-09 13:50:57 +0000 +++ mandos-ctl 2018-02-08 10:23:55 +0000 @@ -1,10 +1,10 @@ #!/usr/bin/python -# -*- mode: python; coding: utf-8; after-save-hook: (lambda () (let ((command (if (and (boundp 'tramp-file-name-structure) (string-match (car tramp-file-name-structure) (buffer-file-name))) (tramp-file-name-localname (tramp-dissect-file-name (buffer-file-name))) (buffer-file-name)))) (if (= (shell-command (format "%s --check" (shell-quote-argument command)) "*Test*") 0) (let ((w (get-buffer-window "*Test*"))) (if w (delete-window w)) (kill-buffer "*Test*")) (display-buffer "*Test*")))); -*- +# -*- mode: python; coding: utf-8 -*- # # Mandos Monitor - Control and monitor the Mandos server # -# Copyright © 2008-2019 Teddy Hogeborn -# Copyright © 2008-2019 Björn Påhlsson +# Copyright © 2008-2018 Teddy Hogeborn +# Copyright © 2008-2018 Björn Påhlsson # # This file is part of Mandos. # @@ -40,211 +40,57 @@ import os import collections import json -import unittest -import logging -import io -import tempfile -import contextlib - -try: - import pydbus - import gi - dbus_python = None -except ImportError: - import dbus as dbus_python - pydbus = None - class gi(object): - """Dummy gi module, for the tests""" - class repository(object): - class GLib(object): - class Error(Exception): - pass - -# Show warnings by default -if not sys.warnoptions: - import warnings - warnings.simplefilter("default") - -log = logging.getLogger(sys.argv[0]) -logging.basicConfig(level="INFO", # Show info level messages - format="%(message)s") # Show basic log messages - -logging.captureWarnings(True) # Show warnings via the logging system + +import dbus if sys.version_info.major == 2: str = unicode - import StringIO - io.StringIO = StringIO.StringIO locale.setlocale(locale.LC_ALL, "") -version = "1.8.3" - - -def main(): - parser = argparse.ArgumentParser() - add_command_line_options(parser) - - options = parser.parse_args() - check_option_syntax(parser, options) - - clientnames = options.client - - if options.debug: - log.setLevel(logging.DEBUG) - - if pydbus is not None: - bus = pydbus_adapter.CachingBus(pydbus) - else: - bus = dbus_python_adapter.CachingBus(dbus_python) - - try: - all_clients = bus.get_clients_and_properties() - except dbus.ConnectFailed as e: - log.critical("Could not connect to Mandos server: %s", e) - sys.exit(1) - except dbus.Error as e: - log.critical( - "Failed to access Mandos server through D-Bus:\n%s", e) - sys.exit(1) - - # Compile dict of (clientpath: properties) to process - if not clientnames: - clients = all_clients - else: - clients = {} - for name in clientnames: - for objpath, properties in all_clients.items(): - if properties["Name"] == name: - clients[objpath] = properties - break - else: - log.critical("Client not found on server: %r", name) - sys.exit(1) - - commands = commands_from_options(options) - - for command in commands: - command.run(clients, bus) - - -def add_command_line_options(parser): - parser.add_argument("--version", action="version", - version="%(prog)s {}".format(version), - help="show version number and exit") - parser.add_argument("-a", "--all", action="store_true", - help="Select all clients") - parser.add_argument("-v", "--verbose", action="store_true", - help="Print all fields") - parser.add_argument("-j", "--dump-json", dest="commands", - action="append_const", default=[], - const=command.DumpJSON(), - help="Dump client data in JSON format") - enable_disable = parser.add_mutually_exclusive_group() - enable_disable.add_argument("-e", "--enable", dest="commands", - action="append_const", default=[], - const=command.Enable(), - help="Enable client") - enable_disable.add_argument("-d", "--disable", dest="commands", - action="append_const", default=[], - const=command.Disable(), - help="disable client") - parser.add_argument("-b", "--bump-timeout", dest="commands", - action="append_const", default=[], - const=command.BumpTimeout(), - help="Bump timeout for client") - start_stop_checker = parser.add_mutually_exclusive_group() - start_stop_checker.add_argument("--start-checker", - dest="commands", - action="append_const", default=[], - const=command.StartChecker(), - help="Start checker for client") - start_stop_checker.add_argument("--stop-checker", dest="commands", - action="append_const", default=[], - const=command.StopChecker(), - help="Stop checker for client") - parser.add_argument("-V", "--is-enabled", dest="commands", - action="append_const", default=[], - const=command.IsEnabled(), - help="Check if client is enabled") - parser.add_argument("-r", "--remove", dest="commands", - action="append_const", default=[], - const=command.Remove(), - help="Remove client") - parser.add_argument("-c", "--checker", dest="commands", - action="append", default=[], - metavar="COMMAND", type=command.SetChecker, - help="Set checker command for client") - parser.add_argument( - "-t", "--timeout", dest="commands", action="append", - default=[], metavar="TIME", - type=command.SetTimeout.argparse(string_to_delta), - help="Set timeout for client") - parser.add_argument( - "--extended-timeout", dest="commands", action="append", - default=[], metavar="TIME", - type=command.SetExtendedTimeout.argparse(string_to_delta), - help="Set extended timeout for client") - parser.add_argument( - "-i", "--interval", dest="commands", action="append", - default=[], metavar="TIME", - type=command.SetInterval.argparse(string_to_delta), - help="Set checker interval for client") - approve_deny_default = parser.add_mutually_exclusive_group() - approve_deny_default.add_argument( - "--approve-by-default", dest="commands", - action="append_const", default=[], - const=command.ApproveByDefault(), - help="Set client to be approved by default") - approve_deny_default.add_argument( - "--deny-by-default", dest="commands", - action="append_const", default=[], - const=command.DenyByDefault(), - help="Set client to be denied by default") - parser.add_argument( - "--approval-delay", dest="commands", action="append", - default=[], metavar="TIME", - type=command.SetApprovalDelay.argparse(string_to_delta), - help="Set delay before client approve/deny") - parser.add_argument( - "--approval-duration", dest="commands", action="append", - default=[], metavar="TIME", - type=command.SetApprovalDuration.argparse(string_to_delta), - help="Set duration of one client approval") - parser.add_argument("-H", "--host", dest="commands", - action="append", default=[], metavar="STRING", - type=command.SetHost, - help="Set host for client") - parser.add_argument( - "-s", "--secret", dest="commands", action="append", - default=[], metavar="FILENAME", - type=command.SetSecret.argparse(argparse.FileType(mode="rb")), - help="Set password blob (file) for client") - approve_deny = parser.add_mutually_exclusive_group() - approve_deny.add_argument( - "-A", "--approve", dest="commands", action="append_const", - default=[], const=command.Approve(), - help="Approve any current client request") - approve_deny.add_argument("-D", "--deny", dest="commands", - action="append_const", default=[], - const=command.Deny(), - help="Deny any current client request") - parser.add_argument("--debug", action="store_true", - help="Debug mode (show D-Bus commands)") - parser.add_argument("--check", action="store_true", - help="Run self-test") - parser.add_argument("client", nargs="*", help="Client name") - - -def string_to_delta(interval): - """Parse a string and return a datetime.timedelta""" - - try: - return rfc3339_duration_to_delta(interval) - except ValueError as e: - log.warning("%s - Parsing as pre-1.6.1 interval instead", - ' '.join(e.args)) - return parse_pre_1_6_1_interval(interval) +tablewords = { + "Name": "Name", + "Enabled": "Enabled", + "Timeout": "Timeout", + "LastCheckedOK": "Last Successful Check", + "LastApprovalRequest": "Last Approval Request", + "Created": "Created", + "Interval": "Interval", + "Host": "Host", + "Fingerprint": "Fingerprint", + "CheckerRunning": "Check Is Running", + "LastEnabled": "Last Enabled", + "ApprovalPending": "Approval Is Pending", + "ApprovedByDefault": "Approved By Default", + "ApprovalDelay": "Approval Delay", + "ApprovalDuration": "Approval Duration", + "Checker": "Checker", + "ExtendedTimeout": "Extended Timeout", + "Expires": "Expires", + "LastCheckerStatus": "Last Checker Status", +} +defaultkeywords = ("Name", "Enabled", "Timeout", "LastCheckedOK") +domain = "se.recompile" +busname = domain + ".Mandos" +server_path = "/" +server_interface = domain + ".Mandos" +client_interface = domain + ".Mandos.Client" +version = "1.7.16" + + +try: + dbus.OBJECT_MANAGER_IFACE +except AttributeError: + dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager" + + +def milliseconds_to_string(ms): + td = datetime.timedelta(0, 0, 0, ms) + return ("{days}{hours:02}:{minutes:02}:{seconds:02}" + .format(days="{}T".format(td.days) if td.days else "", + hours=td.seconds // 3600, + minutes=(td.seconds % 3600) // 60, + seconds=td.seconds % 60)) def rfc3339_duration_to_delta(duration): @@ -256,8 +102,6 @@ datetime.timedelta(0, 60) >>> rfc3339_duration_to_delta("PT60M") datetime.timedelta(0, 3600) - >>> rfc3339_duration_to_delta("P60M") - datetime.timedelta(1680) >>> rfc3339_duration_to_delta("PT24H") datetime.timedelta(1) >>> rfc3339_duration_to_delta("P1W") @@ -266,35 +110,6 @@ datetime.timedelta(0, 330) >>> rfc3339_duration_to_delta("P1DT3M20S") datetime.timedelta(1, 200) - >>> # Can not be empty: - >>> rfc3339_duration_to_delta("") - Traceback (most recent call last): - ... - ValueError: Invalid RFC 3339 duration: "" - >>> # Must start with "P": - >>> rfc3339_duration_to_delta("1D") - Traceback (most recent call last): - ... - ValueError: Invalid RFC 3339 duration: "1D" - >>> # Must use correct order - >>> rfc3339_duration_to_delta("PT1S2M") - Traceback (most recent call last): - ... - ValueError: Invalid RFC 3339 duration: "PT1S2M" - >>> # Time needs time marker - >>> rfc3339_duration_to_delta("P1H2S") - Traceback (most recent call last): - ... - ValueError: Invalid RFC 3339 duration: "P1H2S" - >>> # Weeks can not be combined with anything else - >>> rfc3339_duration_to_delta("P1D2W") - Traceback (most recent call last): - ... - ValueError: Invalid RFC 3339 duration: "P1D2W" - >>> rfc3339_duration_to_delta("P2W2H") - Traceback (most recent call last): - ... - ValueError: Invalid RFC 3339 duration: "P2W2H" """ # Parsing an RFC 3339 duration with regular expressions is not @@ -371,36 +186,34 @@ break else: # No currently valid tokens were found - raise ValueError("Invalid RFC 3339 duration: \"{}\"" + raise ValueError("Invalid RFC 3339 duration: {!r}" .format(duration)) # End token found return value -def parse_pre_1_6_1_interval(interval): - """Parse an interval string as documented by Mandos before 1.6.1, - and return a datetime.timedelta +def string_to_delta(interval): + """Parse a string and return a datetime.timedelta - >>> parse_pre_1_6_1_interval('7d') + >>> string_to_delta('7d') datetime.timedelta(7) - >>> parse_pre_1_6_1_interval('60s') + >>> string_to_delta('60s') datetime.timedelta(0, 60) - >>> parse_pre_1_6_1_interval('60m') + >>> string_to_delta('60m') datetime.timedelta(0, 3600) - >>> parse_pre_1_6_1_interval('24h') + >>> string_to_delta('24h') datetime.timedelta(1) - >>> parse_pre_1_6_1_interval('1w') + >>> string_to_delta('1w') datetime.timedelta(7) - >>> parse_pre_1_6_1_interval('5m 30s') + >>> string_to_delta('5m 30s') datetime.timedelta(0, 330) - >>> parse_pre_1_6_1_interval('') - datetime.timedelta(0) - >>> # Ignore unknown characters, allow any order and repetitions - >>> parse_pre_1_6_1_interval('2dxy7zz11y3m5m') - datetime.timedelta(2, 480, 18000) - """ + try: + return rfc3339_duration_to_delta(interval) + except ValueError: + pass + value = datetime.timedelta(0) regexp = re.compile(r"(\d+)([dsmhw]?)") @@ -420,2070 +233,265 @@ return value -def check_option_syntax(parser, options): - """Apply additional restrictions on options, not expressible in -argparse""" - - def has_commands(options, commands=None): - if commands is None: - commands = (command.Enable, - command.Disable, - command.BumpTimeout, - command.StartChecker, - command.StopChecker, - command.IsEnabled, - command.Remove, - command.SetChecker, - command.SetTimeout, - command.SetExtendedTimeout, - command.SetInterval, - command.ApproveByDefault, - command.DenyByDefault, - command.SetApprovalDelay, - command.SetApprovalDuration, - command.SetHost, - command.SetSecret, - command.Approve, - command.Deny) - return any(isinstance(cmd, commands) - for cmd in options.commands) - - if has_commands(options) and not (options.client or options.all): +def print_clients(clients, keywords): + def valuetostring(value, keyword): + if type(value) is dbus.Boolean: + return "Yes" if value else "No" + if keyword in ("Timeout", "Interval", "ApprovalDelay", + "ApprovalDuration", "ExtendedTimeout"): + return milliseconds_to_string(value) + return str(value) + + # Create format string to print table rows + format_string = " ".join("{{{key}:{width}}}".format( + width=max(len(tablewords[key]), + max(len(valuetostring(client[key], key)) + for client in clients)), + key=key) + for key in keywords) + # Print header line + print(format_string.format(**tablewords)) + for client in clients: + print(format_string + .format(**{key: valuetostring(client[key], key) + for key in keywords})) + + +def has_actions(options): + return any((options.enable, + options.disable, + options.bump_timeout, + options.start_checker, + options.stop_checker, + options.is_enabled, + options.remove, + options.checker is not None, + options.timeout is not None, + options.extended_timeout is not None, + options.interval is not None, + options.approved_by_default is not None, + options.approval_delay is not None, + options.approval_duration is not None, + options.host is not None, + options.secret is not None, + options.approve, + options.deny)) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--version", action="version", + version="%(prog)s {}".format(version), + help="show version number and exit") + parser.add_argument("-a", "--all", action="store_true", + help="Select all clients") + parser.add_argument("-v", "--verbose", action="store_true", + help="Print all fields") + parser.add_argument("-j", "--dump-json", action="store_true", + help="Dump client data in JSON format") + parser.add_argument("-e", "--enable", action="store_true", + help="Enable client") + parser.add_argument("-d", "--disable", action="store_true", + help="disable client") + parser.add_argument("-b", "--bump-timeout", action="store_true", + help="Bump timeout for client") + parser.add_argument("--start-checker", action="store_true", + help="Start checker for client") + parser.add_argument("--stop-checker", action="store_true", + help="Stop checker for client") + parser.add_argument("-V", "--is-enabled", action="store_true", + help="Check if client is enabled") + parser.add_argument("-r", "--remove", action="store_true", + help="Remove client") + parser.add_argument("-c", "--checker", + help="Set checker command for client") + parser.add_argument("-t", "--timeout", + help="Set timeout for client") + parser.add_argument("--extended-timeout", + help="Set extended timeout for client") + parser.add_argument("-i", "--interval", + help="Set checker interval for client") + parser.add_argument("--approve-by-default", action="store_true", + default=None, dest="approved_by_default", + help="Set client to be approved by default") + parser.add_argument("--deny-by-default", action="store_false", + dest="approved_by_default", + help="Set client to be denied by default") + parser.add_argument("--approval-delay", + help="Set delay before client approve/deny") + parser.add_argument("--approval-duration", + help="Set duration of one client approval") + parser.add_argument("-H", "--host", help="Set host for client") + parser.add_argument("-s", "--secret", + type=argparse.FileType(mode="rb"), + help="Set password blob (file) for client") + parser.add_argument("-A", "--approve", action="store_true", + help="Approve any current client request") + parser.add_argument("-D", "--deny", action="store_true", + help="Deny any current client request") + parser.add_argument("--check", action="store_true", + help="Run self-test") + parser.add_argument("client", nargs="*", help="Client name") + options = parser.parse_args() + + if has_actions(options) and not (options.client or options.all): parser.error("Options require clients names or --all.") - if options.verbose and has_commands(options): + if options.verbose and has_actions(options): parser.error("--verbose can only be used alone.") - if (has_commands(options, (command.DumpJSON,)) - and (options.verbose or len(options.commands) > 1)): + if options.dump_json and (options.verbose + or has_actions(options)): parser.error("--dump-json can only be used alone.") - if options.all and not has_commands(options): + if options.all and not has_actions(options): parser.error("--all requires an action.") - if (has_commands(options, (command.IsEnabled,)) - and len(options.client) > 1): - parser.error("--is-enabled requires exactly one client") - if (len(options.commands) > 1 - and has_commands(options, (command.Remove,)) - and not has_commands(options, (command.Deny,))): - parser.error("--remove can only be combined with --deny") - - -class dbus(object): - - class SystemBus(object): - - object_manager_iface = "org.freedesktop.DBus.ObjectManager" - def get_managed_objects(self, busname, objectpath): - return self.call_method("GetManagedObjects", busname, - objectpath, - self.object_manager_iface) - - properties_iface = "org.freedesktop.DBus.Properties" - def set_property(self, busname, objectpath, interface, key, - value): - self.call_method("Set", busname, objectpath, - self.properties_iface, interface, key, - value) - - - class MandosBus(SystemBus): - busname_domain = "se.recompile" - busname = busname_domain + ".Mandos" - server_path = "/" - server_interface = busname_domain + ".Mandos" - client_interface = busname_domain + ".Mandos.Client" - del busname_domain - - def get_clients_and_properties(self): - managed_objects = self.get_managed_objects( - self.busname, self.server_path) - return {objpath: properties[self.client_interface] - for objpath, properties in managed_objects.items() - if self.client_interface in properties} - - def set_client_property(self, objectpath, key, value): - return self.set_property(self.busname, objectpath, - self.client_interface, key, - value) - - def call_client_method(self, objectpath, method, *args): - return self.call_method(method, self.busname, objectpath, - self.client_interface, *args) - - def call_server_method(self, method, *args): - return self.call_method(method, self.busname, - self.server_path, - self.server_interface, *args) - - class Error(Exception): - pass - - class ConnectFailed(Error): - pass - - -class dbus_python_adapter(object): - - class SystemBus(dbus.MandosBus): - """Use dbus-python""" - - def __init__(self, module=dbus_python): - self.dbus_python = module - self.bus = self.dbus_python.SystemBus() - - @contextlib.contextmanager - def convert_exception(self, exception_class=dbus.Error): - try: - yield - except self.dbus_python.exceptions.DBusException as e: - # This does what "raise from" would do - exc = exception_class(*e.args) - exc.__cause__ = e - raise exc - - def call_method(self, methodname, busname, objectpath, - interface, *args): - proxy_object = self.get_object(busname, objectpath) - log.debug("D-Bus: %s:%s:%s.%s(%s)", busname, objectpath, - interface, methodname, - ", ".join(repr(a) for a in args)) - method = getattr(proxy_object, methodname) - with self.convert_exception(): - with dbus_python_adapter.SilenceLogger( - "dbus.proxies"): - value = method(*args, dbus_interface=interface) - return self.type_filter(value) - - def get_object(self, busname, objectpath): - log.debug("D-Bus: Connect to: (busname=%r, path=%r)", - busname, objectpath) - with self.convert_exception(dbus.ConnectFailed): - return self.bus.get_object(busname, objectpath) - - def type_filter(self, value): - """Convert the most bothersome types to Python types""" - if isinstance(value, self.dbus_python.Boolean): - return bool(value) - if isinstance(value, self.dbus_python.ObjectPath): - return str(value) - # Also recurse into dictionaries - if isinstance(value, self.dbus_python.Dictionary): - return {self.type_filter(key): - self.type_filter(subval) - for key, subval in value.items()} - return value - - def set_client_property(self, objectpath, key, value): - if key == "Secret": - if not isinstance(value, bytes): - value = value.encode("utf-8") - value = self.dbus_python.ByteArray(value) - return self.set_property(self.busname, objectpath, - self.client_interface, key, - value) - - class SilenceLogger(object): - "Simple context manager to silence a particular logger" - def __init__(self, loggername): - self.logger = logging.getLogger(loggername) - - def __enter__(self): - self.logger.addFilter(self.nullfilter) - - class NullFilter(logging.Filter): - def filter(self, record): - return False - - nullfilter = NullFilter() - - def __exit__(self, exc_type, exc_val, exc_tb): - self.logger.removeFilter(self.nullfilter) - - - class CachingBus(SystemBus): - """A caching layer for dbus_python_adapter.SystemBus""" - def __init__(self, *args, **kwargs): - self.object_cache = {} - super(dbus_python_adapter.CachingBus, - self).__init__(*args, **kwargs) - def get_object(self, busname, objectpath): - try: - return self.object_cache[(busname, objectpath)] - except KeyError: - new_object = super( - dbus_python_adapter.CachingBus, - self).get_object(busname, objectpath) - self.object_cache[(busname, objectpath)] = new_object - return new_object - - -class pydbus_adapter(object): - class SystemBus(dbus.MandosBus): - def __init__(self, module=pydbus): - self.pydbus = module - self.bus = self.pydbus.SystemBus() - - @contextlib.contextmanager - def convert_exception(self, exception_class=dbus.Error): - try: - yield - except gi.repository.GLib.Error as e: - # This does what "raise from" would do - exc = exception_class(*e.args) - exc.__cause__ = e - raise exc - - def call_method(self, methodname, busname, objectpath, - interface, *args): - proxy_object = self.get(busname, objectpath) - log.debug("D-Bus: %s:%s:%s.%s(%s)", busname, objectpath, - interface, methodname, - ", ".join(repr(a) for a in args)) - method = getattr(proxy_object[interface], methodname) - with self.convert_exception(): - return method(*args) - - def get(self, busname, objectpath): - log.debug("D-Bus: Connect to: (busname=%r, path=%r)", - busname, objectpath) - with self.convert_exception(dbus.ConnectFailed): - if sys.version_info.major <= 2: - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", "", DeprecationWarning, - r"^xml\.etree\.ElementTree$") - return self.bus.get(busname, objectpath) + + if options.check: + import doctest + fail_count, test_count = doctest.testmod() + sys.exit(os.EX_OK if fail_count == 0 else 1) + + try: + bus = dbus.SystemBus() + mandos_dbus_objc = bus.get_object(busname, server_path) + except dbus.exceptions.DBusException: + print("Could not connect to Mandos server", file=sys.stderr) + sys.exit(1) + + mandos_serv = dbus.Interface(mandos_dbus_objc, + dbus_interface=server_interface) + mandos_serv_object_manager = dbus.Interface( + mandos_dbus_objc, dbus_interface=dbus.OBJECT_MANAGER_IFACE) + + # block stderr since dbus library prints to stderr + null = os.open(os.path.devnull, os.O_RDWR) + stderrcopy = os.dup(sys.stderr.fileno()) + os.dup2(null, sys.stderr.fileno()) + os.close(null) + try: + try: + mandos_clients = {path: ifs_and_props[client_interface] + for path, ifs_and_props in + mandos_serv_object_manager + .GetManagedObjects().items() + if client_interface in ifs_and_props} + finally: + # restore stderr + os.dup2(stderrcopy, sys.stderr.fileno()) + os.close(stderrcopy) + except dbus.exceptions.DBusException as e: + print("Access denied: " + "Accessing mandos server through D-Bus: {}".format(e), + file=sys.stderr) + sys.exit(1) + + # Compile dict of (clients: properties) to process + clients = {} + + if options.all or not options.client: + clients = {bus.get_object(busname, path): properties + for path, properties in mandos_clients.items()} + else: + for name in options.client: + for path, client in mandos_clients.items(): + if client["Name"] == name: + client_objc = bus.get_object(busname, path) + clients[client_objc] = client + break + else: + print("Client not found on server: {!r}" + .format(name), file=sys.stderr) + sys.exit(1) + + if not has_actions(options) and clients: + if options.verbose or options.dump_json: + keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK", + "Created", "Interval", "Host", "Fingerprint", + "CheckerRunning", "LastEnabled", + "ApprovalPending", "ApprovedByDefault", + "LastApprovalRequest", "ApprovalDelay", + "ApprovalDuration", "Checker", + "ExtendedTimeout", "Expires", + "LastCheckerStatus") + else: + keywords = defaultkeywords + + if options.dump_json: + json.dump({client["Name"]: {key: + bool(client[key]) + if isinstance(client[key], + dbus.Boolean) + else client[key] + for key in keywords} + for client in clients.values()}, + fp=sys.stdout, indent=4, + separators=(',', ': ')) + print() + else: + print_clients(clients.values(), keywords) + else: + # Process each client in the list by all selected options + for client in clients: + + def set_client_prop(prop, value): + """Set a Client D-Bus property""" + client.Set(client_interface, prop, value, + dbus_interface=dbus.PROPERTIES_IFACE) + + def set_client_prop_ms(prop, value): + """Set a Client D-Bus property, converted + from a string to milliseconds.""" + set_client_prop(prop, + string_to_delta(value).total_seconds() + * 1000) + + if options.remove: + mandos_serv.RemoveClient(client.__dbus_object_path__) + if options.enable: + set_client_prop("Enabled", dbus.Boolean(True)) + if options.disable: + set_client_prop("Enabled", dbus.Boolean(False)) + if options.bump_timeout: + set_client_prop("LastCheckedOK", "") + if options.start_checker: + set_client_prop("CheckerRunning", dbus.Boolean(True)) + if options.stop_checker: + set_client_prop("CheckerRunning", dbus.Boolean(False)) + if options.is_enabled: + if client.Get(client_interface, "Enabled", + dbus_interface=dbus.PROPERTIES_IFACE): + sys.exit(0) else: - return self.bus.get(busname, objectpath) - - def set_property(self, busname, objectpath, interface, key, - value): - proxy_object = self.get(busname, objectpath) - log.debug("D-Bus: %s:%s:%s.Set(%r, %r, %r)", busname, - objectpath, self.properties_iface, interface, - key, value) - setattr(proxy_object[interface], key, value) - - class CachingBus(SystemBus): - """A caching layer for pydbus_adapter.SystemBus""" - def __init__(self, *args, **kwargs): - self.object_cache = {} - super(pydbus_adapter.CachingBus, - self).__init__(*args, **kwargs) - def get(self, busname, objectpath): - try: - return self.object_cache[(busname, objectpath)] - except KeyError: - new_object = (super(pydbus_adapter.CachingBus, self) - .get(busname, objectpath)) - self.object_cache[(busname, objectpath)] = new_object - return new_object - - -def commands_from_options(options): - - commands = list(options.commands) - - def find_cmd(cmd, commands): - i = 0 - for i, c in enumerate(commands): - if isinstance(c, cmd): - return i - return i+1 - - # If command.Remove is present, move any instances of command.Deny - # to occur ahead of command.Remove. - index_of_remove = find_cmd(command.Remove, commands) - before_remove = commands[:index_of_remove] - after_remove = commands[index_of_remove:] - cleaned_after = [] - for cmd in after_remove: - if isinstance(cmd, command.Deny): - before_remove.append(cmd) - else: - cleaned_after.append(cmd) - if cleaned_after != after_remove: - commands = before_remove + cleaned_after - - # If no command option has been given, show table of clients, - # optionally verbosely - if not commands: - commands.append(command.PrintTable(verbose=options.verbose)) - - return commands - - -class command(object): - """A namespace for command classes""" - - class Base(object): - """Abstract base class for commands""" - def run(self, clients, bus=None): - """Normal commands should implement run_on_one_client(), -but commands which want to operate on all clients at the same time can -override this run() method instead. -""" - self.bus = bus - for client, properties in clients.items(): - self.run_on_one_client(client, properties) - - - class IsEnabled(Base): - def run(self, clients, bus=None): - properties = next(iter(clients.values())) - if properties["Enabled"]: - sys.exit(0) - sys.exit(1) - - - class Approve(Base): - def run_on_one_client(self, client, properties): - self.bus.call_client_method(client, "Approve", True) - - - class Deny(Base): - def run_on_one_client(self, client, properties): - self.bus.call_client_method(client, "Approve", False) - - - class Remove(Base): - def run(self, clients, bus): - for clientpath in frozenset(clients.keys()): - bus.call_server_method("RemoveClient", clientpath) - - - class Output(Base): - """Abstract class for commands outputting client details""" - all_keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK", - "Created", "Interval", "Host", "KeyID", - "Fingerprint", "CheckerRunning", - "LastEnabled", "ApprovalPending", - "ApprovedByDefault", "LastApprovalRequest", - "ApprovalDelay", "ApprovalDuration", - "Checker", "ExtendedTimeout", "Expires", - "LastCheckerStatus") - - - class DumpJSON(Output): - def run(self, clients, bus=None): - data = {properties["Name"]: - {key: properties[key] - for key in self.all_keywords} - for properties in clients.values()} - print(json.dumps(data, indent=4, separators=(',', ': '))) - - - class PrintTable(Output): - def __init__(self, verbose=False): - self.verbose = verbose - - def run(self, clients, bus=None): - default_keywords = ("Name", "Enabled", "Timeout", - "LastCheckedOK") - keywords = default_keywords - if self.verbose: - keywords = self.all_keywords - print(self.TableOfClients(clients.values(), keywords)) - - class TableOfClients(object): - tableheaders = { - "Name": "Name", - "Enabled": "Enabled", - "Timeout": "Timeout", - "LastCheckedOK": "Last Successful Check", - "LastApprovalRequest": "Last Approval Request", - "Created": "Created", - "Interval": "Interval", - "Host": "Host", - "Fingerprint": "Fingerprint", - "KeyID": "Key ID", - "CheckerRunning": "Check Is Running", - "LastEnabled": "Last Enabled", - "ApprovalPending": "Approval Is Pending", - "ApprovedByDefault": "Approved By Default", - "ApprovalDelay": "Approval Delay", - "ApprovalDuration": "Approval Duration", - "Checker": "Checker", - "ExtendedTimeout": "Extended Timeout", - "Expires": "Expires", - "LastCheckerStatus": "Last Checker Status", - } - - def __init__(self, clients, keywords): - self.clients = clients - self.keywords = keywords - - def __str__(self): - return "\n".join(self.rows()) - - if sys.version_info.major == 2: - __unicode__ = __str__ - def __str__(self): - return str(self).encode( - locale.getpreferredencoding()) - - def rows(self): - format_string = self.row_formatting_string() - rows = [self.header_line(format_string)] - rows.extend(self.client_line(client, format_string) - for client in self.clients) - return rows - - def row_formatting_string(self): - "Format string used to format table rows" - return " ".join("{{{key}:{width}}}".format( - width=max(len(self.tableheaders[key]), - *(len(self.string_from_client(client, - key)) - for client in self.clients)), - key=key) - for key in self.keywords) - - def string_from_client(self, client, key): - return self.valuetostring(client[key], key) - - @classmethod - def valuetostring(cls, value, keyword): - if isinstance(value, bool): - return "Yes" if value else "No" - if keyword in ("Timeout", "Interval", "ApprovalDelay", - "ApprovalDuration", "ExtendedTimeout"): - return cls.milliseconds_to_string(value) - return str(value) - - def header_line(self, format_string): - return format_string.format(**self.tableheaders) - - def client_line(self, client, format_string): - return format_string.format( - **{key: self.string_from_client(client, key) - for key in self.keywords}) - - @staticmethod - def milliseconds_to_string(ms): - td = datetime.timedelta(0, 0, 0, ms) - return ("{days}{hours:02}:{minutes:02}:{seconds:02}" - .format(days="{}T".format(td.days) - if td.days else "", - hours=td.seconds // 3600, - minutes=(td.seconds % 3600) // 60, - seconds=td.seconds % 60)) - - - class PropertySetter(Base): - "Abstract class for Actions for setting one client property" - - def run_on_one_client(self, client, properties=None): - """Set the Client's D-Bus property""" - self.bus.set_client_property(client, self.propname, - self.value_to_set) - - @property - def propname(self): - raise NotImplementedError() - - - class Enable(PropertySetter): - propname = "Enabled" - value_to_set = True - - - class Disable(PropertySetter): - propname = "Enabled" - value_to_set = False - - - class BumpTimeout(PropertySetter): - propname = "LastCheckedOK" - value_to_set = "" - - - class StartChecker(PropertySetter): - propname = "CheckerRunning" - value_to_set = True - - - class StopChecker(PropertySetter): - propname = "CheckerRunning" - value_to_set = False - - - class ApproveByDefault(PropertySetter): - propname = "ApprovedByDefault" - value_to_set = True - - - class DenyByDefault(PropertySetter): - propname = "ApprovedByDefault" - value_to_set = False - - - class PropertySetterValue(PropertySetter): - """Abstract class for PropertySetter recieving a value as -constructor argument instead of a class attribute.""" - def __init__(self, value): - self.value_to_set = value - - @classmethod - def argparse(cls, argtype): - def cmdtype(arg): - return cls(argtype(arg)) - return cmdtype - - class SetChecker(PropertySetterValue): - propname = "Checker" - - - class SetHost(PropertySetterValue): - propname = "Host" - - - class SetSecret(PropertySetterValue): - propname = "Secret" - - @property - def value_to_set(self): - return self._vts - - @value_to_set.setter - def value_to_set(self, value): - """When setting, read data from supplied file object""" - self._vts = value.read() - value.close() - - - class PropertySetterValueMilliseconds(PropertySetterValue): - """Abstract class for PropertySetterValue taking a value -argument as a datetime.timedelta() but should store it as -milliseconds.""" - - @property - def value_to_set(self): - return self._vts - - @value_to_set.setter - def value_to_set(self, value): - "When setting, convert value from a datetime.timedelta" - self._vts = int(round(value.total_seconds() * 1000)) - - - class SetTimeout(PropertySetterValueMilliseconds): - propname = "Timeout" - - - class SetExtendedTimeout(PropertySetterValueMilliseconds): - propname = "ExtendedTimeout" - - - class SetInterval(PropertySetterValueMilliseconds): - propname = "Interval" - - - class SetApprovalDelay(PropertySetterValueMilliseconds): - propname = "ApprovalDelay" - - - class SetApprovalDuration(PropertySetterValueMilliseconds): - propname = "ApprovalDuration" - - - -class TestCaseWithAssertLogs(unittest.TestCase): - """unittest.TestCase.assertLogs only exists in Python 3.4""" - - if not hasattr(unittest.TestCase, "assertLogs"): - @contextlib.contextmanager - def assertLogs(self, logger, level=logging.INFO): - capturing_handler = self.CapturingLevelHandler(level) - old_level = logger.level - old_propagate = logger.propagate - logger.addHandler(capturing_handler) - logger.setLevel(level) - logger.propagate = False - try: - yield capturing_handler.watcher - finally: - logger.propagate = old_propagate - logger.removeHandler(capturing_handler) - logger.setLevel(old_level) - self.assertGreater(len(capturing_handler.watcher.records), - 0) - - class CapturingLevelHandler(logging.Handler): - def __init__(self, level, *args, **kwargs): - logging.Handler.__init__(self, *args, **kwargs) - self.watcher = self.LoggingWatcher([], []) - def emit(self, record): - self.watcher.records.append(record) - self.watcher.output.append(self.format(record)) - - LoggingWatcher = collections.namedtuple("LoggingWatcher", - ("records", - "output")) - - -class Unique(object): - """Class for objects which exist only to be unique objects, since -unittest.mock.sentinel only exists in Python 3.3""" - - -class Test_string_to_delta(TestCaseWithAssertLogs): - # Just test basic RFC 3339 functionality here, the doc string for - # rfc3339_duration_to_delta() already has more comprehensive - # tests, which are run by doctest. - - def test_rfc3339_zero_seconds(self): - self.assertEqual(datetime.timedelta(), - string_to_delta("PT0S")) - - def test_rfc3339_zero_days(self): - self.assertEqual(datetime.timedelta(), string_to_delta("P0D")) - - def test_rfc3339_one_second(self): - self.assertEqual(datetime.timedelta(0, 1), - string_to_delta("PT1S")) - - def test_rfc3339_two_hours(self): - self.assertEqual(datetime.timedelta(0, 7200), - string_to_delta("PT2H")) - - def test_falls_back_to_pre_1_6_1_with_warning(self): - with self.assertLogs(log, logging.WARNING): - value = string_to_delta("2h") - self.assertEqual(datetime.timedelta(0, 7200), value) - - -class Test_check_option_syntax(unittest.TestCase): - def setUp(self): - self.parser = argparse.ArgumentParser() - add_command_line_options(self.parser) - - def test_actions_requires_client_or_all(self): - for action, value in self.actions.items(): - args = self.actionargs(action, value) - with self.assertParseError(): - self.parse_args(args) - - # This mostly corresponds to the definition from has_commands() in - # check_option_syntax() - actions = { - "--enable": None, - "--disable": None, - "--bump-timeout": None, - "--start-checker": None, - "--stop-checker": None, - "--is-enabled": None, - "--remove": None, - "--checker": "x", - "--timeout": "PT0S", - "--extended-timeout": "PT0S", - "--interval": "PT0S", - "--approve-by-default": None, - "--deny-by-default": None, - "--approval-delay": "PT0S", - "--approval-duration": "PT0S", - "--host": "hostname", - "--secret": "/dev/null", - "--approve": None, - "--deny": None, - } - - @staticmethod - def actionargs(action, value, *args): - if value is not None: - return [action, value] + list(args) - else: - return [action] + list(args) - - @contextlib.contextmanager - def assertParseError(self): - with self.assertRaises(SystemExit) as e: - with self.redirect_stderr_to_devnull(): - yield - # Exit code from argparse is guaranteed to be "2". Reference: - # https://docs.python.org/3/library - # /argparse.html#exiting-methods - self.assertEqual(2, e.exception.code) - - def parse_args(self, args): - options = self.parser.parse_args(args) - check_option_syntax(self.parser, options) - - @staticmethod - @contextlib.contextmanager - def redirect_stderr_to_devnull(): - old_stderr = sys.stderr - with contextlib.closing(open(os.devnull, "w")) as null: - sys.stderr = null - try: - yield - finally: - sys.stderr = old_stderr - - def check_option_syntax(self, options): - check_option_syntax(self.parser, options) - - def test_actions_all_conflicts_with_verbose(self): - for action, value in self.actions.items(): - args = self.actionargs(action, value, "--all", - "--verbose") - with self.assertParseError(): - self.parse_args(args) - - def test_actions_with_client_conflicts_with_verbose(self): - for action, value in self.actions.items(): - args = self.actionargs(action, value, "--verbose", - "client") - with self.assertParseError(): - self.parse_args(args) - - def test_dump_json_conflicts_with_verbose(self): - args = ["--dump-json", "--verbose"] - with self.assertParseError(): - self.parse_args(args) - - def test_dump_json_conflicts_with_action(self): - for action, value in self.actions.items(): - args = self.actionargs(action, value, "--dump-json") - with self.assertParseError(): - self.parse_args(args) - - def test_all_can_not_be_alone(self): - args = ["--all"] - with self.assertParseError(): - self.parse_args(args) - - def test_all_is_ok_with_any_action(self): - for action, value in self.actions.items(): - args = self.actionargs(action, value, "--all") - self.parse_args(args) - - def test_any_action_is_ok_with_one_client(self): - for action, value in self.actions.items(): - args = self.actionargs(action, value, "client") - self.parse_args(args) - - def test_one_client_with_all_actions_except_is_enabled(self): - for action, value in self.actions.items(): - if action == "--is-enabled": - continue - args = self.actionargs(action, value, "client") - self.parse_args(args) - - def test_two_clients_with_all_actions_except_is_enabled(self): - for action, value in self.actions.items(): - if action == "--is-enabled": - continue - args = self.actionargs(action, value, "client1", - "client2") - self.parse_args(args) - - def test_two_clients_are_ok_with_actions_except_is_enabled(self): - for action, value in self.actions.items(): - if action == "--is-enabled": - continue - args = self.actionargs(action, value, "client1", - "client2") - self.parse_args(args) - - def test_is_enabled_fails_without_client(self): - args = ["--is-enabled"] - with self.assertParseError(): - self.parse_args(args) - - def test_is_enabled_fails_with_two_clients(self): - args = ["--is-enabled", "client1", "client2"] - with self.assertParseError(): - self.parse_args(args) - - def test_remove_can_only_be_combined_with_action_deny(self): - for action, value in self.actions.items(): - if action in {"--remove", "--deny"}: - continue - args = self.actionargs(action, value, "--all", - "--remove") - with self.assertParseError(): - self.parse_args(args) - - -class Test_dbus_exceptions(unittest.TestCase): - - def test_dbus_ConnectFailed_is_Error(self): - with self.assertRaises(dbus.Error): - raise dbus.ConnectFailed() - - -class Test_dbus_MandosBus(unittest.TestCase): - - class MockMandosBus(dbus.MandosBus): - def __init__(self): - self._name = "se.recompile.Mandos" - self._server_path = "/" - self._server_interface = "se.recompile.Mandos" - self._client_interface = "se.recompile.Mandos.Client" - self.calls = [] - self.call_method_return = Unique() - - def call_method(self, methodname, busname, objectpath, - interface, *args): - self.calls.append((methodname, busname, objectpath, - interface, args)) - return self.call_method_return - - def setUp(self): - self.bus = self.MockMandosBus() - - def test_set_client_property(self): - self.bus.set_client_property("objectpath", "key", "value") - expected_call = ("Set", self.bus._name, "objectpath", - "org.freedesktop.DBus.Properties", - (self.bus._client_interface, "key", "value")) - self.assertIn(expected_call, self.bus.calls) - - def test_call_client_method(self): - ret = self.bus.call_client_method("objectpath", "methodname") - self.assertIs(self.bus.call_method_return, ret) - expected_call = ("methodname", self.bus._name, "objectpath", - self.bus._client_interface, ()) - self.assertIn(expected_call, self.bus.calls) - - def test_call_client_method_with_args(self): - args = (Unique(), Unique()) - ret = self.bus.call_client_method("objectpath", "methodname", - *args) - self.assertIs(self.bus.call_method_return, ret) - expected_call = ("methodname", self.bus._name, "objectpath", - self.bus._client_interface, - (args[0], args[1])) - self.assertIn(expected_call, self.bus.calls) - - def test_get_clients_and_properties(self): - managed_objects = { - "objectpath": { - self.bus._client_interface: { - "key": "value", - "bool": True, - }, - "irrelevant_interface": { - "key": "othervalue", - "bool": False, - }, - }, - "other_objectpath": { - "other_irrelevant_interface": { - "key": "value 3", - "bool": None, - }, - }, - } - expected_clients_and_properties = { - "objectpath": { - "key": "value", - "bool": True, - } - } - self.bus.call_method_return = managed_objects - ret = self.bus.get_clients_and_properties() - self.assertDictEqual(expected_clients_and_properties, ret) - expected_call = ("GetManagedObjects", self.bus._name, - self.bus._server_path, - "org.freedesktop.DBus.ObjectManager", ()) - self.assertIn(expected_call, self.bus.calls) - - def test_call_server_method(self): - ret = self.bus.call_server_method("methodname") - self.assertIs(self.bus.call_method_return, ret) - expected_call = ("methodname", self.bus._name, - self.bus._server_path, - self.bus._server_interface, ()) - self.assertIn(expected_call, self.bus.calls) - - def test_call_server_method_with_args(self): - args = (Unique(), Unique()) - ret = self.bus.call_server_method("methodname", *args) - self.assertIs(self.bus.call_method_return, ret) - expected_call = ("methodname", self.bus._name, - self.bus._server_path, - self.bus._server_interface, - (args[0], args[1])) - self.assertIn(expected_call, self.bus.calls) - - -class Test_dbus_python_adapter_SystemBus(TestCaseWithAssertLogs): - - def MockDBusPython_func(self, func): - class mock_dbus_python(object): - """mock dbus-python module""" - class exceptions(object): - """Pseudo-namespace""" - class DBusException(Exception): - pass - class SystemBus(object): - @staticmethod - def get_object(busname, objectpath): - DBusObject = collections.namedtuple( - "DBusObject", ("methodname", "Set")) - def method(*args, **kwargs): - self.assertEqual({"dbus_interface": - "interface"}, - kwargs) - return func(*args) - def set_property(interface, key, value, - dbus_interface=None): - self.assertEqual( - "org.freedesktop.DBus.Properties", - dbus_interface) - self.assertEqual("Secret", key) - return func(interface, key, value, - dbus_interface=dbus_interface) - return DBusObject(methodname=method, - Set=set_property) - class Boolean(object): - def __init__(self, value): - self.value = bool(value) - def __bool__(self): - return self.value - if sys.version_info.major == 2: - __nonzero__ = __bool__ - class ObjectPath(str): - pass - class Dictionary(dict): - pass - class ByteArray(bytes): - pass - return mock_dbus_python - - def call_method(self, bus, methodname, busname, objectpath, - interface, *args): - with self.assertLogs(log, logging.DEBUG): - return bus.call_method(methodname, busname, objectpath, - interface, *args) - - def test_call_method_returns(self): - expected_method_return = Unique() - method_args = (Unique(), Unique()) - def func(*args): - self.assertEqual(len(method_args), len(args)) - for marg, arg in zip(method_args, args): - self.assertIs(marg, arg) - return expected_method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface", - *method_args) - self.assertIs(ret, expected_method_return) - - def test_call_method_filters_bool_true(self): - def func(): - return method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - method_return = mock_dbus_python.Boolean(True) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - self.assertTrue(ret) - self.assertNotIsInstance(ret, mock_dbus_python.Boolean) - - def test_call_method_filters_bool_false(self): - def func(): - return method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - method_return = mock_dbus_python.Boolean(False) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - self.assertFalse(ret) - self.assertNotIsInstance(ret, mock_dbus_python.Boolean) - - def test_call_method_filters_objectpath(self): - def func(): - return method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - method_return = mock_dbus_python.ObjectPath("objectpath") - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - self.assertEqual("objectpath", ret) - self.assertIsNot("objectpath", ret) - self.assertNotIsInstance(ret, mock_dbus_python.ObjectPath) - - def test_call_method_filters_booleans_in_dict(self): - def func(): - return method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - method_return = mock_dbus_python.Dictionary( - {mock_dbus_python.Boolean(True): - mock_dbus_python.Boolean(False), - mock_dbus_python.Boolean(False): - mock_dbus_python.Boolean(True)}) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - expected_method_return = {True: False, - False: True} - self.assertEqual(expected_method_return, ret) - self.assertNotIsInstance(ret, mock_dbus_python.Dictionary) - - def test_call_method_filters_objectpaths_in_dict(self): - def func(): - return method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - method_return = mock_dbus_python.Dictionary( - {mock_dbus_python.ObjectPath("objectpath_key_1"): - mock_dbus_python.ObjectPath("objectpath_value_1"), - mock_dbus_python.ObjectPath("objectpath_key_2"): - mock_dbus_python.ObjectPath("objectpath_value_2")}) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - expected_method_return = {str(key): str(value) - for key, value in - method_return.items()} - self.assertEqual(expected_method_return, ret) - self.assertIsInstance(ret, dict) - self.assertNotIsInstance(ret, mock_dbus_python.Dictionary) - - def test_call_method_filters_dict_in_dict(self): - def func(): - return method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - method_return = mock_dbus_python.Dictionary( - {"key1": mock_dbus_python.Dictionary({"key11": "value11", - "key12": "value12"}), - "key2": mock_dbus_python.Dictionary({"key21": "value21", - "key22": "value22"})}) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - expected_method_return = { - "key1": {"key11": "value11", - "key12": "value12"}, - "key2": {"key21": "value21", - "key22": "value22"}, - } - self.assertEqual(expected_method_return, ret) - self.assertIsInstance(ret, dict) - self.assertNotIsInstance(ret, mock_dbus_python.Dictionary) - for key, value in ret.items(): - self.assertIsInstance(value, dict) - self.assertEqual(expected_method_return[key], value) - self.assertNotIsInstance(value, - mock_dbus_python.Dictionary) - - def test_call_method_filters_dict_three_deep(self): - def func(): - return method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - method_return = mock_dbus_python.Dictionary( - {"key1": - mock_dbus_python.Dictionary( - {"key2": - mock_dbus_python.Dictionary( - {"key3": - mock_dbus_python.Boolean(True), - }), - }), - }) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - expected_method_return = {"key1": {"key2": {"key3": True}}} - self.assertEqual(expected_method_return, ret) - self.assertIsInstance(ret, dict) - self.assertNotIsInstance(ret, mock_dbus_python.Dictionary) - self.assertIsInstance(ret["key1"], dict) - self.assertNotIsInstance(ret["key1"], - mock_dbus_python.Dictionary) - self.assertIsInstance(ret["key1"]["key2"], dict) - self.assertNotIsInstance(ret["key1"]["key2"], - mock_dbus_python.Dictionary) - self.assertTrue(ret["key1"]["key2"]["key3"]) - self.assertNotIsInstance(ret["key1"]["key2"]["key3"], - mock_dbus_python.Boolean) - - def test_call_method_handles_exception(self): - dbus_logger = logging.getLogger("dbus.proxies") - - def func(): - dbus_logger.error("Test") - raise mock_dbus_python.exceptions.DBusException() - - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - - class CountingHandler(logging.Handler): - count = 0 - def emit(self, record): - self.count += 1 - - counting_handler = CountingHandler() - - dbus_logger.addHandler(counting_handler) - - try: - with self.assertRaises(dbus.Error) as e: - self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - finally: - dbus_logger.removeFilter(counting_handler) - - self.assertNotIsInstance(e, dbus.ConnectFailed) - - # Make sure the dbus logger was suppressed - self.assertEqual(0, counting_handler.count) - - def test_Set_Secret_sends_bytearray(self): - ret = [None] - def func(*args, **kwargs): - ret[0] = (args, kwargs) - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - bus.set_client_property("objectpath", "Secret", "value") - expected_call = (("se.recompile.Mandos.Client", "Secret", - mock_dbus_python.ByteArray(b"value")), - {"dbus_interface": - "org.freedesktop.DBus.Properties"}) - self.assertEqual(expected_call, ret[0]) - if sys.version_info.major == 2: - self.assertIsInstance(ret[0][0][-1], - mock_dbus_python.ByteArray) - - def test_get_object_converts_to_correct_exception(self): - bus = dbus_python_adapter.SystemBus( - self.fake_dbus_python_raises_exception_on_connect) - with self.assertRaises(dbus.ConnectFailed): - self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - - class fake_dbus_python_raises_exception_on_connect(object): - """fake dbus-python module""" - class exceptions(object): - """Pseudo-namespace""" - class DBusException(Exception): - pass - - @classmethod - def SystemBus(cls): - def get_object(busname, objectpath): - raise cls.exceptions.DBusException() - Bus = collections.namedtuple("Bus", ["get_object"]) - return Bus(get_object=get_object) - - -class Test_dbus_python_adapter_CachingBus(unittest.TestCase): - class mock_dbus_python(object): - """mock dbus-python modules""" - class SystemBus(object): - @staticmethod - def get_object(busname, objectpath): - return Unique() - - def setUp(self): - self.bus = dbus_python_adapter.CachingBus( - self.mock_dbus_python) - - def test_returns_distinct_objectpaths(self): - obj1 = self.bus.get_object("busname", "objectpath1") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get_object("busname", "objectpath2") - self.assertIsInstance(obj2, Unique) - self.assertIsNot(obj1, obj2) - - def test_returns_distinct_busnames(self): - obj1 = self.bus.get_object("busname1", "objectpath") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get_object("busname2", "objectpath") - self.assertIsInstance(obj2, Unique) - self.assertIsNot(obj1, obj2) - - def test_returns_distinct_both(self): - obj1 = self.bus.get_object("busname1", "objectpath") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get_object("busname2", "objectpath") - self.assertIsInstance(obj2, Unique) - self.assertIsNot(obj1, obj2) - - def test_returns_same(self): - obj1 = self.bus.get_object("busname", "objectpath") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get_object("busname", "objectpath") - self.assertIsInstance(obj2, Unique) - self.assertIs(obj1, obj2) - - def test_returns_same_old(self): - obj1 = self.bus.get_object("busname1", "objectpath1") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get_object("busname2", "objectpath2") - self.assertIsInstance(obj2, Unique) - obj1b = self.bus.get_object("busname1", "objectpath1") - self.assertIsInstance(obj1b, Unique) - self.assertIsNot(obj1, obj2) - self.assertIsNot(obj2, obj1b) - self.assertIs(obj1, obj1b) - - -class Test_pydbus_adapter_SystemBus(TestCaseWithAssertLogs): - - def Stub_pydbus_func(self, func): - class stub_pydbus(object): - """stub pydbus module""" - class SystemBus(object): - @staticmethod - def get(busname, objectpath): - DBusObject = collections.namedtuple( - "DBusObject", ("methodname",)) - return {"interface": - DBusObject(methodname=func)} - return stub_pydbus - - def call_method(self, bus, methodname, busname, objectpath, - interface, *args): - with self.assertLogs(log, logging.DEBUG): - return bus.call_method(methodname, busname, objectpath, - interface, *args) - - def test_call_method_returns(self): - expected_method_return = Unique() - method_args = (Unique(), Unique()) - def func(*args): - self.assertEqual(len(method_args), len(args)) - for marg, arg in zip(method_args, args): - self.assertIs(marg, arg) - return expected_method_return - stub_pydbus = self.Stub_pydbus_func(func) - bus = pydbus_adapter.SystemBus(stub_pydbus) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface", - *method_args) - self.assertIs(ret, expected_method_return) - - def test_call_method_handles_exception(self): - dbus_logger = logging.getLogger("dbus.proxies") - - def func(): - raise gi.repository.GLib.Error() - - stub_pydbus = self.Stub_pydbus_func(func) - bus = pydbus_adapter.SystemBus(stub_pydbus) - - with self.assertRaises(dbus.Error) as e: - self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - - self.assertNotIsInstance(e, dbus.ConnectFailed) - - def test_get_converts_to_correct_exception(self): - bus = pydbus_adapter.SystemBus( - self.fake_pydbus_raises_exception_on_connect) - with self.assertRaises(dbus.ConnectFailed): - self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - - class fake_pydbus_raises_exception_on_connect(object): - """fake dbus-python module""" - @classmethod - def SystemBus(cls): - def get(busname, objectpath): - raise gi.repository.GLib.Error() - Bus = collections.namedtuple("Bus", ["get"]) - return Bus(get=get) - - def test_set_property_uses_setattr(self): - class Object(object): - pass - obj = Object() - class pydbus_spy(object): - class SystemBus(object): - @staticmethod - def get(busname, objectpath): - return {"interface": obj} - bus = pydbus_adapter.SystemBus(pydbus_spy) - value = Unique() - bus.set_property("busname", "objectpath", "interface", "key", - value) - self.assertIs(value, obj.key) - - def test_get_suppresses_xml_deprecation_warning(self): - if sys.version_info.major >= 3: - return - class stub_pydbus_get(object): - class SystemBus(object): - @staticmethod - def get(busname, objectpath): - warnings.warn_explicit( - "deprecated", DeprecationWarning, - "xml.etree.ElementTree", 0) - bus = pydbus_adapter.SystemBus(stub_pydbus_get) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - bus.get("busname", "objectpath") - self.assertEqual(0, len(w)) - - -class Test_pydbus_adapter_CachingBus(unittest.TestCase): - class stub_pydbus(object): - """stub pydbus module""" - class SystemBus(object): - @staticmethod - def get(busname, objectpath): - return Unique() - - def setUp(self): - self.bus = pydbus_adapter.CachingBus(self.stub_pydbus) - - def test_returns_distinct_objectpaths(self): - obj1 = self.bus.get("busname", "objectpath1") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get("busname", "objectpath2") - self.assertIsInstance(obj2, Unique) - self.assertIsNot(obj1, obj2) - - def test_returns_distinct_busnames(self): - obj1 = self.bus.get("busname1", "objectpath") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get("busname2", "objectpath") - self.assertIsInstance(obj2, Unique) - self.assertIsNot(obj1, obj2) - - def test_returns_distinct_both(self): - obj1 = self.bus.get("busname1", "objectpath") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get("busname2", "objectpath") - self.assertIsInstance(obj2, Unique) - self.assertIsNot(obj1, obj2) - - def test_returns_same(self): - obj1 = self.bus.get("busname", "objectpath") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get("busname", "objectpath") - self.assertIsInstance(obj2, Unique) - self.assertIs(obj1, obj2) - - def test_returns_same_old(self): - obj1 = self.bus.get("busname1", "objectpath1") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get("busname2", "objectpath2") - self.assertIsInstance(obj2, Unique) - obj1b = self.bus.get("busname1", "objectpath1") - self.assertIsInstance(obj1b, Unique) - self.assertIsNot(obj1, obj2) - self.assertIsNot(obj2, obj1b) - self.assertIs(obj1, obj1b) - - -class Test_commands_from_options(unittest.TestCase): - - def setUp(self): - self.parser = argparse.ArgumentParser() - add_command_line_options(self.parser) - - def test_is_enabled(self): - self.assert_command_from_args(["--is-enabled", "client"], - command.IsEnabled) - - def assert_command_from_args(self, args, command_cls, - **cmd_attrs): - """Assert that parsing ARGS should result in an instance of -COMMAND_CLS with (optionally) all supplied attributes (CMD_ATTRS).""" - options = self.parser.parse_args(args) - check_option_syntax(self.parser, options) - commands = commands_from_options(options) - self.assertEqual(1, len(commands)) - command = commands[0] - self.assertIsInstance(command, command_cls) - for key, value in cmd_attrs.items(): - self.assertEqual(value, getattr(command, key)) - - def test_is_enabled_short(self): - self.assert_command_from_args(["-V", "client"], - command.IsEnabled) - - def test_approve(self): - self.assert_command_from_args(["--approve", "client"], - command.Approve) - - def test_approve_short(self): - self.assert_command_from_args(["-A", "client"], - command.Approve) - - def test_deny(self): - self.assert_command_from_args(["--deny", "client"], - command.Deny) - - def test_deny_short(self): - self.assert_command_from_args(["-D", "client"], command.Deny) - - def test_remove(self): - self.assert_command_from_args(["--remove", "client"], - command.Remove) - - def test_deny_before_remove(self): - options = self.parser.parse_args(["--deny", "--remove", - "client"]) - check_option_syntax(self.parser, options) - commands = commands_from_options(options) - self.assertEqual(2, len(commands)) - self.assertIsInstance(commands[0], command.Deny) - self.assertIsInstance(commands[1], command.Remove) - - def test_deny_before_remove_reversed(self): - options = self.parser.parse_args(["--remove", "--deny", - "--all"]) - check_option_syntax(self.parser, options) - commands = commands_from_options(options) - self.assertEqual(2, len(commands)) - self.assertIsInstance(commands[0], command.Deny) - self.assertIsInstance(commands[1], command.Remove) - - def test_remove_short(self): - self.assert_command_from_args(["-r", "client"], - command.Remove) - - def test_dump_json(self): - self.assert_command_from_args(["--dump-json"], - command.DumpJSON) - - def test_enable(self): - self.assert_command_from_args(["--enable", "client"], - command.Enable) - - def test_enable_short(self): - self.assert_command_from_args(["-e", "client"], - command.Enable) - - def test_disable(self): - self.assert_command_from_args(["--disable", "client"], - command.Disable) - - def test_disable_short(self): - self.assert_command_from_args(["-d", "client"], - command.Disable) - - def test_bump_timeout(self): - self.assert_command_from_args(["--bump-timeout", "client"], - command.BumpTimeout) - - def test_bump_timeout_short(self): - self.assert_command_from_args(["-b", "client"], - command.BumpTimeout) - - def test_start_checker(self): - self.assert_command_from_args(["--start-checker", "client"], - command.StartChecker) - - def test_stop_checker(self): - self.assert_command_from_args(["--stop-checker", "client"], - command.StopChecker) - - def test_approve_by_default(self): - self.assert_command_from_args(["--approve-by-default", - "client"], - command.ApproveByDefault) - - def test_deny_by_default(self): - self.assert_command_from_args(["--deny-by-default", "client"], - command.DenyByDefault) - - def test_checker(self): - self.assert_command_from_args(["--checker", ":", "client"], - command.SetChecker, - value_to_set=":") - - def test_checker_empty(self): - self.assert_command_from_args(["--checker", "", "client"], - command.SetChecker, - value_to_set="") - - def test_checker_short(self): - self.assert_command_from_args(["-c", ":", "client"], - command.SetChecker, - value_to_set=":") - - def test_host(self): - self.assert_command_from_args( - ["--host", "client.example.org", "client"], - command.SetHost, value_to_set="client.example.org") - - def test_host_short(self): - self.assert_command_from_args( - ["-H", "client.example.org", "client"], command.SetHost, - value_to_set="client.example.org") - - def test_secret_devnull(self): - self.assert_command_from_args(["--secret", os.path.devnull, - "client"], command.SetSecret, - value_to_set=b"") - - def test_secret_tempfile(self): - with tempfile.NamedTemporaryFile(mode="r+b") as f: - value = b"secret\0xyzzy\nbar" - f.write(value) - f.seek(0) - self.assert_command_from_args(["--secret", f.name, - "client"], - command.SetSecret, - value_to_set=value) - - def test_secret_devnull_short(self): - self.assert_command_from_args(["-s", os.path.devnull, - "client"], command.SetSecret, - value_to_set=b"") - - def test_secret_tempfile_short(self): - with tempfile.NamedTemporaryFile(mode="r+b") as f: - value = b"secret\0xyzzy\nbar" - f.write(value) - f.seek(0) - self.assert_command_from_args(["-s", f.name, "client"], - command.SetSecret, - value_to_set=value) - - def test_timeout(self): - self.assert_command_from_args(["--timeout", "PT5M", "client"], - command.SetTimeout, - value_to_set=300000) - - def test_timeout_short(self): - self.assert_command_from_args(["-t", "PT5M", "client"], - command.SetTimeout, - value_to_set=300000) - - def test_extended_timeout(self): - self.assert_command_from_args(["--extended-timeout", "PT15M", - "client"], - command.SetExtendedTimeout, - value_to_set=900000) - - def test_interval(self): - self.assert_command_from_args(["--interval", "PT2M", - "client"], command.SetInterval, - value_to_set=120000) - - def test_interval_short(self): - self.assert_command_from_args(["-i", "PT2M", "client"], - command.SetInterval, - value_to_set=120000) - - def test_approval_delay(self): - self.assert_command_from_args(["--approval-delay", "PT30S", - "client"], - command.SetApprovalDelay, - value_to_set=30000) - - def test_approval_duration(self): - self.assert_command_from_args(["--approval-duration", "PT1S", - "client"], - command.SetApprovalDuration, - value_to_set=1000) - - def test_print_table(self): - self.assert_command_from_args([], command.PrintTable, - verbose=False) - - def test_print_table_verbose(self): - self.assert_command_from_args(["--verbose"], - command.PrintTable, - verbose=True) - - def test_print_table_verbose_short(self): - self.assert_command_from_args(["-v"], command.PrintTable, - verbose=True) - - -class TestCommand(unittest.TestCase): - """Abstract class for tests of command classes""" - - class FakeMandosBus(dbus.MandosBus): - def __init__(self, testcase): - self.client_properties = { - "Name": "foo", - "KeyID": ("92ed150794387c03ce684574b1139a65" - "94a34f895daaaf09fd8ea90a27cddb12"), - "Secret": b"secret", - "Host": "foo.example.org", - "Enabled": True, - "Timeout": 300000, - "LastCheckedOK": "2019-02-03T00:00:00", - "Created": "2019-01-02T00:00:00", - "Interval": 120000, - "Fingerprint": ("778827225BA7DE539C5A" - "7CFA59CFF7CDBD9A5920"), - "CheckerRunning": False, - "LastEnabled": "2019-01-03T00:00:00", - "ApprovalPending": False, - "ApprovedByDefault": True, - "LastApprovalRequest": "", - "ApprovalDelay": 0, - "ApprovalDuration": 1000, - "Checker": "fping -q -- %(host)s", - "ExtendedTimeout": 900000, - "Expires": "2019-02-04T00:00:00", - "LastCheckerStatus": 0, - } - self.other_client_properties = { - "Name": "barbar", - "KeyID": ("0558568eedd67d622f5c83b35a115f79" - "6ab612cff5ad227247e46c2b020f441c"), - "Secret": b"secretbar", - "Host": "192.0.2.3", - "Enabled": True, - "Timeout": 300000, - "LastCheckedOK": "2019-02-04T00:00:00", - "Created": "2019-01-03T00:00:00", - "Interval": 120000, - "Fingerprint": ("3E393AEAEFB84C7E89E2" - "F547B3A107558FCA3A27"), - "CheckerRunning": True, - "LastEnabled": "2019-01-04T00:00:00", - "ApprovalPending": False, - "ApprovedByDefault": False, - "LastApprovalRequest": "2019-01-03T00:00:00", - "ApprovalDelay": 30000, - "ApprovalDuration": 93785000, - "Checker": ":", - "ExtendedTimeout": 900000, - "Expires": "2019-02-05T00:00:00", - "LastCheckerStatus": -2, - } - self.clients = collections.OrderedDict( - [ - ("client_objectpath", self.client_properties), - ("other_client_objectpath", - self.other_client_properties), - ]) - self.one_client = {"client_objectpath": - self.client_properties} - self.testcase = testcase - self.calls = [] - - def call_method(self, methodname, busname, objectpath, - interface, *args): - self.testcase.assertEqual("se.recompile.Mandos", busname) - self.calls.append((methodname, busname, objectpath, - interface, args)) - if interface == "org.freedesktop.DBus.Properties": - if methodname == "Set": - self.testcase.assertEqual(3, len(args)) - interface, key, value = args - self.testcase.assertEqual( - "se.recompile.Mandos.Client", interface) - self.clients[objectpath][key] = value - return - elif interface == "se.recompile.Mandos": - self.testcase.assertEqual("RemoveClient", methodname) - self.testcase.assertEqual(1, len(args)) - clientpath = args[0] - del self.clients[clientpath] - return - elif interface == "se.recompile.Mandos.Client": - if methodname == "Approve": - self.testcase.assertEqual(1, len(args)) - return - raise ValueError() - - def setUp(self): - self.bus = self.FakeMandosBus(self) - - -class TestBaseCommands(TestCommand): - - def test_IsEnabled_exits_successfully(self): - with self.assertRaises(SystemExit) as e: - command.IsEnabled().run(self.bus.one_client) - if e.exception.code is not None: - self.assertEqual(0, e.exception.code) - else: - self.assertIsNone(e.exception.code) - - def test_IsEnabled_exits_with_failure(self): - self.bus.client_properties["Enabled"] = False - with self.assertRaises(SystemExit) as e: - command.IsEnabled().run(self.bus.one_client) - if isinstance(e.exception.code, int): - self.assertNotEqual(0, e.exception.code) - else: - self.assertIsNotNone(e.exception.code) - - def test_Approve(self): - busname = "se.recompile.Mandos" - client_interface = "se.recompile.Mandos.Client" - command.Approve().run(self.bus.clients, self.bus) - for clientpath in self.bus.clients: - self.assertIn(("Approve", busname, clientpath, - client_interface, (True,)), self.bus.calls) - - def test_Deny(self): - busname = "se.recompile.Mandos" - client_interface = "se.recompile.Mandos.Client" - command.Deny().run(self.bus.clients, self.bus) - for clientpath in self.bus.clients: - self.assertIn(("Approve", busname, clientpath, - client_interface, (False,)), - self.bus.calls) - - def test_Remove(self): - command.Remove().run(self.bus.clients, self.bus) - for clientpath in self.bus.clients: - self.assertIn(("RemoveClient", dbus_busname, - dbus_server_path, dbus_server_interface, - (clientpath,)), self.bus.calls) - - expected_json = { - "foo": { - "Name": "foo", - "KeyID": ("92ed150794387c03ce684574b1139a65" - "94a34f895daaaf09fd8ea90a27cddb12"), - "Host": "foo.example.org", - "Enabled": True, - "Timeout": 300000, - "LastCheckedOK": "2019-02-03T00:00:00", - "Created": "2019-01-02T00:00:00", - "Interval": 120000, - "Fingerprint": ("778827225BA7DE539C5A" - "7CFA59CFF7CDBD9A5920"), - "CheckerRunning": False, - "LastEnabled": "2019-01-03T00:00:00", - "ApprovalPending": False, - "ApprovedByDefault": True, - "LastApprovalRequest": "", - "ApprovalDelay": 0, - "ApprovalDuration": 1000, - "Checker": "fping -q -- %(host)s", - "ExtendedTimeout": 900000, - "Expires": "2019-02-04T00:00:00", - "LastCheckerStatus": 0, - }, - "barbar": { - "Name": "barbar", - "KeyID": ("0558568eedd67d622f5c83b35a115f79" - "6ab612cff5ad227247e46c2b020f441c"), - "Host": "192.0.2.3", - "Enabled": True, - "Timeout": 300000, - "LastCheckedOK": "2019-02-04T00:00:00", - "Created": "2019-01-03T00:00:00", - "Interval": 120000, - "Fingerprint": ("3E393AEAEFB84C7E89E2" - "F547B3A107558FCA3A27"), - "CheckerRunning": True, - "LastEnabled": "2019-01-04T00:00:00", - "ApprovalPending": False, - "ApprovedByDefault": False, - "LastApprovalRequest": "2019-01-03T00:00:00", - "ApprovalDelay": 30000, - "ApprovalDuration": 93785000, - "Checker": ":", - "ExtendedTimeout": 900000, - "Expires": "2019-02-05T00:00:00", - "LastCheckerStatus": -2, - }, - } - - def test_DumpJSON_normal(self): - with self.capture_stdout_to_buffer() as buffer: - command.DumpJSON().run(self.bus.clients) - json_data = json.loads(buffer.getvalue()) - self.assertDictEqual(self.expected_json, json_data) - - @staticmethod - @contextlib.contextmanager - def capture_stdout_to_buffer(): - capture_buffer = io.StringIO() - old_stdout = sys.stdout - sys.stdout = capture_buffer - try: - yield capture_buffer - finally: - sys.stdout = old_stdout - - def test_DumpJSON_one_client(self): - with self.capture_stdout_to_buffer() as buffer: - command.DumpJSON().run(self.bus.one_client) - json_data = json.loads(buffer.getvalue()) - expected_json = {"foo": self.expected_json["foo"]} - self.assertDictEqual(expected_json, json_data) - - def test_PrintTable_normal(self): - with self.capture_stdout_to_buffer() as buffer: - command.PrintTable().run(self.bus.clients) - expected_output = "\n".join(( - "Name Enabled Timeout Last Successful Check", - "foo Yes 00:05:00 2019-02-03T00:00:00 ", - "barbar Yes 00:05:00 2019-02-04T00:00:00 ", - )) + "\n" - self.assertEqual(expected_output, buffer.getvalue()) - - def test_PrintTable_verbose(self): - with self.capture_stdout_to_buffer() as buffer: - command.PrintTable(verbose=True).run(self.bus.clients) - columns = ( - ( - "Name ", - "foo ", - "barbar ", - ),( - "Enabled ", - "Yes ", - "Yes ", - ),( - "Timeout ", - "00:05:00 ", - "00:05:00 ", - ),( - "Last Successful Check ", - "2019-02-03T00:00:00 ", - "2019-02-04T00:00:00 ", - ),( - "Created ", - "2019-01-02T00:00:00 ", - "2019-01-03T00:00:00 ", - ),( - "Interval ", - "00:02:00 ", - "00:02:00 ", - ),( - "Host ", - "foo.example.org ", - "192.0.2.3 ", - ),( - ("Key ID " - " "), - ("92ed150794387c03ce684574b1139a6594a34f895daaaf09fd8" - "ea90a27cddb12 "), - ("0558568eedd67d622f5c83b35a115f796ab612cff5ad227247e" - "46c2b020f441c "), - ),( - "Fingerprint ", - "778827225BA7DE539C5A7CFA59CFF7CDBD9A5920 ", - "3E393AEAEFB84C7E89E2F547B3A107558FCA3A27 ", - ),( - "Check Is Running ", - "No ", - "Yes ", - ),( - "Last Enabled ", - "2019-01-03T00:00:00 ", - "2019-01-04T00:00:00 ", - ),( - "Approval Is Pending ", - "No ", - "No ", - ),( - "Approved By Default ", - "Yes ", - "No ", - ),( - "Last Approval Request ", - " ", - "2019-01-03T00:00:00 ", - ),( - "Approval Delay ", - "00:00:00 ", - "00:00:30 ", - ),( - "Approval Duration ", - "00:00:01 ", - "1T02:03:05 ", - ),( - "Checker ", - "fping -q -- %(host)s ", - ": ", - ),( - "Extended Timeout ", - "00:15:00 ", - "00:15:00 ", - ),( - "Expires ", - "2019-02-04T00:00:00 ", - "2019-02-05T00:00:00 ", - ),( - "Last Checker Status", - "0 ", - "-2 ", - ) - ) - num_lines = max(len(rows) for rows in columns) - expected_output = ("\n".join("".join(rows[line] - for rows in columns) - for line in range(num_lines)) - + "\n") - self.assertEqual(expected_output, buffer.getvalue()) - - def test_PrintTable_one_client(self): - with self.capture_stdout_to_buffer() as buffer: - command.PrintTable().run(self.bus.one_client) - expected_output = "\n".join(( - "Name Enabled Timeout Last Successful Check", - "foo Yes 00:05:00 2019-02-03T00:00:00 ", - )) + "\n" - self.assertEqual(expected_output, buffer.getvalue()) - - -class TestPropertySetterCmd(TestCommand): - """Abstract class for tests of command.PropertySetter classes""" - - def runTest(self): - if not hasattr(self, "command"): - return # Abstract TestCase class - - if hasattr(self, "values_to_set"): - cmd_args = [(value,) for value in self.values_to_set] - values_to_get = getattr(self, "values_to_get", - self.values_to_set) - else: - cmd_args = [() for x in range(len(self.values_to_get))] - values_to_get = self.values_to_get - for value_to_get, cmd_arg in zip(values_to_get, cmd_args): - for clientpath in self.bus.clients: - self.bus.clients[clientpath][self.propname] = ( - Unique()) - self.command(*cmd_arg).run(self.bus.clients, self.bus) - for clientpath in self.bus.clients: - value = (self.bus.clients[clientpath] - [self.propname]) - self.assertNotIsInstance(value, Unique) - self.assertEqual(value_to_get, value) - - -class TestEnableCmd(TestPropertySetterCmd): - command = command.Enable - propname = "Enabled" - values_to_get = [True] - - -class TestDisableCmd(TestPropertySetterCmd): - command = command.Disable - propname = "Enabled" - values_to_get = [False] - - -class TestBumpTimeoutCmd(TestPropertySetterCmd): - command = command.BumpTimeout - propname = "LastCheckedOK" - values_to_get = [""] - - -class TestStartCheckerCmd(TestPropertySetterCmd): - command = command.StartChecker - propname = "CheckerRunning" - values_to_get = [True] - - -class TestStopCheckerCmd(TestPropertySetterCmd): - command = command.StopChecker - propname = "CheckerRunning" - values_to_get = [False] - - -class TestApproveByDefaultCmd(TestPropertySetterCmd): - command = command.ApproveByDefault - propname = "ApprovedByDefault" - values_to_get = [True] - - -class TestDenyByDefaultCmd(TestPropertySetterCmd): - command = command.DenyByDefault - propname = "ApprovedByDefault" - values_to_get = [False] - - -class TestSetCheckerCmd(TestPropertySetterCmd): - command = command.SetChecker - propname = "Checker" - values_to_set = ["", ":", "fping -q -- %s"] - - -class TestSetHostCmd(TestPropertySetterCmd): - command = command.SetHost - propname = "Host" - values_to_set = ["192.0.2.3", "client.example.org"] - - -class TestSetSecretCmd(TestPropertySetterCmd): - command = command.SetSecret - propname = "Secret" - values_to_set = [io.BytesIO(b""), - io.BytesIO(b"secret\0xyzzy\nbar")] - values_to_get = [f.getvalue() for f in values_to_set] - - -class TestSetTimeoutCmd(TestPropertySetterCmd): - command = command.SetTimeout - propname = "Timeout" - values_to_set = [datetime.timedelta(), - datetime.timedelta(minutes=5), - datetime.timedelta(seconds=1), - datetime.timedelta(weeks=1), - datetime.timedelta(weeks=52)] - values_to_get = [dt.total_seconds()*1000 for dt in values_to_set] - - -class TestSetExtendedTimeoutCmd(TestPropertySetterCmd): - command = command.SetExtendedTimeout - propname = "ExtendedTimeout" - values_to_set = [datetime.timedelta(), - datetime.timedelta(minutes=5), - datetime.timedelta(seconds=1), - datetime.timedelta(weeks=1), - datetime.timedelta(weeks=52)] - values_to_get = [dt.total_seconds()*1000 for dt in values_to_set] - - -class TestSetIntervalCmd(TestPropertySetterCmd): - command = command.SetInterval - propname = "Interval" - values_to_set = [datetime.timedelta(), - datetime.timedelta(minutes=5), - datetime.timedelta(seconds=1), - datetime.timedelta(weeks=1), - datetime.timedelta(weeks=52)] - values_to_get = [dt.total_seconds()*1000 for dt in values_to_set] - - -class TestSetApprovalDelayCmd(TestPropertySetterCmd): - command = command.SetApprovalDelay - propname = "ApprovalDelay" - values_to_set = [datetime.timedelta(), - datetime.timedelta(minutes=5), - datetime.timedelta(seconds=1), - datetime.timedelta(weeks=1), - datetime.timedelta(weeks=52)] - values_to_get = [dt.total_seconds()*1000 for dt in values_to_set] - - -class TestSetApprovalDurationCmd(TestPropertySetterCmd): - command = command.SetApprovalDuration - propname = "ApprovalDuration" - values_to_set = [datetime.timedelta(), - datetime.timedelta(minutes=5), - datetime.timedelta(seconds=1), - datetime.timedelta(weeks=1), - datetime.timedelta(weeks=52)] - values_to_get = [dt.total_seconds()*1000 for dt in values_to_set] - - - -def should_only_run_tests(): - parser = argparse.ArgumentParser(add_help=False) - parser.add_argument("--check", action='store_true') - args, unknown_args = parser.parse_known_args() - run_tests = args.check - if run_tests: - # Remove --check argument from sys.argv - sys.argv[1:] = unknown_args - return run_tests - -# Add all tests from doctest strings -def load_tests(loader, tests, none): - import doctest - tests.addTests(doctest.DocTestSuite()) - return tests + sys.exit(1) + if options.checker is not None: + set_client_prop("Checker", options.checker) + if options.host is not None: + set_client_prop("Host", options.host) + if options.interval is not None: + set_client_prop_ms("Interval", options.interval) + if options.approval_delay is not None: + set_client_prop_ms("ApprovalDelay", + options.approval_delay) + if options.approval_duration is not None: + set_client_prop_ms("ApprovalDuration", + options.approval_duration) + if options.timeout is not None: + set_client_prop_ms("Timeout", options.timeout) + if options.extended_timeout is not None: + set_client_prop_ms("ExtendedTimeout", + options.extended_timeout) + if options.secret is not None: + set_client_prop("Secret", + dbus.ByteArray(options.secret.read())) + if options.approved_by_default is not None: + set_client_prop("ApprovedByDefault", + dbus.Boolean(options + .approved_by_default)) + if options.approve: + client.Approve(dbus.Boolean(True), + dbus_interface=client_interface) + elif options.deny: + client.Approve(dbus.Boolean(False), + dbus_interface=client_interface) + if __name__ == "__main__": - try: - if should_only_run_tests(): - # Call using ./tdd-python-script --check [--verbose] - unittest.main() - else: - main() - finally: - logging.shutdown() + main() === modified file 'mandos-ctl.xml' --- mandos-ctl.xml 2019-03-08 23:55:34 +0000 +++ mandos-ctl.xml 2018-02-08 10:23:55 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -40,7 +40,6 @@ 2016 2017 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -62,22 +61,6 @@ &COMMANDNAME; - - - - - - - - - - - CLIENT - - - - - &COMMANDNAME; @@ -94,10 +77,18 @@ + + + + + + + + + + + + + - @@ -175,18 +172,13 @@ &COMMANDNAME; - - - - - - - - - - - - + + + + + + + CLIENT @@ -198,7 +190,6 @@ - CLIENT @@ -512,15 +503,6 @@ - - - - Show debug output; currently, this means show D-Bus calls. - - - - - @@ -598,7 +580,7 @@ -&COMMANDNAME; --timeout="PT5M" --interval="PT1M" foo1.example.org foo2.example.org +&COMMANDNAME; --timeout="5m" --interval="1m" foo1.example.org foo2.example.org === modified file 'mandos-keygen' --- mandos-keygen 2019-02-11 06:31:42 +0000 +++ mandos-keygen 2018-02-08 10:23:55 +0000 @@ -1,9 +1,9 @@ #!/bin/sh -e # -# Mandos key generator - create new keys for a Mandos client +# Mandos key generator - create a new OpenPGP key for a Mandos client # -# Copyright © 2008-2019 Teddy Hogeborn -# Copyright © 2008-2019 Björn Påhlsson +# Copyright © 2008-2018 Teddy Hogeborn +# Copyright © 2008-2018 Björn Påhlsson # # This file is part of Mandos. # @@ -23,7 +23,7 @@ # Contact the authors at . # -VERSION="1.8.3" +VERSION="1.7.16" KEYDIR="/etc/keys/mandos" KEYTYPE=RSA @@ -34,7 +34,6 @@ KEYEMAIL="" KEYCOMMENT="" KEYEXPIRE=0 -TLS_KEYTYPE=ed25519 FORCE=no SSH=yes KEYCOMMENT_ORIG="$KEYCOMMENT" @@ -45,8 +44,8 @@ fi # Parse options -TEMP=`getopt --options vhpF:d:t:l:s:L:n:e:c:x:T:fS \ - --longoptions version,help,password,passfile:,dir:,type:,length:,subtype:,sublength:,name:,email:,comment:,expire:,tls-keytype:,force,no-ssh \ +TEMP=`getopt --options vhpF:d:t:l:s:L:n:e:c:x:fS \ + --longoptions version,help,password,passfile:,dir:,type:,length:,subtype:,sublength:,name:,email:,comment:,expire:,force,no-ssh \ --name "$0" -- "$@"` help(){ @@ -64,23 +63,21 @@ -v, --version Show program's version number and exit -h, --help Show this help message and exit -d DIR, --dir DIR Target directory for key files - -t TYPE, --type TYPE OpenPGP key type. Default is RSA. + -t TYPE, --type TYPE Key type. Default is RSA. -l BITS, --length BITS - OpenPGP key length in bits. Default is 4096. + Key length in bits. Default is 4096. -s TYPE, --subtype TYPE - OpenPGP subkey type. Default is RSA. + Subkey type. Default is RSA. -L BITS, --sublength BITS - OpenPGP subkey length in bits. Default 4096. + Subkey length in bits. Default is 4096. -n NAME, --name NAME Name of key. Default is the FQDN. -e ADDRESS, --email ADDRESS - Email address of OpenPGP key. Default empty. + Email address of key. Default is empty. -c TEXT, --comment TEXT - Comment field for OpenPGP key. Default empty. + Comment field for key. The default is empty. -x TIME, --expire TIME - OpenPGP key expire time. Default is none. + Key expire time. Default is no expiration. See gpg(1) for syntax. - -T TYPE, --tls-keytype TYPE - TLS key type. Default is ed25519. -f, --force Force overwriting old key files. Password creation options: @@ -109,7 +106,6 @@ -e|--email) KEYEMAIL="$2"; shift 2;; -c|--comment) KEYCOMMENT="$2"; shift 2;; -x|--expire) KEYEXPIRE="$2"; shift 2;; - -T|--tls-keytype) TLS_KEYTYPE="$2"; shift 2;; -f|--force) FORCE=yes; shift;; -S|--no-ssh) SSH=no; shift;; -v|--version) echo "$0 $VERSION"; exit;; @@ -125,8 +121,6 @@ SECKEYFILE="$KEYDIR/seckey.txt" PUBKEYFILE="$KEYDIR/pubkey.txt" -TLS_PRIVKEYFILE="$KEYDIR/tls-privkey.pem" -TLS_PUBKEYFILE="$KEYDIR/tls-pubkey.pem" # Check for some invalid values if [ ! -d "$KEYDIR" ]; then @@ -169,9 +163,7 @@ [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|*) FORCE=0;; esac - if { [ -e "$SECKEYFILE" ] || [ -e "$PUBKEYFILE" ] \ - || [ -e "$TLS_PRIVKEYFILE" ] \ - || [ -e "$TLS_PUBKEYFILE" ]; } \ + if { [ -e "$SECKEYFILE" ] || [ -e "$PUBKEYFILE" ]; } \ && [ "$FORCE" -eq 0 ]; then echo "Refusing to overwrite old key files; use --force" >&2 exit 1 @@ -187,7 +179,6 @@ # Create temporary gpg batch file BATCHFILE="`mktemp -t mandos-keygen-batch.XXXXXXXXXX`" - TLS_PRIVKEYTMP="`mktemp -t mandos-keygen-privkey.XXXXXXXXXX`" fi if [ "$mode" = password ]; then @@ -202,7 +193,6 @@ trap " set +e; \ test -n \"$SECFILE\" && shred --remove \"$SECFILE\"; \ -test -n \"$TLS_PRIVKEYTMP\" && shred --remove \"$TLS_PRIVKEYTMP\"; \ shred --remove \"$RINGDIR\"/sec* 2>/dev/null; test -n \"$BATCHFILE\" && rm --force \"$BATCHFILE\"; \ rm --recursive --force \"$RINGDIR\"; @@ -243,39 +233,6 @@ echo -n "Started: " date fi - - # Generate TLS private key - if certtool --generate-privkey --password='' \ - --outfile "$TLS_PRIVKEYTMP" --sec-param ultra \ - --key-type="$TLS_KEYTYPE" --pkcs8 --no-text 2>/dev/null; then - - # Backup any old key files - if cp --backup=numbered --force "$TLS_PRIVKEYFILE" "$TLS_PRIVKEYFILE" \ - 2>/dev/null; then - shred --remove "$TLS_PRIVKEYFILE" 2>/dev/null || : - fi - if cp --backup=numbered --force "$TLS_PUBKEYFILE" "$TLS_PUBKEYFILE" \ - 2>/dev/null; then - rm --force "$TLS_PUBKEYFILE" - fi - cp --archive "$TLS_PRIVKEYTMP" "$TLS_PRIVKEYFILE" - shred --remove "$TLS_PRIVKEYTMP" 2>/dev/null || : - - ## TLS public key - - # First try certtool from GnuTLS - if ! certtool --password='' --load-privkey="$TLS_PRIVKEYFILE" \ - --outfile="$TLS_PUBKEYFILE" --pubkey-info --no-text \ - 2>/dev/null; then - # Otherwise try OpenSSL - if ! openssl pkey -in "$TLS_PRIVKEYFILE" \ - -out "$TLS_PUBKEYFILE" -pubout; then - rm --force "$TLS_PUBKEYFILE" - # None of the commands succeded; give up - return 1 - fi - fi - fi # Make sure trustdb.gpg exists; # this is a workaround for Debian bug #737128 @@ -296,7 +253,7 @@ # Backup any old key files if cp --backup=numbered --force "$SECKEYFILE" "$SECKEYFILE" \ 2>/dev/null; then - shred --remove "$SECKEYFILE" 2>/dev/null || : + shred --remove "$SECKEYFILE" fi if cp --backup=numbered --force "$PUBKEYFILE" "$PUBKEYFILE" \ 2>/dev/null; then @@ -364,19 +321,6 @@ test -n "$FINGERPRINT" - if [ -r "$TLS_PUBKEYFILE" ]; then - KEY_ID="$(certtool --key-id --hash=sha256 \ - --infile="$TLS_PUBKEYFILE" 2>/dev/null || :)" - - if [ -z "$KEY_ID" ]; then - KEY_ID=$(openssl pkey -pubin -in "$TLS_PUBKEYFILE" \ - -outform der \ - | openssl sha256 \ - | sed --expression='s/^.*[^[:xdigit:]]//') - fi - test -n "$KEY_ID" - fi - FILECOMMENT="Encrypted password for a Mandos client" while [ ! -s "$SECFILE" ]; do @@ -416,11 +360,6 @@ cat <<-EOF [$KEYNAME] host = $KEYNAME - EOF - if [ -n "$KEY_ID" ]; then - echo "key_id = $KEY_ID" - fi - cat <<-EOF fingerprint = $FINGERPRINT secret = EOF @@ -444,7 +383,7 @@ set +e # Remove the password file, if any if [ -n "$SECFILE" ]; then - shred --remove "$SECFILE" 2>/dev/null + shred --remove "$SECFILE" fi # Remove the key rings shred --remove "$RINGDIR"/sec* 2>/dev/null === modified file 'mandos-keygen.xml' --- mandos-keygen.xml 2019-02-10 04:20:26 +0000 +++ mandos-keygen.xml 2018-02-08 10:23:55 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -42,7 +42,6 @@ 2016 2017 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -128,13 +127,6 @@ - - - - - @@ -188,12 +180,12 @@ DESCRIPTION &COMMANDNAME; is a program to generate the - TLS and OpenPGP keys used by + OpenPGP key used by mandos-client - 8mandos. The keys are - normally written to /etc/keys/mandos for later installation into - the initrd image, but this, and most other things, can be - changed with command line options. + 8mandos. The key is + normally written to /etc/mandos for later installation into the + initrd image, but this, and most other things, can be changed + with command line options. This program can also be used with the @@ -236,8 +228,8 @@ DIRECTORY - Target directory for key files. Default is /etc/keys/mandos. + Target directory for key files. Default is + /etc/mandos. @@ -249,7 +241,7 @@ TYPE - OpenPGP key type. Default is RSA. + Key type. Default is RSA. @@ -261,7 +253,7 @@ BITS - OpenPGP key length in bits. Default is 4096. + Key length in bits. Default is 4096. @@ -273,7 +265,8 @@ KEYTYPE - OpenPGP subkey type. Default is RSA + Subkey type. Default is RSA (Elgamal + encryption-only). @@ -285,7 +278,7 @@ BITS - OpenPGP subkey length in bits. Default is 4096. + Subkey length in bits. Default is 4096. @@ -329,18 +322,6 @@ - - - - - TLS key type. Default is ed25519 - - - - - @@ -355,8 +336,8 @@ Prompt for a password and encrypt it with the key already - present in either /etc/keys/mandos or - the directory specified with the + present in either /etc/mandos or the + directory specified with the option. Outputs, on standard output, a section suitable for inclusion in mandos-clients.confOVERVIEW - This program is a small utility to generate new TLS and OpenPGP - keys for new Mandos clients, and to generate sections for - inclusion in clients.conf on the server. + This program is a small utility to generate new OpenPGP keys for + new Mandos clients, and to generate sections for inclusion in + clients.conf on the server. @@ -442,7 +423,7 @@ - /etc/keys/mandos/seckey.txt + /etc/mandos/seckey.txt OpenPGP secret key file which will be created or @@ -451,7 +432,7 @@ - /etc/keys/mandos/pubkey.txt + /etc/mandos/pubkey.txt OpenPGP public key file which will be created or @@ -460,22 +441,6 @@ - /etc/keys/mandos/tls-privkey.pem - - - Private key file which will be created or overwritten. - - - - - /etc/keys/mandos/tls-pubkey.pem - - - Public key file which will be created or overwritten. - - - - /tmp @@ -516,9 +481,9 @@ - Prompt for a password, encrypt it with the keys in /etc/keys/mandos and output a - section suitable for clients.conf. + Prompt for a password, encrypt it with the key in /etc/mandos and output a section + suitable for clients.conf. &COMMANDNAME; --password @@ -526,7 +491,7 @@ - Prompt for a password, encrypt it with the keys in the + Prompt for a password, encrypt it with the key in the client-key directory and output a section suitable for clients.conf. === modified file 'mandos-monitor' --- mandos-monitor 2019-02-11 06:31:42 +0000 +++ mandos-monitor 2018-02-08 10:23:55 +0000 @@ -3,8 +3,8 @@ # # Mandos Monitor - Control and monitor the Mandos server # -# Copyright © 2009-2019 Teddy Hogeborn -# Copyright © 2009-2019 Björn Påhlsson +# Copyright © 2009-2018 Teddy Hogeborn +# Copyright © 2009-2018 Björn Påhlsson # # This file is part of Mandos. # @@ -59,7 +59,7 @@ domain = 'se.recompile' server_interface = domain + '.Mandos' client_interface = domain + '.Mandos.Client' -version = "1.8.3" +version = "1.7.16" try: dbus.OBJECT_MANAGER_IFACE @@ -444,7 +444,7 @@ self.clients_dict = {} # We will add Text widgets to this list - self.log = urwid.SimpleListWalker([]) + self.log = [] self.max_log_length = max_log_length self.log_level = log_level @@ -503,7 +503,7 @@ if self.max_log_length: if len(self.log) > self.max_log_length: del self.log[0:len(self.log)-self.max_log_length-1] - self.logbox.set_focus(len(self.logbox.body.contents)-1, + self.logbox.set_focus(len(self.logbox.body.contents), coming_from="above") self.refresh() === modified file 'mandos-monitor.xml' --- mandos-monitor.xml 2019-02-10 04:20:26 +0000 +++ mandos-monitor.xml 2018-02-08 10:23:55 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -40,7 +40,6 @@ 2016 2017 2018 - 2019 Teddy Hogeborn Björn Påhlsson === modified file 'mandos-options.xml' --- mandos-options.xml 2019-02-09 23:23:26 +0000 +++ mandos-options.xml 2015-07-20 03:03:33 +0000 @@ -48,11 +48,10 @@ GnuTLS priority string for the TLS handshake. - The default is - SECURE128​:!CTYPE-X.509​:+CTYPE-RAWPK​:!RSA​:!VERS-ALL​:+VERS-TLS1.3​:%PROFILE_ULTRA - when using raw public keys in TLS, and - SECURE256​:!CTYPE-X.509​:+CTYPE-OPENPGP​:!RSA​:+SIGN-DSA-SHA256 - when using OpenPGP keys in TLS,. See SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA + :+SIGN-DSA-SHA256. + See gnutls_priority_init 3 for the syntax. Warning: changing this may make the === removed file 'mandos-to-cryptroot-unlock' --- mandos-to-cryptroot-unlock 2018-08-19 14:58:40 +0000 +++ mandos-to-cryptroot-unlock 1970-01-01 00:00:00 +0000 @@ -1,82 +0,0 @@ -#!/bin/sh -# -# Script to get password from plugin-runner to cryptroot-unlock -# -# Copyright © 2018 Teddy Hogeborn -# Copyright © 2018 Björn Påhlsson -# -# This file is part of Mandos. -# -# Mandos 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. -# -# Mandos 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 Mandos. If not, see . -# -# Contact the authors at . - -# This script is made to run in the initramfs, and must not be run in -# the normal system environment. - -# Temporary file for the password -passfile=$(mktemp -p /run -t mandos.XXXXXX) -trap "rm -f -- $passfile 2>/dev/null" EXIT - -# Disable the plugins which conflict with "askpass" as distributed by -# cryptsetup. -cat <<-EOF >>/conf/conf.d/mandos/plugin-runner.conf - - --disable=askpass-fifo - --disable=password-prompt - --disable=plymouth -EOF - -# In case a password is retrieved by other means than by plugin-runner -# (such as typing it on the console into the prompt given by the -# "askpass" program), this dummy plugin will be made to exit -# successfully, thereby forcing plugin-runner to stop all its plugins -# and also exit itself. -cat <<-EOF > /lib/mandos/plugins.d/dummy - #!/bin/sh - - while [ -e /run/mandos-keep-running ]; do - sleep 1 - done - - # exit successfully to force plugin-runner to finish - exit 0 -EOF -chmod u=rwx,go=rx /lib/mandos/plugins.d/dummy - -# This file is the flag which keeps the dummy plugin running -touch /run/mandos-keep-running - -# Keep running plugin-runner and trying any password, until either a -# password is accepted by cryptroot-unlock, or plugin-runner fails, or -# the file /run/mandos-keep-running has been removed. -while type cryptroot-unlock >/dev/null 2>&1; do - /lib/mandos/plugin-runner > "$passfile" & - echo $! > /run/mandos-plugin-runner.pid - wait %% || break - - # Try this password ten times (or ten seconds) - for loop in 1 2 3 4 5 6 7 8 9 10; do - if [ -e /run/mandos-keep-running ]; then - cryptroot-unlock < "$passfile" >/dev/null 2>&1 && break 2 - sleep 1 - else - break 2 - fi - done -done - -exec >/dev/null 2>&1 - -rm -f /run/mandos-plugin-runner.pid /run/mandos-keep-running === modified file 'mandos.conf.xml' --- mandos.conf.xml 2019-02-10 04:20:26 +0000 +++ mandos.conf.xml 2018-02-08 10:23:55 +0000 @@ -3,7 +3,7 @@ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ /etc/mandos/mandos.conf"> - + %common; ]> @@ -43,7 +43,6 @@ 2016 2017 2018 - 2019 Teddy Hogeborn Björn Påhlsson === modified file 'mandos.lsm' --- mandos.lsm 2019-02-11 06:31:42 +0000 +++ mandos.lsm 2017-08-20 19:12:58 +0000 @@ -1,7 +1,7 @@ Begin4 Title: Mandos -Version: 1.8.3 -Entered-date: 2019-02-11 +Version: 1.7.16 +Entered-date: 2017-08-20 Description: The Mandos system allows computers to have encrypted root file systems and at the same time be capable of remote and/or unattended reboots. @@ -12,9 +12,9 @@ Maintained-by: teddy@recompile.se (Teddy Hogeborn), belorn@recompile.se (Björn Påhlsson) Primary-site: https://www.recompile.se/mandos - 182K mandos_1.8.3.orig.tar.gz + 173K mandos_1.7.16.orig.tar.gz Alternate-site: ftp://ftp.recompile.se/pub/mandos - 182K mandos_1.8.3.orig.tar.gz + 173K mandos_1.7.16.orig.tar.gz Platforms: Requires GCC, GNU libC, Avahi, GnuPG, Python 2.7, and various other libraries. While made for Debian GNU/Linux, it is probably portable to other === modified file 'mandos.xml' --- mandos.xml 2019-02-10 04:20:26 +0000 +++ mandos.xml 2018-02-08 10:23:55 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -42,7 +42,6 @@ 2016 2017 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -362,11 +361,11 @@ start a TLS protocol handshake with a slight quirk: the Mandos server program acts as a TLS client while the connecting Mandos client acts as a TLS server. - The Mandos client must supply a TLS public key, and the key ID - of this public key is used by the Mandos server to look up (in a - list read from clients.conf at start time) - which binary blob to give the client. No other authentication - or authorization is done by the server. + The Mandos client must supply an OpenPGP certificate, and the + fingerprint of this certificate is used by the Mandos server to + look up (in a list read from clients.conf + at start time) which binary blob to give the client. No other + authentication or authorization is done by the server. Mandos Protocol (Version 1) @@ -392,7 +391,7 @@ - Public key (part of TLS handshake) + OpenPGP public key (part of TLS handshake) -> @@ -587,6 +586,10 @@ There is no fine-grained control over logging and debug output. + + This server does not check the expire time of clients’ OpenPGP + keys. + @@ -643,12 +646,12 @@ CLIENTS The server only gives out its stored data to clients which - does have the correct key ID of the stored key ID. This is - guaranteed by the fact that the client sends its public key in - the TLS handshake; this ensures it to be genuine. The server - computes the key ID of the key itself and looks up the key ID - in its list of clients. The clients.conf - file (see + does have the OpenPGP key of the stored fingerprint. This is + guaranteed by the fact that the client sends its OpenPGP + public key in the TLS handshake; this ensures it to be + genuine. The server computes the fingerprint of the key + itself and looks up the fingerprint in its list of + clients. The clients.conf file (see mandos-clients.conf 5) must be made non-readable by anyone @@ -712,7 +715,7 @@ GnuTLS is the library this server uses to implement TLS for communicating securely with the client, and at the same time - confidently get the client’s public key. + confidently get the client’s public OpenPGP key. @@ -771,28 +774,13 @@ - RFC 7250: Using Raw Public Keys in Transport - Layer Security (TLS) and Datagram Transport Layer Security - (DTLS) - - - - This is implemented by GnuTLS version 3.6.6 and is, if - present, used by this server so that raw public keys can be - used. - - - - - RFC 6091: Using OpenPGP Keys for Transport Layer Security (TLS) Authentication - This is implemented by GnuTLS before version 3.6.0 and is, - if present, used by this server so that OpenPGP keys can be - used. + This is implemented by GnuTLS and used by this server so + that OpenPGP keys can be used. === modified file 'overview.xml' --- overview.xml 2019-02-09 23:23:26 +0000 +++ overview.xml 2008-09-13 15:36:18 +0000 @@ -8,10 +8,10 @@ program in the initial RAM disk environment which will communicate with a server over a network. All network communication is encrypted using TLS. The - clients are identified by the server using a TLS key; each client - has one unique to it. The server sends the clients an encrypted - password. The encrypted password is decrypted by the clients using - a separate OpenPGP key, and the password is then used to unlock the - root file system, whereupon the computers can continue booting - normally. + clients are identified by the server using an OpenPGP key; each + client has one unique to it. The server sends the clients an + encrypted password. The encrypted password is decrypted by the + clients using the same OpenPGP key, and the password is then used to + unlock the root file system, whereupon the computers can continue + booting normally. === modified file 'plugin-helpers/mandos-client-iprouteadddel.c' --- plugin-helpers/mandos-client-iprouteadddel.c 2018-02-18 01:29:21 +0000 +++ plugin-helpers/mandos-client-iprouteadddel.c 2018-02-08 10:23:55 +0000 @@ -23,7 +23,8 @@ * Contact the authors at . */ -#define _GNU_SOURCE /* program_invocation_short_name */ +#define _GNU_SOURCE /* asprintf(), + program_invocation_short_name */ #include /* bool, false, true */ #include /* fprintf(), stderr, FILE, vfprintf */ #include /* program_invocation_short_name, @@ -243,7 +244,7 @@ } /* Set interface index number on nexthop object */ rtnl_route_nh_set_ifindex(nexthop, ifindex); - /* Set route to use nexthop object */ + /* Set route tu use nexthop object */ rtnl_route_add_nexthop(route, nexthop); /* Add or delete route? */ if(arguments.add){ === modified file 'plugin-runner.c' --- plugin-runner.c 2019-04-08 21:53:22 +0000 +++ plugin-runner.c 2018-02-08 10:23:55 +0000 @@ -313,10 +313,10 @@ __attribute__((nonnull)) static void free_plugin(plugin *plugin_node){ - for(char **arg = (plugin_node->argv)+1; *arg != NULL; arg++){ + free(plugin_node->name); + for(char **arg = plugin_node->argv; *arg != NULL; arg++){ free(*arg); } - free(plugin_node->name); free(plugin_node->argv); for(char **env = plugin_node->environ; *env != NULL; env++){ free(*env); @@ -565,12 +565,10 @@ case '?': /* --help */ state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */ argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP); - __builtin_unreachable(); case -3: /* --usage */ state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */ argp_state_help(state, state->out_stream, ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK); - __builtin_unreachable(); case 'V': /* --version */ fprintf(state->out_stream, "%s\n", argp_program_version); exit(EXIT_SUCCESS); @@ -586,7 +584,6 @@ if(arg[0] == '\0'){ break; } - /* FALLTHROUGH */ default: return ARGP_ERR_UNKNOWN; } @@ -1096,12 +1093,7 @@ new_plugin->pid = pid; new_plugin->fd = pipefd[0]; - - if(debug){ - fprintf(stderr, "Plugin %s started (PID %" PRIdMAX ")\n", - new_plugin->name, (intmax_t) (new_plugin->pid)); - } - + /* Unblock SIGCHLD so signal handler can be run if this process has already completed */ ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK, === modified file 'plugin-runner.xml' --- plugin-runner.xml 2019-02-10 04:20:26 +0000 +++ plugin-runner.xml 2018-02-08 10:23:55 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -42,7 +42,6 @@ 2016 2017 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -635,7 +634,7 @@ -cd /etc/keys/mandos; &COMMANDNAME; --config-file=/etc/mandos/plugin-runner.conf --plugin-dir /usr/lib/x86_64-linux-gnu/mandos/plugins.d --plugin-helper-dir /usr/lib/x86_64-linux-gnu/mandos/plugin-helpers --options-for=mandos-client:--pubkey=pubkey.txt,--seckey=seckey.txt,--tls-pubkey=tls-pubkey.pem,--tls-privkey=tls-privkey.pem +cd /etc/keys/mandos; &COMMANDNAME; --config-file=/etc/mandos/plugin-runner.conf --plugin-dir /usr/lib/x86_64-linux-gnu/mandos/plugins.d --plugin-helper-dir /usr/lib/x86_64-linux-gnu/mandos/plugin-helpers --options-for=mandos-client:--pubkey=pubkey.txt,--seckey=seckey.txt === modified file 'plugins.d/askpass-fifo.c' --- plugins.d/askpass-fifo.c 2019-02-11 07:06:55 +0000 +++ plugins.d/askpass-fifo.c 2018-02-08 10:23:55 +0000 @@ -2,8 +2,8 @@ /* * Askpass-FIFO - Read a password from a FIFO and output it * - * Copyright © 2008-2019 Teddy Hogeborn - * Copyright © 2008-2019 Björn Påhlsson + * Copyright © 2008-2018 Teddy Hogeborn + * Copyright © 2008-2018 Björn Påhlsson * * This file is part of Mandos. * @@ -65,16 +65,10 @@ fprintf(stderr, ": "); fprintf(stderr, "%s\n", strerror(errnum)); error(status, errno, "vasprintf while printing error"); - if(status){ - __builtin_unreachable(); - } return; } fprintf(stderr, "Mandos plugin "); error(status, errnum, "%s", text); - if(status){ - __builtin_unreachable(); - } free(text); } @@ -96,17 +90,14 @@ case ENOTDIR: case ELOOP: error_plus(EX_OSFILE, errno, "mkfifo"); - __builtin_unreachable(); case ENAMETOOLONG: case ENOSPC: case EROFS: default: error_plus(EX_OSERR, errno, "mkfifo"); - __builtin_unreachable(); case ENOENT: /* no "/lib/cryptsetup"? */ error_plus(EX_UNAVAILABLE, errno, "mkfifo"); - __builtin_unreachable(); case EEXIST: break; /* not an error */ } === modified file 'plugins.d/askpass-fifo.xml' --- plugins.d/askpass-fifo.xml 2019-02-10 04:20:26 +0000 +++ plugins.d/askpass-fifo.xml 2018-02-08 10:23:55 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -42,7 +42,6 @@ 2016 2017 2018 - 2019 Teddy Hogeborn Björn Påhlsson === modified file 'plugins.d/mandos-client.c' --- plugins.d/mandos-client.c 2019-02-11 05:14:10 +0000 +++ plugins.d/mandos-client.c 2018-02-08 10:23:55 +0000 @@ -9,8 +9,8 @@ * "browse_callback", and parts of "main". * * Everything else is - * Copyright © 2008-2019 Teddy Hogeborn - * Copyright © 2008-2019 Björn Påhlsson + * Copyright © 2008-2018 Teddy Hogeborn + * Copyright © 2008-2018 Björn Påhlsson * * This file is part of Mandos. * @@ -123,15 +123,9 @@ gnutls_* init_gnutls_session(), GNUTLS_* */ -#if GNUTLS_VERSION_NUMBER < 0x030600 #include /* gnutls_certificate_set_openpgp_key_file(), GNUTLS_OPENPGP_FMT_BASE64 */ -#elif GNUTLS_VERSION_NUMBER >= 0x030606 -#include /* gnutls_pkcs_encrypt_flags_t, - GNUTLS_PKCS_PLAIN, - GNUTLS_PKCS_NULL_PASSWORD */ -#endif /* GPGME */ #include /* All GPGME types, constants and @@ -145,8 +139,6 @@ #define PATHDIR "/conf/conf.d/mandos" #define SECKEY "seckey.txt" #define PUBKEY "pubkey.txt" -#define TLS_PRIVKEY "tls-privkey.pem" -#define TLS_PUBKEY "tls-pubkey.pem" #define HOOKDIR "/lib/mandos/network-hooks.d" bool debug = false; @@ -280,58 +272,6 @@ return true; } -/* Set effective uid to 0, return errno */ -__attribute__((warn_unused_result)) -int raise_privileges(void){ - int old_errno = errno; - int ret = 0; - if(seteuid(0) == -1){ - ret = errno; - } - errno = old_errno; - return ret; -} - -/* Set effective and real user ID to 0. Return errno. */ -__attribute__((warn_unused_result)) -int raise_privileges_permanently(void){ - int old_errno = errno; - int ret = raise_privileges(); - if(ret != 0){ - errno = old_errno; - return ret; - } - if(setuid(0) == -1){ - ret = errno; - } - errno = old_errno; - return ret; -} - -/* Set effective user ID to unprivileged saved user ID */ -__attribute__((warn_unused_result)) -int lower_privileges(void){ - int old_errno = errno; - int ret = 0; - if(seteuid(uid) == -1){ - ret = errno; - } - errno = old_errno; - return ret; -} - -/* Lower privileges permanently */ -__attribute__((warn_unused_result)) -int lower_privileges_permanently(void){ - int old_errno = errno; - int ret = 0; - if(setuid(uid) == -1){ - ret = errno; - } - errno = old_errno; - return ret; -} - /* * Initialize GPGME. */ @@ -357,56 +297,6 @@ return false; } - /* Workaround for systems without a real-time clock; see also - Debian bug #894495: */ - do { - { - time_t currtime = time(NULL); - if(currtime != (time_t)-1){ - struct tm tm; - if(gmtime_r(&currtime, &tm) == NULL) { - perror_plus("gmtime_r"); - break; - } - if(tm.tm_year != 70 or tm.tm_mon != 0){ - break; - } - if(debug){ - fprintf_plus(stderr, "System clock is January 1970"); - } - } else { - if(debug){ - fprintf_plus(stderr, "System clock is invalid"); - } - } - } - struct stat keystat; - ret = fstat(fd, &keystat); - if(ret != 0){ - perror_plus("fstat"); - break; - } - ret = raise_privileges(); - if(ret != 0){ - errno = ret; - perror_plus("Failed to raise privileges"); - break; - } - if(debug){ - fprintf_plus(stderr, - "Setting system clock to key file mtime"); - } - time_t keytime = keystat.st_mtim.tv_sec; - if(stime(&keytime) != 0){ - perror_plus("stime"); - } - ret = lower_privileges(); - if(ret != 0){ - errno = ret; - perror_plus("Failed to lower privileges"); - } - } while(false); - rc = gpgme_data_new_from_fd(&pgp_data, fd); if(rc != GPG_ERR_NO_ERROR){ fprintf_plus(stderr, "bad gpgme_data_new_from_fd: %s: %s\n", @@ -420,81 +310,6 @@ gpgme_strsource(rc), gpgme_strerror(rc)); return false; } - { - gpgme_import_result_t import_result - = gpgme_op_import_result(mc->ctx); - if((import_result->imported < 1 - or import_result->not_imported > 0) - and import_result->unchanged == 0){ - fprintf_plus(stderr, "bad gpgme_op_import_results:\n"); - fprintf_plus(stderr, - "The total number of considered keys: %d\n", - import_result->considered); - fprintf_plus(stderr, - "The number of keys without user ID: %d\n", - import_result->no_user_id); - fprintf_plus(stderr, - "The total number of imported keys: %d\n", - import_result->imported); - fprintf_plus(stderr, "The number of imported RSA keys: %d\n", - import_result->imported_rsa); - fprintf_plus(stderr, "The number of unchanged keys: %d\n", - import_result->unchanged); - fprintf_plus(stderr, "The number of new user IDs: %d\n", - import_result->new_user_ids); - fprintf_plus(stderr, "The number of new sub keys: %d\n", - import_result->new_sub_keys); - fprintf_plus(stderr, "The number of new signatures: %d\n", - import_result->new_signatures); - fprintf_plus(stderr, "The number of new revocations: %d\n", - import_result->new_revocations); - fprintf_plus(stderr, - "The total number of secret keys read: %d\n", - import_result->secret_read); - fprintf_plus(stderr, - "The number of imported secret keys: %d\n", - import_result->secret_imported); - fprintf_plus(stderr, - "The number of unchanged secret keys: %d\n", - import_result->secret_unchanged); - fprintf_plus(stderr, "The number of keys not imported: %d\n", - import_result->not_imported); - for(gpgme_import_status_t import_status - = import_result->imports; - import_status != NULL; - import_status = import_status->next){ - fprintf_plus(stderr, "Import status for key: %s\n", - import_status->fpr); - if(import_status->result != GPG_ERR_NO_ERROR){ - fprintf_plus(stderr, "Import result: %s: %s\n", - gpgme_strsource(import_status->result), - gpgme_strerror(import_status->result)); - } - fprintf_plus(stderr, "Key status:\n"); - fprintf_plus(stderr, - import_status->status & GPGME_IMPORT_NEW - ? "The key was new.\n" - : "The key was not new.\n"); - fprintf_plus(stderr, - import_status->status & GPGME_IMPORT_UID - ? "The key contained new user IDs.\n" - : "The key did not contain new user IDs.\n"); - fprintf_plus(stderr, - import_status->status & GPGME_IMPORT_SIG - ? "The key contained new signatures.\n" - : "The key did not contain new signatures.\n"); - fprintf_plus(stderr, - import_status->status & GPGME_IMPORT_SUBKEY - ? "The key contained new sub keys.\n" - : "The key did not contain new sub keys.\n"); - fprintf_plus(stderr, - import_status->status & GPGME_IMPORT_SECRET - ? "The key contained a secret key.\n" - : "The key did not contain a secret key.\n"); - } - return false; - } - } ret = close(fd); if(ret == -1){ @@ -541,8 +356,9 @@ /* Create new GPGME "context" */ rc = gpgme_new(&(mc->ctx)); if(rc != GPG_ERR_NO_ERROR){ - fprintf_plus(stderr, "bad gpgme_new: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf_plus(stderr, "Mandos plugin mandos-client: " + "bad gpgme_new: %s: %s\n", gpgme_strsource(rc), + gpgme_strerror(rc)); return false; } @@ -584,7 +400,8 @@ /* Create new empty GPGME data buffer for the plaintext */ rc = gpgme_data_new(&dh_plain); if(rc != GPG_ERR_NO_ERROR){ - fprintf_plus(stderr, "bad gpgme_data_new: %s: %s\n", + fprintf_plus(stderr, "Mandos plugin mandos-client: " + "bad gpgme_data_new: %s: %s\n", gpgme_strsource(rc), gpgme_strerror(rc)); gpgme_data_release(dh_crypto); return -1; @@ -603,23 +420,24 @@ if(result == NULL){ fprintf_plus(stderr, "gpgme_op_decrypt_result failed\n"); } else { - if(result->unsupported_algorithm != NULL) { - fprintf_plus(stderr, "Unsupported algorithm: %s\n", - result->unsupported_algorithm); - } - fprintf_plus(stderr, "Wrong key usage: %s\n", - result->wrong_key_usage ? "Yes" : "No"); + fprintf_plus(stderr, "Unsupported algorithm: %s\n", + result->unsupported_algorithm); + fprintf_plus(stderr, "Wrong key usage: %u\n", + result->wrong_key_usage); if(result->file_name != NULL){ fprintf_plus(stderr, "File name: %s\n", result->file_name); } - - for(gpgme_recipient_t r = result->recipients; r != NULL; - r = r->next){ + gpgme_recipient_t recipient; + recipient = result->recipients; + while(recipient != NULL){ fprintf_plus(stderr, "Public key algorithm: %s\n", - gpgme_pubkey_algo_name(r->pubkey_algo)); - fprintf_plus(stderr, "Key ID: %s\n", r->keyid); + gpgme_pubkey_algo_name + (recipient->pubkey_algo)); + fprintf_plus(stderr, "Key ID: %s\n", recipient->keyid); fprintf_plus(stderr, "Secret key available: %s\n", - r->status == GPG_ERR_NO_SECKEY ? "No" : "Yes"); + recipient->status == GPG_ERR_NO_SECKEY + ? "No" : "Yes"); + recipient = recipient->next; } } } @@ -707,6 +525,7 @@ const char *dhparamsfilename, mandos_context *mc){ int ret; + unsigned int uret; if(debug){ fprintf_plus(stderr, "Initializing GnuTLS\n"); @@ -729,34 +548,18 @@ } if(debug){ - fprintf_plus(stderr, "Attempting to use public key %s and" - " private key %s as GnuTLS credentials\n", + fprintf_plus(stderr, "Attempting to use OpenPGP public key %s and" + " secret key %s as GnuTLS credentials\n", pubkeyfilename, seckeyfilename); } -#if GNUTLS_VERSION_NUMBER >= 0x030606 - ret = gnutls_certificate_set_rawpk_key_file - (mc->cred, pubkeyfilename, seckeyfilename, - GNUTLS_X509_FMT_PEM, /* format */ - NULL, /* pass */ - /* key_usage */ - GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT, - NULL, /* names */ - 0, /* names_length */ - /* privkey_flags */ - GNUTLS_PKCS_PLAIN | GNUTLS_PKCS_NULL_PASSWORD, - 0); /* pkcs11_flags */ -#elif GNUTLS_VERSION_NUMBER < 0x030600 ret = gnutls_certificate_set_openpgp_key_file (mc->cred, pubkeyfilename, seckeyfilename, GNUTLS_OPENPGP_FMT_BASE64); -#else -#error "Needs GnuTLS 3.6.6 or later, or before 3.6.0" -#endif if(ret != GNUTLS_E_SUCCESS){ fprintf_plus(stderr, - "Error[%d] while reading the key pair ('%s'," + "Error[%d] while reading the OpenPGP key pair ('%s'," " '%s')\n", ret, pubkeyfilename, seckeyfilename); fprintf_plus(stderr, "The GnuTLS error is: %s\n", safer_gnutls_strerror(ret)); @@ -810,10 +613,6 @@ } params.size += (unsigned int)bytes_read; } - ret = close(dhpfile); - if(ret == -1){ - perror_plus("close"); - } if(params.data == NULL){ dhparamsfilename = NULL; } @@ -833,7 +632,6 @@ } if(dhparamsfilename == NULL){ if(mc->dh_bits == 0){ -#if GNUTLS_VERSION_NUMBER < 0x030600 /* Find out the optimal number of DH bits */ /* Try to read the private key file */ gnutls_datum_t buffer = { .data = NULL, .size = 0 }; @@ -919,7 +717,7 @@ } } } - unsigned int uret = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, sec_param); + uret = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, sec_param); if(uret != 0){ mc->dh_bits = uret; if(debug){ @@ -937,22 +735,19 @@ safer_gnutls_strerror(ret)); goto globalfail; } -#endif - } else { /* dh_bits != 0 */ - if(debug){ - fprintf_plus(stderr, "DH bits explicitly set to %u\n", - mc->dh_bits); - } - ret = gnutls_dh_params_generate2(mc->dh_params, mc->dh_bits); - if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, "Error in GnuTLS prime generation (%u" - " bits): %s\n", mc->dh_bits, - safer_gnutls_strerror(ret)); - goto globalfail; - } - gnutls_certificate_set_dh_params(mc->cred, mc->dh_params); + } else if(debug){ + fprintf_plus(stderr, "DH bits explicitly set to %u\n", + mc->dh_bits); + } + ret = gnutls_dh_params_generate2(mc->dh_params, mc->dh_bits); + if(ret != GNUTLS_E_SUCCESS){ + fprintf_plus(stderr, "Error in GnuTLS prime generation (%u" + " bits): %s\n", mc->dh_bits, + safer_gnutls_strerror(ret)); + goto globalfail; } } + gnutls_certificate_set_dh_params(mc->cred, mc->dh_params); return 0; @@ -969,14 +764,7 @@ int ret; /* GnuTLS session creation */ do { - ret = gnutls_init(session, (GNUTLS_SERVER -#if GNUTLS_VERSION_NUMBER >= 0x030506 - | GNUTLS_NO_TICKETS -#endif -#if GNUTLS_VERSION_NUMBER >= 0x030606 - | GNUTLS_ENABLE_RAWPK -#endif - )); + ret = gnutls_init(session, GNUTLS_SERVER); if(quit_now){ return -1; } @@ -1030,6 +818,58 @@ static void empty_log(__attribute__((unused)) AvahiLogLevel level, __attribute__((unused)) const char *txt){} +/* Set effective uid to 0, return errno */ +__attribute__((warn_unused_result)) +int raise_privileges(void){ + int old_errno = errno; + int ret = 0; + if(seteuid(0) == -1){ + ret = errno; + } + errno = old_errno; + return ret; +} + +/* Set effective and real user ID to 0. Return errno. */ +__attribute__((warn_unused_result)) +int raise_privileges_permanently(void){ + int old_errno = errno; + int ret = raise_privileges(); + if(ret != 0){ + errno = old_errno; + return ret; + } + if(setuid(0) == -1){ + ret = errno; + } + errno = old_errno; + return ret; +} + +/* Set effective user ID to unprivileged saved user ID */ +__attribute__((warn_unused_result)) +int lower_privileges(void){ + int old_errno = errno; + int ret = 0; + if(seteuid(uid) == -1){ + ret = errno; + } + errno = old_errno; + return ret; +} + +/* Lower privileges permanently */ +__attribute__((warn_unused_result)) +int lower_privileges_permanently(void){ + int old_errno = errno; + int ret = 0; + if(setuid(uid) == -1){ + ret = errno; + } + errno = old_errno; + return ret; +} + /* Helper function to add_local_route() and delete_local_route() */ __attribute__((nonnull, warn_unused_result)) static bool add_delete_local_route(const bool add, @@ -1815,18 +1655,8 @@ perror_plus("ioctl SIOCGIFFLAGS"); errno = old_errno; } - if((close(s) == -1) and debug){ - old_errno = errno; - perror_plus("close"); - errno = old_errno; - } return false; } - if((close(s) == -1) and debug){ - old_errno = errno; - perror_plus("close"); - errno = old_errno; - } return true; } @@ -2093,20 +1923,19 @@ return; } } - int devnull = (int)TEMP_FAILURE_RETRY(open("/dev/null", O_RDONLY)); - if(devnull == -1){ - perror_plus("open(\"/dev/null\", O_RDONLY)"); - return; - } int numhooks = scandirat(hookdir_fd, ".", &direntries, runnable_hook, alphasort); if(numhooks == -1){ perror_plus("scandir"); - close(devnull); return; } struct dirent *direntry; int ret; + int devnull = (int)TEMP_FAILURE_RETRY(open("/dev/null", O_RDONLY)); + if(devnull == -1){ + perror_plus("open(\"/dev/null\", O_RDONLY)"); + return; + } for(int i = 0; i < numhooks; i++){ direntry = direntries[i]; if(debug){ @@ -2461,16 +2290,8 @@ int main(int argc, char *argv[]){ mandos_context mc = { .server = NULL, .dh_bits = 0, -#if GNUTLS_VERSION_NUMBER >= 0x030606 - .priority = "SECURE128:!CTYPE-X.509" - ":+CTYPE-RAWPK:!RSA:!VERS-ALL:+VERS-TLS1.3" - ":%PROFILE_ULTRA", -#elif GNUTLS_VERSION_NUMBER < 0x030600 .priority = "SECURE256:!CTYPE-X.509" ":+CTYPE-OPENPGP:!RSA:+SIGN-DSA-SHA256", -#else -#error "Needs GnuTLS 3.6.6 or later, or before 3.6.0" -#endif .current_server = NULL, .interfaces = NULL, .interfaces_size = 0 }; AvahiSServiceBrowser *sb = NULL; @@ -2487,10 +2308,6 @@ AvahiIfIndex if_index = AVAHI_IF_UNSPEC; const char *seckey = PATHDIR "/" SECKEY; const char *pubkey = PATHDIR "/" PUBKEY; -#if GNUTLS_VERSION_NUMBER >= 0x030606 - const char *tls_privkey = PATHDIR "/" TLS_PRIVKEY; - const char *tls_pubkey = PATHDIR "/" TLS_PUBKEY; -#endif const char *dh_params_file = NULL; char *interfaces_hooks = NULL; @@ -2544,23 +2361,7 @@ { .name = "pubkey", .key = 'p', .arg = "FILE", .doc = "OpenPGP public key file base name", - .group = 1 }, - { .name = "tls-privkey", .key = 't', - .arg = "FILE", -#if GNUTLS_VERSION_NUMBER >= 0x030606 - .doc = "TLS private key file base name", -#else - .doc = "Dummy; ignored (requires GnuTLS 3.6.6)", -#endif - .group = 1 }, - { .name = "tls-pubkey", .key = 'T', - .arg = "FILE", -#if GNUTLS_VERSION_NUMBER >= 0x030606 - .doc = "TLS public key file base name", -#else - .doc = "Dummy; ignored (requires GnuTLS 3.6.6)", -#endif - .group = 1 }, + .group = 2 }, { .name = "dh-bits", .key = 129, .arg = "BITS", .doc = "Bit length of the prime number used in the" @@ -2622,16 +2423,6 @@ case 'p': /* --pubkey */ pubkey = arg; break; - case 't': /* --tls-privkey */ -#if GNUTLS_VERSION_NUMBER >= 0x030606 - tls_privkey = arg; -#endif - break; - case 'T': /* --tls-pubkey */ -#if GNUTLS_VERSION_NUMBER >= 0x030606 - tls_pubkey = arg; -#endif - break; case 129: /* --dh-bits */ errno = 0; tmpmax = strtoimax(arg, &tmp, 10); @@ -2672,11 +2463,9 @@ argp_state_help(state, state->out_stream, (ARGP_HELP_STD_HELP | ARGP_HELP_EXIT_ERR) & ~(unsigned int)ARGP_HELP_EXIT_OK); - __builtin_unreachable(); case -3: /* --usage */ argp_state_help(state, state->out_stream, ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR); - __builtin_unreachable(); case 'V': /* --version */ fprintf_plus(state->out_stream, "%s\n", argp_program_version); exit(argp_err_exit_status); @@ -2993,13 +2782,7 @@ goto end; } -#if GNUTLS_VERSION_NUMBER >= 0x030606 - ret = init_gnutls_global(tls_pubkey, tls_privkey, dh_params_file, &mc); -#elif GNUTLS_VERSION_NUMBER < 0x030600 ret = init_gnutls_global(pubkey, seckey, dh_params_file, &mc); -#else -#error "Needs GnuTLS 3.6.6 or later, or before 3.6.0" -#endif if(ret == -1){ fprintf_plus(stderr, "init_gnutls_global failed\n"); exitcode = EX_UNAVAILABLE; @@ -3278,7 +3061,6 @@ | O_PATH)); if(dir_fd == -1){ perror_plus("open"); - return; } int numentries = scandirat(dir_fd, ".", &direntries, notdotentries, alphasort); @@ -3301,7 +3083,7 @@ clean_dir_at(dir_fd, direntries[i]->d_name, level+1); dret = 0; } - if((dret == -1) and (errno != ENOENT)){ + if(dret == -1){ fprintf_plus(stderr, "unlink(\"%s/%s\"): %s\n", dirname, direntries[i]->d_name, strerror(errno)); } @@ -3311,6 +3093,9 @@ /* need to clean even if 0 because man page doesn't specify */ free(direntries); + if(numentries == -1){ + perror_plus("scandirat"); + } dret = unlinkat(base, dirname, AT_REMOVEDIR); if(dret == -1 and errno != ENOENT){ perror_plus("rmdir"); === modified file 'plugins.d/mandos-client.xml' --- plugins.d/mandos-client.xml 2019-02-10 04:20:26 +0000 +++ plugins.d/mandos-client.xml 2018-02-08 10:23:55 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -42,7 +42,6 @@ 2016 2017 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -96,20 +95,6 @@ FILE - - - - - - - - - - @@ -169,10 +154,10 @@ brings up network interfaces, uses the interfaces’ IPv6 link-local addresses to get network connectivity, uses Zeroconf to find servers on the local network, and communicates with - servers using TLS with a raw public key to ensure authenticity - and confidentiality. This client program keeps running, trying - all servers on the network, until it receives a satisfactory - reply or a TERM signal. After all servers have been tried, all + servers using TLS with an OpenPGP key to ensure authenticity and + confidentiality. This client program keeps running, trying all + servers on the network, until it receives a satisfactory reply + or a TERM signal. After all servers have been tried, all servers are periodically retried. If no servers are found it will wait indefinitely for new servers to appear. @@ -322,34 +307,6 @@ - - - - - TLS raw public key file name. The default name is - /conf/conf.d/mandos/tls-pubkey.pem. - - - - - - - - - - TLS secret key file name. The default name is - /conf/conf.d/mandos/tls-privkey.pem. - - - - - @@ -365,10 +322,9 @@ Sets the number of bits to use for the prime number in the TLS Diffie-Hellman key exchange. The default value is - selected automatically based on the GnuTLS security - profile set in its priority string. Note that if the - option is used, the values - from that file will be used instead. + selected automatically based on the OpenPGP key. Note + that if the option is used, + the values from that file will be used instead. @@ -726,20 +682,6 @@ - /conf/conf.d/mandos/tls-pubkey.pem - /conf/conf.d/mandos/tls-privkey.pem - - - Public and private raw key files, in PEM - format. These are the default file names, they can be - changed with the and - options. - - - - /lib/mandos/network-hooks.d @@ -787,18 +729,18 @@ - Run in debug mode, and use custom keys: + Run in debug mode, and use a custom key: -&COMMANDNAME; --debug --pubkey keydir/pubkey.txt --seckey keydir/seckey.txt --tls-pubkey keydir/tls-pubkey.pem --tls-privkey keydir/tls-privkey.pem +&COMMANDNAME; --debug --pubkey keydir/pubkey.txt --seckey keydir/seckey.txt - Run in debug mode, with custom keys, and do not use Zeroconf + Run in debug mode, with a custom key, and do not use Zeroconf to locate a server; connect directly to the IPv6 link-local address fe80::aede:48ff:fe71:f6f2, port 4711, @@ -807,7 +749,7 @@ -&COMMANDNAME; --debug --pubkey keydir/pubkey.txt --seckey keydir/seckey.txt --tls-pubkey keydir/tls-pubkey.pem --tls-privkey keydir/tls-privkey.pem --connect fe80::aede:48ff:fe71:f6f2:4711 --interface eth2 +&COMMANDNAME; --debug --pubkey keydir/pubkey.txt --seckey keydir/seckey.txt --connect fe80::aede:48ff:fe71:f6f2:4711 --interface eth2 @@ -837,12 +779,12 @@ The only remaining weak point is that someone with physical access to the client hard drive might turn off the client - computer, read the OpenPGP and TLS keys directly from the hard - drive, and communicate with the server. To safeguard against - this, the server is supposed to notice the client disappearing - and stop giving out the encrypted data. Therefore, it is - important to set the timeout and checker interval values tightly - on the server. See mandos8. @@ -908,7 +850,7 @@ GnuTLS is the library this client uses to implement TLS for communicating securely with the server, and at the same time - send the public key to the server. + send the public OpenPGP key to the server. @@ -980,28 +922,13 @@ - RFC 7250: Using Raw Public Keys in Transport - Layer Security (TLS) and Datagram Transport Layer Security - (DTLS) - - - - This is implemented by GnuTLS in version 3.6.6 and is, if - present, used by this program so that raw public keys can be - used. - - - - - RFC 6091: Using OpenPGP Keys for Transport Layer Security - This is implemented by GnuTLS before version 3.6.0 and is, - if present, used by this program so that OpenPGP keys can be - used. + This is implemented by GnuTLS and used by this program so + that OpenPGP keys can be used. === modified file 'plugins.d/password-prompt.c' --- plugins.d/password-prompt.c 2019-02-11 07:06:55 +0000 +++ plugins.d/password-prompt.c 2018-02-08 10:23:55 +0000 @@ -2,8 +2,8 @@ /* * Password-prompt - Read a password from the terminal and print it * - * Copyright © 2008-2019 Teddy Hogeborn - * Copyright © 2008-2019 Björn Påhlsson + * Copyright © 2008-2018 Teddy Hogeborn + * Copyright © 2008-2018 Björn Påhlsson * * This file is part of Mandos. * @@ -274,11 +274,9 @@ argp_state_help(state, state->out_stream, (ARGP_HELP_STD_HELP | ARGP_HELP_EXIT_ERR) & ~(unsigned int)ARGP_HELP_EXIT_OK); - __builtin_unreachable(); case -3: /* --usage */ argp_state_help(state, state->out_stream, ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR); - __builtin_unreachable(); case 'V': /* --version */ fprintf(state->out_stream, "%s\n", argp_program_version); exit(argp_err_exit_status); === modified file 'plugins.d/password-prompt.xml' --- plugins.d/password-prompt.xml 2019-02-10 04:20:26 +0000 +++ plugins.d/password-prompt.xml 2018-02-08 10:23:55 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -42,7 +42,6 @@ 2016 2017 2018 - 2019 Teddy Hogeborn Björn Påhlsson === modified file 'plugins.d/plymouth.xml' --- plugins.d/plymouth.xml 2019-02-10 04:20:26 +0000 +++ plugins.d/plymouth.xml 2018-02-08 10:23:55 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -40,7 +40,6 @@ 2016 2017 2018 - 2019 Teddy Hogeborn Björn Påhlsson === modified file 'plugins.d/splashy.xml' --- plugins.d/splashy.xml 2019-02-10 04:20:26 +0000 +++ plugins.d/splashy.xml 2018-02-08 10:23:55 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -42,7 +42,6 @@ 2016 2017 2018 - 2019 Teddy Hogeborn Björn Påhlsson === modified file 'plugins.d/usplash.xml' --- plugins.d/usplash.xml 2019-02-10 04:20:26 +0000 +++ plugins.d/usplash.xml 2018-02-08 10:23:55 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -42,7 +42,6 @@ 2016 2017 2018 - 2019 Teddy Hogeborn Björn Påhlsson