=== modified file '.bzrignore' --- .bzrignore 2019-12-04 23:52:20 +0000 +++ .bzrignore 2012-05-17 01:55:58 +0000 @@ -12,5 +12,3 @@ plugins.d/splashy plugins.d/usplash plugins.d/plymouth -plugin-helpers/mandos-client-iprouteadddel -dracut-module/password-agent === modified file 'DBUS-API' --- DBUS-API 2020-07-04 11:58:52 +0000 +++ DBUS-API 2016-02-28 14:22:10 +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. @@ -55,10 +55,10 @@ | Name | Type | Access | clients.conf | |-------------------------+------+------------+---------------------| + | ApprovedByDefault | b | Read/Write | approved_by_default | | ApprovalDelay (a) | t | Read/Write | approval_delay | | ApprovalDuration (a) | t | Read/Write | approval_duration | | ApprovalPending (b) | b | Read | N/A | - | ApprovedByDefault | b | Read/Write | approved_by_default | | Checker | s | Read/Write | checker | | CheckerRunning (c) | b | Read/Write | N/A | | Created (d) | s | Read | N/A | @@ -68,7 +68,6 @@ | Fingerprint | s | Read | fingerprint | | Host | s | Read/Write | host | | Interval (a) | t | Read/Write | interval | - | KeyID | s | Read | key_id | | LastApprovalRequest (g) | s | Read | N/A | | LastCheckedOK (h) | s | Read/Write | N/A | | LastCheckerStatus (i) | n | Read | N/A | @@ -131,25 +130,24 @@ * Copyright - Copyright © 2010-2020 Teddy Hogeborn - Copyright © 2010-2020 Björn Påhlsson + Copyright © 2010-2016 Teddy Hogeborn + Copyright © 2010-2016 Björn Påhlsson ** License: - - 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 + + This program is free software: you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with Mandos. If not, see . + along with this program. If not, see + . #+STARTUP: showall === modified file 'INSTALL' --- INSTALL 2019-11-03 19:17:57 +0000 +++ INSTALL 2016-03-13 00:37:02 +0000 @@ -38,16 +38,12 @@ "man -l mandos.8". *** 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 https://www.avahi.org/ - + Python 3 https://www.python.org/ - Note: Python 2.7 is still supported, if the "mandos", - "mandos-ctl", and "mandos-monitor" files are edited to contain - "#!/usr/bin/python" instead of python3. - + dbus-python 0.82.4 https://dbus.freedesktop.org/doc/dbus-python/ - + PyGObject 3.8 https://wiki.gnome.org/Projects/PyGObject - + pkg-config https://www.freedesktop.org/wiki/Software/pkg-config/ + + GnuTLS 3.3 http://www.gnutls.org/ + + Avahi 0.6.16 http://www.avahi.org/ + + Python 2.7 https://www.python.org/ + + dbus-python 0.82.4 http://dbus.freedesktop.org/doc/dbus-python/ + + PyGObject 3.7.1 https://wiki.gnome.org/Projects/PyGObject + + pkg-config http://www.freedesktop.org/wiki/Software/pkg-config/ + Urwid 1.0.1 http://urwid.org/ (Only needed by the "mandos-monitor" tool.) @@ -56,33 +52,25 @@ + ssh-keyscan from OpenSSH http://www.openssh.com/ Package names: - avahi-daemon python3 python3-dbus python3-gi python3-urwid - pkg-config fping ssh-client + avahi-daemon python python-dbus python-gi python-urwid pkg-config + fping ssh-client *** Mandos Client - + GNU C Library 2.17 https://gnu.org/software/libc/ - + GnuTLS 3.3 https://www.gnutls.org/ - (but not 3.6.0 or later, until 3.6.6 which works) - + Avahi 0.6.16 https://www.avahi.org/ + + GNU C Library 2.16 https://gnu.org/software/libc/ + + initramfs-tools 0.85i + https://tracker.debian.org/pkg/initramfs-tools + + GnuTLS 3.3 http://www.gnutls.org/ + + 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/ - + pkg-config https://www.freedesktop.org/wiki/Software/pkg-config/ - + libnl-route 3 https://www.infradead.org/~tgr/libnl/ - + GLib 2.40 http://www.gtk.org/ - - One of: - + initramfs-tools 0.85i - https://tracker.debian.org/pkg/initramfs-tools - + dracut 044+241 - http://www.kernel.org/pub/linux/utils/boot/dracut/dracut.html + + pkg-config http://www.freedesktop.org/wiki/Software/pkg-config/ Strongly recommended: + OpenSSH http://www.openssh.com/ Package names: - initramfs-tools dracut libgnutls-dev gnutls-bin libavahi-core-dev - gnupg libgpgme11-dev pkg-config ssh libnl-route-3-dev - libglib2.0-dev + initramfs-tools libgnutls-dev libavahi-core-dev gnupg + libgpgme11-dev pkg-config ssh * Installing the Mandos server @@ -90,7 +78,7 @@ 2. On the computer to run as a Mandos server, run the following command: - For Debian: su - -c 'make install-server' + For Debian: su -c 'make install-server' For Ubuntu: sudo make install-server (This creates a configuration without any clients configured; you @@ -102,14 +90,14 @@ 2. On the computer to run as a Mandos client, run the following command: - For Debian: su - -c 'make install-client' + For Debian: su -c 'make install-client' For Ubuntu: sudo make install-client This will also create an OpenPGP key, which will take some time and entropy, so be patient. 3. Run the following command: - For Debian: su - -c 'mandos-keygen --password' + For Debian: su -c 'mandos-keygen --password' For Ubuntu: sudo mandos-keygen --password When prompted, enter the password/passphrase for the encrypted @@ -127,7 +115,7 @@ # update-initramfs -k all -u 5. On the server computer, start the server by running the command - For Debian: su - -c 'invoke-rc.d mandos start' + For Debian: su -c 'invoke-rc.d mandos start' For Ubuntu: sudo service mandos start At this point, it is possible to verify that the correct password @@ -135,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 2023-02-07 18:59:50 +0000 +++ Makefile 2016-03-19 22:00:38 +0000 @@ -1,4 +1,4 @@ -WARN:=-O -Wall -Wextra -Wdouble-promotion -Wformat=2 -Winit-self \ +WARN=-O -Wall -Wextra -Wdouble-promotion -Wformat=2 -Winit-self \ -Wmissing-include-dirs -Wswitch-default -Wswitch-enum \ -Wunused -Wuninitialized -Wstrict-overflow=5 \ -Wsuggest-attribute=pure -Wsuggest-attribute=const \ @@ -10,14 +10,12 @@ -Wmissing-format-attribute -Wnormalized=nfc -Wpacked \ -Wredundant-decls -Wnested-externs -Winline -Wvla \ -Wvolatile-register-var -Woverlength-strings - -#DEBUG:=-ggdb3 -fsanitize=address $(SANITIZE) -## Check which sanitizing options can be used -#SANITIZE:=$(foreach option,$(ALL_SANITIZE_OPTIONS),$(shell \ -# echo 'int main(){}' | $(CC) --language=c $(option) \ -# /dev/stdin -o /dev/null >/dev/null 2>&1 && echo $(option))) +#DEBUG=-ggdb3 +# For info about _FORTIFY_SOURCE, see feature_test_macros(7) +# and . +FORTIFY=-D_FORTIFY_SOURCE=2 -fstack-protector-all -fPIC # -ALL_SANITIZE_OPTIONS:=-fsanitize=leak -fsanitize=undefined \ +ALL_SANITIZE_OPTIONS:=-fsanitize=address -fsanitize=undefined \ -fsanitize=shift -fsanitize=integer-divide-by-zero \ -fsanitize=unreachable -fsanitize=vla-bound -fsanitize=null \ -fsanitize=return -fsanitize=signed-integer-overflow \ @@ -25,13 +23,13 @@ -fsanitize=object-size -fsanitize=float-divide-by-zero \ -fsanitize=float-cast-overflow -fsanitize=nonnull-attribute \ -fsanitize=returns-nonnull-attribute -fsanitize=bool \ - -fsanitize=enum -fsanitize-address-use-after-scope - -# For info about _FORTIFY_SOURCE, see feature_test_macros(7) -# and . -FORTIFY:=-D_FORTIFY_SOURCE=3 -fstack-protector-all -fPIC -LINK_FORTIFY_LD:=-z relro -z now -LINK_FORTIFY:= + -fsanitize=enum +# Check which sanitizing options can be used +SANITIZE:=$(foreach option,$(ALL_SANITIZE_OPTIONS),$(shell \ + echo 'int main(){}' | $(CC) --language=c $(option) /dev/stdin \ + -o /dev/null >/dev/null 2>&1 && echo $(option))) +LINK_FORTIFY_LD=-z relro -z now +LINK_FORTIFY= # If BROKEN_PIE is set, do not build with -pie ifndef BROKEN_PIE @@ -39,44 +37,35 @@ LINK_FORTIFY += -pie endif #COVERAGE=--coverage -OPTIMIZE:=-Os -fno-strict-aliasing -LANGUAGE:=-std=gnu11 -FEATURES:=-D_FILE_OFFSET_BITS=64 -htmldir:=man -version:=1.8.15 -SED:=sed -PKG_CONFIG?=pkg-config - -USER:=$(firstword $(subst :, ,$(shell getent passwd _mandos \ - || getent passwd nobody || echo 65534))) -GROUP:=$(firstword $(subst :, ,$(shell getent group _mandos \ - || getent group nogroup || echo 65534))) - -LINUXVERSION:=$(shell uname --kernel-release) +OPTIMIZE=-Os -fno-strict-aliasing +LANGUAGE=-std=gnu11 +htmldir=man +version=1.7.7 +SED=sed + +USER=$(firstword $(subst :, ,$(shell getent passwd _mandos || getent passwd nobody || echo 65534))) +GROUP=$(firstword $(subst :, ,$(shell getent group _mandos || getent group nogroup || echo 65534))) ## Use these settings for a traditional /usr/local install -# PREFIX:=$(DESTDIR)/usr/local -# CONFDIR:=$(DESTDIR)/etc/mandos -# KEYDIR:=$(DESTDIR)/etc/mandos/keys -# MANDIR:=$(PREFIX)/man -# INITRAMFSTOOLS:=$(DESTDIR)/etc/initramfs-tools -# DRACUTMODULE:=$(DESTDIR)/usr/lib/dracut/modules.d/90mandos -# STATEDIR:=$(DESTDIR)/var/lib/mandos -# LIBDIR:=$(PREFIX)/lib +# PREFIX=$(DESTDIR)/usr/local +# CONFDIR=$(DESTDIR)/etc/mandos +# KEYDIR=$(DESTDIR)/etc/mandos/keys +# MANDIR=$(PREFIX)/man +# INITRAMFSTOOLS=$(DESTDIR)/etc/initramfs-tools +# STATEDIR=$(DESTDIR)/var/lib/mandos +# LIBDIR=$(PREFIX)/lib ## ## These settings are for a package-type install -PREFIX:=$(DESTDIR)/usr -CONFDIR:=$(DESTDIR)/etc/mandos -KEYDIR:=$(DESTDIR)/etc/keys/mandos -MANDIR:=$(PREFIX)/share/man -INITRAMFSTOOLS:=$(DESTDIR)/usr/share/initramfs-tools -DRACUTMODULE:=$(DESTDIR)/usr/lib/dracut/modules.d/90mandos -STATEDIR:=$(DESTDIR)/var/lib/mandos -LIBDIR:=$(shell \ +PREFIX=$(DESTDIR)/usr +CONFDIR=$(DESTDIR)/etc/mandos +KEYDIR=$(DESTDIR)/etc/keys/mandos +MANDIR=$(PREFIX)/share/man +INITRAMFSTOOLS=$(DESTDIR)/usr/share/initramfs-tools +STATEDIR=$(DESTDIR)/var/lib/mandos +LIBDIR=$(shell \ for d in \ - "/usr/lib/`dpkg-architecture \ - -qDEB_HOST_MULTIARCH 2>/dev/null`" \ + "/usr/lib/`dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null`" \ "`rpm --eval='%{_libdir}' 2>/dev/null`" /usr/lib; do \ if [ -d "$$d" -a "$$d" = "$${d%/}" ]; then \ echo "$(DESTDIR)$$d"; \ @@ -85,32 +74,24 @@ done) ## -SYSTEMD:=$(DESTDIR)$(shell $(PKG_CONFIG) systemd \ - --variable=systemdsystemunitdir) -TMPFILES:=$(DESTDIR)$(shell $(PKG_CONFIG) systemd \ - --variable=tmpfilesdir) -SYSUSERS:=$(DESTDIR)$(shell $(PKG_CONFIG) systemd \ - --variable=sysusersdir) +SYSTEMD=$(DESTDIR)$(shell pkg-config systemd --variable=systemdsystemunitdir) +TMPFILES=$(DESTDIR)$(shell pkg-config systemd --variable=tmpfilesdir) -GNUTLS_CFLAGS:=$(shell $(PKG_CONFIG) --cflags-only-I gnutls) -GNUTLS_LIBS:=$(shell $(PKG_CONFIG) --libs gnutls) -AVAHI_CFLAGS:=$(shell $(PKG_CONFIG) --cflags-only-I avahi-core) -AVAHI_LIBS:=$(shell $(PKG_CONFIG) --libs avahi-core) -GPGME_CFLAGS:=$(shell $(PKG_CONFIG) --cflags-only-I gpgme 2>/dev/null \ - || gpgme-config --cflags; getconf LFS_CFLAGS) -GPGME_LIBS:=$(shell $(PKG_CONFIG) --libs gpgme 2>/dev/null \ - || gpgme-config --libs; getconf LFS_LIBS; \ +GNUTLS_CFLAGS=$(shell pkg-config --cflags-only-I gnutls) +GNUTLS_LIBS=$(shell pkg-config --libs gnutls) +AVAHI_CFLAGS=$(shell pkg-config --cflags-only-I avahi-core) +AVAHI_LIBS=$(shell pkg-config --libs avahi-core) +GPGME_CFLAGS=$(shell gpgme-config --cflags; getconf LFS_CFLAGS) +GPGME_LIBS=$(shell gpgme-config --libs; getconf LFS_LIBS; \ getconf LFS_LDFLAGS) -LIBNL3_CFLAGS:=$(shell $(PKG_CONFIG) --cflags-only-I libnl-route-3.0) -LIBNL3_LIBS:=$(shell $(PKG_CONFIG) --libs libnl-route-3.0) -GLIB_CFLAGS:=$(shell $(PKG_CONFIG) --cflags glib-2.0) -GLIB_LIBS:=$(shell $(PKG_CONFIG) --libs glib-2.0) +LIBNL3_CFLAGS=$(shell pkg-config --cflags-only-I libnl-route-3.0) +LIBNL3_LIBS=$(shell pkg-config --libs libnl-route-3.0) # Do not change these two -CFLAGS+=$(WARN) $(DEBUG) $(FORTIFY) $(COVERAGE) $(OPTIMIZE) \ - $(LANGUAGE) $(FEATURES) -DVERSION='"$(version)"' -LDFLAGS+=-Xlinker --as-needed $(COVERAGE) $(LINK_FORTIFY) $(strip \ - ) $(foreach flag,$(LINK_FORTIFY_LD),-Xlinker $(flag)) +CFLAGS+=$(WARN) $(DEBUG) $(FORTIFY) $(SANITIZE) $(COVERAGE) \ + $(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 DOCBOOKTOMAN=$(strip cd $(dir $<); xsltproc --nonet --xinclude \ @@ -122,9 +103,9 @@ /usr/share/xml/docbook/stylesheet/nwalsh/manpages/docbook.xsl \ $(notdir $<); \ if locale --all 2>/dev/null | grep --regexp='^en_US\.utf8$$' \ - && command -v man >/dev/null; then LANG=en_US.UTF-8 \ - MANWIDTH=80 man --warnings --encoding=UTF-8 --local-file \ - $(notdir $@); fi >/dev/null) + && type man 2>/dev/null; then LANG=en_US.UTF-8 MANWIDTH=80 \ + man --warnings --encoding=UTF-8 --local-file $(notdir $@); \ + fi >/dev/null) DOCBOOKTOHTML=$(strip xsltproc --nonet --xinclude \ --param make.year.ranges 1 \ @@ -136,35 +117,30 @@ /usr/share/xml/docbook/stylesheet/nwalsh/xhtml/docbook.xsl \ $<; $(HTMLPOST) $@) # Fix citerefentry links -HTMLPOST:=$(SED) --in-place \ +HTMLPOST=$(SED) --in-place \ --expression='s/\(\)\([^<]*\)\(<\/span>(\)\([^)]*\)\()<\/span><\/a>\)/\1\3.\5\2\3\4\5\6/g' -PLUGINS:=plugins.d/password-prompt plugins.d/mandos-client \ +PLUGINS=plugins.d/password-prompt plugins.d/mandos-client \ plugins.d/usplash plugins.d/splashy plugins.d/askpass-fifo \ plugins.d/plymouth -PLUGIN_HELPERS:=plugin-helpers/mandos-client-iprouteadddel -CPROGS:=plugin-runner dracut-module/password-agent $(PLUGINS) \ - $(PLUGIN_HELPERS) -PROGS:=mandos mandos-keygen mandos-ctl mandos-monitor $(CPROGS) -DOCS:=mandos.8 mandos-keygen.8 mandos-monitor.8 mandos-ctl.8 \ +PLUGIN_HELPERS=plugin-helpers/mandos-client-iprouteadddel +CPROGS=plugin-runner $(PLUGINS) $(PLUGIN_HELPERS) +PROGS=mandos mandos-keygen mandos-ctl mandos-monitor $(CPROGS) +DOCS=mandos.8 mandos-keygen.8 mandos-monitor.8 mandos-ctl.8 \ mandos.conf.5 mandos-clients.conf.5 plugin-runner.8mandos \ - dracut-module/password-agent.8mandos \ plugins.d/mandos-client.8mandos \ plugins.d/password-prompt.8mandos plugins.d/usplash.8mandos \ plugins.d/splashy.8mandos plugins.d/askpass-fifo.8mandos \ plugins.d/plymouth.8mandos intro.8mandos -htmldocs:=$(addsuffix .xhtml,$(DOCS)) - -objects:=$(addsuffix .o,$(CPROGS)) - -.PHONY: all +htmldocs=$(addsuffix .xhtml,$(DOCS)) + +objects=$(addsuffix .o,$(CPROGS)) + all: $(PROGS) mandos.lsm -.PHONY: doc doc: $(DOCS) -.PHONY: html html: $(htmldocs) %.5: %.xml common.ent legalnotice.xml @@ -229,15 +205,6 @@ overview.xml legalnotice.xml $(DOCBOOKTOHTML) -dracut-module/password-agent.8mandos: \ - dracut-module/password-agent.xml common.ent \ - overview.xml legalnotice.xml - $(DOCBOOKTOMAN) -dracut-module/password-agent.8mandos.xhtml: \ - dracut-module/password-agent.xml common.ent \ - overview.xml legalnotice.xml - $(DOCBOOKTOHTML) - plugins.d/mandos-client.8mandos: plugins.d/mandos-client.xml \ common.ent \ mandos-options.xml \ @@ -286,86 +253,60 @@ --expression='s/\(mandos_\)[0-9.]\+\(\.orig\.tar\.gz\)/\1$(version)\2/' \ $@) -# Need to add the GnuTLS, Avahi and GPGME libraries -plugins.d/mandos-client: CFLAGS += $(GNUTLS_CFLAGS) $(strip \ - ) $(AVAHI_CFLAGS) $(GPGME_CFLAGS) -plugins.d/mandos-client: LDLIBS += $(GNUTLS_LIBS) $(strip \ - ) $(AVAHI_LIBS) $(GPGME_LIBS) - -# Need to add the libnl-route library -plugin-helpers/mandos-client-iprouteadddel: CFLAGS += $(LIBNL3_CFLAGS) -plugin-helpers/mandos-client-iprouteadddel: LDLIBS += $(LIBNL3_LIBS) - -# Need to add the GLib and pthread libraries -dracut-module/password-agent: CFLAGS += $(GLIB_CFLAGS) -# Note: -lpthread is unnecessary with the GNU C library 2.34 or later -dracut-module/password-agent: LDLIBS += $(GLIB_LIBS) -lpthread - -.PHONY: clean +plugins.d/mandos-client: plugins.d/mandos-client.c + $(LINK.c) $^ -lrt $(GNUTLS_LIBS) $(AVAHI_LIBS) $(strip\ + ) $(GPGME_LIBS) $(LOADLIBES) $(LDLIBS) -o $@ + +plugin-helpers/mandos-client-iprouteadddel: plugin-helpers/mandos-client-iprouteadddel.c + $(LINK.c) $(LIBNL3_CFLAGS) $^ $(LIBNL3_LIBS) $(strip\ + ) $(LOADLIBES) $(LDLIBS) -o $@ + +.PHONY : all doc html clean distclean mostlyclean maintainer-clean \ + check run-client run-server install install-html \ + install-server install-client-nokey install-client uninstall \ + uninstall-server uninstall-client purge purge-server \ + purge-client + clean: -rm --force $(CPROGS) $(objects) $(htmldocs) $(DOCS) core -.PHONY: distclean distclean: clean -.PHONY: mostlyclean mostlyclean: clean -.PHONY: maintainer-clean maintainer-clean: clean -rm --force --recursive keydir confdir statedir -.PHONY: check -check: all +check: all ./mandos --check ./mandos-ctl --check - ./mandos-keygen --version - ./plugin-runner --version - ./plugin-helpers/mandos-client-iprouteadddel --version - ./dracut-module/password-agent --test # Run the client with a local config and key -.PHONY: run-client -run-client: all keydir/seckey.txt keydir/pubkey.txt \ - keydir/tls-privkey.pem keydir/tls-pubkey.pem - @echo '######################################################' - @echo '# The following error messages are harmless and can #' - @echo '# be safely ignored: #' - @echo '## From plugin-runner: #' - @echo '# setgid: Operation not permitted #' - @echo '# setuid: Operation not permitted #' - @echo '## From askpass-fifo: #' - @echo '# mkfifo: Permission denied #' - @echo '## From mandos-client: #' - @echo '# Failed to raise privileges: Operation not permi... #' - @echo '# Warning: network hook "*" exited with status * #' - @echo '# ioctl SIOCSIFFLAGS +IFF_UP: Operation not permi... #' - @echo '# Failed to bring up interface "*": Operation not... #' - @echo '# #' - @echo '# (The messages are caused by not running as root, #' - @echo '# but you should NOT run "make run-client" as root #' - @echo '# unless you also unpacked and compiled Mandos as #' - @echo '# root, which is also NOT recommended.) #' - @echo '######################################################' +run-client: all keydir/seckey.txt keydir/pubkey.txt + @echo "###################################################################" + @echo "# The following error messages are harmless and can be safely #" + @echo "# ignored. The messages are caused by not running as root, but #" + @echo "# you should NOT run \"make run-client\" as root unless you also #" + @echo "# unpacked and compiled Mandos as root, which is NOT recommended. #" + @echo "# From plugin-runner: setgid: Operation not permitted #" + @echo "# setuid: Operation not permitted #" + @echo "# From askpass-fifo: mkfifo: Permission denied #" + @echo "# From mandos-client: #" + @echo "# Failed to raise privileges: Operation not permitted #" + @echo "# Warning: network hook \"*\" exited with status * #" + @echo "###################################################################" # We set GNOME_KEYRING_CONTROL to block pam_gnome_keyring ./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 - if ! [ -e keydir/tls-privkey.pem ]; then \ - install --mode=u=rw /dev/null keydir/tls-privkey.pem; \ - fi - if ! [ -e keydir/tls-pubkey.pem ]; then \ - install --mode=u=rw /dev/null keydir/tls-pubkey.pem; \ - fi # Run the server with a local config -.PHONY: run-server run-server: confdir/mandos.conf confdir/clients.conf statedir ./mandos --debug --no-dbus --configdir=confdir \ --statedir=statedir $(SERVERARGS) @@ -374,7 +315,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 @@ -382,16 +323,13 @@ statedir: install --directory statedir -.PHONY: install install: install-server install-client-nokey -.PHONY: install-html install-html: html install --directory $(htmldir) install --mode=u=rw,go=r --target-directory=$(htmldir) \ $(htmldocs) -.PHONY: install-server install-server: doc install --directory $(CONFDIR) if install --directory --mode=u=rwx --owner=$(USER) \ @@ -400,16 +338,10 @@ elif install --directory --mode=u=rwx $(STATEDIR); then \ chown -- $(USER):$(GROUP) $(STATEDIR) || :; \ fi - if [ "$(TMPFILES)" != "$(DESTDIR)" \ - -a -d "$(TMPFILES)" ]; then \ + if [ "$(TMPFILES)" != "$(DESTDIR)" -a -d "$(TMPFILES)" ]; then \ install --mode=u=rw,go=r tmpfiles.d-mandos.conf \ $(TMPFILES)/mandos.conf; \ fi - if [ "$(SYSUSERS)" != "$(DESTDIR)" \ - -a -d "$(SYSUSERS)" ]; then \ - install --mode=u=rw,go=r sysusers.d-mandos.conf \ - $(SYSUSERS)/mandos.conf; \ - fi install --mode=u=rwx,go=rx mandos $(PREFIX)/sbin/mandos install --mode=u=rwx,go=rx --target-directory=$(PREFIX)/sbin \ mandos-ctl @@ -444,17 +376,11 @@ gzip --best --to-stdout intro.8mandos \ > $(MANDIR)/man8/intro.8mandos.gz -.PHONY: install-client-nokey install-client-nokey: all doc install --directory $(LIBDIR)/mandos $(CONFDIR) install --directory --mode=u=rwx $(KEYDIR) \ $(LIBDIR)/mandos/plugins.d \ $(LIBDIR)/mandos/plugin-helpers - if [ "$(SYSUSERS)" != "$(DESTDIR)" \ - -a -d "$(SYSUSERS)" ]; then \ - install --mode=u=rw,go=r sysusers.d-mandos.conf \ - $(SYSUSERS)/mandos-client.conf; \ - fi if [ "$(CONFDIR)" != "$(LIBDIR)/mandos" ]; then \ install --mode=u=rwx \ --directory "$(CONFDIR)/plugins.d" \ @@ -464,9 +390,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 \ @@ -492,23 +415,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 --directory $(DRACUTMODULE) - install --mode=u=rw,go=r --target-directory=$(DRACUTMODULE) \ - dracut-module/ask-password-mandos.path \ - dracut-module/ask-password-mandos.service - install --mode=u=rwxs,go=rx \ - --target-directory=$(DRACUTMODULE) \ - dracut-module/module-setup.sh \ - dracut-module/cmdline-mandos.sh \ - dracut-module/password-agent install --mode=u=rw,go=r plugin-runner.conf $(CONFDIR) gzip --best --to-stdout mandos-keygen.8 \ > $(MANDIR)/man8/mandos-keygen.8.gz @@ -526,29 +436,15 @@ > $(MANDIR)/man8/askpass-fifo.8mandos.gz gzip --best --to-stdout plugins.d/plymouth.8mandos \ > $(MANDIR)/man8/plymouth.8mandos.gz - gzip --best --to-stdout dracut-module/password-agent.8mandos \ - > $(MANDIR)/man8/password-agent.8mandos.gz -.PHONY: install-client install-client: install-client-nokey # Post-installation stuff -$(PREFIX)/sbin/mandos-keygen --dir "$(KEYDIR)" - if command -v update-initramfs >/dev/null; then \ - update-initramfs -k all -u; \ - elif command -v dracut >/dev/null; then \ - for initrd in $(DESTDIR)/boot/initr*-$(LINUXVERSION); do \ - if [ -w "$$initrd" ]; then \ - chmod go-r "$$initrd"; \ - dracut --force "$$initrd"; \ - fi; \ - done; \ - fi + update-initramfs -k all -u echo "Now run mandos-keygen --password --dir $(KEYDIR)" -.PHONY: uninstall uninstall: uninstall-server uninstall-client -.PHONY: uninstall-server uninstall-server: -rm --force $(PREFIX)/sbin/mandos \ $(PREFIX)/sbin/mandos-ctl \ @@ -561,7 +457,6 @@ update-rc.d -f mandos remove -rmdir $(CONFDIR) -.PHONY: uninstall-client uninstall-client: # Refuse to uninstall client if /etc/crypttab is explicitly configured # to use it. @@ -578,12 +473,6 @@ $(INITRAMFSTOOLS)/hooks/mandos \ $(INITRAMFSTOOLS)/conf-hooks.d/mandos \ $(INITRAMFSTOOLS)/scripts/init-premount/mandos \ - $(INITRAMFSTOOLS)/scripts/local-premount/mandos \ - $(DRACUTMODULE)/ask-password-mandos.path \ - $(DRACUTMODULE)/ask-password-mandos.service \ - $(DRACUTMODULE)/module-setup.sh \ - $(DRACUTMODULE)/cmdline-mandos.sh \ - $(DRACUTMODULE)/password-agent \ $(MANDIR)/man8/mandos-keygen.8.gz \ $(MANDIR)/man8/plugin-runner.8mandos.gz \ $(MANDIR)/man8/mandos-client.8mandos.gz @@ -592,21 +481,12 @@ $(MANDIR)/man8/splashy.8mandos.gz \ $(MANDIR)/man8/askpass-fifo.8mandos.gz \ $(MANDIR)/man8/plymouth.8mandos.gz \ - $(MANDIR)/man8/password-agent.8mandos.gz \ -rmdir $(LIBDIR)/mandos/plugins.d $(CONFDIR)/plugins.d \ - $(LIBDIR)/mandos $(CONFDIR) $(KEYDIR) $(DRACUTMODULE) - if command -v update-initramfs >/dev/null; then \ - update-initramfs -k all -u; \ - elif command -v dracut >/dev/null; then \ - for initrd in $(DESTDIR)/boot/initr*-$(LINUXVERSION); do \ - test -w "$$initrd" && dracut --force "$$initrd"; \ - done; \ - fi + $(LIBDIR)/mandos $(CONFDIR) $(KEYDIR) + update-initramfs -k all -u -.PHONY: purge purge: purge-server purge-client -.PHONY: purge-server purge-server: uninstall-server -rm --force $(CONFDIR)/mandos.conf $(CONFDIR)/clients.conf \ $(DESTDIR)/etc/dbus-1/system.d/mandos.conf @@ -617,10 +497,8 @@ $(DESTDIR)/var/run/mandos.pid -rmdir $(CONFDIR) -.PHONY: purge-client 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 2022-04-25 18:46:48 +0000 +++ NEWS 2016-03-19 22:00:38 +0000 @@ -1,200 +1,6 @@ This NEWS file records noteworthy changes, very tersely. See the manual for detailed information. -Version 1.8.15 (2022-04-25) -* Server -** Bug fix: When running "mandos-keygen --password" to read a password - interactively (to save in a section in the clients.conf file), - backslashes in the password are no longer interpreted as backslash - escapes. -** GnuTLS debug output no longer has a "b'" prefix. - -Version 1.8.14 (2021-02-03) -* Client -** Create /dev/fd symlink (if necessary) in plugin-runner(8mandos) and - mandos-client(8mandos) (Workaround for Debian bug #981302) - -Version 1.8.13 (2020-11-30) -* Client -** Fix unreliable test in password-agent(8mandos). - -Version 1.8.12 (2020-07-04) -* Client -** Fix compatibility with the GNU C Library version 2.31. -** In initramfs-tools boots, only use setsid(1) when available. - -Version 1.8.11 (2020-04-08) -* Client -** Fix file descriptor leak when adding or removing local routes to - unreachable hosts on the local network. - -Version 1.8.10 (2020-03-21) -* Server -** Fix bug when setting a client's D-Bus "Secret" property -** Start client checkers after a random delay -** When using systemd, allow easier modification of server options -** Better log messages in mandos-monitor -* Client -** When using dracut & systemd, allow easier modification of options - -Version 1.8.9 (2019-09-03) -* No user-visible changes - -Version 1.8.8 (2019-08-18) -* No user-visible changes - -Version 1.8.7 (2019-08-05) -* Client: -** Always compile with LFS (Large File Support) enabled. -* Server -** Improve intro(8mandos) manual page to cover dracut(8) support. - -Version 1.8.6 (2019-08-03) -* Client: -** dracut support: In password-agent, properly ignore deleted and - renamed question files, and also fix memory alignment issue. - -Version 1.8.5 (2019-07-30) -* Client -** Support dracut(8) as well as initramfs-tools(7). -** Minor bug fix: Allow the mandos-keygen --passfile option to use - passfiles with names starting with "-". -** Document known limitation of mandos-keygen --password; it strips - white space from start and end of the password. -* Server -** Bug fix: The server used to fail to restart if the "port" setting - was used. This has been fixed. -** Minor bug fix: Reap zombies left over from checker runs. (Debian - bug #933387) - -Version 1.8.4 (2019-04-09) -* Client -** Fix minor memory leak in plugin-runner. -* Server -** mandos-ctl now has a --debug option to show D-Bus calls. - -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 -** Bug fix in plymouth plugin: fix memory leak, avoid warning output - -Version 1.7.15 (2017-02-23) -* Server -** Bug fix: Respect the mandos.conf "zeroconf" and "restore" options -* Client -** Bug fix in mandos-keygen: Handle backslashes in passphrases - -Version 1.7.14 (2017-01-25) -* Server -** Use "Requisite" instead of "RequisiteOverridable" in systemd - service file. - -Version 1.7.13 (2016-10-08) -* Client -** Minor bug fix: Don't ask for passphrase or fail when generating - keys using GnuPG 2.1 in a chrooted environment. - -Version 1.7.12 (2016-10-05) -* Client -** Bug fix: Don't crash after exit() when using DH parameters file - -Version 1.7.11 (2016-10-01) -* Client -** Security fix: Don't compile with AddressSanitizer -* Server -** Bug fix: Find GnuTLS library when gnutls28-dev is not installed -** Bug fix: Include "Expires" and "Last Checker Status" in mandos-ctl - verbose output -** New option for mandos-ctl: --dump-json - -Version 1.7.10 (2016-06-23) -* Client -** Security fix: restrict permissions of /etc/mandos/plugin-helpers -* Server -** Bug fix: Make the --interface flag work with Python 2.7 when "cc" - is not installed - -Version 1.7.9 (2016-06-22) -* Client -** Do not include intro(8mandos) man page - -Version 1.7.8 (2016-06-21) -* Client -** Include intro(8mandos) man page -** mandos-keygen: Use ECDSA SSH keys by default -** Bug fix: Work with GnuPG 2 when booting (Debian bug #819982) - by copying /usr/bin/gpg-agent into initramfs -* Server -** Bug fix: Work with GnuPG 2 (don't use --no-use-agent option) -** Bug fix: Make the --interface option work when using Python 2.7 - by trying harder to find SO_BINDTODEVICE - Version 1.7.7 (2016-03-19) * Client ** Fix bug in Plymouth client, broken since 1.7.2 === modified file 'TODO' --- TODO 2020-02-09 03:42:50 +0000 +++ TODO 2016-03-19 03:51:23 +0000 @@ -6,49 +6,40 @@ * mandos-applet * mandos-client -** TODO A ~--server~ option which only adds to the server list. - (Unlike ~--connect~, which implicitly disables ZeroConf.) -** TODO [#B] Use [[man:capabilities][capabilities]] instead of [[info:libc#Setting%20User%20ID][seteuid()]]. - [[https://forums.grsecurity.net/viewtopic.php?f=7&t=2522]] -** TODO [#B] Use ~getaddrinfo(hints=AI_NUMERICHOST)~ instead of ~inet_pton()~ -** TODO [#C] Make ~start_mandos_communication()~ take ~struct server~. -** TODO [#C] ~--interfaces=regex,eth*,noregex~ [[man:bridge-utils-interfaces][bridge-utils-interfaces(5)]] -** TODO [#A] Detect partial writes to stdout and exit with ~EX_TEMPFAIL~ +** TODO A --server option which only adds to the server list. + (Unlike --connect, which implicitly disables zeroconf.) +** TODO [#B] Use capabilities instead of seteuid(). + https://forums.grsecurity.net/viewtopic.php?f=7&t=2522 +** TODO [#B] Use getaddrinfo(hints=AI_NUMERICHOST) instead of inet_pton() +** TODO [#C] Make start_mandos_communication() take "struct server". +** TODO [#C] --interfaces=regex,eth*,noregex (bridge-utils-interfaces(5)) * splashy -** TODO [#B] use [[info:libc#Scanning%20Directory%20Content][scandir(3)]] instead of [[info:libc#Reading/Closing%20Directory][readdir(3)]] -** TODO [#A] Detect partial writes to stdout and exit with ~EX_TEMPFAIL~ +** TODO [#B] use scandir(3) instead of readdir(3) * usplash (Deprecated) ** TODO [#B] Make it work again -** TODO [#B] use [[info:libc#Scanning%20Directory%20Content][scandir(3)]] instead of [[info:libc#Reading/Closing%20Directory][readdir(3)]] -** TODO [#A] Detect partial writes to stdout and exit with ~EX_TEMPFAIL~ +** TODO [#B] use scandir(3) instead of readdir(3) * askpass-fifo -** TODO [#A] Detect partial writes to stdout and exit with ~EX_TEMPFAIL~ * password-prompt -** TODO [#B] lock stdin (with [[info:libc#File%20Locks][flock()]]?) -** TODO [#A] Detect partial writes to stdout and exit with ~EX_TEMPFAIL~ +** TODO [#B] lock stdin (with flock()?) * plymouth -** TODO [#A] Detect partial writes to stdout and exit with ~EX_TEMPFAIL~ * TODO [#B] passdev * plugin-runner ** TODO handle printing for errors for plugins *** Hook up stderr of plugins, buffer them, and prepend "Mandos Plugin [plugin name]" -** TODO [#C] use same file name rules as [[man:run-parts][run-parts(8)]] +** TODO [#C] use same file name rules as run-parts(8) ** kernel command line option for debug info -** TODO [#A] Restart plugins which exit with ~EX_TEMPFAIL~ * mandos (server) -** TODO [#B] ~--notify-command~ +** TODO [#B] --notify-command This would allow the mandos.service to use - ~--notify-command="systemd-notify --pid --ready"~ -** TODO [#B] python-systemd -*** import systemd.daemon; systemd.daemon.notify() + --notify-command="systemd-notify --pid --ready" ** TODO [#B] Log level :BUGS: *** TODO /etc/mandos/clients.d/*.conf Watch this directory and add/remove/update clients? @@ -59,28 +50,33 @@ ** TODO [#B] Global enable/disable flag ** TODO [#B] By-client countdown on number of secrets given ** D-Bus Client method NeedsPassword(50) - Timeout, default disapprove - + SetPass("gazonk", True) -> Approval, persistent + + SetPass(u"gazonk", True) -> Approval, persistent + Approve(False) -> Close client connection immediately ** TODO [#C] python-parsedatetime ** TODO Separate logging logic to own object -** TODO [#B] Limit ~approval_delay~ to max GnuTLS/TLS timeout value -** TODO [#B] break the wait on ~approval_delay~ if connection dies -** TODO Generate ~Client.runtime_expansions~ from client options + extra +** TODO [#B] Limit approval_delay to max gnutls/tls timeout value +** TODO [#B] break the wait on approval_delay if connection dies +** TODO Generate Client.runtime_expansions from client options + extra ** TODO Allow %%(checker)s as a runtime expansion ** TODO D-Bus AddClient() method on server object ** TODO Use org.freedesktop.DBus.Method.NoReply annotation on async methods. :2: ** TODO Save state periodically to recover better from hard shutdowns ** TODO CheckerCompleted method, deprecate CheckedOK -** TODO [[https://standards.freedesktop.org/secret-service/][Secret Service]] API? +** TODO Secret Service API? + http://standards.freedesktop.org/secret-service/ ** TODO Remove D-Bus interfaces with old domain name :2: -** TODO Remove old ~string_to_delta~ format :2: +** TODO Remove old string_to_delta format :2: ** TODO http://0pointer.de/blog/projects/stateless.html -*** File in /usr/lib/sysusers.d to create user+group "~_mandos~" +*** File in /usr/lib/sysusers.d to create user+group "_mandos" ** TODO Error handling on error parsing config files ** TODO init.d script error handling ** TODO D-Bus server properties; address, port, interface, etc. :2: +** Python 3 :2: +*** 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 +*** [#B] --dump option ** TODO Remove old string_to_delta format :2: * TODO mandos-dispatch @@ -88,7 +84,7 @@ arguments. * mandos-monitor -** TODO ~--servicename~ :BUGS: +** TODO --servicename :BUGS: ** TODO help should be toggleable ** Urwid client data displayer Better view of client data in the listing @@ -96,17 +92,17 @@ ** Print a nice "We are sorry" message, save stack trace to log. * mandos-keygen -** TODO "~--secfile~" option +** TODO "--secfile" option Using the "secfile" option instead of "secret" -** TODO [#B] "~--test~" option +** TODO [#B] "--test" option For testing decryption before rebooting. * Package ** /usr/share/initramfs-tools/hooks/mandos -*** TODO [#C] use same file name rules as [[man:run-parts][run-parts(8)]] +*** TODO [#C] use same file name rules as run-parts(8) *** TODO [#C] Do not install in initrd.img if configured not to. Use "/etc/initramfs-tools/hooksconf.d/mandos"? -** TODO [#C] ~$(pkg-config --variable=completionsdir bash-completion)~ +** TODO [#C] $(pkg-config --variable=completionsdir bash-completion) From XML sources directly? * Side Stuff @@ -117,4 +113,3 @@ #+STARTUP: showall -#+FILETAGS: :mandos: === modified file 'clients.conf' --- clients.conf 2022-04-23 20:36:45 +0000 +++ clients.conf 2012-06-23 00:58:49 +0000 @@ -4,7 +4,6 @@ # How long until a client is disabled and not be allowed to get the # data this server holds. -# (RFC 3339 duration syntax) ;timeout = PT5M # How often to run the checker to confirm that a client is still up. @@ -12,13 +11,11 @@ # running. The server will wait for a checker to complete until the # above "timeout" occurs, at which time the client will be disabled, # and any running checker killed. -# (RFC 3339 duration syntax) ;interval = PT2M # Extended timeout is an added timeout that is given once after a # password has been sent sucessfully to a client. This allows for # additional delays caused by file system checks and quota checks. -# (RFC 3339 duration syntax) ;extended_timeout = PT15M # What command to run as "the checker". @@ -28,11 +25,9 @@ ;approved_by_default = True # How long to wait for approval. -# (RFC 3339 duration syntax) ;approval_delay = PT0S # How long one approval will last. -# (RFC 3339 duration syntax) ;approval_duration = PT1S # Whether this client is enabled by default @@ -43,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 ; @@ -76,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 2022-04-25 18:46:48 +0000 +++ common.ent 2016-03-19 22:00:38 +0000 @@ -1,3 +1,3 @@ - + === modified file 'debian/changelog' --- debian/changelog 2022-04-25 18:46:48 +0000 +++ debian/changelog 2016-03-19 22:00:38 +0000 @@ -1,399 +1,3 @@ -mandos (1.8.15-1) unstable; urgency=medium - - * New upstream release. - * debian/po/fr.po: Add missing whitespace to the id and translation - for msgid " ${key_id}". - * debian/mandos-client.lintian-overrides: Remove all empty commented - lines. Rename "setuid-binary" tag to "elevated-privileges". - * debian/control (Standards-Version): Change to "4.6.0". - * debian/copyright: Update copyright year to 2022. - * debian/po/es.po: Add Spanish translation of the debconf template - (Closes: #987595). - - -- Teddy Hogeborn Mon, 25 Apr 2022 20:43:27 +0200 - -mandos (1.8.14-1) unstable; urgency=medium - - * New upstream release. Includes workaround for #981302 - * debian/po/fr.po: Fix msgid to match template. - - -- Teddy Hogeborn Wed, 03 Feb 2021 09:53:29 +0100 - -mandos (1.8.13-1) unstable; urgency=medium - - * New upstream release. - * Fix "password-agent autopkgtest does not seem to be reliable" - by making the test more reliable (Closes: #975457) - * debian/mandos-client.templates: Rename "_description" header to - "_Description". - * debian/control (Standards-Version): Change to "4.5.1". - - -- Teddy Hogeborn Mon, 30 Nov 2020 19:15:43 +0100 - -mandos (1.8.12-1) unstable; urgency=medium - - * New upstream release. - * Fix "FTBFS with glibc 2.31 (uses removed stime function)" by using - clock_settime() instead. (Closes: #964226) - * debian/copyright: Update copyright year to 2020. - - -- Teddy Hogeborn Sat, 04 Jul 2020 14:58:26 +0200 - -mandos (1.8.11-1) unstable; urgency=medium - - * New upstream release. - - -- Teddy Hogeborn Wed, 08 Apr 2020 20:37:32 +0200 - -mandos (1.8.10-1) unstable; urgency=medium - - * Fix "[INTL:pt] Updated Portuguese translation - debconf messages" - by including the contributed translation (Closes: #942595) - * Fix "[INTL:nl] Dutch translation of debconf messages" by including the - contributed translation (Closes: #946006) - * Fix "flaky autopkgtest on arm64" by skipping the flaky test on - non-amd64 (Closes: #953799) - * debian/control (Standards-Version): Update to "4.5.0". - - -- Teddy Hogeborn Sat, 21 Mar 2020 17:45:01 +0100 - -mandos (1.8.9-2) unstable; urgency=medium - - * Fix failing autopkgtest. - * debian/tests/control (mandos-check/Restrictions): Add "allow-stderr". - - -- Teddy Hogeborn Wed, 04 Sep 2019 23:14:06 +0200 - -mandos (1.8.9-1) unstable; urgency=medium - - * New upstream release. - * Fix "Python2 removal in sid/bullseye" by using Python 3 instead - (Closes: #936987) - * debian/control (Build-Depends, Build-Depends-Indep): Move "systemd" - from indep to regular build-depends. - (Build-Depends-Indep, Package: mandos/Depends): Depend on Python 3 and - Python 3 modules instead of Python 2. - - -- Teddy Hogeborn Tue, 03 Sep 2019 20:58:27 +0200 - -mandos (1.8.8-1) unstable; urgency=medium - - * New upstream release. - * debian/po/de.po: New; Fix "[INTL:de] Initial German debconf - translation" by including the contributed translation (Closes: - #934373) - * debian/po/fr.po: New; Fix "[INTL:fr] French debconf templates - translation" by including the contributed translation (Closes: - #934888) - * debian/po/sv.po: New Swedish translation. - * debian/mandos.postinst: Only reload D-Bus daemon if new user was - created. - * debian/mandos.dirs (usr/lib/sysusers.d): New. - * debian/mandos-client.dirs (usr/lib/sysusers.d): - '' - - - -- Teddy Hogeborn Sun, 18 Aug 2019 22:01:13 +0200 - -mandos (1.8.7-1) unstable; urgency=medium - - * New upstream release. - * debian/upstream/metadata: New. - * debian/mandos-client.postrm: Use the same logic as the - update_initramfs function in debian/mandos-client.postinst. - * debian/mandos-client.templates (mandos-client/key_id): Line which - should not be wrapped should be prefixed by a space. - * debian/mandos.templates (mandos/key_id): - '' - - * debian/po/en_US.po: New "translation" from ASCII to UTF-8. - * debian/po/templates.pot: Updated. - * debian/source/lintian-overrides - (package-uses-old-debhelper-compat-version): New; set to "10". - * debian/mandos-client.lintian-overrides - (maintainer-script-supports-ancient-package-version): New. - debian/mandos.lintian-overrides - (maintainer-script-supports-ancient-package-version): - '' - - - -- Teddy Hogeborn Mon, 05 Aug 2019 23:22:00 +0200 - -mandos (1.8.6-1) unstable; urgency=medium - - * New upstream release. - * Fix "mandos FTCBFS: hard codes build architecture pkg-config" - by making pkg-config overridable (Closes: #933701) - * debian/mandos.postinst (configure): After creating (or renaming) user - & group, reload D-Bus daemon (if present). - - -- Teddy Hogeborn Sat, 03 Aug 2019 14:51:01 +0200 - -mandos (1.8.5-1) unstable; urgency=medium - - * New upstream release. - * Fix "does not reap children" by reaping children (Closes: #933387) - * debian/mandos-client.README.Debian: Use new-style interface name. - * debian/tests/control: New file; implements autopkgtest support. - * debian/mandos-client.lintian-overrides - (manpage-has-errors-from-man): Remove; unnecessary. - * debian/mandos.lintian-overrides - (init.d-script-needs-depends-on-lsb-base): - '' - - * debian/mandos-client.postinst (update_initramfs): Upstream now - supports dracut(8), so update commands here to and run the correct - command to update initramfs. - * debian/control (Build-Depends): Add GLib -dev package. - (mandos-client/Depends): Add dracut(8) as an alternative dependency to - initramfs-tools. - (mandos-client/Conflicts): New; set to "dracut-config-generic". - (debian/mandos-client.README.Debian): Update for dracut(8) support. - * debian/mandos-client.templates: Reflowed by debconf-gettextize(1). - * debian/mandos.templates: - '' - - * debian/po/POTFILES.in: New. - * debian/po/templates.pot: - '' - - * debian/source/lintian-overrides: New. - * debian/control (Standards-Version): Update to "4.4.0". - - -- Teddy Hogeborn Tue, 30 Jul 2019 20:41:29 +0200 - -mandos (1.8.4-1) unstable; urgency=medium - - * Fix "dirs in initrd are not accessible by mandos plugin-runner" by - making sure UMASK is set, no matter what other packages have installed - in "/usr/share/initramfs-tools/conf-hooks.d". (Closes: #926641) - * Fix "LeakSanitizer: detected memory leaks, fails to decrypt" - by fixing memory leak in plugin-runner. (Closes: #926643) - * debian/mandos-client.dirs: Add - "usr/share/initramfs-tools/conf-hooks.d", needed by fix for #926641. - - -- Teddy Hogeborn Tue, 09 Apr 2019 22:05:39 +0200 - -mandos (1.8.3-3) unstable; urgency=medium - - * Fix "src:mandos: modifies d/control during build" by not doing that - anymore. (Closes: #922202) - * debian/rules (override_dh_shlibdeps-arch): Commented out. - - -- Teddy Hogeborn Wed, 13 Feb 2019 09:52:39 +0100 - -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. - * debian/copyright (License): Use program name explicitly. - (Format): Use https in URL. - * debian/control (Priority): Change from "extra" to "optional". - (Standards-Version): Update to "4.0.1". - - -- Teddy Hogeborn Sun, 20 Aug 2017 21:05:26 +0200 - -mandos (1.7.15-1) unstable; urgency=medium - - * New upstream release. - * Upstream release fixes "Seems not to be honoring zeroconf option at - mandos.conf" (Closes: #855589) - * debian/mandos.lintian-overrides (mandos): Add new line - "init.d-script-needs-depends-on-lsb-base etc/init.d/mandos (line 49)". - * debian/copyright: Update copyright year to 2017. - - -- Teddy Hogeborn Thu, 23 Feb 2017 21:29:36 +0100 - -mandos (1.7.14-1) unstable; urgency=medium - - * New upstream release. - * debian/mandos-client.postinst (create_key): Stop GPG agent after - running mandos-keygen. - * debian/control (Package: mandos/Depends): Add "systemd-sysv | lsb-base - (>= 3.0-6)", change "gnupg" to "gnupg2 | gnupg", and change - "libgpgme11-dev" to "libgpgme-dev | libgpgme11-dev". - - -- Teddy Hogeborn Wed, 25 Jan 2017 20:36:03 +0100 - -mandos (1.7.13-1) unstable; urgency=medium - - * New upstream release. - * Fix "fails to install noninteractively" by using the "%no-protection" - statement in the GnuPG batch parameter file. (Closes: #840001) - - -- Teddy Hogeborn Sat, 08 Oct 2016 06:31:07 +0200 - -mandos (1.7.12-1) unstable; urgency=medium - - * New upstream release. - - -- Teddy Hogeborn Wed, 05 Oct 2016 22:06:55 +0200 - -mandos (1.7.11-1) unstable; urgency=high - - * New upstream release. - * debian/control (Source: mandos/Vcs-Bzr): Change to use HTTPS. - (Vcs-Browser): - '' - - - -- Teddy Hogeborn Sat, 01 Oct 2016 16:20:48 +0200 - -mandos (1.7.10-1) unstable; urgency=high - - * New upstream release. - * debian/rules (override_dh_fixperms-arch): Also exclude - "etc/mandos/plugin-helpers" from changes by dh_fixperms. - * debian/mandos-client.postinst: Fix the permissions of - "/etc/mandos/plugin-helpers" for those systems which had a fresh - install of an older version. - - -- Teddy Hogeborn Thu, 23 Jun 2016 22:00:29 +0200 - -mandos (1.7.9-1) unstable; urgency=medium - - * New upstream release. - - -- Teddy Hogeborn Wed, 22 Jun 2016 07:30:12 +0200 - -mandos (1.7.8-1) unstable; urgency=medium - - * New upstream release. - * Fix "bad gpgme_op_decrypt: GPGME: Decryption failed." by copying - /usr/bin/gpg-agent into initramfs (Closes: #819982) - * debian/control (Homepage): Change URL to use HTTPS. - (Standards-Version): Update to 3.9.8. - * debian/copyright (Source): Change URL to HTTPS. - * debian/mandos-client.README.Debian: Change wording to match updated - capabilities. - - -- Teddy Hogeborn Tue, 21 Jun 2016 21:36:10 +0200 - mandos (1.7.7-1) unstable; urgency=medium * New upstream release. === modified file 'debian/compat' --- debian/compat 2018-02-06 20:03:50 +0000 +++ debian/compat 2013-10-20 15:25:09 +0000 @@ -1,1 +1,1 @@ -10 +9 === modified file 'debian/control' --- debian/control 2022-04-24 16:17:18 +0000 +++ debian/control 2016-03-23 07:11:22 +0000 @@ -1,32 +1,27 @@ Source: mandos Section: admin -Priority: optional +Priority: extra Maintainer: Mandos Maintainers Uploaders: Teddy Hogeborn , Björn Påhlsson -Build-Depends: debhelper (>= 10), docbook-xml, docbook-xsl, - libavahi-core-dev, libgpgme-dev | libgpgme11-dev, - libglib2.0-dev (>=2.40), libgnutls28-dev (>= 3.3.0), - libgnutls28-dev (>= 3.6.6) | libgnutls28-dev (<< 3.6.0), - xsltproc, pkg-config, libnl-route-3-dev, systemd -Build-Depends-Indep: python3 (>= 3), python3-dbus, python3-gi, - po-debconf -Standards-Version: 4.6.0 -Vcs-Bzr: https://ftp.recompile.se/pub/mandos/trunk -Vcs-Browser: https://bzr.recompile.se/loggerhead/mandos/trunk/files +Build-Depends: debhelper (>= 9), docbook-xml, docbook-xsl, + libavahi-core-dev, libgpgme11-dev, libgnutls28-dev (>= 3.3.0) + | gnutls-dev (>= 3.3.0), xsltproc, pkg-config, + libnl-route-3-dev +Build-Depends-Indep: systemd, python (>= 2.7), python (<< 3), + python-dbus, python-gi +Standards-Version: 3.9.7 +Vcs-Bzr: http://ftp.recompile.se/pub/mandos/trunk +Vcs-Browser: http://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}, python3 (>= 3), libgnutls30 (>= 3.3.0), - libgnutls30 (>= 3.6.6) | libgnutls30 (<< 3.6.0), - python3-dbus, python3-gi, avahi-daemon, adduser, - python3-urwid, gnupg2 | gnupg, - systemd-sysv | lsb-base (>= 3.0-6), - debconf (>= 1.5.5) | debconf-2.0 +Depends: ${misc:Depends}, python (>= 2.7), python (<< 3), + libgnutls28-dev (>= 3.3.0) | libgnutls30 (>= 3.3.0), + python-dbus, python-gi, avahi-daemon, adduser, python-urwid, + gnupg 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,25 +30,20 @@ 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) | dracut (>= 044+241-3), - 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 -Conflicts: dracut-config-generic Description: do unattended reboots with an encrypted root file system This is the client part of the Mandos system, which allows computers to have encrypted root file systems and at the @@ -62,9 +52,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 2022-04-24 16:54:30 +0000 +++ debian/copyright 2016-03-23 07:11:22 +0000 @@ -1,26 +1,25 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: Mandos Upstream-Contact: Mandos Source: Files: * -Copyright: Copyright © 2008-2022 Teddy Hogeborn - Copyright © 2008-2022 Björn Påhlsson +Copyright: Copyright © 2008-2016 Teddy Hogeborn + Copyright © 2008-2016 Björn Påhlsson License: GPL-3+ - 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 + This program is free software: you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + . + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License - along with Mandos. If not, see . + along with this program. If not, see + . . On Debian systems, the complete text of the GNU General Public License can be found in "/usr/share/common-licenses/GPL-3". === modified file 'debian/mandos-client.README.Debian' --- debian/mandos-client.README.Debian 2019-07-27 10:11:45 +0000 +++ debian/mandos-client.README.Debian 2015-07-12 01:25:14 +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 @@ -45,15 +43,11 @@ automatically detected. If this should result in incorrect interfaces, edit the DEVICE setting in the "/etc/initramfs-tools/initramfs.conf" file. (The default setting is - empty, meaning it will autodetect the interfaces.) *If* the DEVICE + empty, meaning it will autodetect the interface.) *If* the DEVICE setting is changed, it will be necessary to update the initrd image - by running this command: + by running the command - (For initramfs-tools:) update-initramfs -k all -u - - (For dracut:) - dpkg-reconfigure dracut The device can also be overridden at boot time on the Linux kernel command line using the sixth colon-separated field of the "ip=" @@ -64,12 +58,12 @@ Note that since the network interfaces are used in the initial RAM disk environment, the network interfaces *must* exist at that stage. Thus, an interface can *not* be a pseudo-interface such as "br0" or - "tun0"; instead, only real interfaces (such as "enp1s0" or "eth0") - can be used. This can be overcome by writing a "network hook" - program to create an interface (see mandos-client(8mandos)) and - placing it in "/etc/mandos/network-hooks.d", from where it will be - copied into the initial RAM disk. Example network hook scripts can - be found in "/usr/share/doc/mandos-client/examples/network-hooks.d". + "tun0"; instead, only real interfaces (such as "eth0") can be used. + This can be overcome by writing a "network hook" program to create + an interface (see mandos-client(8mandos)) and placing it in + "/etc/mandos/network-hooks.d", from where it will be copied into the + initial RAM disk. Example network hook scripts can be found in + "/usr/share/doc/mandos-client/examples/network-hooks.d". * User-Supplied Plugins @@ -77,11 +71,7 @@ to the normal Mandos plugins. When adding or changing plugins, do not forget to update the initital RAM disk image: - (For initramfs-tools:) update-initramfs -k all -u - - (For dracut:) - dpkg-reconfigure dracut * Do *NOT* Edit "/etc/crypttab" @@ -100,7 +90,7 @@ "mandos=connect::" on the kernel command line. - For very advanced users, it is possible to specify simply + For very advanced users, it it possible to specify simply "mandos=connect" on the kernel command line to make the system only set up the network (using the data in the "ip=" option) and not pass any extra "--connect" options to mandos-client at boot. For this to @@ -116,4 +106,4 @@ policy or other reasons, simply replace the existing dhparams.pem file and update the initital RAM disk image. - -- Teddy Hogeborn , Mon, 15 Jul 2019 16:47:02 +0200 + -- Teddy Hogeborn , Sun, 12 Jul 2015 03:24:24 +0200 === modified file 'debian/mandos-client.dirs' --- debian/mandos-client.dirs 2019-08-18 04:14:31 +0000 +++ debian/mandos-client.dirs 2009-02-07 04:50:39 +0000 @@ -1,8 +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 -usr/lib/sysusers.d === modified file 'debian/mandos-client.lintian-overrides' --- debian/mandos-client.lintian-overrides 2022-04-23 21:14:38 +0000 +++ debian/mandos-client.lintian-overrides 2016-03-19 04:21:00 +0000 @@ -1,40 +1,32 @@ # This directory contains secret client key files. +# mandos-client binary: non-standard-dir-perm etc/keys/mandos/ 0700 != 0755 # The directory /usr/lib//mandos/plugins.d contains setuid -# binaries which are only meant to be run inside an initial RAM disk +# binaries which are not meant to be run outside an initial RAM disk # environment (except for test purposes). It would be insecure to # allow anyone to run them. +# mandos-client binary: non-standard-dir-perm usr/lib/*/mandos/plugins.d/ 0700 != 0755 # Likewise for helper executables for plugins mandos-client binary: non-standard-dir-perm usr/lib/*/mandos/plugin-helpers/ 0700 != 0755 # These binaries must be setuid root, since they need root powers, but # are started by plugin-runner(8mandos), which runs all plugins as -# user/group "_mandos". These binaries are never run in a running -# system, but only in an initial RAM disk environment. Here they are +# user/group "_mandos". These binaries are not run in a running +# system, but in an initial RAM disk environment. Here they are # protected from non-root access by the directory permissions, above. -mandos-client binary: elevated-privileges usr/lib/*/mandos/plugins.d/mandos-client 4755 root/root -mandos-client binary: elevated-privileges usr/lib/*/mandos/plugins.d/askpass-fifo 4755 root/root -mandos-client binary: elevated-privileges usr/lib/*/mandos/plugins.d/splashy 4755 root/root -mandos-client binary: elevated-privileges usr/lib/*/mandos/plugins.d/usplash 4755 root/root -mandos-client binary: elevated-privileges usr/lib/*/mandos/plugins.d/plymouth 4755 root/root +# +mandos-client binary: setuid-binary usr/lib/*/mandos/plugins.d/mandos-client 4755 root/root +mandos-client binary: setuid-binary usr/lib/*/mandos/plugins.d/askpass-fifo 4755 root/root +mandos-client binary: setuid-binary usr/lib/*/mandos/plugins.d/splashy 4755 root/root +mandos-client binary: setuid-binary usr/lib/*/mandos/plugins.d/usplash 4755 root/root +mandos-client binary: setuid-binary usr/lib/*/mandos/plugins.d/plymouth 4755 root/root # The directory /etc/mandos/plugins.d can be used by local system # administrators to place plugins in, overriding and complementing # /usr/lib//mandos/plugins.d, and must be likewise protected. +# 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 - -# These are very important to work around bugs or changes in the old -# versions, and there is no pressing need to remove them. -mandos-client binary: maintainer-script-supports-ancient-package-version * === modified file 'debian/mandos-client.postinst' --- debian/mandos-client.postinst 2019-07-27 10:11:45 +0000 +++ debian/mandos-client.postinst 2016-03-19 04:21:00 +0000 @@ -15,34 +15,12 @@ # 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 update_initramfs() { - if command -v update-initramfs >/dev/null; then - update-initramfs -k all -u - elif command -v dracut >/dev/null; then - dracut_version="`dpkg-query --showformat='${Version}' --show dracut`" - if dpkg --compare-versions "$dracut_version" lt 043-1 \ - && bash -c '. /etc/dracut.conf; . /etc/dracut.conf.d/*; [ "$hostonly" != yes ]'; then - echo 'Dracut is not configured to use hostonly mode!' >&2 - return 1 - fi - # Logic taken from dracut.postinst - for kernel in /boot/vmlinu[xz]-*; do - kversion="${kernel#/boot/vmlinu[xz]-}" - # Dracut preserves old permissions of initramfs image - # files, so we adjust permissions before creating new - # initramfs image containing secret keys. - chmod go-r /boot/initrd.img-"$kversion" - if [ "$kversion" != "*" ]; then - /etc/kernel/postinst.d/dracut "$kversion" - fi - done - fi + update-initramfs -u -k all if dpkg --compare-versions "$2" lt-nl "1.0.10-1"; then # Make old initrd.img files unreadable too, in case they were @@ -72,79 +50,13 @@ 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 } create_dh_params(){ @@ -175,10 +87,10 @@ 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 + if dpkg --compare-versions "$2" lt-nl "1.7.7-1"; then PLUGINHELPERDIR=/usr/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null)/mandos/plugin-helpers if ! dpkg-statoverride --list "$PLUGINHELPERDIR" \ >/dev/null 2>&1; then === modified file 'debian/mandos-client.postrm' --- debian/mandos-client.postrm 2019-08-05 14:31:51 +0000 +++ debian/mandos-client.postrm 2015-07-27 09:23:56 +0000 @@ -31,17 +31,7 @@ # Update the initial RAM file system image update_initramfs() { - if command -v update-initramfs >/dev/null; then - update-initramfs -k all -u - elif command -v dracut >/dev/null; then - # Logic taken from dracut.postinst - for kernel in /boot/vmlinu[xz]-*; do - kversion="${kernel#/boot/vmlinu[xz]-}" - if [ "$kversion" != "*" ]; then - /etc/kernel/postinst.d/dracut "$kversion" - fi - done - fi + update-initramfs -u -k all || : } case "$1" in @@ -54,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 2020-11-30 16:19:48 +0000 +++ debian/mandos-client.templates 1970-01-01 00:00:00 +0000 @@ -1,19 +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.dirs' --- debian/mandos.dirs 2019-08-18 00:23:21 +0000 +++ debian/mandos.dirs 2016-03-19 12:10:15 +0000 @@ -7,4 +7,3 @@ var/lib/mandos lib/systemd/system usr/lib/tmpfiles.d -usr/lib/sysusers.d === modified file 'debian/mandos.lintian-overrides' --- debian/mandos.lintian-overrides 2019-08-05 21:14:05 +0000 +++ debian/mandos.lintian-overrides 2008-10-01 15:29:01 +0000 @@ -2,15 +2,3 @@ # it, so it must be kept unreadable for non-root users. # mandos binary: non-standard-file-perm etc/mandos/clients.conf 0600 != 0644 - -# 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 - -# These are very important to work around bugs or changes in the old -# versions, and there is no pressing need to remove them. -mandos binary: maintainer-script-supports-ancient-package-version * === modified file 'debian/mandos.postinst' --- debian/mandos.postinst 2019-08-18 00:05:36 +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 @@ -27,11 +25,6 @@ *:Mandos\ password\ system,,,:/nonexistent:/bin/false) usermod --login _mandos mandos groupmod --new-name _mandos mandos - # Reload D-Bus daemon to be aware of the _mandos - # user & group - if [ -x /etc/init.d/dbus ]; then - invoke-rc.d dbus force-reload || : - fi ;; esac fi @@ -41,11 +34,6 @@ --home /nonexistent --no-create-home --group \ --disabled-password --gecos "Mandos password system" \ _mandos - # Reload D-Bus daemon to be aware of the _mandos user & - # group - if [ -x /etc/init.d/dbus ]; then - invoke-rc.d dbus force-reload || : - fi elif dpkg --compare-versions "$2" eq 1.7.4-1 \ || dpkg --compare-versions "$2" eq "1.7.4-1~bpo8+1" then @@ -65,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 2020-11-30 09:40:14 +0000 +++ debian/mandos.templates 1970-01-01 00:00:00 +0000 @@ -1,29 +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 clients 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 created by a bug in Mandos client 1.8.0, have been - removed from /etc/mandos/clients.conf === removed file 'debian/po/POTFILES.in' --- debian/po/POTFILES.in 2019-07-27 19:28:14 +0000 +++ debian/po/POTFILES.in 1970-01-01 00:00:00 +0000 @@ -1,2 +0,0 @@ -[type: gettext/rfc822deb] mandos.templates -[type: gettext/rfc822deb] mandos-client.templates === removed file 'debian/po/de.po' --- debian/po/de.po 2019-08-16 19:28:16 +0000 +++ debian/po/de.po 1970-01-01 00:00:00 +0000 @@ -1,155 +0,0 @@ -# German debconf translation of mandos. -# This file is distributed under the same license as the mandos package. -# Copyright (C) 2008-2019 Teddy Hogeborn and Björn Påhlsson -# Copyright (C) of this file 2019 Chris Leick . -# -msgid "" -msgstr "" -"Project-Id-Version: mandos 1.8.7-1\n" -"Report-Msgid-Bugs-To: mandos@packages.debian.org\n" -"POT-Creation-Date: 2019-08-05 22:57+0200\n" -"PO-Revision-Date: 2019-08-10 12:06+0100\n" -"Last-Translator: Chris Leick \n" -"Language-Team: German \n" -"Language: de\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "New client option \"key_id\" is REQUIRED on server" -msgstr "Auf diesem Server ist die Client-Option »key_id« ERFORDERLICH" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"A new \"key_id\" client option is REQUIRED in the clients.conf file, " -"otherwise the client most likely will not reboot unattended. This option:" -msgstr "" -"In der Datei clients.conf ist eine neue Client-Option »key_id« ERFORDERLICH, " -"andernfalls werden die Clients höchstwahrscheinlich nicht unbeaufsichtigt neu " -"starten. Die Option" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " key_id = " -msgstr " key_id = " - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"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):" -msgstr "" -"muss der Datei /etc/mandos/clients.conf kurz vor der Option »fingerprint« " -"auf jedem Mandos-Client hinzugefügt werden. Sie müssen diese Datei bearbeiten " -"und diese Option auf allen Clients hinzufügen. Um die korrekte " -"Schlüsselkennung für jeden Client anzusehen, führen Sie (auf jedem Client) " -"diesen Befehl aus:" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " mandos-keygen -F/dev/null|grep ^key_id" -msgstr " mandos-keygen -F/dev/null|grep ^key_id" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"Note: the clients must all also be using GnuTLS 3.6.6 or later; the server " -"cannot serve passwords for both old and new clients!" -msgstr "" -"Hinweis: Die Clients müssen außerdem alle GnuTLS 3.6.6 oder neuer nutzen; der " -"Server kann keine Passwörter für sowohl alte als auch neue Clients anbieten!" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"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." -msgstr "" -"Begründung: Mit GnuTLS 3.6.6 wurde erzwungen, dass Mandos die Benutzung von " -"OpenPGP als TLS-Sitzungsschlüssel stoppt. Auf jedem Client wird ein neues " -"TLS-Schlüsselpaar erzeugt und zur Identifizierung benutzt, aber der " -"öffentliche Schlüssel muss auf diesem Server hinzugefügt werden, da dies nun " -"zur Identifizierung des Clients auf dem Server verwendet wird." - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "Bad key IDs have been removed from clients.conf" -msgstr "Falsche Schlüsselkennungen wurden aus der clients.conf entfernt." - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "" -"Bad key IDs, which were created by a bug in Mandos client 1.8.0, have been " -"removed from /etc/mandos/clients.conf" -msgstr "" -"Falsche Schlüsselkennungen, die durch einen Fehler im Mandos-Client 1.8.0 " -"erzeugt wurden, wurden aus /etc/mandos/clients.conf entfernt." - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "New client option \"${key_id}\" is REQUIRED on server" -msgstr "Auf dem Server ist die neue Client-Option »${key_id}« ERFORDERLICH." - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"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:" -msgstr "" -"In der Datei clients.conf des Servers ist eine neue Client-Option »key_id« " -"ERFORDERLICH, andernfalls wird dieser Rechner höchstwahrscheinlich nicht " -"unbeaufsichtigt neu starten. Die Option " - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid " ${key_id}" -msgstr " ${key_id}" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"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." -msgstr "" -"muss (in einer einzigen Zeile!) der Datei /etc/mandos/clients.conf auf dem " -"Mandos-Server kurz vor der Option »fingerprint« für diesen Mandos-Client " -"hinzugefügt werden. Sie müssen diese Datei auf diesem Server bearbeiten und " -"diese Option hinzufügen." - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"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." -msgstr "" -"Mit GnuTLS 3.6.6 wurde erzwungen, dass Mandos die Benutzung von OpenPGP als " -"TLS-Sitzungsschlüssel stoppt. Ein neues TLS-Schlüsselpaar wurde erzeugt und " -"wird zur Identifizierung benutzt, aber die Schlüsselkennung des öffentlichen " -"Schlüssels muss auf diesem Server hinzugefügt werden, da dies nun " -"zur Identifizierung des Clients auf dem Server verwendet wird." === removed file 'debian/po/en_US.po' --- debian/po/en_US.po 2019-08-05 21:00:35 +0000 +++ debian/po/en_US.po 1970-01-01 00:00:00 +0000 @@ -1,150 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the mandos package. -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: mandos\n" -"Report-Msgid-Bugs-To: mandos@packages.debian.org\n" -"POT-Creation-Date: 2019-08-05 22:57+0200\n" -"PO-Revision-Date: 2019-08-05 22:59+0200\n" -"Last-Translator: Teddy Hogeborn \n" -"Language-Team: English\n" -"Language: en_US\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "New client option \"key_id\" is REQUIRED on server" -msgstr "New client option “key_id” is REQUIRED on server" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"A new \"key_id\" client option is REQUIRED in the clients.conf file, " -"otherwise the client most likely will not reboot unattended. This option:" -msgstr "" -"A new “key_id” client option is REQUIRED in the clients.conf file, otherwise " -"the client most likely will not reboot unattended. This option:" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " key_id = " -msgstr " key_id = " - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"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):" -msgstr "" -"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):" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " mandos-keygen -F/dev/null|grep ^key_id" -msgstr " mandos-keygen -F/dev/null|grep ^key id" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"Note: the clients must all also be using GnuTLS 3.6.6 or later; the server " -"cannot serve passwords for both old and new clients!" -msgstr "" -"Note: the clients must all also be using GnuTLS 3.6.6 or later; the server " -"cannot serve passwords for both old and new clients!" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"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." -msgstr "" -"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. " - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "Bad key IDs have been removed from clients.conf" -msgstr "Bad key IDs have been removed from clients.conf" - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "" -"Bad key IDs, which were created by a bug in Mandos client 1.8.0, have been " -"removed from /etc/mandos/clients.conf" -msgstr "" -"Bad key IDs, which were created by a bug in Mandos client 1.8.0, have been " -"removed from /etc/mandos/clients.conf" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "New client option \"${key_id}\" is REQUIRED on server" -msgstr "New client option “${key_id}” is REQUIRED on server" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"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:" -msgstr "" -"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:" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid " ${key_id}" -msgstr " ${key_id}" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"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." -msgstr "" -"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." - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"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." -msgstr "" -"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." === removed file 'debian/po/es.po' --- debian/po/es.po 2022-04-25 18:28:52 +0000 +++ debian/po/es.po 1970-01-01 00:00:00 +0000 @@ -1,155 +0,0 @@ -# mandos po-debconf translation to Spanish. -# Copyright (C) 2021 -# This file is distributed under the same license as the mandos package. -# Camaleón , 2021. -# -msgid "" -msgstr "" -"Project-Id-Version: mandos\n" -"Report-Msgid-Bugs-To: Mandos Maintainers \n" -"POT-Creation-Date: 2021-04-14 17:23+0000\n" -"PO-Revision-Date: 2021-04-15 17:43+0200\n" -"Last-Translator: Camaleón \n" -"Language-Team: Debian Spanish \n" -"Language: es\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "New client option \"key_id\" is REQUIRED on server" -msgstr "Se requiere una nueva opción del cliente «key_id» en el servidor" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"A new \"key_id\" client option is REQUIRED in the clients.conf file, " -"otherwise the client most likely will not reboot unattended. This option:" -msgstr "" -"Se requiere una nueva opción de cliente «key_id» en el archivo clients." -"conf, de lo contrario es probable que el cliente no pueda reiniciarse " -"sin supervisión. Debe añadir esta opción:" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " key_id = " -msgstr " key_id = " - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"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):" -msgstr "" -"en el archivo «/etc/mandos/clients.conf», justo antes de la opción «fingerprint»" -"para cada cliente Mandos. Debe editar ese archivo y añadir esta opción para " -"todos los clientes. Para ver el identificador de la clave correcto de cada cliente, " -"ejecute la siguiente orden en cada uno de los clientes:" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " mandos-keygen -F/dev/null|grep ^key_id" -msgstr "mandos-keygen -F/dev/null|grep ^key_id" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"Note: the clients must all also be using GnuTLS 3.6.6 or later; the server " -"cannot serve passwords for both old and new clients!" -msgstr "" -"Nota: todos los clientes deben usar también GnuTLS 3.6.6 o una versión superior; " -"¡el servidor no puede servir contraseñas para clientes antiguos y nuevos al " -"mismo tiempo!" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"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." -msgstr "" -"Explicación: Con GnuTLS 3.6.6, Mandos se ha visto obligado a dejar de usar " -"claves OpenPGP como claves de sesión TLS. Se generará un nuevo par de claves " -"para cada cliente que se utilizarán para su identificación, pero necesita añadir " -"el identificador de la clave pública a este servidor, ya que ahora se utilizará " -"para identificar al cliente en el servidor." - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "Bad key IDs have been removed from clients.conf" -msgstr "Se han eliminado identificadores de claves incorrectas de clients.conf" - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "" -"Bad key IDs, which were created by a bug in Mandos client 1.8.0, have been " -"removed from /etc/mandos/clients.conf" -msgstr "" -"Se han eliminado los identificadores de claves incorrectas de «/etc/mandos/clients" -".conf» que se habían creado debido a un fallo en la versión 1.8.0 del cliente " -"Mandos." - -#. Type: note -#. Description -#: ../mandos-client.templates:1001 -msgid "New client option \"${key_id}\" is REQUIRED on server" -msgstr "Se requiere una nueva opción del cliente «${key_id}» en el servidor" - -#. Type: note -#. Description -#: ../mandos-client.templates:1001 -msgid "" -"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:" -msgstr "" -"Se requiere una nueva opción del cliente «key_id» en el archivo clients." -"conf del servidor, de lo contrario es probable que este equipo no pueda " -"reiniciarse sin supervisión. Debe añadir esta opción:" - -#. Type: note -#. Description -#: ../mandos-client.templates:1001 -msgid " ${key_id}" -msgstr " ${key_id}" - -#. Type: note -#. Description -#: ../mandos-client.templates:1001 -msgid "" -"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." -msgstr "" -"(¡en una sola línea!) en el servidor Mandos, en el archivo «/etc/mandos/" -"clients.conf», justo antes de la opción «fingerprint» para este cliente Mandos. " -"Debe editar ese archivo en el servidor y añadir esta opción." - -#. Type: note -#. Description -#: ../mandos-client.templates:1001 -msgid "" -"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." -msgstr "" -"Con GnuTLS 3.6.6, Mandos se ha visto obligado a dejar de usar claves OpenPGP " -"como claves de sesión TLS. Se ha generado un nuevo par de claves TLS que se " -"utilizarán para su identificación, pero necesita añadir el identificador de la " -"clave pública al servidor, ya que ahora se utilizará para identificar al cliente " -"en el servidor." === removed file 'debian/po/fr.po' --- debian/po/fr.po 2021-02-04 17:59:45 +0000 +++ debian/po/fr.po 1970-01-01 00:00:00 +0000 @@ -1,156 +0,0 @@ -# Translation of mandos debconf templates to French -# Copyright (C) 2019, French l10n team -# This file is distributed under the same license as the mandos package. -# Grégoire Scano , 2019. -msgid "" -msgstr "" -"Project-Id-Version: mandos\n" -"Report-Msgid-Bugs-To: mandos@packages.debian.org\n" -"POT-Creation-Date: 2019-07-27 21:06+0200\n" -"PO-Revision-Date: 2019-08-11 15:58+0800\n" -"Last-Translator: Grégoire Scano \n" -"Language-Team: French \n" -"Language: fr_FR\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "New client option \"key_id\" is REQUIRED on server" -msgstr "La nouvelle option de client « key_id » est NÉCESSAIRE sur le serveur" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"A new \"key_id\" client option is REQUIRED in the clients.conf file, " -"otherwise the client most likely will not reboot unattended. This option:" -msgstr "" -"Une nouvelle option de client « key_id » est NÉCESSAIRE dans le fichier " -"clients.conf, autrement le client ne redémarrera probablement pas de lui-" -"même. Cette option :" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "key_id = " -msgstr "key_id = " - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"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):" -msgstr "" -"doit être ajoutée dans le fichier /etc/mandos/clients.conf, juste avant " -"l'option « fingerprint », pour chaque client Mandos. Vous devez éditer ce " -"fichier et ajouter cette option pour tous les clients. Pour voir " -"l'identifiant de clef correct pour chaque client, exécutez la commande (sur " -"chaque client) :" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " mandos-keygen -F/dev/null|grep ^key_id" -msgstr " mandos-keygen -F/dev/null|grep ^key_id" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"Note: the clients must all also be using GnuTLS 3.6.6 or later; the server " -"cannot serve passwords for both old and new clients!" -msgstr "" -"Note : les clients doivent également tous utiliser GnuTLS 3.6.6 ou " -"ultérieur ; le serveur ne peut pas servir des mots de passe pour des clients " -"anciens et récents en même temps !" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"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." -msgstr "" -"Explication : avec GnuTLS 3.6.6, Mandos a été contraint d'arrêter d'utiliser " -"des clefs OpenPGP comme clefs de session TLS. Une nouvelle paire de clefs " -"TLS sera générée pour chaque client et sera utilisée pour l'identification, " -"mais l'identifiant de la clef publique doit être ajouté à ce serveur, " -"puisqu'il sera utilisé pour identifier le client auprès du serveur." - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "Bad key IDs have been removed from clients.conf" -msgstr "Les identifiants de clef incorrects ont été supprimés de clients.conf" - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "" -"Bad key IDs, which were created by a bug in Mandos client 1.8.0, have been " -"removed from /etc/mandos/clients.conf" -msgstr "" -"Les identifiants de clef incorrects, créés par un bogue dans le client " -"Mandos 1.8.0, ont été supprimés de /etc/mandos/clients.conf" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "New client option \"${key_id}\" is REQUIRED on server" -msgstr "" -"La nouvelle option de client « ${key_id} » est NÉCESSAIRE sur le serveur" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"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:" -msgstr "" -"Une nouvelle option de client « key_id » est NÉCESSAIRE dans le fichier " -"clients.conf du serveur, autrement cette machine ne pourra pas redémarrer " -"d'elle-même. Cette option :" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid " ${key_id}" -msgstr " ${key_id}" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"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." -msgstr "" -"doit être ajoutée (tout sur une seule ligne !) sur le serveur Mandos hôte, " -"dans le fichier /etc/mandos/clients.conf, juste avant l'option " -"« fingerprint » de ce client Mandos. Vous devez éditer ce fichier sur ce " -"serveur et ajouter cette option." - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"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." -msgstr "" -"Avec GnuTLS 3.6.6, Mandos a été contraint d'arrêter d'utiliser des clefs " -"OpenPGP comme clefs de session TLS. Une nouvelle paire de clefs TLS a été " -"générée et sera utilisée pour l'identification, mais l'identifiant de la " -"clef publique doit être ajouté au serveur, puisqu'il sera utilisé pour " -"identifier le client auprès du serveur." === removed file 'debian/po/nl.po' --- debian/po/nl.po 2019-12-05 03:38:07 +0000 +++ debian/po/nl.po 1970-01-01 00:00:00 +0000 @@ -1,156 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the mandos package. -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: mandos_1.8.9-2\n" -"Report-Msgid-Bugs-To: mandos@packages.debian.org\n" -"POT-Creation-Date: 2019-08-05 22:57+0200\n" -"PO-Revision-Date: 2019-11-12 14:26+0100\n" -"Language: nl\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Last-Translator: Frans Spiesschaert \n" -"Language-Team: Debian Dutch l10n Team \n" -"X-Generator: Poedit 1.8.11\n" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "New client option \"key_id\" is REQUIRED on server" -msgstr "Nieuwe client-optie \"key_id\" is VERPLICHT op de server" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"A new \"key_id\" client option is REQUIRED in the clients.conf file, " -"otherwise the client most likely will not reboot unattended. This option:" -msgstr "" -"In het bestand clients.conf is een nieuwe client-optie \"key_id\" VERPLICHT, " -"anders zal de client hoogstwaarschijnlijk niet onbeheerd heropstarten. Deze " -"optie:" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " key_id = " -msgstr " key_id = " - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"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):" -msgstr "" -"moet voor elke Mandos-client toegevoegd worden in het bestand /etc/mandos/" -"clients.conf, net voor de optie \"fingerprint\". U moet dat bestand bewerken " -"en deze optie voor alle clients toevoegen. Om van elke client het correcte " -"key_id te kennen, moet u het volgende commando (op iedere client) uitvoeren:" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " mandos-keygen -F/dev/null|grep ^key_id" -msgstr " mandos-keygen -F/dev/null|grep ^key_id" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"Note: the clients must all also be using GnuTLS 3.6.6 or later; the server " -"cannot serve passwords for both old and new clients!" -msgstr "" -"Opmerking: alle clients moeten GnuTLS 3.6.6 of recenter gebruiken; de server " -"is niet in staat om wachtwoorden te geven voor zowel oude als nieuwe clients!" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"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." -msgstr "" -"Motivering: met de komst van GnuTLS 3.6.6 werd Mandos gedwongen te stoppen " -"met het gebruik van OpenPGP-sleutels als TLS-sessiesleutels. Op iedere " -"client zal een nieuw TLS-sleutelpaar gegenereerd worden om als identificatie " -"te dienen, maar de sleutel-ID van de publieke sleutel moet op deze server " -"toegevoegd worden, aangezien deze nu gebruikt zal worden om de client bij de " -"server te identificeren." - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "Bad key IDs have been removed from clients.conf" -msgstr "Slechte sleutel-ID's werden uit clients.conf verwijderd" - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "" -"Bad key IDs, which were created by a bug in Mandos client 1.8.0, have been " -"removed from /etc/mandos/clients.conf" -msgstr "" -"Slechte sleutel-ID's die wegens een bug in Mandos client 1.8.0 gecreëerd " -"werden, werden verwijderd uit /etc/mandos/clients.conf" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "New client option \"${key_id}\" is REQUIRED on server" -msgstr "Nieuwe client-optie \"${key_id}\" is VERPLICHT op de server" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"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:" -msgstr "" -"In het bestand clients.conf op de server is een nieuwe client-optie \"key_id" -"\" VERPLICHT, anders zal deze computer hoogstwaarschijnlijk niet onbeheerd " -"heropstarten. Deze optie:" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid " ${key_id}" -msgstr " ${key_id}" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"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." -msgstr "" -"moet voor deze Mandos-client op de Mandos-servercomputer toegevoegd worden " -"(alles op één regel!) in het bestand /etc/mandos/clients.conf, net voor de " -"optie \"fingerprint\". U moet dat bestand op de server bewerken en deze " -"optie toevoegen." - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"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." -msgstr "" -"Met de komst van GnuTLS 3.6.6 werd Mandos gedwongen te stoppen met het " -"gebruik van OpenPGP-sleutels als TLS-sessiesleutels. Een nieuw TLS-" -"sleutelpaar werd gegenereerd om als identificatie te dienen, maar de sleutel-" -"ID van de publieke sleutel moet op de server toegevoegd worden, aangezien " -"deze nu gebruikt zal worden om de client bij de server te identificeren." === removed file 'debian/po/pt.po' --- debian/po/pt.po 2019-10-19 17:37:00 +0000 +++ debian/po/pt.po 1970-01-01 00:00:00 +0000 @@ -1,158 +0,0 @@ -# Translation of mandos debconf messages to European Portuguese -# Copyright (C) 2019 THE mandos'S COPYRIGHT HOLDER -# This file is distributed under the same license as the mandos package. -# -# Américo Monteiro , 2019. -msgid "" -msgstr "" -"Project-Id-Version: mandos 1.8.9-2\n" -"Report-Msgid-Bugs-To: mandos@packages.debian.org\n" -"POT-Creation-Date: 2019-08-05 22:57+0200\n" -"PO-Revision-Date: 2019-10-18 18:45+0000\n" -"Last-Translator: Américo Monteiro \n" -"Language-Team: Portuguese <>\n" -"Language: pt\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Lokalize 2.0\n" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "New client option \"key_id\" is REQUIRED on server" -msgstr "Nova opção \"key_id\" de cliente é NECESSÁRIA no servidor" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"A new \"key_id\" client option is REQUIRED in the clients.conf file, " -"otherwise the client most likely will not reboot unattended. This option:" -msgstr "" -"Uma nova opção de cliente \"key_id\" é NECESSÁRIA no ficheiro clients.conf, " -"caso contrário o mais provável é o cliente não conseguir reinicicar sozinho. " -"Esta opção:" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " key_id = " -msgstr " key_id = " - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"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):" -msgstr "" -"tem de ser adicionada ao ficheiro /etc/mandos/clients.conf, logo antes " -"da opção \"fingerprint\", para cada cliente Mandos. Você tem de editar esse " -"ficheiro e adicionar esta opção para todos os clientes. Para ver a key ID " -"para cada cliente, corra este comando (em cada cliente):" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " mandos-keygen -F/dev/null|grep ^key_id" -msgstr " mandos-keygen -F/dev/null|grep ^key_id" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"Note: the clients must all also be using GnuTLS 3.6.6 or later; the server " -"cannot serve passwords for both old and new clients!" -msgstr "" -"Note: os clientes têm de também usar GnuTLS 3.6.6 ou posterior; o servidor " -"não consegue servir palavras passe para ambos clientes antigos e novos!" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"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." -msgstr "" -"Razão: Com GnuTLS 3.6.6, o Mandos foi forçado a parar de usar chaves OpenPGP " -"como chaves de sessão TLS. Será gerado um novo par de chaves TLS em cada " -"cliente e será usado como identificação, mas o ID de chave da chave pública " -"precisa de ser adicionada a este servidor, pois esta irá agora ser usada " -"para identificar o cliente no servidor." - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "Bad key IDs have been removed from clients.conf" -msgstr "IDs de chave errados foram removidos de clients.conf" - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "" -"Bad key IDs, which were created by a bug in Mandos client 1.8.0, have been " -"removed from /etc/mandos/clients.conf" -msgstr "" -"IDs de chave errados, que foram criados por um bug no cliente Mandos 1.8.0, " -"foram removidos de /etc/mandos/clients.conf" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "New client option \"${key_id}\" is REQUIRED on server" -msgstr "Nova opção \"${key_id}\" de cliente é NECESSÁRIA no servidor" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"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:" -msgstr "" -"Uma nova opção \"key_id\" de cliente é NECESSÁRIA no ficheiro clients.conf " -"do servidor, caso contrário, é bem provável que este computador não consiga " -"reiniciar sozinho. Esta opção:" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid " ${key_id}" -msgstr " ${key_id}" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"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." -msgstr "" -"tem de ser adicionada (toda numa linha) na máquina servidor do Mandos, no " -"ficheiro /etc/mandos/clients.conf, logo antes da opção \"fingerprint\" para " -"este cliente Mandos. Você tem de editar esse ficheiro nesse servidor e " -"adicionar esta opção." - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"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." -msgstr "" -"Com GnuTLS 3.6.6, o Mandos foi forçado a parar de usar chaves OpenPGP " -"como chaves de sessão TLS. Foi gerado um novo par de chaves TLS e será " -"usado como identificação, mas o ID de chave da chave pública precisa de ser " -"adicionada ao servidor, pois esta irá agora ser usada para identificar o " -"cliente no servidor." - - === removed file 'debian/po/sv.po' --- debian/po/sv.po 2019-08-16 20:47:52 +0000 +++ debian/po/sv.po 1970-01-01 00:00:00 +0000 @@ -1,156 +0,0 @@ -# Translation of mandos debconf templates to Swedish -# Copyright (C) 2019, Mandos Maintainers -# This file is distributed under the same license as the mandos package. -# Teddy Hogeborn , 2019. -# -msgid "" -msgstr "" -"Project-Id-Version: mandos\n" -"Report-Msgid-Bugs-To: mandos@packages.debian.org\n" -"POT-Creation-Date: 2019-08-05 22:57+0200\n" -"PO-Revision-Date: 2019-08-16 22:45+0200\n" -"Last-Translator: Teddy Hogeborn \n" -"Language-Team: Swedish \n" -"Language: sv\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "New client option \"key_id\" is REQUIRED on server" -msgstr "Ny klientinställning ”key_id” KRÄVS på servern" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"A new \"key_id\" client option is REQUIRED in the clients.conf file, " -"otherwise the client most likely will not reboot unattended. This option:" -msgstr "" -"En ny klientinställning, ”key_id”, KRÄVS i filen clients.conf, annars\n" -"kommer klienten antagligen inte att starta upp av sig själv. Denna\n" -"inställning:" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " key_id = " -msgstr " key_id = " - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"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):" -msgstr "" -"måste läggas till i filen /etc/mandos/clients.conf, precis ovanför\n" -"inställningen ”fingerprint”, för varje Mandosklient. Du måste ändra i\n" -"den filen och lägga till den inställningen för alla klienter. För att\n" -"se det korrekta nyckel-IDt för varje klient, kör följande kommando (på\n" -"varje klient):" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " mandos-keygen -F/dev/null|grep ^key_id" -msgstr " mandos-keygen -F/dev/null|grep ^key_id" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"Note: the clients must all also be using GnuTLS 3.6.6 or later; the server " -"cannot serve passwords for both old and new clients!" -msgstr "" -"Observera: Alla klienter måste också använda GnuTLS 3.6.6 eller nyare;\n" -"servern kan inte ge lösenord till både nya och gamla klienter!" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"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." -msgstr "" -"Förklaring: Med GnuTLS 3.6.6 så har Mandos nödgats att sluta använda\n" -"OpenPGP-nycklar som TLS-sessionsnycklar. Ett nytt TLS-nyckelpar\n" -"kommer att genereras på varje klient och kommer att användas för\n" -"identifiering, men nyckel-IDt för den publika nyckeln måste läggas\n" -"till på denna server, då denna numera kommer att användas för att\n" -"identifiera klienten för servern." - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "Bad key IDs have been removed from clients.conf" -msgstr "Dåliga nyckel-IDn har tagits bort från clients.conf" - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "" -"Bad key IDs, which were created by a bug in Mandos client 1.8.0, have been " -"removed from /etc/mandos/clients.conf" -msgstr "" -"Dåliga nyckel-IDn, som skapats av en bugg i Mandosklienten 1.8.0, har\n" -"tagits bort från /etc/mandos/clients.conf" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "New client option \"${key_id}\" is REQUIRED on server" -msgstr "Ny klientinställning ”${key_id}” KRÄVS på servern" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"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:" -msgstr "" -"En ny klientinställning, ”key_id”, KRÄVS i serverns clients.conf-fil,\n" -"annars kommer denna dator antagligen inte att starta upp av sig själv.\n" -"Denna inställning:" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid " ${key_id}" -msgstr " ${key_id}" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"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." -msgstr "" -"måste läggas till (allt på en rad!) på Mandosservervärddatorn, i filen\n" -"/etc/mandos/clients.conf, precis ovanför inställningen ”fingerprint”,\n" -"för denna Mandosklient. Du måste ändra i den filen och lägga till den\n" -"inställningen." - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"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." -msgstr "" -"Med GnuTLS 3.6.6 så har Mandos nödgats att sluta använda\n" -"OpenPGP-nycklar som TLS-sessionsnycklar. Ett nytt TLS-nyckelpar har\n" -"genererats och kommer att användas för identifiering, men nyckel-IDt\n" -"för den publika nyckeln måste läggas till på servern, då detta numera\n" -"kommer att användas för att identifiera klienten för servern." === removed file 'debian/po/templates.pot' --- debian/po/templates.pot 2019-08-05 21:00:35 +0000 +++ debian/po/templates.pot 1970-01-01 00:00:00 +0000 @@ -1,127 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the mandos package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: mandos\n" -"Report-Msgid-Bugs-To: mandos@packages.debian.org\n" -"POT-Creation-Date: 2019-08-05 22:57+0200\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=CHARSET\n" -"Content-Transfer-Encoding: 8bit\n" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "New client option \"key_id\" is REQUIRED on server" -msgstr "" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"A new \"key_id\" client option is REQUIRED in the clients.conf file, " -"otherwise the client most likely will not reboot unattended. This option:" -msgstr "" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " key_id = " -msgstr "" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"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):" -msgstr "" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " mandos-keygen -F/dev/null|grep ^key_id" -msgstr "" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"Note: the clients must all also be using GnuTLS 3.6.6 or later; the server " -"cannot serve passwords for both old and new clients!" -msgstr "" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"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." -msgstr "" - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "Bad key IDs have been removed from clients.conf" -msgstr "" - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "" -"Bad key IDs, which were created by a bug in Mandos client 1.8.0, have been " -"removed from /etc/mandos/clients.conf" -msgstr "" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "New client option \"${key_id}\" is REQUIRED on server" -msgstr "" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"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:" -msgstr "" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid " ${key_id}" -msgstr "" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"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." -msgstr "" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"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." -msgstr "" === modified file 'debian/rules' --- debian/rules 2019-04-09 22:31:23 +0000 +++ debian/rules 2016-03-19 03:19:04 +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 $@ @@ -32,9 +22,8 @@ override_dh_fixperms-arch: 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 +32,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 --in-place --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 === removed file 'debian/source/lintian-overrides' --- debian/source/lintian-overrides 2019-08-05 21:03:31 +0000 +++ debian/source/lintian-overrides 1970-01-01 00:00:00 +0000 @@ -1,7 +0,0 @@ -# We are both upstream and Debian maintainer for this package, so the -# .asc signature can not exist until after the orig.tar.gz has been -# built as part of the Debian package build. -mandos source: orig-tarball-missing-upstream-signature mandos_*.tar.gz - -# We want to backport to stretch for as long as reasonably practical -mandos source: package-uses-old-debhelper-compat-version 10 === removed directory 'debian/tests' === removed file 'debian/tests/control' --- debian/tests/control 2019-09-04 05:31:20 +0000 +++ debian/tests/control 1970-01-01 00:00:00 +0000 @@ -1,33 +0,0 @@ -Test-Command: /usr/sbin/mandos --check -Restrictions: superficial, allow-stderr -Features: test-name=mandos-check -Depends: mandos - -Test-Command: /usr/sbin/mandos-ctl --check --verbose -Restrictions: allow-stderr -Features: test-name=mandos-ctl -Depends: mandos - -Test-Command: /usr/sbin/mandos-keygen --version -Restrictions: superficial -Features: test-name=mandos-keygen-version -Depends: mandos-client - -Test-Command: /usr/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null)/mandos/plugin-runner --version -Restrictions: needs-root, superficial -Features: test-name=plugin-runner-version -Depends: mandos-client - -Test-Command: /usr/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null)/mandos/plugin-helpers/mandos-client-iprouteadddel --version -Restrictions: needs-root, superficial -Features: test-name=mandos-client-iprouteadddel-version -Depends: mandos-client - -Test-Command: /usr/lib/dracut/modules.d/90mandos/password-agent --test --verbose -Features: test-name=password-agent -Depends: mandos-client - -Test-Command: /usr/lib/dracut/modules.d/90mandos/password-agent --test --verbose -p /task-creators/start_mandos_client/suid -Restrictions: needs-root -Features: test-name=password-agent-suid -Depends: mandos-client === removed file 'debian/upstream/metadata' --- debian/upstream/metadata 2019-08-04 12:39:39 +0000 +++ debian/upstream/metadata 1970-01-01 00:00:00 +0000 @@ -1,13 +0,0 @@ -# -*- yaml -*- ---- -Bug-Submit: mailto:mandos-dev@recompile.se -Changelog: https://bzr.recompile.se/loggerhead/mandos/trunk/view/head:/NEWS -Contact: mandos@recompile.se -Documentation: https://www.recompile.se/mandos/man/intro.8mandos -FAQ: https://www.recompile.se/mandos/man/intro.8mandos#faq -Name: Mandos -Other-References: https://www.recompile.se/mandos -Registration: https://mail.recompile.se/cgi-bin/mailman/listinfo/mandos-dev -Repository: https://ftp.recompile.se/pub/mandos/trunk -Repository-Browse: https://bzr.recompile.se/loggerhead/mandos/trunk/files -Security-Contact: mandos@recompile.se === modified file 'debian/watch' --- debian/watch 2019-02-11 05:15:24 +0000 +++ debian/watch 2014-02-16 02:42:42 +0000 @@ -1,3 +1,3 @@ -version=4 -opts=pgpmode=auto \ - https://ftp.recompile.se/pub/@PACKAGE@/@PACKAGE@@ANY_VERSION@\.orig@ARCHIVE_EXT@ +version=3 +opts=pgpsigurlmangle=s/$/.asc/ \ + ftp://ftp.recompile.se/pub/mandos/mandos[-_]([^\s]+?)(?:\.orig)?\.tar\.(?:gz|bz2|7z|xz) === removed directory 'dracut-module' === removed file 'dracut-module/ask-password-mandos.path' --- dracut-module/ask-password-mandos.path 2019-07-27 10:11:45 +0000 +++ dracut-module/ask-password-mandos.path 1970-01-01 00:00:00 +0000 @@ -1,47 +0,0 @@ -# -*- systemd -*- -# -# Copyright © 2019 Teddy Hogeborn -# Copyright © 2019 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 systemd.path(5) unit will wait until there are any password -# questions present, represented by files named "ask.*" in the -# /run/systemd/ask-password directory, and then start the -# "ask-password-mandos.service" systemd.service(5) unit. - -# This file should be installed in the root file system as -# "/usr/lib/dracut/modules.d/90mandos/ask-password-mandos.path" and -# will be installed in the initramfs image file as -# "/lib/systemd/system/ask-password-mandos.path", and symlinked to -# "/lib/systemd/system//sysinit.target.wants/ask-password-mandos.path" -# by dracut when dracut creates the initramfs image file. - -[Unit] -Description=Forward Password Requests to remote Mandos server -Documentation=man:intro(8mandos) man:password-agent(8mandos) man:mandos-client(8mandos) -DefaultDependencies=no -Conflicts=shutdown.target -Before=basic.target shutdown.target -ConditionKernelCommandLine=!mandos=off -ConditionFileIsExecutable=/lib/mandos/password-agent -ConditionPathIsMountPoint=!/sysroot - -[Path] -PathExistsGlob=/run/systemd/ask-password/ask.* -MakeDirectory=yes === removed file 'dracut-module/ask-password-mandos.service' --- dracut-module/ask-password-mandos.service 2020-07-04 11:58:52 +0000 +++ dracut-module/ask-password-mandos.service 1970-01-01 00:00:00 +0000 @@ -1,51 +0,0 @@ -# -*- systemd -*- -# -# Copyright © 2019-2020 Teddy Hogeborn -# Copyright © 2019-2020 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 systemd.service(5) unit file will start the Mandos -# password-agent(8mandos) program, which will in turn run -# mandos-client(8mandos) to get a password and send the password to -# any and all active password questions using the systemd “Password -# Agent” mechanism. - -# This file should be installed in the root file system as -# "/usr/lib/dracut/modules.d/90mandos/ask-password-mandos.service" and -# will be installed in the initramfs image file as -# "/lib/systemd/system/ask-password-mandos.service" by dracut when -# dracut creates the initramfs image file. - -[Unit] -Description=Forward Password Requests to remote Mandos server -Documentation=man:intro(8mandos) man:password-agent(8mandos) man:mandos-client(8mandos) -DefaultDependencies=no -Conflicts=shutdown.target -Before=shutdown.target -ConditionKernelCommandLine=!mandos=off -ConditionFileIsExecutable=/lib/mandos/password-agent -ConditionFileIsExecutable=/lib/mandos/mandos-client -ConditionFileNotEmpty=/etc/mandos/keys/pubkey.txt -ConditionFileNotEmpty=/etc/mandos/keys/seckey.txt -ConditionFileNotEmpty=/etc/mandos/keys/tls-pubkey.pem -ConditionFileNotEmpty=/etc/mandos/keys/tls-privkey.pem -ConditionPathIsMountPoint=!/sysroot - -[Service] -ExecStart=/lib/mandos/password-agent $PASSWORD_AGENT_OPTIONS -- /lib/mandos/mandos-client --pubkey=/etc/mandos/keys/pubkey.txt --seckey=/etc/mandos/keys/seckey.txt --tls-pubkey=/etc/mandos/keys/tls-pubkey.pem --tls-privkey=/etc/mandos/keys/tls-privkey.pem $MANDOS_CLIENT_OPTIONS === removed file 'dracut-module/cmdline-mandos.sh' --- dracut-module/cmdline-mandos.sh 2019-07-27 10:11:45 +0000 +++ dracut-module/cmdline-mandos.sh 1970-01-01 00:00:00 +0000 @@ -1,74 +0,0 @@ -#!/bin/sh -# -# This file should be present in the root file system directory -# /usr/lib/dracut/modules.d/90mandos. When dracut creates the -# initramfs image, dracut will run the "module-setup.sh" file in the -# same directory, which (when *not* using the "systemd" dracut module) -# will copy this file ("cmdline-mandos.sh") into the initramfs as -# "/lib/dracut/hooks/cmdline/20-cmdline-mandos.sh". -# -# Despite the above #!/bin/sh line and the executable flag, this file -# is not executed; this file is sourced by the /init script in the -# initramfs image created by dracut. - -if getargbool 1 mandos && [ -e /lib/dracut-crypt-lib.sh ]; then - cat >> /lib/dracut-crypt-lib.sh <<- "EOF" - ask_for_password(){ - local cmd; local prompt; local tries=3 - local ply_cmd; local ply_prompt; local ply_tries=3 - local tty_cmd; local tty_prompt; local tty_tries=3 - local ret - - while [ $# -gt 0 ]; do - case "$1" in - --cmd) ply_cmd="$2"; tty_cmd="$2"; shift;; - --ply-cmd) ply_cmd="$2"; shift;; - --tty-cmd) tty_cmd="$2"; shift;; - --prompt) ply_prompt="$2"; tty_prompt="$2"; shift;; - --ply-prompt) ply_prompt="$2"; shift;; - --tty-prompt) tty_prompt="$2"; shift;; - --tries) ply_tries="$2"; tty_tries="$2"; shift;; - --ply-tries) ply_tries="$2"; shift;; - --tty-tries) tty_tries="$2"; shift;; - --tty-echo-off) tty_echo_off=yes;; - -*) :;; - esac - shift - done - if [ -z "$ply_cmd" ]; then - ply_cmd="$tty_cmd" - fi - # Extract device and luksname from $ply_cmd - set -- $ply_cmd - shift - for arg in "$@"; do - case "$arg" in - -*) :;; - *) - if [ -z "$device" ]; then - device="$arg" - else - luksname="$arg" - break - fi - ;; - esac - done - { flock -s 9; - if [ -z "$ply_prompt" ]; then - if [ -z "$tty_prompt" ]; then - CRYPTTAB_SOURCE="$device" cryptsource="$device" CRYPTTAB_NAME="$luksname" crypttarget="$luksname" /lib/mandos/plugin-runner --config-file=/etc/mandos/plugin-runner.conf | $ply_cmd - else - CRYPTTAB_SOURCE="$device" cryptsource="$device" CRYPTTAB_NAME="$luksname" crypttarget="$luksname" /lib/mandos/plugin-runner --options-for=password-prompt:--prompt="${tty_prompt}" --config-file=/etc/mandos/plugin-runner.conf | $ply_cmd - fi - else - if [ -z "$tty_prompt" ]; then - CRYPTTAB_SOURCE="$device" cryptsource="$device" CRYPTTAB_NAME="$luksname" crypttarget="$luksname" /lib/mandos/plugin-runner --options-for=plymouth:--prompt="${ply_prompt}" --config-file=/etc/mandos/plugin-runner.conf | $ply_cmd - else - CRYPTTAB_SOURCE="$device" cryptsource="$device" CRYPTTAB_NAME="$luksname" crypttarget="$luksname" /lib/mandos/plugin-runner --options-for=password-prompt:--prompt="${tty_prompt}" --options-for=plymouth:--prompt="${ply_prompt}" --config-file=/etc/mandos/plugin-runner.conf | $ply_cmd - fi - fi - } 9>/.console_lock - } - EOF -fi === removed file 'dracut-module/module-setup.sh' --- dracut-module/module-setup.sh 2020-02-05 21:39:28 +0000 +++ dracut-module/module-setup.sh 1970-01-01 00:00:00 +0000 @@ -1,257 +0,0 @@ -#!/bin/sh -# -# This file should be present in the root file system directory -# /usr/lib/dracut/modules.d/90mandos. When dracut creates the -# initramfs image, dracut will source this file and run the shell -# functions defined in this file: "install", "check", "depends", -# "cmdline", and "installkernel". -# -# Despite the above #!/bin/sh line and the executable flag, this file -# is not executed; this file is sourced by dracut when creating the -# initramfs image file. - -mandos_libdir(){ - for dir in /usr/lib \ - "/usr/lib/`dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null`" \ - "`rpm --eval='%{_libdir}' 2>/dev/null`" /usr/local/lib; do - if [ -d "$dir"/mandos ]; then - echo "$dir"/mandos - return - fi - done - # Mandos not found - return 1 -} - -mandos_keydir(){ - for dir in /etc/keys/mandos /etc/mandos/keys; do - if [ -d "$dir" ]; then - echo "$dir" - return - fi - done - # Mandos key directory not found - return 1 -} - -check(){ - if [ "${hostonly:-no}" = "no" ]; then - dwarning "Mandos: Dracut not in hostonly mode" - return 1 - fi - - local libdir=`mandos_libdir` - if [ -z "$libdir" ]; then - dwarning "Mandos lib directory not found" - return 1 - fi - - local keydir=`mandos_keydir` - if [ -z "$keydir" ]; then - dwarning "Mandos key directory not found" - return 1 - fi -} - -install(){ - chmod go+w,+t "$initdir"/tmp - local libdir=`mandos_libdir` - local keydir=`mandos_keydir` - set `{ getent passwd _mandos \ - || getent passwd nobody \ - || echo ::65534:65534:::; } \ - | cut --delimiter=: --fields=3,4 --only-delimited \ - --output-delimiter=" "` - local mandos_user="$1" - local mandos_group="$2" - inst "${libdir}" /lib/mandos - if dracut_module_included "systemd"; then - plugindir=/lib/mandos - inst "${libdir}/plugins.d/mandos-client" \ - "${plugindir}/mandos-client" - chmod u-s "${initdir}/${plugindir}/mandos-client" - inst "${moddir}/ask-password-mandos.service" \ - "${systemdsystemunitdir}/ask-password-mandos.service" - if [ -d /etc/systemd/system/ask-password-mandos.service.d ]; then - inst /etc/systemd/system/ask-password-mandos.service.d - inst_multiple -o /etc/systemd/system/ask-password-mandos.service.d/*.conf - fi - if [ ${mandos_user} != 65534 ]; then - sed --in-place \ - --expression="s,^ExecStart=/lib/mandos/password-agent ,&--user=${mandos_user} ," \ - "${initdir}/${systemdsystemunitdir}/ask-password-mandos.service" - fi - if [ ${mandos_group} != 65534 ]; then - sed --in-place \ - --expression="s,^ExecStart=/lib/mandos/password-agent ,&--group=${mandos_group} ," \ - "${initdir}/${systemdsystemunitdir}/ask-password-mandos.service" - fi - else - inst_hook cmdline 20 "$moddir"/cmdline-mandos.sh - plugindir=/lib/mandos/plugins.d - inst "${libdir}/plugin-runner" /lib/mandos/plugin-runner - inst /etc/mandos/plugin-runner.conf - sed --in-place \ - --expression='1i--options-for=mandos-client:--pubkey=/etc/mandos/keys/pubkey.txt,--seckey=/etc/mandos/keys/seckey.txt,--tls-pubkey=/etc/mandos/keys/tls-pubkey.pem,--tls-privkey=/etc/mandos/keys/tls-privkey.pem' \ - "${initdir}/etc/mandos/plugin-runner.conf" - if [ ${mandos_user} != 65534 ]; then - sed --in-place --expression="1i--userid=${mandos_user}" \ - "${initdir}/etc/mandos/plugin-runner.conf" - fi - if [ ${mandos_group} != 65534 ]; then - sed --in-place \ - --expression="1i--groupid=${mandos_group}" \ - "${initdir}/etc/mandos/plugin-runner.conf" - fi - inst "${libdir}/plugins.d" "$plugindir" - chown ${mandos_user}:${mandos_group} "${initdir}/${plugindir}" - # Copy the packaged plugins - for file in "$libdir"/plugins.d/*; do - base="`basename \"$file\"`" - # Is this plugin overridden? - if [ -e "/etc/mandos/plugins.d/$base" ]; then - continue - fi - case "$base" in - *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) - : ;; - "*") dwarning "Mandos client plugin directory is empty." >&2 ;; - askpass-fifo) : ;; # Ignore packaged for dracut - *) inst "${file}" "${plugindir}/${base}" ;; - esac - done - # Copy any user-supplied plugins - for file in /etc/mandos/plugins.d/*; do - base="`basename \"$file\"`" - case "$base" in - *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) - : ;; - "*") : ;; - *) inst "$file" "${plugindir}/${base}" ;; - esac - done - # Copy any user-supplied plugin helpers - for file in /etc/mandos/plugin-helpers/*; do - base="`basename \"$file\"`" - case "$base" in - *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) - : ;; - "*") : ;; - *) inst "$file" "/lib/mandos/plugin-helpers/$base";; - esac - done - fi - # Copy network hooks - for hook in /etc/mandos/network-hooks.d/*; do - basename=`basename "$hook"` - case "$basename" in - "*") continue ;; - *[!A-Za-z0-9_.-]*) continue ;; - *) test -d "$hook" || inst "$hook" "/lib/mandos/network-hooks.d/$basename" ;; - esac - if [ -x "$hook" ]; then - # Copy any files needed by the network hook - MANDOSNETHOOKDIR=/etc/mandos/network-hooks.d MODE=files \ - VERBOSITY=0 "$hook" files | while read file target; do - if [ ! -e "${file}" ]; then - dwarning "WARNING: file ${file} not found, requested by Mandos network hook '${basename}'" >&2 - fi - if [ -z "${target}" ]; then - inst "$file" - else - inst "$file" "$target" - fi - done - fi - done - # Copy the packaged plugin helpers - for file in "$libdir"/plugin-helpers/*; do - base="`basename \"$file\"`" - # Is this plugin overridden? - if [ -e "/etc/mandos/plugin-helpers/$base" ]; then - continue - fi - case "$base" in - *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) - : ;; - "*") : ;; - *) inst "$file" "/lib/mandos/plugin-helpers/$base";; - esac - done - local gpg=/usr/bin/gpg - if [ -e /usr/bin/gpgconf ]; then - inst /usr/bin/gpgconf - gpg="`/usr/bin/gpgconf|sed --quiet --expression='s/^gpg:[^:]*://p'`" - gpgagent="`/usr/bin/gpgconf|sed --quiet --expression='s/^gpg-agent:[^:]*://p'`" - # Newer versions of GnuPG 2 requires the gpg-agent binary - if [ -e "$gpgagent" ]; then - inst "$gpgagent" - fi - fi - inst "$gpg" - if dracut_module_included "systemd"; then - inst "${moddir}/password-agent" /lib/mandos/password-agent - inst "${moddir}/ask-password-mandos.path" \ - "${systemdsystemunitdir}/ask-password-mandos.path" - ln_r "${systemdsystemunitdir}/ask-password-mandos.path" \ - "${systemdsystemunitdir}/sysinit.target.wants/ask-password-mandos.path" - fi - # Key files - for file in "$keydir"/*; do - if [ -d "$file" ]; then - continue - fi - case "$file" in - *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) - : ;; - "*") : ;; - *) - inst "$file" "/etc/mandos/keys/`basename \"$file\"`" - chown ${mandos_user}:${mandos_group} \ - "${initdir}/etc/mandos/keys/`basename \"$file\"`" - if [ `basename "$file"` = dhparams.pem ]; then - # Use Diffie-Hellman parameters file - if dracut_module_included "systemd"; then - sed --in-place \ - --expression='/^ExecStart/s/ \$MANDOS_CLIENT_OPTIONS/ --dh-params=\/etc\/mandos\/keys\/dhparams.pem&/' \ - "${initdir}/${systemdsystemunitdir}/ask-password-mandos.service" - else - sed --in-place \ - --expression="1i--options-for=mandos-client:--dh-params=/etc/mandos/keys/dhparams.pem" \ - "${initdir}/etc/mandos/plugin-runner.conf" - fi - fi - ;; - esac - done -} - -installkernel(){ - instmods =drivers/net - hostonly='' instmods ipv6 - # Copy any kernel modules needed by network hooks - for hook in /etc/mandos/network-hooks.d/*; do - basename=`basename "$hook"` - case "$basename" in - "*") continue ;; - *[!A-Za-z0-9_.-]*) continue ;; - esac - if [ -x "$hook" ]; then - # Copy and load any modules needed by the network hook - MANDOSNETHOOKDIR=/etc/mandos/network-hooks.d MODE=modules \ - VERBOSITY=0 "$hook" modules | while read module; do - if [ -z "${target}" ]; then - instmods "$module" - fi - done - fi - done -} - -depends(){ - echo crypt -} - -cmdline(){ - : -} === removed file 'dracut-module/password-agent.c' --- dracut-module/password-agent.c 2022-04-24 16:54:30 +0000 +++ dracut-module/password-agent.c 1970-01-01 00:00:00 +0000 @@ -1,8263 +0,0 @@ -/* -*- coding: utf-8; lexical-binding: t -*- */ -/* - * Mandos password agent - Simple password agent to run Mandos client - * - * Copyright © 2019-2022 Teddy Hogeborn - * Copyright © 2019-2022 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 . - */ - -#define _GNU_SOURCE /* pipe2(), O_CLOEXEC, setresgid(), - setresuid(), asprintf(), getline(), - basename() */ -#include /* uintmax_t, strtoumax(), PRIuMAX, - PRIdMAX, intmax_t, uint32_t, - SCNx32, SCNuMAX, SCNxMAX */ -#include /* size_t, NULL */ -#include /* pid_t, uid_t, gid_t, getuid(), - getpid() */ -#include /* bool, true, false */ -#include /* struct sigaction, sigset_t, - sigemptyset(), sigaddset(), - SIGCHLD, pthread_sigmask(), - SIG_BLOCK, SIG_SETMASK, SA_RESTART, - SA_NOCLDSTOP, sigfillset(), kill(), - SIGTERM, sigdelset(), SIGKILL, - NSIG, sigismember(), SA_ONSTACK, - SIG_DFL, SIG_IGN, SIGINT, SIGQUIT, - SIGHUP, SIGSTOP, SIG_UNBLOCK */ -#include /* uid_t, gid_t, close(), pipe2(), - fork(), _exit(), dup2(), - STDOUT_FILENO, setresgid(), - setresuid(), execv(), ssize_t, - read(), dup3(), getuid(), dup(), - STDERR_FILENO, pause(), write(), - rmdir(), unlink(), getpid() */ -#include /* EXIT_SUCCESS, EXIT_FAILURE, - malloc(), free(), realloc(), - setenv(), calloc(), mkdtemp(), - mkostemp() */ -#include /* not, or, and, xor */ -#include /* error() */ -#include /* EX_USAGE, EX_OSERR, EX_OSFILE */ -#include /* errno, error_t, EACCES, - ENAMETOOLONG, ENOENT, ENOTDIR, - ENOMEM, EEXIST, ECHILD, EPERM, - EAGAIN, EINTR, ENOBUFS, EADDRINUSE, - ECONNREFUSED, ECONNRESET, - ETOOMANYREFS, EMSGSIZE, EBADF, - EINVAL */ -#include /* strdup(), memcpy(), - explicit_bzero(), memset(), - strcmp(), strlen(), strncpy(), - memcmp(), basename(), strerror() */ -#include /* argz_create(), argz_count(), - argz_extract(), argz_next(), - argz_add() */ -#include /* epoll_create1(), EPOLL_CLOEXEC, - epoll_ctl(), EPOLL_CTL_ADD, - struct epoll_event, EPOLLIN, - EPOLLRDHUP, EPOLLOUT, - epoll_pwait() */ -#include /* struct timespec, clock_gettime(), - CLOCK_MONOTONIC */ -#include /* struct argp_option, OPTION_HIDDEN, - OPTION_ALIAS, struct argp_state, - ARGP_ERR_UNKNOWN, ARGP_KEY_ARGS, - struct argp, argp_parse(), - ARGP_NO_EXIT */ -#include /* SIZE_MAX, uint32_t */ -#include /* munlock(), mlock() */ -#include /* O_CLOEXEC, O_NONBLOCK, fcntl(), - F_GETFD, F_GETFL, FD_CLOEXEC, - open(), O_WRONLY, O_NOCTTY, - O_RDONLY, O_NOFOLLOW */ -#include /* waitpid(), WNOHANG, WIFEXITED(), - WEXITSTATUS() */ -#include /* PIPE_BUF, NAME_MAX, INT_MAX */ -#include /* inotify_init1(), IN_NONBLOCK, - IN_CLOEXEC, inotify_add_watch(), - IN_CLOSE_WRITE, IN_MOVED_TO, - IN_MOVED_FROM, IN_DELETE, - IN_EXCL_UNLINK, IN_ONLYDIR, - struct inotify_event */ -#include /* fnmatch(), FNM_FILE_NAME */ -#include /* asprintf(), FILE, stderr, fopen(), - fclose(), getline(), sscanf(), - feof(), ferror(), rename(), - fdopen(), fprintf(), fscanf() */ -#include /* GKeyFile, g_key_file_free(), g_key_file_new(), - GError, g_key_file_load_from_file(), - G_KEY_FILE_NONE, TRUE, G_FILE_ERROR_NOENT, - g_key_file_get_string(), guint64, - g_key_file_get_uint64(), - G_KEY_FILE_ERROR_KEY_NOT_FOUND, gconstpointer, - g_assert_true(), g_assert_nonnull(), - g_assert_null(), g_assert_false(), - g_assert_cmpint(), g_assert_cmpuint(), - g_test_skip(), g_assert_cmpstr(), - g_test_message(), g_test_init(), g_test_add(), - g_test_run(), GOptionContext, - g_option_context_new(), - g_option_context_set_help_enabled(), FALSE, - g_option_context_set_ignore_unknown_options(), - gboolean, GOptionEntry, G_OPTION_ARG_NONE, - g_option_context_add_main_entries(), - g_option_context_parse(), - g_option_context_free(), g_error() */ -#include /* struct sockaddr_un, SUN_LEN */ -#include /* AF_LOCAL, socket(), PF_LOCAL, - SOCK_DGRAM, SOCK_NONBLOCK, - SOCK_CLOEXEC, connect(), - struct sockaddr, socklen_t, - shutdown(), SHUT_RD, send(), - MSG_NOSIGNAL, bind(), recv(), - socketpair() */ -#include /* globfree(), glob_t, glob(), - GLOB_ERR, GLOB_NOSORT, GLOB_MARK, - GLOB_ABORTED, GLOB_NOMATCH, - GLOB_NOSPACE */ - -/* End of includes */ - -/* Start of declarations of private types and functions */ - -/* microseconds of CLOCK_MONOTONIC absolute time; 0 means unset */ -typedef uintmax_t mono_microsecs; - -/* "task_queue" - A queue of tasks to be run */ -typedef struct { - struct task_struct *tasks; /* Tasks in this queue */ - size_t length; /* Number of tasks */ - /* Memory allocated for "tasks", in bytes */ - size_t allocated; - /* Time when this queue should be run, at the latest */ - mono_microsecs next_run; -} __attribute__((designated_init)) task_queue; - -/* "task_func" - A function type for task functions - - I.e. functions for the code which runs when a task is run, all have - this type */ -typedef void (task_func) (const struct task_struct, - task_queue *const) - __attribute__((nonnull)); - -/* "buffer" - A data buffer for a growing array of bytes - - Used for the "password" variable */ -typedef struct { - char *data; - size_t length; - size_t allocated; -} __attribute__((designated_init)) buffer; - -/* "string_set" - A set type which can contain strings - - Used by the "cancelled_filenames" variable */ -typedef struct { - char *argz; /* Do not access these except in */ - size_t argz_len; /* the string_set_* functions */ -} __attribute__((designated_init)) string_set; - -/* "task_context" - local variables for tasks - - This data structure distinguishes between different tasks which are - using the same function. This data structure is passed to every - task function when each task is run. - - Note that not every task uses every struct member. */ -typedef struct task_struct { - task_func *const func; /* The function run by this task */ - char *const question_filename; /* The question file */ - const pid_t pid; /* Mandos client process ID */ - const int epoll_fd; /* The epoll set file descriptor */ - bool *const quit_now; /* Set to true on fatal errors */ - const int fd; /* General purpose file descriptor */ - bool *const mandos_client_exited; /* Set true when client exits */ - buffer *const password; /* As read from client process */ - bool *const password_is_read; /* "password" is done growing */ - char *filename; /* General purpose file name */ - /* A set of strings of all the file names of questions which have - been cancelled for any reason; tasks pertaining to these question - files should not be run */ - string_set *const cancelled_filenames; - const mono_microsecs notafter; /* "NotAfter" from question file */ - /* Updated before each queue run; is compared with queue.next_run */ - const mono_microsecs *const current_time; -} __attribute__((designated_init)) task_context; - -/* Declare all our functions here so we can define them in any order - below. Note: test functions are *not* declared here, they are - declared in the test section. */ -__attribute__((warn_unused_result)) -static bool should_only_run_tests(int *, char **[]); -__attribute__((warn_unused_result, cold)) -static bool run_tests(int, char *[]); -static void handle_sigchld(__attribute__((unused)) int sig){} -__attribute__((warn_unused_result, malloc)) -task_queue *create_queue(void); -__attribute__((nonnull, warn_unused_result)) -bool add_to_queue(task_queue *const, const task_context); -__attribute__((nonnull)) -void cleanup_task(const task_context *const); -__attribute__((nonnull)) -void cleanup_queue(task_queue *const *const); -__attribute__((pure, nonnull, warn_unused_result)) -bool queue_has_question(const task_queue *const); -__attribute__((nonnull)) -void cleanup_close(const int *const); -__attribute__((nonnull)) -void cleanup_string(char *const *const); -__attribute__((nonnull)) -void cleanup_buffer(buffer *const); -__attribute__((pure, nonnull, warn_unused_result)) -bool string_set_contains(const string_set, const char *const); -__attribute__((nonnull, warn_unused_result)) -bool string_set_add(string_set *const, const char *const); -__attribute__((nonnull)) -void string_set_clear(string_set *); -void string_set_swap(string_set *const, string_set *const); -__attribute__((nonnull, warn_unused_result)) -bool start_mandos_client(task_queue *const, const int, bool *const, - bool *const, buffer *const, bool *const, - const struct sigaction *const, - const sigset_t, const char *const, - const uid_t, const gid_t, - const char *const *const); -__attribute__((nonnull)) -task_func wait_for_mandos_client_exit; -__attribute__((nonnull)) -task_func read_mandos_client_output; -__attribute__((warn_unused_result)) -bool add_inotify_dir_watch(task_queue *const, const int, bool *const, - buffer *const, const char *const, - string_set *, const mono_microsecs *const, - bool *const, bool *const); -__attribute__((nonnull)) -task_func read_inotify_event; -__attribute__((nonnull)) -task_func open_and_parse_question; -__attribute__((nonnull)) -task_func cancel_old_question; -__attribute__((nonnull)) -task_func connect_question_socket; -__attribute__((nonnull)) -task_func send_password_to_socket; -__attribute__((warn_unused_result)) -bool add_existing_questions(task_queue *const, const int, - buffer *const, string_set *, - const mono_microsecs *const, - bool *const, bool *const, - const char *const); -__attribute__((nonnull, warn_unused_result)) -bool wait_for_event(const int, const mono_microsecs, - const mono_microsecs); -bool run_queue(task_queue **const, string_set *const, bool *const); -bool clear_all_fds_from_epoll_set(const int); -mono_microsecs get_current_time(void); -__attribute__((nonnull, warn_unused_result)) -bool setup_signal_handler(struct sigaction *const); -__attribute__((nonnull)) -bool restore_signal_handler(const struct sigaction *const); -__attribute__((nonnull, warn_unused_result)) -bool block_sigchld(sigset_t *const); -__attribute__((nonnull)) -bool restore_sigmask(const sigset_t *const); -__attribute__((nonnull)) -bool parse_arguments(int, char *[], const bool, char **, char **, - uid_t *const , gid_t *const, char **, size_t *); - -/* End of declarations of private types and functions */ - -/* Start of "main" section; this section LACKS TESTS! - - Code here should be as simple as possible. */ - -/* These are required to be global by Argp */ -const char *argp_program_version = "password-agent " VERSION; -const char *argp_program_bug_address = ""; - -int main(int argc, char *argv[]){ - - /* If the --test option is passed, skip all normal operations and - instead only run the run_tests() function, which also does all - its own option parsing, so we don't have to do anything here. */ - if(should_only_run_tests(&argc, &argv)){ - if(run_tests(argc, argv)){ - return EXIT_SUCCESS; /* All tests successful */ - } - return EXIT_FAILURE; /* Some test(s) failed */ - } - - __attribute__((cleanup(cleanup_string))) - char *agent_directory = NULL; - - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - - uid_t user = 0; - gid_t group = 0; - - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - if(not parse_arguments(argc, argv, true, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)){ - /* This should never happen, since "true" is passed as the third - argument to parse_arguments() above, which should make - argp_parse() call exit() if any parsing error occurs. */ - error(EX_USAGE, errno, "Failed to parse arguments"); - } - - const char default_agent_directory[] = "/run/systemd/ask-password"; - const char default_helper_directory[] - = "/lib/mandos/plugin-helpers"; - const char *const default_argv[] - = {"/lib/mandos/plugins.d/mandos-client", NULL }; - - /* Set variables to default values if unset */ - if(agent_directory == NULL){ - agent_directory = strdup(default_agent_directory); - if(agent_directory == NULL){ - error(EX_OSERR, errno, "Failed strdup()"); - } - } - if(helper_directory == NULL){ - helper_directory = strdup(default_helper_directory); - if(helper_directory == NULL){ - error(EX_OSERR, errno, "Failed strdup()"); - } - } - if(user == 0){ - user = 65534; /* nobody */ - } - if(group == 0){ - group = 65534; /* nogroup */ - } - /* If parse_opt did not create an argz vector, create one with - default values */ - if(mandos_argz == NULL){ -#ifdef __GNUC__ -#pragma GCC diagnostic push - /* argz_create() takes a non-const argv for some unknown reason - - argz_create() isn't modifying the strings, just copying them. - Therefore, this cast to non-const should be safe. */ -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - errno = argz_create((char *const *)default_argv, &mandos_argz, - &mandos_argz_length); -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - if(errno != 0){ - error(EX_OSERR, errno, "Failed argz_create()"); - } - } - /* Use argz vector to create a normal argv, usable by execv() */ - - char **mandos_argv = malloc((argz_count(mandos_argz, - mandos_argz_length) - + 1) * sizeof(char *)); - if(mandos_argv == NULL){ - error_t saved_errno = errno; - free(mandos_argz); - error(EX_OSERR, saved_errno, "Failed malloc()"); - } - argz_extract(mandos_argz, mandos_argz_length, mandos_argv); - - sigset_t orig_sigmask; - if(not block_sigchld(&orig_sigmask)){ - return EX_OSERR; - } - - struct sigaction old_sigchld_action; - if(not setup_signal_handler(&old_sigchld_action)){ - return EX_OSERR; - } - - mono_microsecs current_time = 0; - - bool mandos_client_exited = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - if(epoll_fd < 0){ - error(EX_OSERR, errno, "Failed to create epoll set fd"); - } - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - if(queue == NULL){ - error(EX_OSERR, errno, "Failed to create task queue"); - } - - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - bool password_is_read = false; - - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - - /* Add tasks to queue */ - if(not start_mandos_client(queue, epoll_fd, &mandos_client_exited, - &quit_now, &password, &password_is_read, - &old_sigchld_action, orig_sigmask, - helper_directory, user, group, - (const char *const *)mandos_argv)){ - return EX_OSERR; /* Error has already been printed */ - } - /* These variables were only for start_mandos_client() and are not - needed anymore */ - free(mandos_argv); - free(mandos_argz); - mandos_argz = NULL; - if(not add_inotify_dir_watch(queue, epoll_fd, &quit_now, &password, - agent_directory, &cancelled_filenames, - ¤t_time, &mandos_client_exited, - &password_is_read)){ - switch(errno){ /* Error has already been printed */ - case EACCES: - case ENAMETOOLONG: - case ENOENT: - case ENOTDIR: - return EX_OSFILE; - default: - return EX_OSERR; - } - } - if(not add_existing_questions(queue, epoll_fd, &password, - &cancelled_filenames, ¤t_time, - &mandos_client_exited, - &password_is_read, agent_directory)){ - return EXIT_FAILURE; /* Error has already been printed */ - } - - /* Run queue */ - do { - current_time = get_current_time(); - if(not wait_for_event(epoll_fd, queue->next_run, current_time)){ - const error_t saved_errno = errno; - error(EXIT_FAILURE, saved_errno, "Failure while waiting for" - " events"); - } - - current_time = get_current_time(); - if(not run_queue(&queue, &cancelled_filenames, &quit_now)){ - const error_t saved_errno = errno; - error(EXIT_FAILURE, saved_errno, "Failure while running queue"); - } - - /* When no tasks about questions are left in the queue, break out - of the loop (and implicitly exit the program) */ - } while(queue_has_question(queue)); - - restore_signal_handler(&old_sigchld_action); - restore_sigmask(&orig_sigmask); - - return EXIT_SUCCESS; -} - -__attribute__((warn_unused_result)) -mono_microsecs get_current_time(void){ - struct timespec currtime; - if(clock_gettime(CLOCK_MONOTONIC, &currtime) != 0){ - error(0, errno, "Failed to get current time"); - return 0; - } - return ((mono_microsecs)currtime.tv_sec * 1000000) /* seconds */ - + ((mono_microsecs)currtime.tv_nsec / 1000); /* nanoseconds */ -} - -/* End of "main" section */ - -/* Start of regular code section; ALL this code has tests */ - -__attribute__((nonnull)) -bool parse_arguments(int argc, char *argv[], const bool exit_failure, - char **agent_directory, char **helper_directory, - uid_t *const user, gid_t *const group, - char **mandos_argz, size_t *mandos_argz_length){ - - const struct argp_option options[] = { - { .name="agent-directory",.key='d', .arg="DIRECTORY", - .doc="Systemd password agent directory" }, - { .name="helper-directory",.key=128, .arg="DIRECTORY", - .doc="Mandos Client password helper directory" }, - { .name="plugin-helper-dir", .key=129, /* From plugin-runner */ - .flags=OPTION_HIDDEN | OPTION_ALIAS }, - { .name="user", .key='u', .arg="USERID", - .doc="User ID the Mandos Client will use as its unprivileged" - " user" }, - { .name="userid", .key=130, /* From plugin--runner */ - .flags=OPTION_HIDDEN | OPTION_ALIAS }, - { .name="group", .key='g', .arg="GROUPID", - .doc="Group ID the Mandos Client will use as its unprivileged" - " group" }, - { .name="groupid", .key=131, /* From plugin--runner */ - .flags=OPTION_HIDDEN | OPTION_ALIAS }, - { .name="test", .key=255, /* See should_only_run_tests() */ - .doc="Skip normal operation, and only run self-tests. See" - " --test --help.", .group=10, }, - { NULL }, - }; - - __attribute__((nonnull(3))) - error_t parse_opt(int key, char *arg, struct argp_state *state){ - errno = 0; - switch(key){ - case 'd': /* --agent-directory */ - *agent_directory = strdup(arg); - break; - case 128: /* --helper-directory */ - case 129: /* --plugin-helper-dir */ - *helper_directory = strdup(arg); - break; - case 'u': /* --user */ - case 130: /* --userid */ - { - char *tmp; - uintmax_t tmp_id = 0; - errno = 0; - tmp_id = (uid_t)strtoumax(arg, &tmp, 10); - if(errno != 0 or tmp == arg or *tmp != '\0' - or tmp_id != (uid_t)tmp_id or (uid_t)tmp_id == 0){ - return ARGP_ERR_UNKNOWN; - } - *user = (uid_t)tmp_id; - errno = 0; - break; - } - case 'g': /* --group */ - case 131: /* --groupid */ - { - char *tmp; - uintmax_t tmp_id = 0; - errno = 0; - tmp_id = (uid_t)strtoumax(arg, &tmp, 10); - if(errno != 0 or tmp == arg or *tmp != '\0' - or tmp_id != (gid_t)tmp_id or (gid_t)tmp_id == 0){ - return ARGP_ERR_UNKNOWN; - } - *group = (gid_t)tmp_id; - errno = 0; - break; - } - case ARGP_KEY_ARGS: - /* Copy arguments into argz vector */ - return argz_create(state->argv + state->next, mandos_argz, - mandos_argz_length); - default: - return ARGP_ERR_UNKNOWN; - } - return errno; - } - - const struct argp argp = { - .options=options, - .parser=parse_opt, - .args_doc="[MANDOS_CLIENT [OPTION...]]\n--test", - .doc = "Mandos password agent -- runs Mandos client as a" - " systemd password agent", - }; - - errno = argp_parse(&argp, argc, argv, - exit_failure ? 0 : ARGP_NO_EXIT, NULL, NULL); - - return errno == 0; -} - -__attribute__((nonnull, warn_unused_result)) -bool block_sigchld(sigset_t *const orig_sigmask){ - sigset_t sigchld_sigmask; - if(sigemptyset(&sigchld_sigmask) < 0){ - error(0, errno, "Failed to empty signal set"); - return false; - } - if(sigaddset(&sigchld_sigmask, SIGCHLD) < 0){ - error(0, errno, "Failed to add SIGCHLD to signal set"); - return false; - } - if(pthread_sigmask(SIG_BLOCK, &sigchld_sigmask, orig_sigmask) != 0){ - error(0, errno, "Failed to block SIGCHLD signal"); - return false; - } - return true; -} - -__attribute__((nonnull, warn_unused_result, const)) -bool restore_sigmask(const sigset_t *const orig_sigmask){ - if(pthread_sigmask(SIG_SETMASK, orig_sigmask, NULL) != 0){ - error(0, errno, "Failed to restore blocked signals"); - return false; - } - return true; -} - -__attribute__((nonnull, warn_unused_result)) -bool setup_signal_handler(struct sigaction *const old_sigchld_action){ - struct sigaction sigchld_action = { - .sa_handler=handle_sigchld, - .sa_flags=SA_RESTART | SA_NOCLDSTOP, - }; - /* Set all signals in "sa_mask" struct member; this makes all - signals automatically blocked during signal handler */ - if(sigfillset(&sigchld_action.sa_mask) != 0){ - error(0, errno, "Failed to do sigfillset()"); - return false; - } - if(sigaction(SIGCHLD, &sigchld_action, old_sigchld_action) != 0){ - error(0, errno, "Failed to set SIGCHLD signal handler"); - return false; - } - return true; -} - -__attribute__((nonnull, warn_unused_result)) -bool restore_signal_handler(const struct sigaction *const - old_sigchld_action){ - if(sigaction(SIGCHLD, old_sigchld_action, NULL) != 0){ - error(0, errno, "Failed to restore signal handler"); - return false; - } - return true; -} - -__attribute__((warn_unused_result, malloc)) -task_queue *create_queue(void){ - task_queue *queue = malloc(sizeof(task_queue)); - if(queue){ - queue->tasks = NULL; - queue->length = 0; - queue->allocated = 0; - queue->next_run = 0; - } - return queue; -} - -__attribute__((nonnull, warn_unused_result)) -bool add_to_queue(task_queue *const queue, const task_context task){ - if((queue->length + 1) > (SIZE_MAX / sizeof(task_context))){ - /* overflow */ - error(0, ENOMEM, "Failed to allocate %" PRIuMAX - " tasks for queue->tasks", (uintmax_t)(queue->length + 1)); - errno = ENOMEM; - return false; - } - const size_t needed_size = sizeof(task_context)*(queue->length + 1); - if(needed_size > (queue->allocated)){ - task_context *const new_tasks = realloc(queue->tasks, - needed_size); - if(new_tasks == NULL){ - error(0, errno, "Failed to allocate %" PRIuMAX - " bytes for queue->tasks", (uintmax_t)needed_size); - return false; - } - queue->tasks = new_tasks; - queue->allocated = needed_size; - } - /* Using memcpy here is necessary because doing */ - /* queue->tasks[queue->length++] = task; */ - /* would violate const-ness of task members */ - memcpy(&(queue->tasks[queue->length++]), &task, - sizeof(task_context)); - return true; -} - -__attribute__((nonnull)) -void cleanup_task(const task_context *const task){ - const error_t saved_errno = errno; - /* free and close all task data */ - free(task->question_filename); - if(task->filename != task->question_filename){ - free(task->filename); - } - if(task->pid > 0){ - kill(task->pid, SIGTERM); - } - if(task->fd > 0){ - close(task->fd); - } - errno = saved_errno; -} - -__attribute__((nonnull)) -void free_queue(task_queue *const queue){ - free(queue->tasks); - free(queue); -} - -__attribute__((nonnull)) -void cleanup_queue(task_queue *const *const queue){ - if(*queue == NULL){ - return; - } - for(size_t i = 0; i < (*queue)->length; i++){ - const task_context *const task = ((*queue)->tasks)+i; - cleanup_task(task); - } - free_queue(*queue); -} - -__attribute__((pure, nonnull, warn_unused_result)) -bool queue_has_question(const task_queue *const queue){ - for(size_t i=0; i < queue->length; i++){ - if(queue->tasks[i].question_filename != NULL){ - return true; - } - } - return false; -} - -__attribute__((nonnull)) -void cleanup_close(const int *const fd){ - const error_t saved_errno = errno; - close(*fd); - errno = saved_errno; -} - -__attribute__((nonnull)) -void cleanup_string(char *const *const ptr){ - free(*ptr); -} - -__attribute__((nonnull)) -void cleanup_buffer(buffer *buf){ - if(buf->allocated > 0){ -#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 25) - explicit_bzero(buf->data, buf->allocated); -#else - memset(buf->data, '\0', buf->allocated); -#endif - } - if(buf->data != NULL){ - if(munlock(buf->data, buf->allocated) != 0){ - error(0, errno, "Failed to unlock memory of old buffer"); - } - free(buf->data); - buf->data = NULL; - } - buf->length = 0; - buf->allocated = 0; -} - -__attribute__((pure, nonnull, warn_unused_result)) -bool string_set_contains(const string_set set, const char *const str){ - for(const char *s = set.argz; s != NULL and set.argz_len > 0; - s = argz_next(set.argz, set.argz_len, s)){ - if(strcmp(s, str) == 0){ - return true; - } - } - return false; -} - -__attribute__((nonnull, warn_unused_result)) -bool string_set_add(string_set *const set, const char *const str){ - if(string_set_contains(*set, str)){ - return true; - } - error_t error = argz_add(&set->argz, &set->argz_len, str); - if(error == 0){ - return true; - } - errno = error; - return false; -} - -__attribute__((nonnull)) -void string_set_clear(string_set *set){ - free(set->argz); - set->argz = NULL; - set->argz_len = 0; -} - -__attribute__((nonnull)) -void string_set_swap(string_set *const set1, string_set *const set2){ - /* Swap contents of two string sets */ - { - char *const tmp_argz = set1->argz; - set1->argz = set2->argz; - set2->argz = tmp_argz; - } - { - const size_t tmp_argz_len = set1->argz_len; - set1->argz_len = set2->argz_len; - set2->argz_len = tmp_argz_len; - } -} - -__attribute__((nonnull, warn_unused_result)) -bool start_mandos_client(task_queue *const queue, - const int epoll_fd, - bool *const mandos_client_exited, - bool *const quit_now, buffer *const password, - bool *const password_is_read, - const struct sigaction *const - old_sigchld_action, const sigset_t sigmask, - const char *const helper_directory, - const uid_t user, const gid_t group, - const char *const *const argv){ - int pipefds[2]; - if(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK) != 0){ - error(0, errno, "Failed to pipe2(..., O_CLOEXEC | O_NONBLOCK)"); - return false; - } - - const pid_t pid = fork(); - if(pid == 0){ - if(not restore_signal_handler(old_sigchld_action)){ - _exit(EXIT_FAILURE); - } - if(not restore_sigmask(&sigmask)){ - _exit(EXIT_FAILURE); - } - if(close(pipefds[0]) != 0){ - error(0, errno, "Failed to close() parent pipe fd"); - _exit(EXIT_FAILURE); - } - if(dup2(pipefds[1], STDOUT_FILENO) == -1){ - error(0, errno, "Failed to dup2() pipe fd to stdout"); - _exit(EXIT_FAILURE); - } - if(close(pipefds[1]) != 0){ - error(0, errno, "Failed to close() old child pipe fd"); - _exit(EXIT_FAILURE); - } - if(setenv("MANDOSPLUGINHELPERDIR", helper_directory, 1) != 0){ - error(0, errno, "Failed to setenv(\"MANDOSPLUGINHELPERDIR\"," - " \"%s\", 1)", helper_directory); - _exit(EXIT_FAILURE); - } - if(group != 0 and setresgid(group, 0, 0) == -1){ - error(0, errno, "Failed to setresgid(-1, %" PRIuMAX ", %" - PRIuMAX")", (uintmax_t)group, (uintmax_t)group); - _exit(EXIT_FAILURE); - } - if(user != 0 and setresuid(user, 0, 0) == -1){ - error(0, errno, "Failed to setresuid(-1, %" PRIuMAX ", %" - PRIuMAX")", (uintmax_t)user, (uintmax_t)user); - _exit(EXIT_FAILURE); - } -#ifdef __GNUC__ -#pragma GCC diagnostic push - /* For historical reasons, the "argv" argument to execv() is not - const, but it is safe to override this. */ -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - execv(argv[0], (char **)argv); -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - error(0, errno, "execv(\"%s\", ...) failed", argv[0]); - _exit(EXIT_FAILURE); - } - close(pipefds[1]); - - if(pid == -1){ - error(0, errno, "Failed to fork()"); - close(pipefds[0]); - return false; - } - - if(not add_to_queue(queue, (task_context){ - .func=wait_for_mandos_client_exit, - .pid=pid, - .mandos_client_exited=mandos_client_exited, - .quit_now=quit_now, - })){ - error(0, errno, "Failed to add wait_for_mandos_client to queue"); - close(pipefds[0]); - return false; - } - - const int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, pipefds[0], - &(struct epoll_event) - { .events=EPOLLIN | EPOLLRDHUP }); - if(ret != 0 and errno != EEXIST){ - error(0, errno, "Failed to add file descriptor to epoll set"); - close(pipefds[0]); - return false; - } - - return add_to_queue(queue, (task_context){ - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=quit_now, - .password=password, - .password_is_read=password_is_read, - }); -} - -__attribute__((nonnull)) -void wait_for_mandos_client_exit(const task_context task, - task_queue *const queue){ - const pid_t pid = task.pid; - bool *const mandos_client_exited = task.mandos_client_exited; - bool *const quit_now = task.quit_now; - - int status; - switch(waitpid(pid, &status, WNOHANG)){ - case 0: /* Not exited yet */ - if(not add_to_queue(queue, task)){ - error(0, errno, "Failed to add myself to queue"); - *quit_now = true; - } - break; - case -1: /* Error */ - error(0, errno, "waitpid(%" PRIdMAX ") failed", (intmax_t)pid); - if(errno != ECHILD){ - kill(pid, SIGTERM); - } - *quit_now = true; - break; - default: /* Has exited */ - *mandos_client_exited = true; - if((not WIFEXITED(status)) - or (WEXITSTATUS(status) != EXIT_SUCCESS)){ - error(0, 0, "Mandos client failed or was killed"); - *quit_now = true; - } - } -} - -__attribute__((nonnull)) -void read_mandos_client_output(const task_context task, - task_queue *const queue){ - buffer *const password = task.password; - bool *const quit_now = task.quit_now; - bool *const password_is_read = task.password_is_read; - const int fd = task.fd; - const int epoll_fd = task.epoll_fd; - - const size_t new_potential_size = (password->length + PIPE_BUF); - if(password->allocated < new_potential_size){ - char *const new_buffer = calloc(new_potential_size, 1); - if(new_buffer == NULL){ - error(0, errno, "Failed to allocate %" PRIuMAX - " bytes for password", (uintmax_t)new_potential_size); - *quit_now = true; - close(fd); - return; - } - if(mlock(new_buffer, new_potential_size) != 0){ - /* Warn but do not treat as fatal error */ - if(errno != EPERM and errno != ENOMEM){ - error(0, errno, "Failed to lock memory for password"); - } - } - if(password->length > 0){ - memcpy(new_buffer, password->data, password->length); -#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 25) - explicit_bzero(password->data, password->allocated); -#else - memset(password->data, '\0', password->allocated); -#endif - } - if(password->data != NULL){ - if(munlock(password->data, password->allocated) != 0){ - error(0, errno, "Failed to unlock memory of old buffer"); - } - free(password->data); - } - password->data = new_buffer; - password->allocated = new_potential_size; - } - - const ssize_t read_length = read(fd, password->data - + password->length, PIPE_BUF); - - if(read_length == 0){ /* EOF */ - *password_is_read = true; - close(fd); - return; - } - if(read_length < 0 and errno != EAGAIN){ /* Actual error */ - error(0, errno, "Failed to read password from Mandos client"); - *quit_now = true; - close(fd); - return; - } - if(read_length > 0){ /* Data has been read */ - password->length += (size_t)read_length; - } - - /* Either data was read, or EAGAIN was indicated, meaning no data - available yet */ - - /* Re-add the fd to the epoll set */ - const int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, - &(struct epoll_event) - { .events=EPOLLIN | EPOLLRDHUP }); - if(ret != 0 and errno != EEXIST){ - error(0, errno, "Failed to re-add file descriptor to epoll set"); - *quit_now = true; - close(fd); - return; - } - - /* Re-add myself to the queue */ - if(not add_to_queue(queue, task)){ - error(0, errno, "Failed to add myself to queue"); - *quit_now = true; - close(fd); - } -} - -__attribute__((nonnull, warn_unused_result)) -bool add_inotify_dir_watch(task_queue *const queue, - const int epoll_fd, bool *const quit_now, - buffer *const password, - const char *const dir, - string_set *cancelled_filenames, - const mono_microsecs *const current_time, - bool *const mandos_client_exited, - bool *const password_is_read){ - const int fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); - if(fd == -1){ - error(0, errno, "Failed to create inotify instance"); - return false; - } - - if(inotify_add_watch(fd, dir, IN_CLOSE_WRITE | IN_MOVED_TO - | IN_MOVED_FROM| IN_DELETE | IN_EXCL_UNLINK - | IN_ONLYDIR) - == -1){ - error(0, errno, "Failed to create inotify watch on %s", dir); - return false; - } - - /* Add the inotify fd to the epoll set */ - const int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, - &(struct epoll_event) - { .events=EPOLLIN | EPOLLRDHUP }); - if(ret != 0 and errno != EEXIST){ - error(0, errno, "Failed to add file descriptor to epoll set"); - close(fd); - return false; - } - - const task_context read_inotify_event_task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .quit_now=quit_now, - .password=password, - .fd=fd, - .filename=strdup(dir), - .cancelled_filenames=cancelled_filenames, - .current_time=current_time, - .mandos_client_exited=mandos_client_exited, - .password_is_read=password_is_read, - }; - if(read_inotify_event_task.filename == NULL){ - error(0, errno, "Failed to strdup(\"%s\")", dir); - close(fd); - return false; - } - - return add_to_queue(queue, read_inotify_event_task); -} - -__attribute__((nonnull)) -void read_inotify_event(const task_context task, - task_queue *const queue){ - const int fd = task.fd; - const int epoll_fd = task.epoll_fd; - char *const filename = task.filename; - bool *quit_now = task.quit_now; - buffer *const password = task.password; - string_set *const cancelled_filenames = task.cancelled_filenames; - const mono_microsecs *const current_time = task.current_time; - bool *const mandos_client_exited = task.mandos_client_exited; - bool *const password_is_read = task.password_is_read; - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - struct { - struct inotify_event event; - char name_buffer[NAME_MAX + 1]; - } ievent_buffer; - struct inotify_event *const ievent = &ievent_buffer.event; - - const ssize_t read_length = read(fd, ievent, ievent_size); - if(read_length == 0){ /* EOF */ - error(0, 0, "Got EOF from inotify fd for directory %s", filename); - *quit_now = true; - cleanup_task(&task); - return; - } - if(read_length < 0 and errno != EAGAIN){ /* Actual error */ - error(0, errno, "Failed to read from inotify fd for directory %s", - filename); - *quit_now = true; - cleanup_task(&task); - return; - } - if(read_length > 0 /* Data has been read */ - and fnmatch("ask.*", ievent->name, FNM_FILE_NAME) == 0){ - char *question_filename = NULL; - const ssize_t question_filename_length - = asprintf(&question_filename, "%s/%s", filename, ievent->name); - if(question_filename_length < 0){ - error(0, errno, "Failed to create file name from directory name" - " %s and file name %s", filename, ievent->name); - } else { - if(ievent->mask & (IN_CLOSE_WRITE | IN_MOVED_TO)){ - if(not add_to_queue(queue, (task_context){ - .func=open_and_parse_question, - .epoll_fd=epoll_fd, - .question_filename=question_filename, - .filename=question_filename, - .password=password, - .cancelled_filenames=cancelled_filenames, - .current_time=current_time, - .mandos_client_exited=mandos_client_exited, - .password_is_read=password_is_read, - })){ - error(0, errno, "Failed to add open_and_parse_question task" - " for file name %s to queue", filename); - } else { - /* Force the added task (open_and_parse_question) to run - immediately */ - queue->next_run = 1; - } - } else if(ievent->mask & (IN_MOVED_FROM | IN_DELETE)){ - if(not string_set_add(cancelled_filenames, - question_filename)){ - error(0, errno, "Could not add question %s to" - " cancelled_questions", question_filename); - *quit_now = true; - free(question_filename); - cleanup_task(&task); - return; - } - free(question_filename); - } - } - } - - /* Either data was read, or EAGAIN was indicated, meaning no data - available yet */ - - /* Re-add myself to the queue */ - if(not add_to_queue(queue, task)){ - error(0, errno, "Failed to re-add read_inotify_event(%s) to" - " queue", filename); - *quit_now = true; - cleanup_task(&task); - return; - } - - /* Re-add the fd to the epoll set */ - const int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, - &(struct epoll_event) - { .events=EPOLLIN | EPOLLRDHUP }); - if(ret != 0 and errno != EEXIST){ - error(0, errno, "Failed to re-add inotify file descriptor %d for" - " directory %s to epoll set", fd, filename); - /* Force the added task (read_inotify_event) to run again, at most - one second from now */ - if((queue->next_run == 0) - or (queue->next_run > (*current_time + 1000000))){ - queue->next_run = *current_time + 1000000; - } - } -} - -__attribute__((nonnull)) -void open_and_parse_question(const task_context task, - task_queue *const queue){ - __attribute__((cleanup(cleanup_string))) - char *question_filename = task.question_filename; - const int epoll_fd = task.epoll_fd; - buffer *const password = task.password; - string_set *const cancelled_filenames = task.cancelled_filenames; - const mono_microsecs *const current_time = task.current_time; - bool *const mandos_client_exited = task.mandos_client_exited; - bool *const password_is_read = task.password_is_read; - - /* We use the GLib "Key-value file parser" functions to parse the - question file. See for - specification of contents */ - __attribute__((nonnull)) - void cleanup_g_key_file(GKeyFile **key_file){ - if(*key_file != NULL){ - g_key_file_free(*key_file); - } - } - - __attribute__((cleanup(cleanup_g_key_file))) - GKeyFile *key_file = g_key_file_new(); - if(key_file == NULL){ - error(0, errno, "Failed g_key_file_new() for \"%s\"", - question_filename); - return; - } - GError *glib_error = NULL; - if(g_key_file_load_from_file(key_file, question_filename, - G_KEY_FILE_NONE, &glib_error) != TRUE){ - /* If a file was removed, we should ignore it, so */ - /* only show error message if file actually existed */ - if(glib_error->code != G_FILE_ERROR_NOENT){ - error(0, 0, "Failed to load question data from file \"%s\": %s", - question_filename, glib_error->message); - } - return; - } - - __attribute__((cleanup(cleanup_string))) - char *socket_name = g_key_file_get_string(key_file, "Ask", - "Socket", - &glib_error); - if(socket_name == NULL){ - error(0, 0, "Question file \"%s\" did not contain \"Socket\": %s", - question_filename, glib_error->message); - return; - } - - if(strlen(socket_name) == 0){ - error(0, 0, "Question file \"%s\" had empty \"Socket\" value", - question_filename); - return; - } - - const guint64 pid = g_key_file_get_uint64(key_file, "Ask", "PID", - &glib_error); - if(glib_error != NULL){ - error(0, 0, "Question file \"%s\" contained bad \"PID\": %s", - question_filename, glib_error->message); - return; - } - - if((pid != (guint64)((pid_t)pid)) - or (kill((pid_t)pid, 0) != 0)){ - error(0, 0, "PID %" PRIuMAX " in question file \"%s\" is bad or" - " does not exist", (uintmax_t)pid, question_filename); - return; - } - - guint64 notafter = g_key_file_get_uint64(key_file, "Ask", - "NotAfter", &glib_error); - if(glib_error != NULL){ - if(glib_error->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND){ - error(0, 0, "Question file \"%s\" contained bad \"NotAfter\":" - " %s", question_filename, glib_error->message); - } - notafter = 0; - } - if(notafter != 0){ - if(queue->next_run == 0 or (queue->next_run > notafter)){ - queue->next_run = notafter; - } - if(*current_time >= notafter){ - return; - } - } - - const task_context connect_question_socket_task = { - .func=connect_question_socket, - .question_filename=strdup(question_filename), - .epoll_fd=epoll_fd, - .password=password, - .filename=strdup(socket_name), - .cancelled_filenames=task.cancelled_filenames, - .mandos_client_exited=mandos_client_exited, - .password_is_read=password_is_read, - .current_time=current_time, - }; - if(connect_question_socket_task.question_filename == NULL - or connect_question_socket_task.filename == NULL - or not add_to_queue(queue, connect_question_socket_task)){ - error(0, errno, "Failed to add connect_question_socket for socket" - " %s (from \"%s\") to queue", socket_name, - question_filename); - cleanup_task(&connect_question_socket_task); - return; - } - /* Force the added task (connect_question_socket) to run - immediately */ - queue->next_run = 1; - - if(notafter > 0){ - char *const dup_filename = strdup(question_filename); - const task_context cancel_old_question_task = { - .func=cancel_old_question, - .question_filename=dup_filename, - .notafter=notafter, - .filename=dup_filename, - .cancelled_filenames=cancelled_filenames, - .current_time=current_time, - }; - if(cancel_old_question_task.question_filename == NULL - or not add_to_queue(queue, cancel_old_question_task)){ - error(0, errno, "Failed to add cancel_old_question for file " - "\"%s\" to queue", question_filename); - cleanup_task(&cancel_old_question_task); - return; - } - } -} - -__attribute__((nonnull)) -void cancel_old_question(const task_context task, - task_queue *const queue){ - char *const question_filename = task.question_filename; - string_set *const cancelled_filenames = task.cancelled_filenames; - const mono_microsecs notafter = task.notafter; - const mono_microsecs *const current_time = task.current_time; - - if(*current_time >= notafter){ - if(not string_set_add(cancelled_filenames, question_filename)){ - error(0, errno, "Failed to cancel question for file %s", - question_filename); - } - cleanup_task(&task); - return; - } - - if(not add_to_queue(queue, task)){ - error(0, errno, "Failed to add cancel_old_question for file " - "%s to queue", question_filename); - cleanup_task(&task); - return; - } - - if((queue->next_run == 0) or (queue->next_run > notafter)){ - queue->next_run = notafter; - } -} - -__attribute__((nonnull)) -void connect_question_socket(const task_context task, - task_queue *const queue){ - char *const question_filename = task.question_filename; - char *const filename = task.filename; - const int epoll_fd = task.epoll_fd; - buffer *const password = task.password; - string_set *const cancelled_filenames = task.cancelled_filenames; - bool *const mandos_client_exited = task.mandos_client_exited; - bool *const password_is_read = task.password_is_read; - const mono_microsecs *const current_time = task.current_time; - - struct sockaddr_un sock_name = { .sun_family=AF_LOCAL }; - - if(sizeof(sock_name.sun_path) <= strlen(filename)){ - error(0, 0, "Socket filename is larger than" - " sizeof(sockaddr_un.sun_path); %" PRIuMAX ": \"%s\"", - (uintmax_t)sizeof(sock_name.sun_path), filename); - if(not string_set_add(cancelled_filenames, question_filename)){ - error(0, errno, "Failed to cancel question for file %s", - question_filename); - } - cleanup_task(&task); - return; - } - - const int fd = socket(PF_LOCAL, SOCK_DGRAM - | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); - if(fd < 0){ - error(0, errno, - "Failed to create socket(PF_LOCAL, SOCK_DGRAM, 0)"); - if(not add_to_queue(queue, task)){ - error(0, errno, "Failed to add connect_question_socket for file" - " \"%s\" and socket \"%s\" to queue", question_filename, - filename); - cleanup_task(&task); - } else { - /* Force the added task (connect_question_socket) to run - immediately */ - queue->next_run = 1; - } - return; - } - - strncpy(sock_name.sun_path, filename, sizeof(sock_name.sun_path)); - if(connect(fd, (struct sockaddr *)&sock_name, - (socklen_t)SUN_LEN(&sock_name)) != 0){ - error(0, errno, "Failed to connect socket to \"%s\"", filename); - if(not add_to_queue(queue, task)){ - error(0, errno, "Failed to add connect_question_socket for file" - " \"%s\" and socket \"%s\" to queue", question_filename, - filename); - cleanup_task(&task); - } else { - /* Force the added task (connect_question_socket) to run again, - at most one second from now */ - if((queue->next_run == 0) - or (queue->next_run > (*current_time + 1000000))){ - queue->next_run = *current_time + 1000000; - } - } - return; - } - - /* Not necessary, but we can try, and merely warn on failure */ - if(shutdown(fd, SHUT_RD) != 0){ - error(0, errno, "Failed to shutdown reading from socket \"%s\"", - filename); - } - - /* Add the fd to the epoll set */ - if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, - &(struct epoll_event){ .events=EPOLLOUT }) - != 0){ - error(0, errno, "Failed to add inotify file descriptor %d for" - " socket %s to epoll set", fd, filename); - if(not add_to_queue(queue, task)){ - error(0, errno, "Failed to add connect_question_socket for file" - " \"%s\" and socket \"%s\" to queue", question_filename, - filename); - cleanup_task(&task); - } else { - /* Force the added task (connect_question_socket) to run again, - at most one second from now */ - if((queue->next_run == 0) - or (queue->next_run > (*current_time + 1000000))){ - queue->next_run = *current_time + 1000000; - } - } - return; - } - - /* add task send_password_to_socket to queue */ - const task_context send_password_to_socket_task = { - .func=send_password_to_socket, - .question_filename=question_filename, - .filename=filename, - .epoll_fd=epoll_fd, - .fd=fd, - .password=password, - .cancelled_filenames=cancelled_filenames, - .mandos_client_exited=mandos_client_exited, - .password_is_read=password_is_read, - .current_time=current_time, - }; - - if(not add_to_queue(queue, send_password_to_socket_task)){ - error(0, errno, "Failed to add send_password_to_socket for" - " file \"%s\" and socket \"%s\" to queue", - question_filename, filename); - cleanup_task(&send_password_to_socket_task); - } -} - -__attribute__((nonnull)) -void send_password_to_socket(const task_context task, - task_queue *const queue){ - char *const question_filename=task.question_filename; - char *const filename=task.filename; - const int epoll_fd=task.epoll_fd; - const int fd=task.fd; - buffer *const password=task.password; - string_set *const cancelled_filenames=task.cancelled_filenames; - bool *const mandos_client_exited = task.mandos_client_exited; - bool *const password_is_read = task.password_is_read; - const mono_microsecs *const current_time = task.current_time; - - if(*mandos_client_exited and *password_is_read){ - - const size_t send_buffer_length = password->length + 2; - char *send_buffer = malloc(send_buffer_length); - if(send_buffer == NULL){ - error(0, errno, "Failed to allocate send_buffer"); - } else { - if(mlock(send_buffer, send_buffer_length) != 0){ - /* Warn but do not treat as fatal error */ - if(errno != EPERM and errno != ENOMEM){ - error(0, errno, "Failed to lock memory for password" - " buffer"); - } - } - /* “[…] send a single datagram to the socket consisting of the - password string either prefixed with "+" or with "-" - depending on whether the password entry was successful or - not. You may but don't have to include a final NUL byte in - your message. - - — (Tue, 15 Sep 2020 - 14:24:20 GMT) - */ - send_buffer[0] = '+'; /* Prefix with "+" */ - /* Always add an extra NUL */ - send_buffer[password->length + 1] = '\0'; - if(password->length > 0){ - memcpy(send_buffer + 1, password->data, password->length); - } - errno = 0; - ssize_t ssret = send(fd, send_buffer, send_buffer_length, - MSG_NOSIGNAL); - const error_t saved_errno = (ssret < 0) ? errno : 0; -#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 25) - explicit_bzero(send_buffer, send_buffer_length); -#else - memset(send_buffer, '\0', send_buffer_length); -#endif - if(munlock(send_buffer, send_buffer_length) != 0){ - error(0, errno, "Failed to unlock memory of send buffer"); - } - free(send_buffer); - if(ssret < 0 or ssret < (ssize_t)send_buffer_length){ - switch(saved_errno){ - case EINTR: - case ENOBUFS: - case ENOMEM: - case EADDRINUSE: - case ECONNREFUSED: - case ECONNRESET: - case ENOENT: - case ETOOMANYREFS: - case EAGAIN: - /* Retry, below */ - break; - case EMSGSIZE: - error(0, saved_errno, "Password of size %" PRIuMAX - " is too big", (uintmax_t)password->length); -#if __GNUC__ < 7 - /* FALLTHROUGH */ -#else - __attribute__((fallthrough)); -#endif - case 0: - if(ssret >= 0 and ssret < (ssize_t)send_buffer_length){ - error(0, 0, "Password only partially sent to socket %s: %" - PRIuMAX " out of %" PRIuMAX " bytes sent", filename, - (uintmax_t)ssret, (uintmax_t)send_buffer_length); - } -#if __GNUC__ < 7 - /* FALLTHROUGH */ -#else - __attribute__((fallthrough)); -#endif - default: - error(0, saved_errno, "Failed to send() to socket %s", - filename); - if(not string_set_add(cancelled_filenames, - question_filename)){ - error(0, errno, "Failed to cancel question for file %s", - question_filename); - } - cleanup_task(&task); - return; - } - } else { - /* Success */ - cleanup_task(&task); - return; - } - } - } - - /* We failed or are not ready yet; retry later */ - - if(not add_to_queue(queue, task)){ - error(0, errno, "Failed to add send_password_to_socket for" - " file %s and socket %s to queue", question_filename, - filename); - cleanup_task(&task); - } - - /* Add the fd to the epoll set */ - if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, - &(struct epoll_event){ .events=EPOLLOUT }) - != 0){ - error(0, errno, "Failed to add socket file descriptor %d for" - " socket %s to epoll set", fd, filename); - /* Force the added task (send_password_to_socket) to run again, at - most one second from now */ - if((queue->next_run == 0) - or (queue->next_run > (*current_time + 1000000))){ - queue->next_run = *current_time + 1000000; - } - } -} - -__attribute__((warn_unused_result)) -bool add_existing_questions(task_queue *const queue, - const int epoll_fd, - buffer *const password, - string_set *cancelled_filenames, - const mono_microsecs *const current_time, - bool *const mandos_client_exited, - bool *const password_is_read, - const char *const dirname){ - __attribute__((cleanup(cleanup_string))) - char *dir_pattern = NULL; - const int ret = asprintf(&dir_pattern, "%s/ask.*", dirname); - if(ret < 0 or dir_pattern == NULL){ - error(0, errno, "Could not create glob pattern for directory %s", - dirname); - return false; - } - __attribute__((cleanup(globfree))) - glob_t question_filenames = {}; - switch(glob(dir_pattern, GLOB_ERR | GLOB_NOSORT | GLOB_MARK, - NULL, &question_filenames)){ - case GLOB_ABORTED: - default: - error(0, errno, "Failed to open directory %s", dirname); - return false; - case GLOB_NOMATCH: - error(0, errno, "There are no question files in %s", dirname); - return false; - case GLOB_NOSPACE: - error(0, errno, "Could not allocate memory for question file" - " names in %s", dirname); -#if __GNUC__ < 7 - /* FALLTHROUGH */ -#else - __attribute__((fallthrough)); -#endif - case 0: - for(size_t i = 0; i < question_filenames.gl_pathc; i++){ - char *const question_filename = strdup(question_filenames - .gl_pathv[i]); - const task_context task = { - .func=open_and_parse_question, - .epoll_fd=epoll_fd, - .question_filename=question_filename, - .filename=question_filename, - .password=password, - .cancelled_filenames=cancelled_filenames, - .current_time=current_time, - .mandos_client_exited=mandos_client_exited, - .password_is_read=password_is_read, - }; - - if(question_filename == NULL - or not add_to_queue(queue, task)){ - error(0, errno, "Failed to add open_and_parse_question for" - " file %s to queue", - question_filenames.gl_pathv[i]); - free(question_filename); - } else { - queue->next_run = 1; - } - } - return true; - } -} - -__attribute__((nonnull, warn_unused_result)) -bool wait_for_event(const int epoll_fd, - const mono_microsecs queue_next_run, - const mono_microsecs current_time){ - __attribute__((const)) - int milliseconds_to_wait(const mono_microsecs currtime, - const mono_microsecs nextrun){ - if(currtime >= nextrun){ - return 0; - } - const uintmax_t wait_time_ms = (nextrun - currtime) / 1000; - if(wait_time_ms > (uintmax_t)INT_MAX){ - return INT_MAX; - } - return (int)wait_time_ms; - } - - const int wait_time_ms = milliseconds_to_wait(current_time, - queue_next_run); - - /* Prepare unblocking of SIGCHLD during epoll_pwait */ - sigset_t temporary_unblocked_sigmask; - /* Get current signal mask */ - if(pthread_sigmask(-1, NULL, &temporary_unblocked_sigmask) != 0){ - return false; - } - /* Remove SIGCHLD from the signal mask */ - if(sigdelset(&temporary_unblocked_sigmask, SIGCHLD) != 0){ - return false; - } - struct epoll_event events[8]; /* Ignored */ - int ret = epoll_pwait(epoll_fd, events, - sizeof(events) / sizeof(struct epoll_event), - queue_next_run == 0 ? -1 : (int)wait_time_ms, - &temporary_unblocked_sigmask); - if(ret < 0 and errno != EINTR){ - error(0, errno, "Failed epoll_pwait(epfd=%d, ..., timeout=%d," - " ...", epoll_fd, - queue_next_run == 0 ? -1 : (int)wait_time_ms); - return false; - } - return clear_all_fds_from_epoll_set(epoll_fd); -} - -bool clear_all_fds_from_epoll_set(const int epoll_fd){ - /* Create a new empty epoll set */ - __attribute__((cleanup(cleanup_close))) - const int new_epoll_fd = epoll_create1(EPOLL_CLOEXEC); - if(new_epoll_fd < 0){ - return false; - } - /* dup3() the new epoll set fd over the old one, replacing it */ - if(dup3(new_epoll_fd, epoll_fd, O_CLOEXEC) < 0){ - return false; - } - return true; -} - -__attribute__((nonnull, warn_unused_result)) -bool run_queue(task_queue **const queue, - string_set *const cancelled_filenames, - bool *const quit_now){ - - task_queue *new_queue = create_queue(); - if(new_queue == NULL){ - return false; - } - - __attribute__((cleanup(string_set_clear))) - string_set old_cancelled_filenames = {}; - string_set_swap(cancelled_filenames, &old_cancelled_filenames); - - /* Declare i outside the for loop, since we might need i after the - loop in case we aborted in the middle */ - size_t i; - for(i=0; i < (*queue)->length and not *quit_now; i++){ - task_context *const task = &((*queue)->tasks[i]); - const char *const question_filename = task->question_filename; - /* Skip any task referencing a cancelled question filename */ - if(question_filename != NULL - and string_set_contains(old_cancelled_filenames, - question_filename)){ - cleanup_task(task); - continue; - } - task->func(*task, new_queue); - } - - if(*quit_now){ - /* we might be in the middle of the queue, so clean up any - remaining tasks in the current queue */ - for(; i < (*queue)->length; i++){ - cleanup_task(&((*queue)->tasks[i])); - } - free_queue(*queue); - *queue = new_queue; - new_queue = NULL; - return false; - } - free_queue(*queue); - *queue = new_queue; - new_queue = NULL; - - return true; -} - -/* End of regular code section */ - -/* Start of tests section; here are the tests for the above code */ - -/* This "fixture" data structure is used by the test setup and - teardown functions */ -typedef struct { - struct sigaction orig_sigaction; - sigset_t orig_sigmask; -} test_fixture; - -static void test_setup(test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - g_assert_true(setup_signal_handler(&fixture->orig_sigaction)); - g_assert_true(block_sigchld(&fixture->orig_sigmask)); -} - -static void test_teardown(test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - g_assert_true(restore_signal_handler(&fixture->orig_sigaction)); - g_assert_true(restore_sigmask(&fixture->orig_sigmask)); -} - -/* Utility function used by tests to search queue for matching task */ -__attribute__((pure, nonnull, warn_unused_result)) -static task_context *find_matching_task(const task_queue *const queue, - const task_context task){ - /* The argument "task" structure is a pattern to match; 0 in any - member means any value matches, otherwise the value must match. - The filename strings are compared by strcmp(), not by pointer. */ - for(size_t i = 0; i < queue->length; i++){ - task_context *const current_task = queue->tasks+i; - /* Check all members of task_context, if set to a non-zero value. - If a member does not match, continue to next task in queue */ - - /* task_func *const func */ - if(task.func != NULL and current_task->func != task.func){ - continue; - } - /* char *const question_filename; */ - if(task.question_filename != NULL - and (current_task->question_filename == NULL - or strcmp(current_task->question_filename, - task.question_filename) != 0)){ - continue; - } - /* const pid_t pid; */ - if(task.pid != 0 and current_task->pid != task.pid){ - continue; - } - /* const int epoll_fd; */ - if(task.epoll_fd != 0 - and current_task->epoll_fd != task.epoll_fd){ - continue; - } - /* bool *const quit_now; */ - if(task.quit_now != NULL - and current_task->quit_now != task.quit_now){ - continue; - } - /* const int fd; */ - if(task.fd != 0 and current_task->fd != task.fd){ - continue; - } - /* bool *const mandos_client_exited; */ - if(task.mandos_client_exited != NULL - and current_task->mandos_client_exited - != task.mandos_client_exited){ - continue; - } - /* buffer *const password; */ - if(task.password != NULL - and current_task->password != task.password){ - continue; - } - /* bool *const password_is_read; */ - if(task.password_is_read != NULL - and current_task->password_is_read != task.password_is_read){ - continue; - } - /* char *filename; */ - if(task.filename != NULL - and (current_task->filename == NULL - or strcmp(current_task->filename, task.filename) != 0)){ - continue; - } - /* string_set *const cancelled_filenames; */ - if(task.cancelled_filenames != NULL - and current_task->cancelled_filenames - != task.cancelled_filenames){ - continue; - } - /* const mono_microsecs notafter; */ - if(task.notafter != 0 - and current_task->notafter != task.notafter){ - continue; - } - /* const mono_microsecs *const current_time; */ - if(task.current_time != NULL - and current_task->current_time != task.current_time){ - continue; - } - /* Current task matches all members; return it */ - return current_task; - } - /* No task in queue matches passed pattern task */ - return NULL; -} - -static void test_create_queue(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *const queue = create_queue(); - g_assert_nonnull(queue); - g_assert_null(queue->tasks); - g_assert_true(queue->length == 0); - g_assert_true(queue->next_run == 0); -} - -static task_func dummy_func; - -static void test_add_to_queue(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - g_assert_true(add_to_queue(queue, - (task_context){ .func=dummy_func })); - g_assert_true(queue->length == 1); - g_assert_nonnull(queue->tasks); - g_assert_true(queue->tasks[0].func == dummy_func); -} - -static void test_add_to_queue_overflow(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - g_assert_true(queue->length == 0); - queue->length = SIZE_MAX / sizeof(task_context); /* fake max size */ - - FILE *real_stderr = stderr; - FILE *devnull = fopen("/dev/null", "we"); - g_assert_nonnull(devnull); - stderr = devnull; - const bool ret = add_to_queue(queue, - (task_context){ .func=dummy_func }); - g_assert_true(errno == ENOMEM); - g_assert_false(ret); - stderr = real_stderr; - g_assert_cmpint(fclose(devnull), ==, 0); - queue->length = 0; /* Restore real size */ -} - -static void dummy_func(__attribute__((unused)) - const task_context task, - __attribute__((unused)) - task_queue *const queue){ -} - -static void test_queue_has_question_empty(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - g_assert_false(queue_has_question(queue)); -} - -static void test_queue_has_question_false(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - g_assert_true(add_to_queue(queue, - (task_context){ .func=dummy_func })); - g_assert_false(queue_has_question(queue)); -} - -static void test_queue_has_question_true(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - char *const question_filename - = strdup("/nonexistent/question_filename"); - g_assert_nonnull(question_filename); - task_context task = { - .func=dummy_func, - .question_filename=question_filename, - }; - g_assert_true(add_to_queue(queue, task)); - g_assert_true(queue_has_question(queue)); -} - -static void test_queue_has_question_false2(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - task_context task = { .func=dummy_func }; - g_assert_true(add_to_queue(queue, task)); - g_assert_true(add_to_queue(queue, task)); - g_assert_cmpint((int)queue->length, ==, 2); - g_assert_false(queue_has_question(queue)); -} - -static void test_queue_has_question_true2(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - task_context task1 = { .func=dummy_func }; - g_assert_true(add_to_queue(queue, task1)); - char *const question_filename - = strdup("/nonexistent/question_filename"); - g_assert_nonnull(question_filename); - task_context task2 = { - .func=dummy_func, - .question_filename=question_filename, - }; - g_assert_true(add_to_queue(queue, task2)); - g_assert_cmpint((int)queue->length, ==, 2); - g_assert_true(queue_has_question(queue)); -} - -static void test_cleanup_buffer(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - buffer buf = {}; - - const size_t buffersize = 10; - - buf.data = malloc(buffersize); - g_assert_nonnull(buf.data); - if(mlock(buf.data, buffersize) != 0){ - g_assert_true(errno == EPERM or errno == ENOMEM); - } - - cleanup_buffer(&buf); - g_assert_null(buf.data); -} - -static -void test_string_set_new_set_contains_nothing(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(string_set_clear))) - string_set set = {}; - g_assert_false(string_set_contains(set, "")); /* Empty string */ - g_assert_false(string_set_contains(set, "test_string")); -} - -static void -test_string_set_with_added_string_contains_it(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(string_set_clear))) - string_set set = {}; - g_assert_true(string_set_add(&set, "test_string")); - g_assert_true(string_set_contains(set, "test_string")); -} - -static void -test_string_set_cleared_does_not_contain_str(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(string_set_clear))) - string_set set = {}; - g_assert_true(string_set_add(&set, "test_string")); - string_set_clear(&set); - g_assert_false(string_set_contains(set, "test_string")); -} - -static -void test_string_set_swap_one_with_empty(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(string_set_clear))) - string_set set1 = {}; - __attribute__((cleanup(string_set_clear))) - string_set set2 = {}; - g_assert_true(string_set_add(&set1, "test_string1")); - string_set_swap(&set1, &set2); - g_assert_false(string_set_contains(set1, "test_string1")); - g_assert_true(string_set_contains(set2, "test_string1")); -} - -static -void test_string_set_swap_empty_with_one(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(string_set_clear))) - string_set set1 = {}; - __attribute__((cleanup(string_set_clear))) - string_set set2 = {}; - g_assert_true(string_set_add(&set2, "test_string2")); - string_set_swap(&set1, &set2); - g_assert_true(string_set_contains(set1, "test_string2")); - g_assert_false(string_set_contains(set2, "test_string2")); -} - -static void test_string_set_swap_one_with_one(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(string_set_clear))) - string_set set1 = {}; - __attribute__((cleanup(string_set_clear))) - string_set set2 = {}; - g_assert_true(string_set_add(&set1, "test_string1")); - g_assert_true(string_set_add(&set2, "test_string2")); - string_set_swap(&set1, &set2); - g_assert_false(string_set_contains(set1, "test_string1")); - g_assert_true(string_set_contains(set1, "test_string2")); - g_assert_false(string_set_contains(set2, "test_string2")); - g_assert_true(string_set_contains(set2, "test_string1")); -} - -static bool fd_has_cloexec_and_nonblock(const int); - -static bool epoll_set_contains(int, int, uint32_t); - -static void test_start_mandos_client(test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - - bool mandos_client_exited = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - buffer password = {}; - bool password_is_read = false; - const char helper_directory[] = "/nonexistent"; - const char *const argv[] = { "/bin/true", NULL }; - - g_assert_true(start_mandos_client(queue, epoll_fd, - &mandos_client_exited, &quit_now, - &password, &password_is_read, - &fixture->orig_sigaction, - fixture->orig_sigmask, - helper_directory, 0, 0, argv)); - - g_assert_cmpuint((unsigned int)queue->length, >=, 2); - - const task_context *const added_wait_task - = find_matching_task(queue, (task_context){ - .func=wait_for_mandos_client_exit, - .mandos_client_exited=&mandos_client_exited, - .quit_now=&quit_now, - }); - g_assert_nonnull(added_wait_task); - g_assert_cmpint(added_wait_task->pid, >, 0); - g_assert_cmpint(kill(added_wait_task->pid, SIGKILL), ==, 0); - waitpid(added_wait_task->pid, NULL, 0); - - const task_context *const added_read_task - = find_matching_task(queue, (task_context){ - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .password=&password, - .password_is_read=&password_is_read, - .quit_now=&quit_now, - }); - g_assert_nonnull(added_read_task); - g_assert_cmpint(added_read_task->fd, >, 2); - g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd)); - g_assert_true(epoll_set_contains(epoll_fd, added_read_task->fd, - EPOLLIN | EPOLLRDHUP)); -} - -static bool fd_has_cloexec_and_nonblock(const int fd){ - const int socket_fd_flags = fcntl(fd, F_GETFD, 0); - const int socket_file_flags = fcntl(fd, F_GETFL, 0); - return ((socket_fd_flags >= 0) - and (socket_fd_flags & FD_CLOEXEC) - and (socket_file_flags >= 0) - and (socket_file_flags & O_NONBLOCK)); -} - -__attribute__((const)) -bool is_privileged(void){ - uid_t user = getuid() + 1; - if(user == 0){ /* Overflow check */ - user++; - } - gid_t group = getuid() + 1; - if(group == 0){ /* Overflow check */ - group++; - } - const pid_t pid = fork(); - if(pid == 0){ /* Child */ - if(setresgid((uid_t)-1, group, group) == -1){ - if(errno != EPERM){ - error(EXIT_FAILURE, errno, "Failed to setresgid(-1, %" PRIuMAX - ", %" PRIuMAX")", (uintmax_t)group, (uintmax_t)group); - } - exit(EXIT_FAILURE); - } - if(setresuid((uid_t)-1, user, user) == -1){ - if(errno != EPERM){ - error(EXIT_FAILURE, errno, "Failed to setresuid(-1, %" PRIuMAX - ", %" PRIuMAX")", (uintmax_t)user, (uintmax_t)user); - } - exit(EXIT_FAILURE); - } - exit(EXIT_SUCCESS); - } - if(pid == -1){ - error(EXIT_FAILURE, errno, "Failed to fork()"); - } - - int status; - waitpid(pid, &status, 0); - if(WIFEXITED(status) and (WEXITSTATUS(status) == EXIT_SUCCESS)){ - return true; - } - return false; -} - -static bool epoll_set_contains(int epoll_fd, int fd, uint32_t events){ - /* Only scan for events in this eventmask */ - const uint32_t eventmask = EPOLLIN | EPOLLOUT | EPOLLRDHUP; - __attribute__((cleanup(cleanup_string))) - char *fdinfo_name = NULL; - int ret = asprintf(&fdinfo_name, "/proc/self/fdinfo/%d", epoll_fd); - g_assert_cmpint(ret, >, 0); - g_assert_nonnull(fdinfo_name); - - FILE *fdinfo = fopen(fdinfo_name, "r"); - g_assert_nonnull(fdinfo); - uint32_t reported_events; - buffer line = {}; - int found_fd = -1; - - do { - if(getline(&line.data, &line.allocated, fdinfo) < 0){ - break; - } - /* See proc(5) for format of /proc/PID/fdinfo/FD for epoll fd's */ - if(sscanf(line.data, "tfd: %d events: %" SCNx32 " ", - &found_fd, &reported_events) == 2){ - if(found_fd == fd){ - break; - } - } - } while(not feof(fdinfo) and not ferror(fdinfo)); - g_assert_cmpint(fclose(fdinfo), ==, 0); - free(line.data); - if(found_fd != fd){ - return false; - } - - if(events == 0){ - /* Don't check events if none are given */ - return true; - } - return (reported_events & eventmask) == (events & eventmask); -} - -static void test_start_mandos_client_execv(test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - bool mandos_client_exited = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - const char helper_directory[] = "/nonexistent"; - /* Can't execv("/", ...), so this should fail */ - const char *const argv[] = { "/", NULL }; - - { - __attribute__((cleanup(cleanup_close))) - const int devnull_fd = open("/dev/null", - O_WRONLY | O_CLOEXEC | O_NOCTTY); - g_assert_cmpint(devnull_fd, >=, 0); - __attribute__((cleanup(cleanup_close))) - const int real_stderr_fd = dup(STDERR_FILENO); - g_assert_cmpint(real_stderr_fd, >=, 0); - dup2(devnull_fd, STDERR_FILENO); - - const bool success = start_mandos_client(queue, epoll_fd, - &mandos_client_exited, - &quit_now, - &password, - (bool[]){false}, - &fixture->orig_sigaction, - fixture->orig_sigmask, - helper_directory, 0, 0, - argv); - dup2(real_stderr_fd, STDERR_FILENO); - g_assert_true(success); - } - g_assert_cmpuint((unsigned int)queue->length, ==, 2); - - struct timespec starttime, currtime; - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0); - do { - queue->next_run = 0; - string_set cancelled_filenames = {}; - - { - __attribute__((cleanup(cleanup_close))) - const int devnull_fd = open("/dev/null", - O_WRONLY | O_CLOEXEC | O_NOCTTY); - g_assert_cmpint(devnull_fd, >=, 0); - __attribute__((cleanup(cleanup_close))) - const int real_stderr_fd = dup(STDERR_FILENO); - g_assert_cmpint(real_stderr_fd, >=, 0); - g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0)); - dup2(devnull_fd, STDERR_FILENO); - const bool success = run_queue(&queue, &cancelled_filenames, - &quit_now); - dup2(real_stderr_fd, STDERR_FILENO); - if(not success){ - break; - } - } - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0); - } while(((queue->length) > 0) - and (not quit_now) - and ((currtime.tv_sec - starttime.tv_sec) < 10)); - - g_assert_true(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - g_assert_true(mandos_client_exited); -} - -static void test_start_mandos_client_suid_euid(test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - if(not is_privileged()){ - g_test_skip("Not privileged"); - return; - } - - bool mandos_client_exited = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - bool password_is_read = false; - const char helper_directory[] = "/nonexistent"; - const char *const argv[] = { "/usr/bin/id", "--user", NULL }; - uid_t user = 1000; - gid_t group = 1001; - - const bool success = start_mandos_client(queue, epoll_fd, - &mandos_client_exited, - &quit_now, &password, - &password_is_read, - &fixture->orig_sigaction, - fixture->orig_sigmask, - helper_directory, user, - group, argv); - g_assert_true(success); - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - struct timespec starttime, currtime; - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0); - do { - queue->next_run = 0; - string_set cancelled_filenames = {}; - g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0)); - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0); - } while(((queue->length) > 0) - and (not quit_now) - and ((currtime.tv_sec - starttime.tv_sec) < 10)); - - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - g_assert_true(mandos_client_exited); - - g_assert_true(password_is_read); - g_assert_nonnull(password.data); - - uintmax_t id; - g_assert_cmpint(sscanf(password.data, "%" SCNuMAX "\n", &id), - ==, 1); - g_assert_true((uid_t)id == id); - - g_assert_cmpuint((unsigned int)id, ==, 0); -} - -static void test_start_mandos_client_suid_egid(test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - if(not is_privileged()){ - g_test_skip("Not privileged"); - return; - } - - bool mandos_client_exited = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - bool password_is_read = false; - const char helper_directory[] = "/nonexistent"; - const char *const argv[] = { "/usr/bin/id", "--group", NULL }; - uid_t user = 1000; - gid_t group = 1001; - - const bool success = start_mandos_client(queue, epoll_fd, - &mandos_client_exited, - &quit_now, &password, - &password_is_read, - &fixture->orig_sigaction, - fixture->orig_sigmask, - helper_directory, user, - group, argv); - g_assert_true(success); - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - struct timespec starttime, currtime; - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0); - do { - queue->next_run = 0; - string_set cancelled_filenames = {}; - g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0)); - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0); - } while(((queue->length) > 0) - and (not quit_now) - and ((currtime.tv_sec - starttime.tv_sec) < 10)); - - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - g_assert_true(mandos_client_exited); - - g_assert_true(password_is_read); - g_assert_nonnull(password.data); - - uintmax_t id; - g_assert_cmpint(sscanf(password.data, "%" SCNuMAX "\n", &id), - ==, 1); - g_assert_true((gid_t)id == id); - - g_assert_cmpuint((unsigned int)id, ==, 0); -} - -static void test_start_mandos_client_suid_ruid(test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - if(not is_privileged()){ - g_test_skip("Not privileged"); - return; - } - - bool mandos_client_exited = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - bool password_is_read = false; - const char helper_directory[] = "/nonexistent"; - const char *const argv[] = { "/usr/bin/id", "--user", "--real", - NULL }; - uid_t user = 1000; - gid_t group = 1001; - - const bool success = start_mandos_client(queue, epoll_fd, - &mandos_client_exited, - &quit_now, &password, - &password_is_read, - &fixture->orig_sigaction, - fixture->orig_sigmask, - helper_directory, user, - group, argv); - g_assert_true(success); - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - struct timespec starttime, currtime; - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0); - do { - queue->next_run = 0; - string_set cancelled_filenames = {}; - g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0)); - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0); - } while(((queue->length) > 0) - and (not quit_now) - and ((currtime.tv_sec - starttime.tv_sec) < 10)); - - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - g_assert_true(mandos_client_exited); - - g_assert_true(password_is_read); - g_assert_nonnull(password.data); - - uintmax_t id; - g_assert_cmpint(sscanf(password.data, "%" SCNuMAX "\n", &id), - ==, 1); - g_assert_true((uid_t)id == id); - - g_assert_cmpuint((unsigned int)id, ==, user); -} - -static void test_start_mandos_client_suid_rgid(test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - if(not is_privileged()){ - g_test_skip("Not privileged"); - return; - } - - bool mandos_client_exited = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - bool password_is_read = false; - const char helper_directory[] = "/nonexistent"; - const char *const argv[] = { "/usr/bin/id", "--group", "--real", - NULL }; - uid_t user = 1000; - gid_t group = 1001; - - const bool success = start_mandos_client(queue, epoll_fd, - &mandos_client_exited, - &quit_now, &password, - &password_is_read, - &fixture->orig_sigaction, - fixture->orig_sigmask, - helper_directory, user, - group, argv); - g_assert_true(success); - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - struct timespec starttime, currtime; - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0); - do { - queue->next_run = 0; - string_set cancelled_filenames = {}; - g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0)); - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0); - } while(((queue->length) > 0) - and (not quit_now) - and ((currtime.tv_sec - starttime.tv_sec) < 10)); - - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - g_assert_true(mandos_client_exited); - - g_assert_true(password_is_read); - g_assert_nonnull(password.data); - - uintmax_t id; - g_assert_cmpint(sscanf(password.data, "%" SCNuMAX "\n", &id), - ==, 1); - g_assert_true((gid_t)id == id); - - g_assert_cmpuint((unsigned int)id, ==, group); -} - -static void test_start_mandos_client_read(test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - bool mandos_client_exited = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - bool password_is_read = false; - const char dummy_test_password[] = "dummy test password"; - const char helper_directory[] = "/nonexistent"; - const char *const argv[] = { "/bin/echo", "-n", dummy_test_password, - NULL }; - - const bool success = start_mandos_client(queue, epoll_fd, - &mandos_client_exited, - &quit_now, &password, - &password_is_read, - &fixture->orig_sigaction, - fixture->orig_sigmask, - helper_directory, 0, 0, - argv); - g_assert_true(success); - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - struct timespec starttime, currtime; - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0); - do { - queue->next_run = 0; - string_set cancelled_filenames = {}; - g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0)); - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0); - } while(((queue->length) > 0) - and (not quit_now) - and ((currtime.tv_sec - starttime.tv_sec) < 10)); - - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - g_assert_true(mandos_client_exited); - - g_assert_true(password_is_read); - g_assert_cmpint((int)password.length, ==, - sizeof(dummy_test_password)-1); - g_assert_nonnull(password.data); - g_assert_cmpint(memcmp(dummy_test_password, password.data, - sizeof(dummy_test_password)-1), ==, 0); -} - -static -void test_start_mandos_client_helper_directory(test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - bool mandos_client_exited = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - bool password_is_read = false; - const char helper_directory[] = "/nonexistent"; - const char *const argv[] = { "/bin/sh", "-c", - "echo -n ${MANDOSPLUGINHELPERDIR}", NULL }; - - const bool success = start_mandos_client(queue, epoll_fd, - &mandos_client_exited, - &quit_now, &password, - &password_is_read, - &fixture->orig_sigaction, - fixture->orig_sigmask, - helper_directory, 0, 0, - argv); - g_assert_true(success); - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - struct timespec starttime, currtime; - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0); - do { - queue->next_run = 0; - string_set cancelled_filenames = {}; - g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0)); - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0); - } while(((queue->length) > 0) - and (not quit_now) - and ((currtime.tv_sec - starttime.tv_sec) < 10)); - - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - g_assert_true(mandos_client_exited); - - g_assert_true(password_is_read); - g_assert_cmpint((int)password.length, ==, - sizeof(helper_directory)-1); - g_assert_nonnull(password.data); - g_assert_cmpint(memcmp(helper_directory, password.data, - sizeof(helper_directory)-1), ==, 0); -} - -__attribute__((nonnull, warn_unused_result)) -static bool proc_status_sigblk_to_sigset(const char *const, - sigset_t *const); - -static void test_start_mandos_client_sigmask(test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - bool mandos_client_exited = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - bool password_is_read = false; - const char helper_directory[] = "/nonexistent"; - /* see proc(5) for format of /proc/self/status */ - const char *const argv[] = { "/usr/bin/awk", - "$1==\"SigBlk:\"{ print $2 }", "/proc/self/status", NULL }; - - g_assert_true(start_mandos_client(queue, epoll_fd, - &mandos_client_exited, &quit_now, - &password, &password_is_read, - &fixture->orig_sigaction, - fixture->orig_sigmask, - helper_directory, 0, 0, argv)); - - struct timespec starttime, currtime; - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0); - do { - queue->next_run = 0; - string_set cancelled_filenames = {}; - g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0)); - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0); - } while((not (mandos_client_exited and password_is_read)) - and (not quit_now) - and ((currtime.tv_sec - starttime.tv_sec) < 10)); - g_assert_true(mandos_client_exited); - g_assert_true(password_is_read); - - sigset_t parsed_sigmask; - g_assert_true(proc_status_sigblk_to_sigset(password.data, - &parsed_sigmask)); - - for(int signum = 1; signum < NSIG; signum++){ - const bool has_signal = sigismember(&parsed_sigmask, signum); - if(sigismember(&fixture->orig_sigmask, signum)){ - g_assert_true(has_signal); - } else { - g_assert_false(has_signal); - } - } -} - -__attribute__((nonnull, warn_unused_result)) -static bool proc_status_sigblk_to_sigset(const char *const sigblk, - sigset_t *const sigmask){ - /* parse /proc/PID/status SigBlk value and convert to a sigset_t */ - uintmax_t scanned_sigmask; - if(sscanf(sigblk, "%" SCNxMAX " ", &scanned_sigmask) != 1){ - return false; - } - if(sigemptyset(sigmask) != 0){ - return false; - } - for(int signum = 1; signum < NSIG; signum++){ - if(scanned_sigmask & ((uintmax_t)1 << (signum-1))){ - if(sigaddset(sigmask, signum) != 0){ - return false; - } - } - } - return true; -} - -static void run_task_with_stderr_to_dev_null(const task_context task, - task_queue *const queue); - -static -void test_wait_for_mandos_client_exit_badpid(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - - bool mandos_client_exited = false; - bool quit_now = false; - - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - const task_context task = { - .func=wait_for_mandos_client_exit, - .pid=1, - .mandos_client_exited=&mandos_client_exited, - .quit_now=&quit_now, - }; - run_task_with_stderr_to_dev_null(task, queue); - - g_assert_false(mandos_client_exited); - g_assert_true(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); -} - -static void run_task_with_stderr_to_dev_null(const task_context task, - task_queue *const queue){ - FILE *real_stderr = stderr; - FILE *devnull = fopen("/dev/null", "we"); - g_assert_nonnull(devnull); - - stderr = devnull; - task.func(task, queue); - stderr = real_stderr; - - g_assert_cmpint(fclose(devnull), ==, 0); -} - -static -void test_wait_for_mandos_client_exit_noexit(test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - bool mandos_client_exited = false; - bool quit_now = false; - - pid_t create_eternal_process(void){ - const pid_t pid = fork(); - if(pid == 0){ /* Child */ - if(not restore_signal_handler(&fixture->orig_sigaction)){ - _exit(EXIT_FAILURE); - } - if(not restore_sigmask(&fixture->orig_sigmask)){ - _exit(EXIT_FAILURE); - } - while(true){ - pause(); - } - } - return pid; - } - pid_t pid = create_eternal_process(); - g_assert_true(pid != -1); - - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - const task_context task = { - .func=wait_for_mandos_client_exit, - .pid=pid, - .mandos_client_exited=&mandos_client_exited, - .quit_now=&quit_now, - }; - task.func(task, queue); - - g_assert_false(mandos_client_exited); - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=wait_for_mandos_client_exit, - .pid=task.pid, - .mandos_client_exited=&mandos_client_exited, - .quit_now=&quit_now, - })); -} - -static -void test_wait_for_mandos_client_exit_success(test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - bool mandos_client_exited = false; - bool quit_now = false; - - pid_t create_successful_process(void){ - const pid_t pid = fork(); - if(pid == 0){ /* Child */ - if(not restore_signal_handler(&fixture->orig_sigaction)){ - _exit(EXIT_FAILURE); - } - if(not restore_sigmask(&fixture->orig_sigmask)){ - _exit(EXIT_FAILURE); - } - exit(EXIT_SUCCESS); - } - return pid; - } - const pid_t pid = create_successful_process(); - g_assert_true(pid != -1); - - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - const task_context initial_task = { - .func=wait_for_mandos_client_exit, - .pid=pid, - .mandos_client_exited=&mandos_client_exited, - .quit_now=&quit_now, - }; - g_assert_true(add_to_queue(queue, initial_task)); - - struct timespec starttime, currtime; - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0); - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - do { - queue->next_run = 0; - g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0)); - g_assert_true(run_queue(&queue, (string_set[]){{}}, &quit_now)); - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0); - } while((not mandos_client_exited) - and (not quit_now) - and ((currtime.tv_sec - starttime.tv_sec) < 10)); - - g_assert_true(mandos_client_exited); - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); -} - -static -void test_wait_for_mandos_client_exit_failure(test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - bool mandos_client_exited = false; - bool quit_now = false; - - pid_t create_failing_process(void){ - const pid_t pid = fork(); - if(pid == 0){ /* Child */ - if(not restore_signal_handler(&fixture->orig_sigaction)){ - _exit(EXIT_FAILURE); - } - if(not restore_sigmask(&fixture->orig_sigmask)){ - _exit(EXIT_FAILURE); - } - exit(EXIT_FAILURE); - } - return pid; - } - const pid_t pid = create_failing_process(); - g_assert_true(pid != -1); - - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - g_assert_true(add_to_queue(queue, (task_context){ - .func=wait_for_mandos_client_exit, - .pid=pid, - .mandos_client_exited=&mandos_client_exited, - .quit_now=&quit_now, - })); - - g_assert_true(sigismember(&fixture->orig_sigmask, SIGCHLD) == 0); - - __attribute__((cleanup(cleanup_close))) - const int devnull_fd = open("/dev/null", - O_WRONLY | O_CLOEXEC | O_NOCTTY); - g_assert_cmpint(devnull_fd, >=, 0); - __attribute__((cleanup(cleanup_close))) - const int real_stderr_fd = dup(STDERR_FILENO); - g_assert_cmpint(real_stderr_fd, >=, 0); - - struct timespec starttime, currtime; - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0); - do { - g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0)); - dup2(devnull_fd, STDERR_FILENO); - const bool success = run_queue(&queue, &cancelled_filenames, - &quit_now); - dup2(real_stderr_fd, STDERR_FILENO); - if(not success){ - break; - } - - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0); - } while((not mandos_client_exited) - and (not quit_now) - and ((currtime.tv_sec - starttime.tv_sec) < 10)); - - g_assert_true(quit_now); - g_assert_true(mandos_client_exited); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); -} - -static -void test_wait_for_mandos_client_exit_killed(test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - bool mandos_client_exited = false; - bool quit_now = false; - - pid_t create_killed_process(void){ - const pid_t pid = fork(); - if(pid == 0){ /* Child */ - if(not restore_signal_handler(&fixture->orig_sigaction)){ - _exit(EXIT_FAILURE); - } - if(not restore_sigmask(&fixture->orig_sigmask)){ - _exit(EXIT_FAILURE); - } - while(true){ - pause(); - } - } - kill(pid, SIGKILL); - return pid; - } - const pid_t pid = create_killed_process(); - g_assert_true(pid != -1); - - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - g_assert_true(add_to_queue(queue, (task_context){ - .func=wait_for_mandos_client_exit, - .pid=pid, - .mandos_client_exited=&mandos_client_exited, - .quit_now=&quit_now, - })); - - __attribute__((cleanup(cleanup_close))) - const int devnull_fd = open("/dev/null", - O_WRONLY | O_CLOEXEC, O_NOCTTY); - g_assert_cmpint(devnull_fd, >=, 0); - __attribute__((cleanup(cleanup_close))) - const int real_stderr_fd = dup(STDERR_FILENO); - g_assert_cmpint(real_stderr_fd, >=, 0); - - struct timespec starttime, currtime; - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0); - do { - g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0)); - dup2(devnull_fd, STDERR_FILENO); - const bool success = run_queue(&queue, &cancelled_filenames, - &quit_now); - dup2(real_stderr_fd, STDERR_FILENO); - if(not success){ - break; - } - - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0); - } while((not mandos_client_exited) - and (not quit_now) - and ((currtime.tv_sec - starttime.tv_sec) < 10)); - - g_assert_true(mandos_client_exited); - g_assert_true(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); -} - -static bool epoll_set_does_not_contain(int, int); - -static -void test_read_mandos_client_output_readerror(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - - /* Reading /proc/self/mem from offset 0 will always give EIO */ - const int fd = open("/proc/self/mem", - O_RDONLY | O_CLOEXEC | O_NOCTTY); - - bool password_is_read = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .fd=fd, - .password=&password, - .password_is_read=&password_is_read, - .quit_now=&quit_now, - }; - run_task_with_stderr_to_dev_null(task, queue); - g_assert_false(password_is_read); - g_assert_cmpint((int)password.length, ==, 0); - g_assert_true(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_true(epoll_set_does_not_contain(epoll_fd, fd)); - - g_assert_cmpint(close(fd), ==, -1); -} - -static bool epoll_set_does_not_contain(int epoll_fd, int fd){ - return not epoll_set_contains(epoll_fd, fd, 0); -} - -static -void test_read_mandos_client_output_nodata(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - - bool password_is_read = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .password=&password, - .password_is_read=&password_is_read, - .quit_now=&quit_now, - }; - task.func(task, queue); - g_assert_false(password_is_read); - g_assert_cmpint((int)password.length, ==, 0); - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .password=&password, - .password_is_read=&password_is_read, - .quit_now=&quit_now, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); - - g_assert_cmpint(close(pipefds[1]), ==, 0); -} - -static void test_read_mandos_client_output_eof(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - g_assert_cmpint(close(pipefds[1]), ==, 0); - - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - - bool password_is_read = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .password=&password, - .password_is_read=&password_is_read, - .quit_now=&quit_now, - }; - task.func(task, queue); - g_assert_true(password_is_read); - g_assert_cmpint((int)password.length, ==, 0); - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_true(epoll_set_does_not_contain(epoll_fd, pipefds[0])); - - g_assert_cmpint(close(pipefds[0]), ==, -1); -} - -static -void test_read_mandos_client_output_once(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - const char dummy_test_password[] = "dummy test password"; - /* Start with a pre-allocated buffer */ - __attribute__((cleanup(cleanup_buffer))) - buffer password = { - .data=malloc(sizeof(dummy_test_password)), - .length=0, - .allocated=sizeof(dummy_test_password), - }; - g_assert_nonnull(password.data); - if(mlock(password.data, password.allocated) != 0){ - g_assert_true(errno == EPERM or errno == ENOMEM); - } - - bool password_is_read = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - g_assert_true(sizeof(dummy_test_password) <= PIPE_BUF); - g_assert_cmpint((int)write(pipefds[1], dummy_test_password, - sizeof(dummy_test_password)), - ==, (int)sizeof(dummy_test_password)); - - task_context task = { - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .password=&password, - .password_is_read=&password_is_read, - .quit_now=&quit_now, - }; - task.func(task, queue); - - g_assert_false(password_is_read); - g_assert_cmpint((int)password.length, ==, - (int)sizeof(dummy_test_password)); - g_assert_nonnull(password.data); - g_assert_cmpint(memcmp(password.data, dummy_test_password, - sizeof(dummy_test_password)), ==, 0); - - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .password=&password, - .password_is_read=&password_is_read, - .quit_now=&quit_now, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); - - g_assert_cmpint(close(pipefds[1]), ==, 0); -} - -static -void test_read_mandos_client_output_malloc(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - const char dummy_test_password[] = "dummy test password"; - /* Start with an empty buffer */ - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - - bool password_is_read = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - g_assert_true(sizeof(dummy_test_password) <= PIPE_BUF); - g_assert_cmpint((int)write(pipefds[1], dummy_test_password, - sizeof(dummy_test_password)), - ==, (int)sizeof(dummy_test_password)); - - task_context task = { - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .password=&password, - .password_is_read=&password_is_read, - .quit_now=&quit_now, - }; - task.func(task, queue); - - g_assert_false(password_is_read); - g_assert_cmpint((int)password.length, ==, - (int)sizeof(dummy_test_password)); - g_assert_nonnull(password.data); - g_assert_cmpint(memcmp(password.data, dummy_test_password, - sizeof(dummy_test_password)), ==, 0); - - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .password=&password, - .password_is_read=&password_is_read, - .quit_now=&quit_now, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); - - g_assert_cmpint(close(pipefds[1]), ==, 0); -} - -static -void test_read_mandos_client_output_append(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - const char dummy_test_password[] = "dummy test password"; - __attribute__((cleanup(cleanup_buffer))) - buffer password = { - .data=malloc(PIPE_BUF), - .length=PIPE_BUF, - .allocated=PIPE_BUF, - }; - g_assert_nonnull(password.data); - if(mlock(password.data, password.allocated) != 0){ - g_assert_true(errno == EPERM or errno == ENOMEM); - } - - memset(password.data, 'x', PIPE_BUF); - char password_expected[PIPE_BUF]; - memcpy(password_expected, password.data, PIPE_BUF); - - bool password_is_read = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - g_assert_true(sizeof(dummy_test_password) <= PIPE_BUF); - g_assert_cmpint((int)write(pipefds[1], dummy_test_password, - sizeof(dummy_test_password)), - ==, (int)sizeof(dummy_test_password)); - - task_context task = { - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .password=&password, - .password_is_read=&password_is_read, - .quit_now=&quit_now, - }; - task.func(task, queue); - - g_assert_false(password_is_read); - g_assert_cmpint((int)password.length, ==, - PIPE_BUF + sizeof(dummy_test_password)); - g_assert_nonnull(password.data); - g_assert_cmpint(memcmp(password_expected, password.data, PIPE_BUF), - ==, 0); - g_assert_cmpint(memcmp(password.data + PIPE_BUF, - dummy_test_password, - sizeof(dummy_test_password)), ==, 0); - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .password=&password, - .password_is_read=&password_is_read, - .quit_now=&quit_now, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); -} - -static char *make_temporary_directory(void); - -static void test_add_inotify_dir_watch(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - - g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now, - &password, tempdir, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read)); - - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - const task_context *const added_read_task - = find_matching_task(queue, (task_context){ - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .quit_now=&quit_now, - .password=&password, - .filename=tempdir, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }); - g_assert_nonnull(added_read_task); - - g_assert_cmpint(added_read_task->fd, >, 2); - g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd)); - g_assert_true(epoll_set_contains(added_read_task->epoll_fd, - added_read_task->fd, - EPOLLIN | EPOLLRDHUP)); - - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static char *make_temporary_directory(void){ - char *name = strdup("/tmp/mandosXXXXXX"); - g_assert_nonnull(name); - char *result = mkdtemp(name); - if(result == NULL){ - free(name); - } - return result; -} - -static void test_add_inotify_dir_watch_fail(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - - const char nonexistent_dir[] = "/nonexistent"; - - FILE *real_stderr = stderr; - FILE *devnull = fopen("/dev/null", "we"); - g_assert_nonnull(devnull); - stderr = devnull; - g_assert_false(add_inotify_dir_watch(queue, epoll_fd, &quit_now, - &password, nonexistent_dir, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read)); - stderr = real_stderr; - g_assert_cmpint(fclose(devnull), ==, 0); - - g_assert_cmpuint((unsigned int)queue->length, ==, 0); -} - -static void test_add_inotify_dir_watch_nondir(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - - const char not_a_directory[] = "/dev/tty"; - - FILE *real_stderr = stderr; - FILE *devnull = fopen("/dev/null", "we"); - g_assert_nonnull(devnull); - stderr = devnull; - g_assert_false(add_inotify_dir_watch(queue, epoll_fd, &quit_now, - &password, not_a_directory, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read)); - stderr = real_stderr; - g_assert_cmpint(fclose(devnull), ==, 0); - - g_assert_cmpuint((unsigned int)queue->length, ==, 0); -} - -static void test_add_inotify_dir_watch_EAGAIN(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - - g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now, - &password, tempdir, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read)); - - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - const task_context *const added_read_task - = find_matching_task(queue, - (task_context){ .func=read_inotify_event }); - g_assert_nonnull(added_read_task); - - g_assert_cmpint(added_read_task->fd, >, 2); - g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd)); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - struct inotify_event *ievent = malloc(ievent_size); - g_assert_nonnull(ievent); - - g_assert_cmpint(read(added_read_task->fd, ievent, ievent_size), ==, - -1); - g_assert_cmpint(errno, ==, EAGAIN); - - free(ievent); - - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static char *make_temporary_file_in_directory(const char - *const dir); - -static -void test_add_inotify_dir_watch_IN_CLOSE_WRITE(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - - g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now, - &password, tempdir, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read)); - - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - const task_context *const added_read_task - = find_matching_task(queue, - (task_context){ .func=read_inotify_event }); - g_assert_nonnull(added_read_task); - - g_assert_cmpint(added_read_task->fd, >, 2); - g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd)); - - __attribute__((cleanup(cleanup_string))) - char *filename = make_temporary_file_in_directory(tempdir); - g_assert_nonnull(filename); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - struct inotify_event *ievent = malloc(ievent_size); - g_assert_nonnull(ievent); - - ssize_t read_size = 0; - read_size = read(added_read_task->fd, ievent, ievent_size); - - g_assert_cmpint((int)read_size, >, 0); - g_assert_true(ievent->mask & IN_CLOSE_WRITE); - g_assert_cmpstr(ievent->name, ==, basename(filename)); - - free(ievent); - - g_assert_cmpint(unlink(filename), ==, 0); - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static char *make_temporary_prefixed_file_in_directory(const char - *const prefix, - const char - *const dir){ - char *filename = NULL; - g_assert_cmpint(asprintf(&filename, "%s/%sXXXXXX", dir, prefix), - >, 0); - g_assert_nonnull(filename); - const int fd = mkostemp(filename, O_CLOEXEC); - g_assert_cmpint(fd, >=, 0); - g_assert_cmpint(close(fd), ==, 0); - return filename; -} - -static char *make_temporary_file_in_directory(const char - *const dir){ - return make_temporary_prefixed_file_in_directory("temp", dir); -} - -static -void test_add_inotify_dir_watch_IN_MOVED_TO(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - - __attribute__((cleanup(cleanup_string))) - char *watchdir = make_temporary_directory(); - g_assert_nonnull(watchdir); - - g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now, - &password, watchdir, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read)); - - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - const task_context *const added_read_task - = find_matching_task(queue, - (task_context){ .func=read_inotify_event }); - g_assert_nonnull(added_read_task); - - g_assert_cmpint(added_read_task->fd, >, 2); - g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd)); - - char *sourcedir = make_temporary_directory(); - g_assert_nonnull(sourcedir); - - __attribute__((cleanup(cleanup_string))) - char *filename = make_temporary_file_in_directory(sourcedir); - g_assert_nonnull(filename); - - __attribute__((cleanup(cleanup_string))) - char *targetfilename = NULL; - g_assert_cmpint(asprintf(&targetfilename, "%s/%s", watchdir, - basename(filename)), >, 0); - g_assert_nonnull(targetfilename); - - g_assert_cmpint(rename(filename, targetfilename), ==, 0); - g_assert_cmpint(rmdir(sourcedir), ==, 0); - free(sourcedir); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - struct inotify_event *ievent = malloc(ievent_size); - g_assert_nonnull(ievent); - - ssize_t read_size = read(added_read_task->fd, ievent, ievent_size); - - g_assert_cmpint((int)read_size, >, 0); - g_assert_true(ievent->mask & IN_MOVED_TO); - g_assert_cmpstr(ievent->name, ==, basename(targetfilename)); - - free(ievent); - - g_assert_cmpint(unlink(targetfilename), ==, 0); - g_assert_cmpint(rmdir(watchdir), ==, 0); -} - -static -void test_add_inotify_dir_watch_IN_MOVED_FROM(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - - __attribute__((cleanup(cleanup_string))) - char *tempfilename = make_temporary_file_in_directory(tempdir); - g_assert_nonnull(tempfilename); - - __attribute__((cleanup(cleanup_string))) - char *targetdir = make_temporary_directory(); - g_assert_nonnull(targetdir); - - __attribute__((cleanup(cleanup_string))) - char *targetfilename = NULL; - g_assert_cmpint(asprintf(&targetfilename, "%s/%s", targetdir, - basename(tempfilename)), >, 0); - g_assert_nonnull(targetfilename); - - g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now, - &password, tempdir, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read)); - - g_assert_cmpint(rename(tempfilename, targetfilename), ==, 0); - - const task_context *const added_read_task - = find_matching_task(queue, - (task_context){ .func=read_inotify_event }); - g_assert_nonnull(added_read_task); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - struct inotify_event *ievent = malloc(ievent_size); - g_assert_nonnull(ievent); - - ssize_t read_size = read(added_read_task->fd, ievent, ievent_size); - - g_assert_cmpint((int)read_size, >, 0); - g_assert_true(ievent->mask & IN_MOVED_FROM); - g_assert_cmpstr(ievent->name, ==, basename(tempfilename)); - - free(ievent); - - g_assert_cmpint(unlink(targetfilename), ==, 0); - g_assert_cmpint(rmdir(targetdir), ==, 0); - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static -void test_add_inotify_dir_watch_IN_DELETE(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - - __attribute__((cleanup(cleanup_string))) - char *tempfile = make_temporary_file_in_directory(tempdir); - g_assert_nonnull(tempfile); - - g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now, - &password, tempdir, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read)); - g_assert_cmpint(unlink(tempfile), ==, 0); - - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - const task_context *const added_read_task - = find_matching_task(queue, - (task_context){ .func=read_inotify_event }); - g_assert_nonnull(added_read_task); - - g_assert_cmpint(added_read_task->fd, >, 2); - g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd)); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - struct inotify_event *ievent = malloc(ievent_size); - g_assert_nonnull(ievent); - - ssize_t read_size = 0; - read_size = read(added_read_task->fd, ievent, ievent_size); - - g_assert_cmpint((int)read_size, >, 0); - g_assert_true(ievent->mask & IN_DELETE); - g_assert_cmpstr(ievent->name, ==, basename(tempfile)); - - free(ievent); - - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static -void test_add_inotify_dir_watch_IN_EXCL_UNLINK(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - - __attribute__((cleanup(cleanup_string))) - char *tempfile = make_temporary_file_in_directory(tempdir); - g_assert_nonnull(tempfile); - int tempfile_fd = open(tempfile, O_WRONLY | O_CLOEXEC | O_NOCTTY - | O_NOFOLLOW); - g_assert_cmpint(tempfile_fd, >, 2); - - g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now, - &password, tempdir, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read)); - g_assert_cmpint(unlink(tempfile), ==, 0); - - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - const task_context *const added_read_task - = find_matching_task(queue, - (task_context){ .func=read_inotify_event }); - g_assert_nonnull(added_read_task); - - g_assert_cmpint(added_read_task->fd, >, 2); - g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd)); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - struct inotify_event *ievent = malloc(ievent_size); - g_assert_nonnull(ievent); - - ssize_t read_size = 0; - read_size = read(added_read_task->fd, ievent, ievent_size); - - g_assert_cmpint((int)read_size, >, 0); - g_assert_true(ievent->mask & IN_DELETE); - g_assert_cmpstr(ievent->name, ==, basename(tempfile)); - - g_assert_cmpint(close(tempfile_fd), ==, 0); - - /* IN_EXCL_UNLINK should make the closing of the previously unlinked - file not appear as an ievent, so we should not see it now. */ - read_size = read(added_read_task->fd, ievent, ievent_size); - g_assert_cmpint((int)read_size, ==, -1); - g_assert_true(errno == EAGAIN); - - free(ievent); - - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static void test_read_inotify_event_readerror(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - const mono_microsecs current_time = 0; - - /* Reading /proc/self/mem from offset 0 will always result in EIO */ - const int fd = open("/proc/self/mem", - O_RDONLY | O_CLOEXEC | O_NOCTTY); - - bool quit_now = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=fd, - .quit_now=&quit_now, - .filename=strdup("/nonexistent"), - .cancelled_filenames = &(string_set){}, - .notafter=0, - .current_time=¤t_time, - }; - g_assert_nonnull(task.filename); - run_task_with_stderr_to_dev_null(task, queue); - g_assert_true(quit_now); - g_assert_true(queue->next_run == 0); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_true(epoll_set_does_not_contain(epoll_fd, fd)); - - g_assert_cmpint(close(fd), ==, -1); -} - -static void test_read_inotify_event_bad_epoll(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - const mono_microsecs current_time = 17; - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - const int epoll_fd = pipefds[0]; /* This will obviously fail */ - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=strdup("/nonexistent"), - .cancelled_filenames = &(string_set){}, - .notafter=0, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - g_assert_nonnull(task.filename); - run_task_with_stderr_to_dev_null(task, queue); - - g_assert_nonnull(find_matching_task(queue, task)); - g_assert_true(queue->next_run == 1000000 + current_time); - - g_assert_cmpint(close(pipefds[0]), ==, 0); - g_assert_cmpint(close(pipefds[1]), ==, 0); -} - -static void test_read_inotify_event_nodata(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - const mono_microsecs current_time = 0; - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=strdup("/nonexistent"), - .cancelled_filenames = &(string_set){}, - .notafter=0, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - g_assert_nonnull(task.filename); - task.func(task, queue); - g_assert_false(quit_now); - g_assert_true(queue->next_run == 0); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=task.filename, - .cancelled_filenames=task.cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); - - g_assert_cmpint(close(pipefds[1]), ==, 0); -} - -static void test_read_inotify_event_eof(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - const mono_microsecs current_time = 0; - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - g_assert_cmpint(close(pipefds[1]), ==, 0); - - bool quit_now = false; - buffer password = {}; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=strdup("/nonexistent"), - .cancelled_filenames = &(string_set){}, - .notafter=0, - .current_time=¤t_time, - }; - run_task_with_stderr_to_dev_null(task, queue); - g_assert_true(quit_now); - g_assert_true(queue->next_run == 0); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_true(epoll_set_does_not_contain(epoll_fd, pipefds[0])); - - g_assert_cmpint(close(pipefds[0]), ==, -1); -} - -static -void test_read_inotify_event_IN_CLOSE_WRITE(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - const mono_microsecs current_time = 0; - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_max_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - g_assert_cmpint(ievent_max_size, <=, PIPE_BUF); - struct { - struct inotify_event event; - char name_buffer[NAME_MAX + 1]; - } ievent_buffer; - struct inotify_event *const ievent = &ievent_buffer.event; - - const char dummy_file_name[] = "ask.dummy_file_name"; - ievent->mask = IN_CLOSE_WRITE; - ievent->len = sizeof(dummy_file_name); - memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name)); - const size_t ievent_size = (sizeof(struct inotify_event) - + sizeof(dummy_file_name)); - g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size), - ==, ievent_size); - g_assert_cmpint(close(pipefds[1]), ==, 0); - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=strdup("/nonexistent"), - .cancelled_filenames = &(string_set){}, - .notafter=0, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - task.func(task, queue); - g_assert_false(quit_now); - g_assert_true(queue->next_run != 0); - g_assert_cmpuint((unsigned int)queue->length, >=, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=task.filename, - .cancelled_filenames=task.cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); - - g_assert_cmpuint((unsigned int)queue->length, >=, 2); - - __attribute__((cleanup(cleanup_string))) - char *filename = NULL; - g_assert_cmpint(asprintf(&filename, "%s/%s", task.filename, - dummy_file_name), >, 0); - g_assert_nonnull(filename); - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=open_and_parse_question, - .epoll_fd=epoll_fd, - .filename=filename, - .question_filename=filename, - .password=&password, - .cancelled_filenames=task.cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); -} - -static -void test_read_inotify_event_IN_MOVED_TO(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - const mono_microsecs current_time = 0; - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_max_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - g_assert_cmpint(ievent_max_size, <=, PIPE_BUF); - struct { - struct inotify_event event; - char name_buffer[NAME_MAX + 1]; - } ievent_buffer; - struct inotify_event *const ievent = &ievent_buffer.event; - - const char dummy_file_name[] = "ask.dummy_file_name"; - ievent->mask = IN_MOVED_TO; - ievent->len = sizeof(dummy_file_name); - memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name)); - const size_t ievent_size = (sizeof(struct inotify_event) - + sizeof(dummy_file_name)); - g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size), - ==, ievent_size); - g_assert_cmpint(close(pipefds[1]), ==, 0); - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=strdup("/nonexistent"), - .cancelled_filenames = &(string_set){}, - .notafter=0, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - task.func(task, queue); - g_assert_false(quit_now); - g_assert_true(queue->next_run != 0); - g_assert_cmpuint((unsigned int)queue->length, >=, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=task.filename, - .cancelled_filenames=task.cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); - - g_assert_cmpuint((unsigned int)queue->length, >=, 2); - - __attribute__((cleanup(cleanup_string))) - char *filename = NULL; - g_assert_cmpint(asprintf(&filename, "%s/%s", task.filename, - dummy_file_name), >, 0); - g_assert_nonnull(filename); - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=open_and_parse_question, - .epoll_fd=epoll_fd, - .filename=filename, - .question_filename=filename, - .password=&password, - .cancelled_filenames=task.cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); -} - -static -void test_read_inotify_event_IN_MOVED_FROM(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_max_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - g_assert_cmpint(ievent_max_size, <=, PIPE_BUF); - struct { - struct inotify_event event; - char name_buffer[NAME_MAX + 1]; - } ievent_buffer; - struct inotify_event *const ievent = &ievent_buffer.event; - - const char dummy_file_name[] = "ask.dummy_file_name"; - ievent->mask = IN_MOVED_FROM; - ievent->len = sizeof(dummy_file_name); - memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name)); - const size_t ievent_size = (sizeof(struct inotify_event) - + sizeof(dummy_file_name)); - g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size), - ==, ievent_size); - g_assert_cmpint(close(pipefds[1]), ==, 0); - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=strdup("/nonexistent"), - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - task.func(task, queue); - g_assert_false(quit_now); - g_assert_true(queue->next_run == 0); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=task.filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); - - __attribute__((cleanup(cleanup_string))) - char *filename = NULL; - g_assert_cmpint(asprintf(&filename, "%s/%s", task.filename, - dummy_file_name), >, 0); - g_assert_nonnull(filename); - g_assert_true(string_set_contains(*task.cancelled_filenames, - filename)); -} - -static void test_read_inotify_event_IN_DELETE(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_max_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - g_assert_cmpint(ievent_max_size, <=, PIPE_BUF); - struct { - struct inotify_event event; - char name_buffer[NAME_MAX + 1]; - } ievent_buffer; - struct inotify_event *const ievent = &ievent_buffer.event; - - const char dummy_file_name[] = "ask.dummy_file_name"; - ievent->mask = IN_DELETE; - ievent->len = sizeof(dummy_file_name); - memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name)); - const size_t ievent_size = (sizeof(struct inotify_event) - + sizeof(dummy_file_name)); - g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size), - ==, ievent_size); - g_assert_cmpint(close(pipefds[1]), ==, 0); - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=strdup("/nonexistent"), - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - task.func(task, queue); - g_assert_false(quit_now); - g_assert_true(queue->next_run == 0); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=task.filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); - - __attribute__((cleanup(cleanup_string))) - char *filename = NULL; - g_assert_cmpint(asprintf(&filename, "%s/%s", task.filename, - dummy_file_name), >, 0); - g_assert_nonnull(filename); - g_assert_true(string_set_contains(*task.cancelled_filenames, - filename)); -} - -static void -test_read_inotify_event_IN_CLOSE_WRITE_badname(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - const mono_microsecs current_time = 0; - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_max_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - g_assert_cmpint(ievent_max_size, <=, PIPE_BUF); - struct { - struct inotify_event event; - char name_buffer[NAME_MAX + 1]; - } ievent_buffer; - struct inotify_event *const ievent = &ievent_buffer.event; - - const char dummy_file_name[] = "ignored.dummy_file_name"; - ievent->mask = IN_CLOSE_WRITE; - ievent->len = sizeof(dummy_file_name); - memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name)); - const size_t ievent_size = (sizeof(struct inotify_event) - + sizeof(dummy_file_name)); - g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size), - ==, ievent_size); - g_assert_cmpint(close(pipefds[1]), ==, 0); - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=strdup("/nonexistent"), - .cancelled_filenames = &(string_set){}, - .notafter=0, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - task.func(task, queue); - g_assert_false(quit_now); - g_assert_true(queue->next_run == 0); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=task.filename, - .cancelled_filenames=task.cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); -} - -static void -test_read_inotify_event_IN_MOVED_TO_badname(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - const mono_microsecs current_time = 0; - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_max_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - g_assert_cmpint(ievent_max_size, <=, PIPE_BUF); - struct { - struct inotify_event event; - char name_buffer[NAME_MAX + 1]; - } ievent_buffer; - struct inotify_event *const ievent = &ievent_buffer.event; - - const char dummy_file_name[] = "ignored.dummy_file_name"; - ievent->mask = IN_MOVED_TO; - ievent->len = sizeof(dummy_file_name); - memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name)); - const size_t ievent_size = (sizeof(struct inotify_event) - + sizeof(dummy_file_name)); - g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size), - ==, ievent_size); - g_assert_cmpint(close(pipefds[1]), ==, 0); - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=strdup("/nonexistent"), - .cancelled_filenames = &(string_set){}, - .notafter=0, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - task.func(task, queue); - g_assert_false(quit_now); - g_assert_true(queue->next_run == 0); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=task.filename, - .cancelled_filenames=task.cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); -} - -static void -test_read_inotify_event_IN_MOVED_FROM_badname(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_max_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - g_assert_cmpint(ievent_max_size, <=, PIPE_BUF); - struct { - struct inotify_event event; - char name_buffer[NAME_MAX + 1]; - } ievent_buffer; - struct inotify_event *const ievent = &ievent_buffer.event; - - const char dummy_file_name[] = "ignored.dummy_file_name"; - ievent->mask = IN_MOVED_FROM; - ievent->len = sizeof(dummy_file_name); - memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name)); - const size_t ievent_size = (sizeof(struct inotify_event) - + sizeof(dummy_file_name)); - g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size), - ==, ievent_size); - g_assert_cmpint(close(pipefds[1]), ==, 0); - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=strdup("/nonexistent"), - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - task.func(task, queue); - g_assert_false(quit_now); - g_assert_true(queue->next_run == 0); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=task.filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); - - __attribute__((cleanup(cleanup_string))) - char *filename = NULL; - g_assert_cmpint(asprintf(&filename, "%s/%s", task.filename, - dummy_file_name), >, 0); - g_assert_nonnull(filename); - g_assert_false(string_set_contains(cancelled_filenames, filename)); -} - -static -void test_read_inotify_event_IN_DELETE_badname(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_max_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - g_assert_cmpint(ievent_max_size, <=, PIPE_BUF); - struct { - struct inotify_event event; - char name_buffer[NAME_MAX + 1]; - } ievent_buffer; - struct inotify_event *const ievent = &ievent_buffer.event; - - const char dummy_file_name[] = "ignored.dummy_file_name"; - ievent->mask = IN_DELETE; - ievent->len = sizeof(dummy_file_name); - memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name)); - const size_t ievent_size = (sizeof(struct inotify_event) - + sizeof(dummy_file_name)); - g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size), - ==, ievent_size); - g_assert_cmpint(close(pipefds[1]), ==, 0); - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=strdup("/nonexistent"), - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - task.func(task, queue); - g_assert_false(quit_now); - g_assert_true(queue->next_run == 0); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=task.filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); - - __attribute__((cleanup(cleanup_string))) - char *filename = NULL; - g_assert_cmpint(asprintf(&filename, "%s/%s", task.filename, - dummy_file_name), >, 0); - g_assert_nonnull(filename); - g_assert_false(string_set_contains(cancelled_filenames, filename)); -} - -static -void test_open_and_parse_question_ENOENT(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - char *const filename = strdup("/nonexistent"); - g_assert_nonnull(filename); - task_context task = { - .func=open_and_parse_question, - .question_filename=filename, - .epoll_fd=epoll_fd, - .password=(buffer[]){{}}, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=(mono_microsecs[]){0}, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - task.func(task, queue); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); -} - -static void test_open_and_parse_question_EIO(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - const mono_microsecs current_time = 0; - - char *filename = strdup("/proc/self/mem"); - task_context task = { - .func=open_and_parse_question, - .question_filename=filename, - .epoll_fd=epoll_fd, - .password=&password, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - run_task_with_stderr_to_dev_null(task, queue); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); -} - -static void -test_open_and_parse_question_parse_error(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - __attribute__((cleanup(cleanup_string))) - char *tempfilename = strdup("/tmp/mandosXXXXXX"); - g_assert_nonnull(tempfilename); - int tempfile = mkostemp(tempfilename, O_CLOEXEC); - g_assert_cmpint(tempfile, >, 0); - const char bad_data[] = "this is bad syntax\n"; - g_assert_cmpint(write(tempfile, bad_data, sizeof(bad_data)), - ==, sizeof(bad_data)); - g_assert_cmpint(close(tempfile), ==, 0); - - char *const filename = strdup(tempfilename); - g_assert_nonnull(filename); - task_context task = { - .func=open_and_parse_question, - .question_filename=filename, - .epoll_fd=epoll_fd, - .password=(buffer[]){{}}, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=(mono_microsecs[]){0}, - .mandos_client_exited=(bool[]){false}, - .password_is_read=(bool[]){false}, - }; - run_task_with_stderr_to_dev_null(task, queue); - - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_cmpint(unlink(tempfilename), ==, 0); -} - -static -void test_open_and_parse_question_nosocket(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - __attribute__((cleanup(cleanup_string))) - char *tempfilename = strdup("/tmp/mandosXXXXXX"); - g_assert_nonnull(tempfilename); - int questionfile = mkostemp(tempfilename, O_CLOEXEC); - g_assert_cmpint(questionfile, >, 0); - FILE *qf = fdopen(questionfile, "w"); - g_assert_cmpint(fprintf(qf, "[Ask]\nPID=1\n"), >, 0); - g_assert_cmpint(fclose(qf), ==, 0); - - char *const filename = strdup(tempfilename); - g_assert_nonnull(filename); - task_context task = { - .func=open_and_parse_question, - .question_filename=filename, - .epoll_fd=epoll_fd, - .password=(buffer[]){{}}, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=(mono_microsecs[]){0}, - .mandos_client_exited=(bool[]){false}, - .password_is_read=(bool[]){false}, - }; - run_task_with_stderr_to_dev_null(task, queue); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_cmpint(unlink(tempfilename), ==, 0); -} - -static -void test_open_and_parse_question_badsocket(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - __attribute__((cleanup(cleanup_string))) - char *tempfilename = strdup("/tmp/mandosXXXXXX"); - g_assert_nonnull(tempfilename); - int questionfile = mkostemp(tempfilename, O_CLOEXEC); - g_assert_cmpint(questionfile, >, 0); - FILE *qf = fdopen(questionfile, "w"); - g_assert_cmpint(fprintf(qf, "[Ask]\nSocket=\nPID=1\n"), >, 0); - g_assert_cmpint(fclose(qf), ==, 0); - - char *const filename = strdup(tempfilename); - g_assert_nonnull(filename); - task_context task = { - .func=open_and_parse_question, - .question_filename=filename, - .epoll_fd=epoll_fd, - .password=(buffer[]){{}}, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=(mono_microsecs[]){0}, - .mandos_client_exited=(bool[]){false}, - .password_is_read=(bool[]){false}, - }; - run_task_with_stderr_to_dev_null(task, queue); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_cmpint(unlink(tempfilename), ==, 0); -} - -static -void test_open_and_parse_question_nopid(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - __attribute__((cleanup(cleanup_string))) - char *tempfilename = strdup("/tmp/mandosXXXXXX"); - g_assert_nonnull(tempfilename); - int questionfile = mkostemp(tempfilename, O_CLOEXEC); - g_assert_cmpint(questionfile, >, 0); - FILE *qf = fdopen(questionfile, "w"); - g_assert_cmpint(fprintf(qf, "[Ask]\nSocket=/nonexistent\n"), >, 0); - g_assert_cmpint(fclose(qf), ==, 0); - - char *const filename = strdup(tempfilename); - g_assert_nonnull(filename); - task_context task = { - .func=open_and_parse_question, - .question_filename=filename, - .epoll_fd=epoll_fd, - .password=(buffer[]){{}}, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=(mono_microsecs[]){0}, - .mandos_client_exited=(bool[]){false}, - .password_is_read=(bool[]){false}, - }; - run_task_with_stderr_to_dev_null(task, queue); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_cmpint(unlink(tempfilename), ==, 0); -} - -static -void test_open_and_parse_question_badpid(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - __attribute__((cleanup(cleanup_string))) - char *tempfilename = strdup("/tmp/mandosXXXXXX"); - g_assert_nonnull(tempfilename); - int questionfile = mkostemp(tempfilename, O_CLOEXEC); - g_assert_cmpint(questionfile, >, 0); - FILE *qf = fdopen(questionfile, "w"); - g_assert_cmpint(fprintf(qf, "[Ask]\nSocket=/nonexistent\nPID=\n"), - >, 0); - g_assert_cmpint(fclose(qf), ==, 0); - - char *const filename = strdup(tempfilename); - g_assert_nonnull(filename); - task_context task = { - .func=open_and_parse_question, - .question_filename=filename, - .epoll_fd=epoll_fd, - .password=(buffer[]){{}}, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=(mono_microsecs[]){0}, - .mandos_client_exited=(bool[]){false}, - .password_is_read=(bool[]){false}, - }; - run_task_with_stderr_to_dev_null(task, queue); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_cmpint(unlink(tempfilename), ==, 0); -} - -static void -test_open_and_parse_question_noexist_pid(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - const mono_microsecs current_time = 0; - - /* Find value of sysctl kernel.pid_max */ - uintmax_t pid_max = 0; - FILE *sysctl_pid_max = fopen("/proc/sys/kernel/pid_max", "r"); - g_assert_nonnull(sysctl_pid_max); - g_assert_cmpint(fscanf(sysctl_pid_max, "%" PRIuMAX, &pid_max), - ==, 1); - g_assert_cmpint(fclose(sysctl_pid_max), ==, 0); - - pid_t nonexisting_pid = ((pid_t)pid_max)+1; - g_assert_true(nonexisting_pid > 0); /* Overflow check */ - - __attribute__((cleanup(cleanup_string))) - char *tempfilename = strdup("/tmp/mandosXXXXXX"); - g_assert_nonnull(tempfilename); - int questionfile = mkostemp(tempfilename, O_CLOEXEC); - g_assert_cmpint(questionfile, >, 0); - FILE *qf = fdopen(questionfile, "w"); - g_assert_cmpint(fprintf(qf, "[Ask]\nSocket=/nonexistent\nPID=%" - PRIuMAX"\n", (uintmax_t)nonexisting_pid), - >, 0); - g_assert_cmpint(fclose(qf), ==, 0); - - char *const question_filename = strdup(tempfilename); - g_assert_nonnull(question_filename); - task_context task = { - .func=open_and_parse_question, - .question_filename=question_filename, - .epoll_fd=epoll_fd, - .password=&password, - .filename=question_filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - run_task_with_stderr_to_dev_null(task, queue); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_cmpint(unlink(tempfilename), ==, 0); -} - -static void -test_open_and_parse_question_no_notafter(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - const mono_microsecs current_time = 0; - - __attribute__((cleanup(cleanup_string))) - char *tempfilename = strdup("/tmp/mandosXXXXXX"); - g_assert_nonnull(tempfilename); - int questionfile = mkostemp(tempfilename, O_CLOEXEC); - g_assert_cmpint(questionfile, >, 0); - FILE *qf = fdopen(questionfile, "w"); - g_assert_cmpint(fprintf(qf, "[Ask]\nSocket=/nonexistent\nPID=%" - PRIuMAX "\n", (uintmax_t)getpid()), >, 0); - g_assert_cmpint(fclose(qf), ==, 0); - - char *const filename = strdup(tempfilename); - g_assert_nonnull(filename); - task_context task = { - .func=open_and_parse_question, - .question_filename=filename, - .epoll_fd=epoll_fd, - .password=&password, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - task.func(task, queue); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - __attribute__((cleanup(cleanup_string))) - char *socket_filename = strdup("/nonexistent"); - g_assert_nonnull(socket_filename); - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=connect_question_socket, - .question_filename=tempfilename, - .filename=socket_filename, - .epoll_fd=epoll_fd, - .password=&password, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(queue->next_run != 0); - - g_assert_cmpint(unlink(tempfilename), ==, 0); -} - -static void -test_open_and_parse_question_bad_notafter(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - const mono_microsecs current_time = 0; - - __attribute__((cleanup(cleanup_string))) - char *tempfilename = strdup("/tmp/mandosXXXXXX"); - g_assert_nonnull(tempfilename); - int questionfile = mkostemp(tempfilename, O_CLOEXEC); - g_assert_cmpint(questionfile, >, 0); - FILE *qf = fdopen(questionfile, "w"); - g_assert_cmpint(fprintf(qf, "[Ask]\nSocket=/nonexistent\nPID=%" - PRIuMAX "\nNotAfter=\n", - (uintmax_t)getpid()), >, 0); - g_assert_cmpint(fclose(qf), ==, 0); - - char *const filename = strdup(tempfilename); - g_assert_nonnull(filename); - task_context task = { - .func=open_and_parse_question, - .question_filename=filename, - .epoll_fd=epoll_fd, - .password=&password, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - run_task_with_stderr_to_dev_null(task, queue); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - __attribute__((cleanup(cleanup_string))) - char *socket_filename = strdup("/nonexistent"); - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=connect_question_socket, - .question_filename=tempfilename, - .filename=socket_filename, - .epoll_fd=epoll_fd, - .password=&password, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - g_assert_true(queue->next_run != 0); - - g_assert_cmpint(unlink(tempfilename), ==, 0); -} - -static -void assert_open_and_parse_question_with_notafter(const mono_microsecs - current_time, - const mono_microsecs - notafter, - const mono_microsecs - next_queue_run){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - queue->next_run = next_queue_run; - - __attribute__((cleanup(cleanup_string))) - char *tempfilename = strdup("/tmp/mandosXXXXXX"); - g_assert_nonnull(tempfilename); - int questionfile = mkostemp(tempfilename, O_CLOEXEC); - g_assert_cmpint(questionfile, >, 0); - FILE *qf = fdopen(questionfile, "w"); - g_assert_cmpint(fprintf(qf, "[Ask]\nSocket=/nonexistent\nPID=%" - PRIuMAX "\nNotAfter=%" PRIuMAX "\n", - (uintmax_t)getpid(), notafter), >, 0); - g_assert_cmpint(fclose(qf), ==, 0); - - char *const filename = strdup(tempfilename); - g_assert_nonnull(filename); - task_context task = { - .func=open_and_parse_question, - .question_filename=filename, - .epoll_fd=epoll_fd, - .password=&password, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - task.func(task, queue); - - if(queue->length >= 1){ - __attribute__((cleanup(cleanup_string))) - char *socket_filename = strdup("/nonexistent"); - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=connect_question_socket, - .filename=socket_filename, - .epoll_fd=epoll_fd, - .password=&password, - .current_time=¤t_time, - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - g_assert_true(queue->next_run != 0); - } - - if(notafter == 0){ - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - } else if(current_time >= notafter) { - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - } else { - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=cancel_old_question, - .question_filename=tempfilename, - .filename=tempfilename, - .notafter=notafter, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - })); - } - g_assert_true(queue->next_run == 1); - - g_assert_cmpint(unlink(tempfilename), ==, 0); -} - -static void -test_open_and_parse_question_notafter_0(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* current_time, notafter, next_queue_run */ - assert_open_and_parse_question_with_notafter(0, 0, 0); -} - -static void -test_open_and_parse_question_notafter_1(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* current_time, notafter, next_queue_run */ - assert_open_and_parse_question_with_notafter(0, 1, 0); -} - -static void -test_open_and_parse_question_notafter_1_1(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* current_time, notafter, next_queue_run */ - assert_open_and_parse_question_with_notafter(0, 1, 1); -} - -static void -test_open_and_parse_question_notafter_1_2(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* current_time, notafter, next_queue_run */ - assert_open_and_parse_question_with_notafter(0, 1, 2); -} - -static void -test_open_and_parse_question_equal_notafter(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* current_time, notafter, next_queue_run */ - assert_open_and_parse_question_with_notafter(1, 1, 0); -} - -static void -test_open_and_parse_question_late_notafter(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* current_time, notafter, next_queue_run */ - assert_open_and_parse_question_with_notafter(2, 1, 0); -} - -static void assert_cancel_old_question_param(const mono_microsecs - next_queue_run, - const mono_microsecs - notafter, - const mono_microsecs - current_time, - const mono_microsecs - next_set_to){ - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - queue->next_run = next_queue_run; - - char *const question_filename = strdup("/nonexistent"); - g_assert_nonnull(question_filename); - task_context task = { - .func=cancel_old_question, - .question_filename=question_filename, - .filename=question_filename, - .notafter=notafter, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - }; - task.func(task, queue); - - if(current_time >= notafter){ - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - g_assert_true(string_set_contains(cancelled_filenames, - "/nonexistent")); - } else { - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=cancel_old_question, - .question_filename=question_filename, - .filename=question_filename, - .notafter=notafter, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - })); - - g_assert_false(string_set_contains(cancelled_filenames, - question_filename)); - } - g_assert_cmpuint((unsigned int)queue->next_run, ==, - (unsigned int)next_set_to); -} - -static void test_cancel_old_question_0_1_2(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* next_queue_run unset, - cancellation should happen because time has come, - next_queue_run should be unchanged */ - /* next_queue_run, notafter, current_time, next_set_to */ - assert_cancel_old_question_param(0, 1, 2, 0); -} - -static void test_cancel_old_question_0_2_1(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* If next_queue_run is 0, meaning unset, and notafter is 2, - and current_time is not yet notafter or greater, - update value of next_queue_run to value of notafter */ - /* next_queue_run, notafter, current_time, next_set_to */ - assert_cancel_old_question_param(0, 2, 1, 2); -} - -static void test_cancel_old_question_1_2_3(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* next_queue_run 1, - cancellation should happen because time has come, - next_queue_run should be unchanged */ - /* next_queue_run, notafter, current_time, next_set_to */ - assert_cancel_old_question_param(1, 2, 3, 1); -} - -static void test_cancel_old_question_1_3_2(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* If next_queue_run is set, - and current_time is not yet notafter or greater, - and notafter is larger than next_queue_run - next_queue_run should be unchanged */ - /* next_queue_run, notafter, current_time, next_set_to */ - assert_cancel_old_question_param(1, 3, 2, 1); -} - -static void test_cancel_old_question_2_1_3(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* next_queue_run 2, - cancellation should happen because time has come, - next_queue_run should be unchanged */ - /* next_queue_run, notafter, current_time, next_set_to */ - assert_cancel_old_question_param(2, 1, 3, 2); -} - -static void test_cancel_old_question_2_3_1(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* If next_queue_run is set, - and current_time is not yet notafter or greater, - and notafter is larger than next_queue_run - next_queue_run should be unchanged */ - /* next_queue_run, notafter, current_time, next_set_to */ - assert_cancel_old_question_param(2, 3, 1, 2); -} - -static void test_cancel_old_question_3_1_2(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* next_queue_run 3, - cancellation should happen because time has come, - next_queue_run should be unchanged */ - /* next_queue_run, notafter, current_time, next_set_to */ - assert_cancel_old_question_param(3, 1, 2, 3); -} - -static void test_cancel_old_question_3_2_1(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* If next_queue_run is set, - and current_time is not yet notafter or greater, - and notafter is smaller than next_queue_run - update value of next_queue_run to value of notafter */ - /* next_queue_run, notafter, current_time, next_set_to */ - assert_cancel_old_question_param(3, 2, 1, 2); -} - -static void -test_connect_question_socket_name_too_long(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - const char question_filename[] = "/nonexistent/question"; - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - struct sockaddr_un unix_socket = { .sun_family=AF_LOCAL }; - char socket_name[sizeof(unix_socket.sun_path)]; - memset(socket_name, 'x', sizeof(socket_name)); - socket_name[sizeof(socket_name)-1] = '\0'; - char *filename = NULL; - g_assert_cmpint(asprintf(&filename, "%s/%s", tempdir, socket_name), - >, 0); - g_assert_nonnull(filename); - - task_context task = { - .func=connect_question_socket, - .question_filename=strdup(question_filename), - .epoll_fd=epoll_fd, - .password=(buffer[]){{}}, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=(bool[]){false}, - .password_is_read=(bool[]){false}, - .current_time=(mono_microsecs[]){0}, - }; - g_assert_nonnull(task.question_filename); - run_task_with_stderr_to_dev_null(task, queue); - - g_assert_true(string_set_contains(cancelled_filenames, - question_filename)); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - g_assert_true(queue->next_run == 0); - - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static -void test_connect_question_socket_connect_fail(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - const char question_filename[] = "/nonexistent/question"; - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 3; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - char socket_name[] = "nonexistent"; - char *filename = NULL; - g_assert_cmpint(asprintf(&filename, "%s/%s", tempdir, socket_name), - >, 0); - g_assert_nonnull(filename); - - task_context task = { - .func=connect_question_socket, - .question_filename=strdup(question_filename), - .epoll_fd=epoll_fd, - .password=(buffer[]){{}}, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=(bool[]){false}, - .password_is_read=(bool[]){false}, - .current_time=¤t_time, - }; - g_assert_nonnull(task.question_filename); - run_task_with_stderr_to_dev_null(task, queue); - - g_assert_nonnull(find_matching_task(queue, task)); - - g_assert_false(string_set_contains(cancelled_filenames, - question_filename)); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - g_assert_true(queue->next_run == 1000000 + current_time); - - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static -void test_connect_question_socket_bad_epoll(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = open("/dev/null", - O_WRONLY | O_CLOEXEC | O_NOCTTY); - __attribute__((cleanup(cleanup_string))) - char *const question_filename = strdup("/nonexistent/question"); - g_assert_nonnull(question_filename); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 5; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - __attribute__((cleanup(cleanup_close))) - const int sock_fd = socket(PF_LOCAL, SOCK_DGRAM - | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); - g_assert_cmpint(sock_fd, >=, 0); - struct sockaddr_un sock_name = { .sun_family=AF_LOCAL }; - const char socket_name[] = "socket_name"; - __attribute__((cleanup(cleanup_string))) - char *filename = NULL; - g_assert_cmpint(asprintf(&filename, "%s/%s", tempdir, socket_name), - >, 0); - g_assert_nonnull(filename); - g_assert_cmpint((int)strlen(filename), <, - (int)sizeof(sock_name.sun_path)); - strncpy(sock_name.sun_path, filename, sizeof(sock_name.sun_path)); - sock_name.sun_path[sizeof(sock_name.sun_path)-1] = '\0'; - g_assert_cmpint((int)bind(sock_fd, (struct sockaddr *)&sock_name, - (socklen_t)SUN_LEN(&sock_name)), >=, 0); - task_context task = { - .func=connect_question_socket, - .question_filename=strdup(question_filename), - .epoll_fd=epoll_fd, - .password=(buffer[]){{}}, - .filename=strdup(filename), - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=(bool[]){false}, - .password_is_read=(bool[]){false}, - .current_time=¤t_time, - }; - g_assert_nonnull(task.question_filename); - run_task_with_stderr_to_dev_null(task, queue); - - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - const task_context *const added_task - = find_matching_task(queue, task); - g_assert_nonnull(added_task); - g_assert_true(queue->next_run == 1000000 + current_time); - - g_assert_cmpint(unlink(filename), ==, 0); - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static -void test_connect_question_socket_usable(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_string))) - char *const question_filename = strdup("/nonexistent/question"); - g_assert_nonnull(question_filename); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - const mono_microsecs current_time = 0; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - __attribute__((cleanup(cleanup_close))) - const int sock_fd = socket(PF_LOCAL, SOCK_DGRAM - | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); - g_assert_cmpint(sock_fd, >=, 0); - struct sockaddr_un sock_name = { .sun_family=AF_LOCAL }; - const char socket_name[] = "socket_name"; - __attribute__((cleanup(cleanup_string))) - char *filename = NULL; - g_assert_cmpint(asprintf(&filename, "%s/%s", tempdir, socket_name), - >, 0); - g_assert_nonnull(filename); - g_assert_cmpint((int)strlen(filename), <, - (int)sizeof(sock_name.sun_path)); - strncpy(sock_name.sun_path, filename, sizeof(sock_name.sun_path)); - sock_name.sun_path[sizeof(sock_name.sun_path)-1] = '\0'; - g_assert_cmpint((int)bind(sock_fd, (struct sockaddr *)&sock_name, - (socklen_t)SUN_LEN(&sock_name)), >=, 0); - task_context task = { - .func=connect_question_socket, - .question_filename=strdup(question_filename), - .epoll_fd=epoll_fd, - .password=&password, - .filename=strdup(filename), - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - .current_time=¤t_time, - }; - g_assert_nonnull(task.question_filename); - task.func(task, queue); - - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - const task_context *const added_task - = find_matching_task(queue, (task_context){ - .func=send_password_to_socket, - .question_filename=question_filename, - .filename=filename, - .epoll_fd=epoll_fd, - .password=&password, - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - .current_time=¤t_time, - }); - g_assert_nonnull(added_task); - g_assert_cmpint(added_task->fd, >, 0); - - g_assert_true(epoll_set_contains(epoll_fd, added_task->fd, - EPOLLOUT)); - - const int fd = added_task->fd; - g_assert_cmpint(fd, >, 0); - g_assert_true(fd_has_cloexec_and_nonblock(fd)); - - /* write to fd */ - char write_data[PIPE_BUF]; - { - /* Construct test password buffer */ - /* Start with + since that is what the real protocol uses */ - write_data[0] = '+'; - /* Set a special character at string end just to mark the end */ - write_data[sizeof(write_data)-2] = 'y'; - /* Set NUL at buffer end, as suggested by the protocol */ - write_data[sizeof(write_data)-1] = '\0'; - /* Fill rest of password with 'x' */ - memset(write_data+1, 'x', sizeof(write_data)-3); - g_assert_cmpint((int)send(fd, write_data, sizeof(write_data), - MSG_NOSIGNAL), ==, sizeof(write_data)); - } - - /* read from sock_fd */ - char read_data[sizeof(write_data)]; - g_assert_cmpint((int)read(sock_fd, read_data, sizeof(read_data)), - ==, sizeof(read_data)); - - g_assert_true(memcmp(write_data, read_data, sizeof(write_data)) - == 0); - - /* writing to sock_fd should fail */ - g_assert_cmpint(send(sock_fd, write_data, sizeof(write_data), - MSG_NOSIGNAL), <, 0); - - /* reading from fd should fail */ - g_assert_cmpint((int)recv(fd, read_data, sizeof(read_data), - MSG_NOSIGNAL), <, 0); - - g_assert_cmpint(unlink(filename), ==, 0); - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static void -test_send_password_to_socket_client_not_exited(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_string))) - char *const question_filename = strdup("/nonexistent/question"); - g_assert_nonnull(question_filename); - __attribute__((cleanup(cleanup_string))) - char *const filename = strdup("/nonexistent/socket"); - g_assert_nonnull(filename); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - buffer password = {}; - bool password_is_read = true; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - int socketfds[2]; - g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM - | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, - socketfds), ==, 0); - __attribute__((cleanup(cleanup_close))) - const int read_socket = socketfds[0]; - const int write_socket = socketfds[1]; - task_context task = { - .func=send_password_to_socket, - .question_filename=strdup(question_filename), - .filename=strdup(filename), - .epoll_fd=epoll_fd, - .fd=write_socket, - .password=&password, - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=(bool[]){false}, - .password_is_read=&password_is_read, - .current_time=(mono_microsecs[]){0}, - }; - g_assert_nonnull(task.question_filename); - - task.func(task, queue); - - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - const task_context *const added_task - = find_matching_task(queue, task); - g_assert_nonnull(added_task); - g_assert_cmpuint((unsigned int)password.length, ==, 0); - g_assert_true(password_is_read); - - g_assert_cmpint(added_task->fd, >, 0); - g_assert_true(epoll_set_contains(epoll_fd, added_task->fd, - EPOLLOUT)); -} - -static void -test_send_password_to_socket_password_not_read(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_string))) - char *const question_filename = strdup("/nonexistent/question"); - g_assert_nonnull(question_filename); - __attribute__((cleanup(cleanup_string))) - char *const filename = strdup("/nonexistent/socket"); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - buffer password = {}; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - int socketfds[2]; - g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM - | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, - socketfds), ==, 0); - __attribute__((cleanup(cleanup_close))) - const int read_socket = socketfds[0]; - const int write_socket = socketfds[1]; - task_context task = { - .func=send_password_to_socket, - .question_filename=strdup(question_filename), - .filename=strdup(filename), - .epoll_fd=epoll_fd, - .fd=write_socket, - .password=&password, - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=(bool[]){false}, - .password_is_read=(bool[]){false}, - .current_time=(mono_microsecs[]){0}, - }; - g_assert_nonnull(task.question_filename); - - task.func(task, queue); - - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - const task_context *const added_task = find_matching_task(queue, - task); - g_assert_nonnull(added_task); - g_assert_cmpuint((unsigned int)password.length, ==, 0); - g_assert_true(queue->next_run == 0); - - g_assert_cmpint(added_task->fd, >, 0); - g_assert_true(epoll_set_contains(epoll_fd, added_task->fd, - EPOLLOUT)); -} - -static -void test_send_password_to_socket_EMSGSIZE(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - const char question_filename[] = "/nonexistent/question"; - char *const filename = strdup("/nonexistent/socket"); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - int socketfds[2]; - - /* Find a message size which triggers EMSGSIZE */ - __attribute__((cleanup(cleanup_string))) - char *message_buffer = NULL; - size_t message_size = PIPE_BUF + 1; - for(ssize_t ssret = 0; ssret >= 0; message_size += 1024){ - if(message_size >= 1024*1024*1024){ /* 1 GiB */ - g_test_skip("Skipping EMSGSIZE test: Will not try 1GiB"); - return; - } - message_buffer = realloc(message_buffer, message_size); - if(message_buffer == NULL){ - g_test_skip("Skipping EMSGSIZE test"); - g_test_message("Failed to malloc() %" PRIuMAX " bytes", - (uintmax_t)message_size); - return; - } - /* Fill buffer with 'x' */ - memset(message_buffer, 'x', message_size); - /* Create a new socketpair for each message size to avoid having - to empty the pipe by reading the message to a separate buffer - */ - g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM - | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, - socketfds), ==, 0); - ssret = send(socketfds[1], message_buffer, message_size, - MSG_NOSIGNAL); - error_t saved_errno = errno; - g_assert_cmpint(close(socketfds[0]), ==, 0); - g_assert_cmpint(close(socketfds[1]), ==, 0); - - if(ssret < 0){ - if(saved_errno != EMSGSIZE) { - g_test_skip("Skipping EMSGSIZE test"); - g_test_message("Error on send(%" PRIuMAX " bytes): %s", - (uintmax_t)message_size, - strerror(saved_errno)); - return; - } - break; - } else if(ssret != (ssize_t)message_size){ - g_test_skip("Skipping EMSGSIZE test"); - g_test_message("Partial send(): %" PRIuMAX " of %" PRIdMAX - " bytes", (uintmax_t)ssret, - (intmax_t)message_size); - return; - } - } - g_test_message("EMSGSIZE triggered by %" PRIdMAX " bytes", - (intmax_t)message_size); - - buffer password = { - .data=message_buffer, - .length=message_size - 2, /* Compensate for added '+' and NUL */ - .allocated=message_size, - }; - if(mlock(password.data, password.allocated) != 0){ - g_assert_true(errno == EPERM or errno == ENOMEM); - } - - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM - | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, - socketfds), ==, 0); - __attribute__((cleanup(cleanup_close))) - const int read_socket = socketfds[0]; - __attribute__((cleanup(cleanup_close))) - const int write_socket = socketfds[1]; - task_context task = { - .func=send_password_to_socket, - .question_filename=strdup(question_filename), - .filename=filename, - .epoll_fd=epoll_fd, - .fd=write_socket, - .password=&password, - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=(bool[]){true}, - .password_is_read=(bool[]){true}, - .current_time=(mono_microsecs[]){0}, - }; - g_assert_nonnull(task.question_filename); - - run_task_with_stderr_to_dev_null(task, queue); - - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - g_assert_true(string_set_contains(cancelled_filenames, - question_filename)); -} - -static void test_send_password_to_socket_retry(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_string))) - char *const question_filename = strdup("/nonexistent/question"); - g_assert_nonnull(question_filename); - __attribute__((cleanup(cleanup_string))) - char *const filename = strdup("/nonexistent/socket"); - g_assert_nonnull(filename); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - int socketfds[2]; - g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM - | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, - socketfds), ==, 0); - __attribute__((cleanup(cleanup_close))) - const int read_socket = socketfds[0]; - const int write_socket = socketfds[1]; - /* Close the server side socket to force ECONNRESET on client */ - g_assert_cmpint(close(read_socket), ==, 0); - task_context task = { - .func=send_password_to_socket, - .question_filename=strdup(question_filename), - .filename=strdup(filename), - .epoll_fd=epoll_fd, - .fd=write_socket, - .password=&password, - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=(bool[]){true}, - .password_is_read=(bool[]){true}, - .current_time=(mono_microsecs[]){0}, - }; - g_assert_nonnull(task.question_filename); - - task.func(task, queue); - - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - const task_context *const added_task = find_matching_task(queue, - task); - g_assert_nonnull(added_task); - g_assert_cmpuint((unsigned int)password.length, ==, 0); - - g_assert_true(epoll_set_contains(epoll_fd, added_task->fd, - EPOLLOUT)); -} - -static -void test_send_password_to_socket_bad_epoll(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = open("/dev/null", - O_WRONLY | O_CLOEXEC | O_NOCTTY); - __attribute__((cleanup(cleanup_string))) - char *const question_filename = strdup("/nonexistent/question"); - g_assert_nonnull(question_filename); - __attribute__((cleanup(cleanup_string))) - char *const filename = strdup("/nonexistent/socket"); - g_assert_nonnull(filename); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - - const mono_microsecs current_time = 11; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - int socketfds[2]; - g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM - | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, - socketfds), ==, 0); - __attribute__((cleanup(cleanup_close))) - const int read_socket = socketfds[0]; - const int write_socket = socketfds[1]; - /* Close the server side socket to force ECONNRESET on client */ - g_assert_cmpint(close(read_socket), ==, 0); - task_context task = { - .func=send_password_to_socket, - .question_filename=strdup(question_filename), - .filename=strdup(filename), - .epoll_fd=epoll_fd, - .fd=write_socket, - .password=&password, - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=(bool[]){true}, - .password_is_read=(bool[]){true}, - .current_time=¤t_time, - }; - g_assert_nonnull(task.question_filename); - - run_task_with_stderr_to_dev_null(task, queue); - - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - const task_context *const added_task = find_matching_task(queue, - task); - g_assert_nonnull(added_task); - g_assert_true(queue->next_run == current_time + 1000000); - g_assert_cmpuint((unsigned int)password.length, ==, 0); -} - -static void assert_send_password_to_socket_password(buffer password){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - char *const question_filename = strdup("/nonexistent/question"); - g_assert_nonnull(question_filename); - char *const filename = strdup("/nonexistent/socket"); - g_assert_nonnull(filename); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - int socketfds[2]; - g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM - | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, - socketfds), ==, 0); - __attribute__((cleanup(cleanup_close))) - const int read_socket = socketfds[0]; - const int write_socket = socketfds[1]; - task_context task = { - .func=send_password_to_socket, - .question_filename=question_filename, - .filename=filename, - .epoll_fd=epoll_fd, - .fd=write_socket, - .password=&password, - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=(bool[]){true}, - .password_is_read=(bool[]){true}, - .current_time=(mono_microsecs[]){0}, - }; - - char *expected_written_data = malloc(password.length + 2); - g_assert_nonnull(expected_written_data); - expected_written_data[0] = '+'; - expected_written_data[password.length + 1] = '\0'; - if(password.length > 0){ - g_assert_nonnull(password.data); - memcpy(expected_written_data + 1, password.data, password.length); - } - - task.func(task, queue); - - char buf[PIPE_BUF]; - g_assert_cmpint((int)read(read_socket, buf, PIPE_BUF), ==, - (int)(password.length + 2)); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_true(memcmp(expected_written_data, buf, - password.length + 2) == 0); - - g_assert_true(epoll_set_does_not_contain(epoll_fd, write_socket)); - - free(expected_written_data); -} - -static void -test_send_password_to_socket_null_password(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - assert_send_password_to_socket_password(password); -} - -static void -test_send_password_to_socket_empty_password(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_buffer))) - buffer password = { - .data=malloc(1), /* because malloc(0) may return NULL */ - .length=0, - .allocated=0, /* deliberate lie */ - }; - g_assert_nonnull(password.data); - assert_send_password_to_socket_password(password); -} - -static void -test_send_password_to_socket_empty_str_pass(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_buffer))) - buffer password = { - .data=strdup(""), - .length=0, - .allocated=1, - }; - if(mlock(password.data, password.allocated) != 0){ - g_assert_true(errno == EPERM or errno == ENOMEM); - } - assert_send_password_to_socket_password(password); -} - -static void -test_send_password_to_socket_text_password(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - const char dummy_test_password[] = "dummy test password"; - __attribute__((cleanup(cleanup_buffer))) - buffer password = { - .data = strdup(dummy_test_password), - .length = strlen(dummy_test_password), - .allocated = sizeof(dummy_test_password), - }; - if(mlock(password.data, password.allocated) != 0){ - g_assert_true(errno == EPERM or errno == ENOMEM); - } - assert_send_password_to_socket_password(password); -} - -static void -test_send_password_to_socket_binary_password(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_buffer))) - buffer password = { - .data=malloc(255), - .length=255, - .allocated=255, - }; - g_assert_nonnull(password.data); - if(mlock(password.data, password.allocated) != 0){ - g_assert_true(errno == EPERM or errno == ENOMEM); - } - char c = 1; /* Start at 1, avoiding NUL */ - for(int i=0; i < 255; i++){ - password.data[i] = c++; - } - assert_send_password_to_socket_password(password); -} - -static void -test_send_password_to_socket_nuls_in_password(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - char test_password[] = {'\0', 'a', '\0', 'b', '\0', 'c', '\0'}; - __attribute__((cleanup(cleanup_buffer))) - buffer password = { - .data=malloc(sizeof(test_password)), - .length=sizeof(test_password), - .allocated=sizeof(test_password), - }; - g_assert_nonnull(password.data); - if(mlock(password.data, password.allocated) !=0){ - g_assert_true(errno == EPERM or errno == ENOMEM); - } - memcpy(password.data, test_password, password.allocated); - assert_send_password_to_socket_password(password); -} - -static bool assert_add_existing_questions_to_devnull(task_queue - *const, - const int, - buffer *const, - string_set *, - const - mono_microsecs - *const, - bool *const, - bool *const, - const char - *const); - -static void test_add_existing_questions_ENOENT(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - - g_assert_false(assert_add_existing_questions_to_devnull - (queue, - epoll_fd, - (buffer[]){{}}, /* password */ - &cancelled_filenames, - (mono_microsecs[]){0}, /* current_time */ - (bool[]){false}, /* mandos_client_exited */ - (bool[]){false}, /* password_is_read */ - "/nonexistent")); /* dirname */ - - g_assert_cmpuint((unsigned int)queue->length, ==, 0); -} - -static -bool assert_add_existing_questions_to_devnull(task_queue - *const queue, - const int - epoll_fd, - buffer *const - password, - string_set - *cancelled_filenames, - const mono_microsecs - *const current_time, - bool *const - mandos_client_exited, - bool *const - password_is_read, - const char *const - dirname){ - __attribute__((cleanup(cleanup_close))) - const int devnull_fd = open("/dev/null", - O_WRONLY | O_CLOEXEC | O_NOCTTY); - g_assert_cmpint(devnull_fd, >=, 0); - __attribute__((cleanup(cleanup_close))) - const int real_stderr_fd = dup(STDERR_FILENO); - g_assert_cmpint(real_stderr_fd, >=, 0); - dup2(devnull_fd, STDERR_FILENO); - const bool ret = add_existing_questions(queue, epoll_fd, password, - cancelled_filenames, - current_time, - mandos_client_exited, - password_is_read, dirname); - dup2(real_stderr_fd, STDERR_FILENO); - return ret; -} - -static -void test_add_existing_questions_no_questions(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - - g_assert_false(assert_add_existing_questions_to_devnull - (queue, - epoll_fd, - (buffer[]){{}}, /* password */ - &cancelled_filenames, - (mono_microsecs[]){0}, /* current_time */ - (bool[]){false}, /* mandos_client_exited */ - (bool[]){false}, /* password_is_read */ - tempdir)); - - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static char *make_question_file_in_directory(const char *const); - -static -void test_add_existing_questions_one_question(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - __attribute__((cleanup(cleanup_string))) - char *question_filename - = make_question_file_in_directory(tempdir); - g_assert_nonnull(question_filename); - - g_assert_true(assert_add_existing_questions_to_devnull - (queue, - epoll_fd, - &password, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read, - tempdir)); - - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=open_and_parse_question, - .epoll_fd=epoll_fd, - .filename=question_filename, - .question_filename=question_filename, - .password=&password, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(queue->next_run == 1); - - g_assert_cmpint(unlink(question_filename), ==, 0); - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static char *make_question_file_in_directory(const char - *const dir){ - return make_temporary_prefixed_file_in_directory("ask.", dir); -} - -static -void test_add_existing_questions_two_questions(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - __attribute__((cleanup(cleanup_string))) - char *question_filename1 - = make_question_file_in_directory(tempdir); - g_assert_nonnull(question_filename1); - __attribute__((cleanup(cleanup_string))) - char *question_filename2 - = make_question_file_in_directory(tempdir); - g_assert_nonnull(question_filename2); - - g_assert_true(assert_add_existing_questions_to_devnull - (queue, - epoll_fd, - &password, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read, - tempdir)); - - g_assert_cmpuint((unsigned int)queue->length, ==, 2); - - g_assert_true(queue->next_run == 1); - - __attribute__((cleanup(string_set_clear))) - string_set seen_questions = {}; - - bool queue_contains_question_opener(char *const question_filename){ - return(find_matching_task(queue, (task_context){ - .func=open_and_parse_question, - .epoll_fd=epoll_fd, - .question_filename=question_filename, - .password=&password, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }) != NULL); - } - - g_assert_true(queue_contains_question_opener(question_filename1)); - g_assert_true(queue_contains_question_opener(question_filename2)); - - g_assert_true(queue->next_run == 1); - - g_assert_cmpint(unlink(question_filename1), ==, 0); - g_assert_cmpint(unlink(question_filename2), ==, 0); - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static void -test_add_existing_questions_non_questions(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - __attribute__((cleanup(cleanup_string))) - char *question_filename1 - = make_temporary_file_in_directory(tempdir); - g_assert_nonnull(question_filename1); - __attribute__((cleanup(cleanup_string))) - char *question_filename2 - = make_temporary_file_in_directory(tempdir); - g_assert_nonnull(question_filename2); - - g_assert_false(assert_add_existing_questions_to_devnull - (queue, - epoll_fd, - (buffer[]){{}}, /* password */ - &cancelled_filenames, - (mono_microsecs[]){0}, /* current_time */ - (bool[]){false}, /* mandos_client_exited */ - (bool[]){false}, /* password_is_read */ - tempdir)); - - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_cmpint(unlink(question_filename1), ==, 0); - g_assert_cmpint(unlink(question_filename2), ==, 0); - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static void -test_add_existing_questions_both_types(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - __attribute__((cleanup(cleanup_string))) - char *tempfilename1 = make_temporary_file_in_directory(tempdir); - g_assert_nonnull(tempfilename1); - __attribute__((cleanup(cleanup_string))) - char *tempfilename2 = make_temporary_file_in_directory(tempdir); - g_assert_nonnull(tempfilename2); - __attribute__((cleanup(cleanup_string))) - char *question_filename - = make_question_file_in_directory(tempdir); - g_assert_nonnull(question_filename); - - g_assert_true(assert_add_existing_questions_to_devnull - (queue, - epoll_fd, - &password, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read, - tempdir)); - - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=open_and_parse_question, - .epoll_fd=epoll_fd, - .filename=question_filename, - .question_filename=question_filename, - .password=&password, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(queue->next_run == 1); - - g_assert_cmpint(unlink(tempfilename1), ==, 0); - g_assert_cmpint(unlink(tempfilename2), ==, 0); - g_assert_cmpint(unlink(question_filename), ==, 0); - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static void test_wait_for_event_timeout(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - - g_assert_true(wait_for_event(epoll_fd, 1, 0)); -} - -static void test_wait_for_event_event(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - __attribute__((cleanup(cleanup_close))) - const int read_pipe = pipefds[0]; - __attribute__((cleanup(cleanup_close))) - const int write_pipe = pipefds[1]; - g_assert_cmpint(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, read_pipe, - &(struct epoll_event) - { .events=EPOLLIN | EPOLLRDHUP }), ==, 0); - g_assert_cmpint((int)write(write_pipe, "x", 1), ==, 1); - - g_assert_true(wait_for_event(epoll_fd, 0, 0)); -} - -static void test_wait_for_event_sigchld(test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - const pid_t pid = fork(); - if(pid == 0){ /* Child */ - if(not restore_signal_handler(&fixture->orig_sigaction)){ - _exit(EXIT_FAILURE); - } - if(not restore_sigmask(&fixture->orig_sigmask)){ - _exit(EXIT_FAILURE); - } - exit(EXIT_SUCCESS); - } - g_assert_true(pid != -1); - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - - g_assert_true(wait_for_event(epoll_fd, 0, 0)); - - int status; - g_assert_true(waitpid(pid, &status, 0) == pid); - g_assert_true(WIFEXITED(status)); - g_assert_cmpint(WEXITSTATUS(status), ==, EXIT_SUCCESS); -} - -static void test_run_queue_zeroes_next_run(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - queue->next_run = 1; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - bool quit_now = false; - - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->next_run, ==, 0); -} - -static -void test_run_queue_clears_cancelled_filenames(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - bool quit_now = false; - const char question_filename[] = "/nonexistent/question_filename"; - g_assert_true(string_set_add(&cancelled_filenames, - question_filename)); - - g_assert_true(add_to_queue(queue, - (task_context){ .func=dummy_func })); - - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)(queue->length), ==, 0); - g_assert_false(string_set_contains(cancelled_filenames, - question_filename)); -} - -static -void test_run_queue_skips_cancelled_filenames(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - bool quit_now = false; - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - __attribute__((cleanup(cleanup_close))) - const int read_pipe = pipefds[0]; - g_assert_cmpint(close(pipefds[1]), ==, 0); - const char question_filename[] = "/nonexistent/question_filename"; - g_assert_true(string_set_add(&cancelled_filenames, - question_filename)); - __attribute__((nonnull)) - void quit_func(const task_context task, - __attribute__((unused)) task_queue *const q){ - g_assert_nonnull(task.quit_now); - *task.quit_now = true; - } - task_context task = { - .func=quit_func, - .question_filename=strdup(question_filename), - .quit_now=&quit_now, - .fd=read_pipe, - }; - g_assert_nonnull(task.question_filename); - - g_assert_true(add_to_queue(queue, task)); - - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_false(quit_now); - - /* read_pipe should be closed already */ - errno = 0; - bool read_pipe_closed = (close(read_pipe) == -1); - read_pipe_closed &= (errno == EBADF); - g_assert_true(read_pipe_closed); -} - -static void test_run_queue_one_task(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - bool quit_now = false; - - __attribute__((nonnull)) - void next_run_func(__attribute__((unused)) - const task_context task, - task_queue *const q){ - q->next_run = 1; - } - - task_context task = { - .func=next_run_func, - }; - g_assert_true(add_to_queue(queue, task)); - - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_cmpuint((unsigned int)(queue->next_run), ==, 1); - g_assert_cmpuint((unsigned int)(queue->length), ==, 0); -} - -static void test_run_queue_two_tasks(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - queue->next_run = 1; - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - bool quit_now = false; - bool mandos_client_exited = false; - - __attribute__((nonnull)) - void next_run_func(__attribute__((unused)) - const task_context task, - task_queue *const q){ - q->next_run = 1; - } - - __attribute__((nonnull)) - void exited_func(const task_context task, - __attribute__((unused)) task_queue *const q){ - *task.mandos_client_exited = true; - } - - task_context task1 = { - .func=next_run_func, - }; - g_assert_true(add_to_queue(queue, task1)); - - task_context task2 = { - .func=exited_func, - .mandos_client_exited=&mandos_client_exited, - }; - g_assert_true(add_to_queue(queue, task2)); - - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)(queue->next_run), ==, 1); - g_assert_true(mandos_client_exited); - g_assert_cmpuint((unsigned int)(queue->length), ==, 0); -} - -static void test_run_queue_two_tasks_quit(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - bool quit_now = false; - bool mandos_client_exited = false; - bool password_is_read = false; - - __attribute__((nonnull)) - void set_exited_func(const task_context task, - __attribute__((unused)) task_queue *const q){ - *task.mandos_client_exited = true; - *task.quit_now = true; - } - task_context task1 = { - .func=set_exited_func, - .quit_now=&quit_now, - .mandos_client_exited=&mandos_client_exited, - }; - g_assert_true(add_to_queue(queue, task1)); - - __attribute__((nonnull)) - void set_read_func(const task_context task, - __attribute__((unused)) task_queue *const q){ - *task.quit_now = true; - *task.password_is_read = true; - } - task_context task2 = { - .func=set_read_func, - .quit_now=&quit_now, - .password_is_read=&password_is_read, - }; - g_assert_true(add_to_queue(queue, task2)); - - g_assert_false(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_true(quit_now); - g_assert_true(mandos_client_exited xor password_is_read); - g_assert_cmpuint((unsigned int)(queue->length), ==, 0); -} - -static void test_run_queue_two_tasks_cleanup(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - __attribute__((cleanup(cleanup_close))) - const int read_pipe = pipefds[0]; - __attribute__((cleanup(cleanup_close))) - const int write_pipe = pipefds[1]; - bool quit_now = false; - - __attribute__((nonnull)) - void read_func(const task_context task, - __attribute__((unused)) task_queue *const q){ - *task.quit_now = true; - } - task_context task1 = { - .func=read_func, - .quit_now=&quit_now, - .fd=read_pipe, - }; - g_assert_true(add_to_queue(queue, task1)); - - __attribute__((nonnull)) - void write_func(const task_context task, - __attribute__((unused)) task_queue *const q){ - *task.quit_now = true; - } - task_context task2 = { - .func=write_func, - .quit_now=&quit_now, - .fd=write_pipe, - }; - g_assert_true(add_to_queue(queue, task2)); - - g_assert_false(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_true(quit_now); - - /* Either read_pipe or write_pipe should be closed already */ - errno = 0; - bool close_read_pipe = (close(read_pipe) == -1); - close_read_pipe &= (errno == EBADF); - errno = 0; - bool close_write_pipe = (close(write_pipe) == -1); - close_write_pipe &= (errno == EBADF); - g_assert_true(close_read_pipe xor close_write_pipe); - g_assert_cmpuint((unsigned int)(queue->length), ==, 0); -} - -static void test_setup_signal_handler(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* Save current SIGCHLD action, whatever it is */ - struct sigaction expected_sigchld_action; - g_assert_cmpint(sigaction(SIGCHLD, NULL, &expected_sigchld_action), - ==, 0); - - /* Act; i.e. run the setup_signal_handler() function */ - struct sigaction actual_old_sigchld_action; - g_assert_true(setup_signal_handler(&actual_old_sigchld_action)); - - /* Check that the function correctly set "actual_old_sigchld_action" - to the same values as the previously saved - "expected_sigchld_action" */ - /* Check member sa_handler */ - g_assert_true(actual_old_sigchld_action.sa_handler - == expected_sigchld_action.sa_handler); - /* Check member sa_mask */ - for(int signum = 1; signum < NSIG; signum++){ - const int expected_old_block_state - = sigismember(&expected_sigchld_action.sa_mask, signum); - g_assert_cmpint(expected_old_block_state, >=, 0); - const int actual_old_block_state - = sigismember(&actual_old_sigchld_action.sa_mask, signum); - g_assert_cmpint(actual_old_block_state, >=, 0); - g_assert_cmpint(actual_old_block_state, - ==, expected_old_block_state); - } - /* Check member sa_flags */ - g_assert_true((actual_old_sigchld_action.sa_flags - & (SA_NOCLDSTOP | SA_ONSTACK | SA_RESTART)) - == (expected_sigchld_action.sa_flags - & (SA_NOCLDSTOP | SA_ONSTACK | SA_RESTART))); - - /* Retrieve the current signal handler for SIGCHLD as set by - setup_signal_handler() */ - struct sigaction actual_new_sigchld_action; - g_assert_cmpint(sigaction(SIGCHLD, NULL, - &actual_new_sigchld_action), ==, 0); - /* Check that the signal handler (member sa_handler) is correctly - set to the "handle_sigchld" function */ - g_assert_true(actual_new_sigchld_action.sa_handler != SIG_DFL); - g_assert_true(actual_new_sigchld_action.sa_handler != SIG_IGN); - g_assert_true(actual_new_sigchld_action.sa_handler - == handle_sigchld); - /* Check (in member sa_mask) that at least a handful of signals are - actually blocked during the signal handler */ - for(int signum = 1; signum < NSIG; signum++){ - int actual_new_block_state; - switch(signum){ - case SIGTERM: - case SIGINT: - case SIGQUIT: - case SIGHUP: - actual_new_block_state - = sigismember(&actual_new_sigchld_action.sa_mask, signum); - g_assert_cmpint(actual_new_block_state, ==, 1); - continue; - case SIGKILL: /* non-blockable */ - case SIGSTOP: /* non-blockable */ - case SIGCHLD: /* always blocked */ - default: - continue; - } - } - /* Check member sa_flags */ - g_assert_true((actual_new_sigchld_action.sa_flags - & (SA_NOCLDSTOP | SA_ONSTACK | SA_RESTART)) - == (SA_NOCLDSTOP | SA_RESTART)); - - /* Restore signal handler */ - g_assert_cmpint(sigaction(SIGCHLD, &expected_sigchld_action, NULL), - ==, 0); -} - -static void test_restore_signal_handler(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* Save current SIGCHLD action, whatever it is */ - struct sigaction expected_sigchld_action; - g_assert_cmpint(sigaction(SIGCHLD, NULL, &expected_sigchld_action), - ==, 0); - /* Since we haven't established a signal handler yet, there should - not be one established. But another test may have relied on - restore_signal_handler() to restore the signal handler, and if - restore_signal_handler() is buggy (which we should be prepared - for in this test) the signal handler may not have been restored - properly; check for this: */ - g_assert_true(expected_sigchld_action.sa_handler != handle_sigchld); - - /* Establish a signal handler */ - struct sigaction sigchld_action = { - .sa_handler=handle_sigchld, - .sa_flags=SA_RESTART | SA_NOCLDSTOP, - }; - g_assert_cmpint(sigfillset(&sigchld_action.sa_mask), ==, 0); - g_assert_cmpint(sigaction(SIGCHLD, &sigchld_action, NULL), ==, 0); - - /* Act; i.e. run the restore_signal_handler() function */ - g_assert_true(restore_signal_handler(&expected_sigchld_action)); - - /* Retrieve the restored signal handler data */ - struct sigaction actual_restored_sigchld_action; - g_assert_cmpint(sigaction(SIGCHLD, NULL, - &actual_restored_sigchld_action), ==, 0); - - /* Check that the function correctly restored the signal action, as - saved in "actual_restored_sigchld_action", to the same values as - the previously saved "expected_sigchld_action" */ - /* Check member sa_handler */ - g_assert_true(actual_restored_sigchld_action.sa_handler - == expected_sigchld_action.sa_handler); - /* Check member sa_mask */ - for(int signum = 1; signum < NSIG; signum++){ - const int expected_old_block_state - = sigismember(&expected_sigchld_action.sa_mask, signum); - g_assert_cmpint(expected_old_block_state, >=, 0); - const int actual_restored_block_state - = sigismember(&actual_restored_sigchld_action.sa_mask, signum); - g_assert_cmpint(actual_restored_block_state, >=, 0); - g_assert_cmpint(actual_restored_block_state, - ==, expected_old_block_state); - } - /* Check member sa_flags */ - g_assert_true((actual_restored_sigchld_action.sa_flags - & (SA_NOCLDSTOP | SA_ONSTACK | SA_RESTART)) - == (expected_sigchld_action.sa_flags - & (SA_NOCLDSTOP | SA_ONSTACK | SA_RESTART))); -} - -static void test_block_sigchld(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* Save original signal mask */ - sigset_t expected_sigmask; - g_assert_cmpint(pthread_sigmask(-1, NULL, &expected_sigmask), - ==, 0); - - /* Make sure SIGCHLD is unblocked for this test */ - sigset_t sigchld_sigmask; - g_assert_cmpint(sigemptyset(&sigchld_sigmask), ==, 0); - g_assert_cmpint(sigaddset(&sigchld_sigmask, SIGCHLD), ==, 0); - g_assert_cmpint(pthread_sigmask(SIG_UNBLOCK, &sigchld_sigmask, - NULL), ==, 0); - - /* Act; i.e. run the block_sigchld() function */ - sigset_t actual_old_sigmask; - g_assert_true(block_sigchld(&actual_old_sigmask)); - - /* Check the actual_old_sigmask; it should be the same as the - previously saved signal mask "expected_sigmask". */ - for(int signum = 1; signum < NSIG; signum++){ - const int expected_old_block_state - = sigismember(&expected_sigmask, signum); - g_assert_cmpint(expected_old_block_state, >=, 0); - const int actual_old_block_state - = sigismember(&actual_old_sigmask, signum); - g_assert_cmpint(actual_old_block_state, >=, 0); - g_assert_cmpint(actual_old_block_state, - ==, expected_old_block_state); - } - - /* Retrieve the newly set signal mask */ - sigset_t actual_sigmask; - g_assert_cmpint(pthread_sigmask(-1, NULL, &actual_sigmask), ==, 0); - - /* SIGCHLD should be blocked */ - g_assert_cmpint(sigismember(&actual_sigmask, SIGCHLD), ==, 1); - - /* Restore signal mask */ - g_assert_cmpint(pthread_sigmask(SIG_SETMASK, &expected_sigmask, - NULL), ==, 0); -} - -static void test_restore_sigmask(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* Save original signal mask */ - sigset_t orig_sigmask; - g_assert_cmpint(pthread_sigmask(-1, NULL, &orig_sigmask), ==, 0); - - /* Make sure SIGCHLD is blocked for this test */ - sigset_t sigchld_sigmask; - g_assert_cmpint(sigemptyset(&sigchld_sigmask), ==, 0); - g_assert_cmpint(sigaddset(&sigchld_sigmask, SIGCHLD), ==, 0); - g_assert_cmpint(pthread_sigmask(SIG_BLOCK, &sigchld_sigmask, - NULL), ==, 0); - - /* Act; i.e. run the restore_sigmask() function */ - g_assert_true(restore_sigmask(&orig_sigmask)); - - /* Retrieve the newly restored signal mask */ - sigset_t restored_sigmask; - g_assert_cmpint(pthread_sigmask(-1, NULL, &restored_sigmask), - ==, 0); - - /* Check the restored_sigmask; it should be the same as the - previously saved signal mask "orig_sigmask". */ - for(int signum = 1; signum < NSIG; signum++){ - const int orig_block_state = sigismember(&orig_sigmask, signum); - g_assert_cmpint(orig_block_state, >=, 0); - const int restored_block_state = sigismember(&restored_sigmask, - signum); - g_assert_cmpint(restored_block_state, >=, 0); - g_assert_cmpint(restored_block_state, ==, orig_block_state); - } - - /* Restore signal mask */ - g_assert_cmpint(pthread_sigmask(SIG_SETMASK, &orig_sigmask, - NULL), ==, 0); -} - -static void test_parse_arguments_noargs(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - char *agent_directory = NULL; - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_true(parse_arguments(argc, argv, false, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)); - g_assert_null(agent_directory); - g_assert_null(helper_directory); - g_assert_true(user == 0); - g_assert_true(group == 0); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -__attribute__((nonnull)) -static bool parse_arguments_devnull(int argc, char *argv[], - const bool exit_failure, - char **agent_directory, - char **helper_directory, - uid_t *const user, - gid_t *const group, - char **mandos_argz, - size_t *mandos_argz_length){ - - FILE *real_stderr = stderr; - FILE *devnull = fopen("/dev/null", "we"); - g_assert_nonnull(devnull); - stderr = devnull; - - const bool ret = parse_arguments(argc, argv, exit_failure, - agent_directory, - helper_directory, user, group, - mandos_argz, mandos_argz_length); - const error_t saved_errno = errno; - - stderr = real_stderr; - g_assert_cmpint(fclose(devnull), ==, 0); - - errno = saved_errno; - - return ret; -} - -static void test_parse_arguments_invalid(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("--invalid"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - char *agent_directory = NULL; - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_false(parse_arguments_devnull(argc, argv, false, - &agent_directory, - &helper_directory, &user, - &group, &mandos_argz, - &mandos_argz_length)); - - g_assert_true(errno == EINVAL); - g_assert_null(agent_directory); - g_assert_null(helper_directory); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static void test_parse_arguments_long_dir(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("--agent-directory"), - strdup("/tmp"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - __attribute__((cleanup(cleanup_string))) - char *agent_directory = NULL; - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_true(parse_arguments(argc, argv, false, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)); - - g_assert_cmpstr(agent_directory, ==, "/tmp"); - g_assert_null(helper_directory); - g_assert_true(user == 0); - g_assert_true(group == 0); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static void test_parse_arguments_short_dir(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("-d"), - strdup("/tmp"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - __attribute__((cleanup(cleanup_string))) - char *agent_directory = NULL; - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_true(parse_arguments(argc, argv, false, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)); - - g_assert_cmpstr(agent_directory, ==, "/tmp"); - g_assert_null(helper_directory); - g_assert_true(user == 0); - g_assert_true(group == 0); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static -void test_parse_arguments_helper_directory(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("--helper-directory"), - strdup("/tmp"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_true(parse_arguments(argc, argv, false, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)); - - g_assert_cmpstr(helper_directory, ==, "/tmp"); - g_assert_null(agent_directory); - g_assert_true(user == 0); - g_assert_true(group == 0); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static -void test_parse_arguments_plugin_helper_dir(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("--plugin-helper-dir"), - strdup("/tmp"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_true(parse_arguments(argc, argv, false, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)); - - g_assert_cmpstr(helper_directory, ==, "/tmp"); - g_assert_null(agent_directory); - g_assert_true(user == 0); - g_assert_true(group == 0); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static void test_parse_arguments_user(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("--user"), - strdup("1000"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_true(parse_arguments(argc, argv, false, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)); - - g_assert_null(helper_directory); - g_assert_null(agent_directory); - g_assert_cmpuint((unsigned int)user, ==, 1000); - g_assert_true(group == 0); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static void test_parse_arguments_user_invalid(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - char *argv[] = { - strdup("prgname"), - strdup("--user"), - strdup("invalid"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_false(parse_arguments_devnull(argc, argv, false, - &agent_directory, - &helper_directory, &user, - &group, &mandos_argz, - &mandos_argz_length)); - - g_assert_null(helper_directory); - g_assert_null(agent_directory); - g_assert_cmpuint((unsigned int)user, ==, 0); - g_assert_true(group == 0); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static -void test_parse_arguments_user_zero_invalid(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("--user"), - strdup("0"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_false(parse_arguments_devnull(argc, argv, false, - &agent_directory, - &helper_directory, &user, - &group, &mandos_argz, - &mandos_argz_length)); - - g_assert_null(helper_directory); - g_assert_null(agent_directory); - g_assert_cmpuint((unsigned int)user, ==, 0); - g_assert_true(group == 0); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static void test_parse_arguments_group(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("--group"), - strdup("1000"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_true(parse_arguments(argc, argv, false, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)); - - g_assert_null(helper_directory); - g_assert_null(agent_directory); - g_assert_true(user == 0); - g_assert_cmpuint((unsigned int)group, ==, 1000); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static void test_parse_arguments_group_invalid(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - char *argv[] = { - strdup("prgname"), - strdup("--group"), - strdup("invalid"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_false(parse_arguments_devnull(argc, argv, false, - &agent_directory, - &helper_directory, &user, - &group, &mandos_argz, - &mandos_argz_length)); - - g_assert_null(helper_directory); - g_assert_null(agent_directory); - g_assert_true(user == 0); - g_assert_true(group == 0); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static -void test_parse_arguments_group_zero_invalid(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("--group"), - strdup("0"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_false(parse_arguments_devnull(argc, argv, false, - &agent_directory, - &helper_directory, &user, - &group, &mandos_argz, - &mandos_argz_length)); - - g_assert_null(helper_directory); - g_assert_null(agent_directory); - g_assert_cmpuint((unsigned int)group, ==, 0); - g_assert_true(group == 0); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static void test_parse_arguments_mandos_noargs(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - char *argv[] = { - strdup("prgname"), - strdup("mandos-client"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - __attribute__((cleanup(cleanup_string))) - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_true(parse_arguments(argc, argv, false, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)); - - g_assert_null(agent_directory); - g_assert_null(helper_directory); - g_assert_true(user == 0); - g_assert_true(group == 0); - g_assert_cmpstr(mandos_argz, ==, "mandos-client"); - g_assert_cmpuint((unsigned int)argz_count(mandos_argz, - mandos_argz_length), - ==, 1); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static void test_parse_arguments_mandos_args(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("mandos-client"), - strdup("one"), - strdup("two"), - strdup("three"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - __attribute__((cleanup(cleanup_string))) - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_true(parse_arguments(argc, argv, false, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)); - - g_assert_null(agent_directory); - g_assert_null(helper_directory); - g_assert_true(user == 0); - g_assert_true(group == 0); - char *marg = mandos_argz; - g_assert_cmpstr(marg, ==, "mandos-client"); - marg = argz_next(mandos_argz, mandos_argz_length, marg); - g_assert_cmpstr(marg, ==, "one"); - marg = argz_next(mandos_argz, mandos_argz_length, marg); - g_assert_cmpstr(marg, ==, "two"); - marg = argz_next(mandos_argz, mandos_argz_length, marg); - g_assert_cmpstr(marg, ==, "three"); - g_assert_cmpuint((unsigned int)argz_count(mandos_argz, - mandos_argz_length), - ==, 4); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static void test_parse_arguments_all_args(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("--agent-directory"), - strdup("/tmp"), - strdup("--helper-directory"), - strdup("/var/tmp"), - strdup("--user"), - strdup("1"), - strdup("--group"), - strdup("2"), - strdup("mandos-client"), - strdup("one"), - strdup("two"), - strdup("three"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - __attribute__((cleanup(cleanup_string))) - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_true(parse_arguments(argc, argv, false, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)); - - g_assert_cmpstr(agent_directory, ==, "/tmp"); - g_assert_cmpstr(helper_directory, ==, "/var/tmp"); - g_assert_true(user == 1); - g_assert_true(group == 2); - char *marg = mandos_argz; - g_assert_cmpstr(marg, ==, "mandos-client"); - marg = argz_next(mandos_argz, mandos_argz_length, marg); - g_assert_cmpstr(marg, ==, "one"); - marg = argz_next(mandos_argz, mandos_argz_length, marg); - g_assert_cmpstr(marg, ==, "two"); - marg = argz_next(mandos_argz, mandos_argz_length, marg); - g_assert_cmpstr(marg, ==, "three"); - g_assert_cmpuint((unsigned int)argz_count(mandos_argz, - mandos_argz_length), - ==, 4); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static void test_parse_arguments_mixed(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("mandos-client"), - strdup("--user"), - strdup("1"), - strdup("one"), - strdup("--agent-directory"), - strdup("/tmp"), - strdup("two"), - strdup("three"), - strdup("--helper-directory=/var/tmp"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - __attribute__((cleanup(cleanup_string))) - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_true(parse_arguments(argc, argv, false, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)); - - g_assert_cmpstr(agent_directory, ==, "/tmp"); - g_assert_cmpstr(helper_directory, ==, "/var/tmp"); - g_assert_true(user == 1); - g_assert_true(group == 0); - char *marg = mandos_argz; - g_assert_cmpstr(marg, ==, "mandos-client"); - marg = argz_next(mandos_argz, mandos_argz_length, marg); - g_assert_cmpstr(marg, ==, "one"); - marg = argz_next(mandos_argz, mandos_argz_length, marg); - g_assert_cmpstr(marg, ==, "two"); - marg = argz_next(mandos_argz, mandos_argz_length, marg); - g_assert_cmpstr(marg, ==, "three"); - g_assert_cmpuint((unsigned int)argz_count(mandos_argz, - mandos_argz_length), - ==, 4); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -/* End of tests section */ - -/* Test boilerplate section; New tests should be added to the test - suite definition here, in the "run_tests" function. - - Finally, this section also contains the should_only_run_tests() - function used by main() for deciding if tests should be run or to - start normally. */ - -__attribute__((cold)) -static bool run_tests(int argc, char *argv[]){ - g_test_init(&argc, &argv, NULL); - - /* A macro to add a test with no setup or teardown functions */ -#define test_add(testpath, testfunc) \ - do { \ - g_test_add((testpath), test_fixture, NULL, NULL, \ - (testfunc), NULL); \ - } while(false) - - /* Test the signal-related functions first, since some other tests - depend on these functions in their setups and teardowns */ - test_add("/signal-handling/setup", test_setup_signal_handler); - test_add("/signal-handling/restore", test_restore_signal_handler); - test_add("/signal-handling/block", test_block_sigchld); - test_add("/signal-handling/restore-sigmask", test_restore_sigmask); - - /* Regular non-signal-related tests; these use no setups or - teardowns */ - test_add("/parse_arguments/noargs", test_parse_arguments_noargs); - test_add("/parse_arguments/invalid", test_parse_arguments_invalid); - test_add("/parse_arguments/long-dir", - test_parse_arguments_long_dir); - test_add("/parse_arguments/short-dir", - test_parse_arguments_short_dir); - test_add("/parse_arguments/helper-directory", - test_parse_arguments_helper_directory); - test_add("/parse_arguments/plugin-helper-dir", - test_parse_arguments_plugin_helper_dir); - test_add("/parse_arguments/user", test_parse_arguments_user); - test_add("/parse_arguments/user-invalid", - test_parse_arguments_user_invalid); - test_add("/parse_arguments/user-zero-invalid", - test_parse_arguments_user_zero_invalid); - test_add("/parse_arguments/group", test_parse_arguments_group); - test_add("/parse_arguments/group-invalid", - test_parse_arguments_group_invalid); - test_add("/parse_arguments/group-zero-invalid", - test_parse_arguments_group_zero_invalid); - test_add("/parse_arguments/mandos-noargs", - test_parse_arguments_mandos_noargs); - test_add("/parse_arguments/mandos-args", - test_parse_arguments_mandos_args); - test_add("/parse_arguments/all-args", - test_parse_arguments_all_args); - test_add("/parse_arguments/mixed", test_parse_arguments_mixed); - test_add("/queue/create", test_create_queue); - test_add("/queue/add", test_add_to_queue); - test_add("/queue/add/overflow", test_add_to_queue_overflow); - test_add("/queue/has_question/empty", - test_queue_has_question_empty); - test_add("/queue/has_question/false", - test_queue_has_question_false); - test_add("/queue/has_question/true", test_queue_has_question_true); - test_add("/queue/has_question/false2", - test_queue_has_question_false2); - test_add("/queue/has_question/true2", - test_queue_has_question_true2); - test_add("/buffer/cleanup", test_cleanup_buffer); - test_add("/string_set/net-set-contains-nothing", - test_string_set_new_set_contains_nothing); - test_add("/string_set/with-added-string-contains-it", - test_string_set_with_added_string_contains_it); - test_add("/string_set/cleared-does-not-contain-string", - test_string_set_cleared_does_not_contain_str); - test_add("/string_set/swap/one-with-empty", - test_string_set_swap_one_with_empty); - test_add("/string_set/swap/empty-with-one", - test_string_set_swap_empty_with_one); - test_add("/string_set/swap/one-with-one", - test_string_set_swap_one_with_one); - - /* A macro to add a test using the setup and teardown functions */ -#define test_add_st(path, func) \ - do { \ - g_test_add((path), test_fixture, NULL, test_setup, (func), \ - test_teardown); \ - } while(false) - - /* Signal-related tests; these use setups and teardowns which - establish, during each test run, a signal handler for, and a - signal mask blocking, the SIGCHLD signal, just like main() */ - test_add_st("/wait_for_event/timeout", test_wait_for_event_timeout); - test_add_st("/wait_for_event/event", test_wait_for_event_event); - test_add_st("/wait_for_event/sigchld", test_wait_for_event_sigchld); - test_add_st("/run_queue/zeroes-next-run", - test_run_queue_zeroes_next_run); - test_add_st("/run_queue/clears-cancelled_filenames", - test_run_queue_clears_cancelled_filenames); - test_add_st("/run_queue/skips-cancelled-filenames", - test_run_queue_skips_cancelled_filenames); - test_add_st("/run_queue/one-task", test_run_queue_one_task); - test_add_st("/run_queue/two-tasks", test_run_queue_two_tasks); - test_add_st("/run_queue/two-tasks/quit", - test_run_queue_two_tasks_quit); - test_add_st("/run_queue/two-tasks-cleanup", - test_run_queue_two_tasks_cleanup); - test_add_st("/task-creators/start_mandos_client", - test_start_mandos_client); - test_add_st("/task-creators/start_mandos_client/execv", - test_start_mandos_client_execv); - test_add_st("/task-creators/start_mandos_client/suid/euid", - test_start_mandos_client_suid_euid); - test_add_st("/task-creators/start_mandos_client/suid/egid", - test_start_mandos_client_suid_egid); - test_add_st("/task-creators/start_mandos_client/suid/ruid", - test_start_mandos_client_suid_ruid); - test_add_st("/task-creators/start_mandos_client/suid/rgid", - test_start_mandos_client_suid_rgid); - test_add_st("/task-creators/start_mandos_client/read", - test_start_mandos_client_read); - test_add_st("/task-creators/start_mandos_client/helper-directory", - test_start_mandos_client_helper_directory); - test_add_st("/task-creators/start_mandos_client/sigmask", - test_start_mandos_client_sigmask); - test_add_st("/task/wait_for_mandos_client_exit/badpid", - test_wait_for_mandos_client_exit_badpid); - test_add_st("/task/wait_for_mandos_client_exit/noexit", - test_wait_for_mandos_client_exit_noexit); - test_add_st("/task/wait_for_mandos_client_exit/success", - test_wait_for_mandos_client_exit_success); - test_add_st("/task/wait_for_mandos_client_exit/failure", - test_wait_for_mandos_client_exit_failure); - test_add_st("/task/wait_for_mandos_client_exit/killed", - test_wait_for_mandos_client_exit_killed); - test_add_st("/task/read_mandos_client_output/readerror", - test_read_mandos_client_output_readerror); - test_add_st("/task/read_mandos_client_output/nodata", - test_read_mandos_client_output_nodata); - test_add_st("/task/read_mandos_client_output/eof", - test_read_mandos_client_output_eof); - test_add_st("/task/read_mandos_client_output/once", - test_read_mandos_client_output_once); - test_add_st("/task/read_mandos_client_output/malloc", - test_read_mandos_client_output_malloc); - test_add_st("/task/read_mandos_client_output/append", - test_read_mandos_client_output_append); - test_add_st("/task-creators/add_inotify_dir_watch", - test_add_inotify_dir_watch); - test_add_st("/task-creators/add_inotify_dir_watch/fail", - test_add_inotify_dir_watch_fail); - test_add_st("/task-creators/add_inotify_dir_watch/not-a-directory", - test_add_inotify_dir_watch_nondir); - test_add_st("/task-creators/add_inotify_dir_watch/EAGAIN", - test_add_inotify_dir_watch_EAGAIN); - test_add_st("/task-creators/add_inotify_dir_watch/IN_CLOSE_WRITE", - test_add_inotify_dir_watch_IN_CLOSE_WRITE); - test_add_st("/task-creators/add_inotify_dir_watch/IN_MOVED_TO", - test_add_inotify_dir_watch_IN_MOVED_TO); - test_add_st("/task-creators/add_inotify_dir_watch/IN_MOVED_FROM", - test_add_inotify_dir_watch_IN_MOVED_FROM); - test_add_st("/task-creators/add_inotify_dir_watch/IN_EXCL_UNLINK", - test_add_inotify_dir_watch_IN_EXCL_UNLINK); - test_add_st("/task-creators/add_inotify_dir_watch/IN_DELETE", - test_add_inotify_dir_watch_IN_DELETE); - test_add_st("/task/read_inotify_event/readerror", - test_read_inotify_event_readerror); - test_add_st("/task/read_inotify_event/bad-epoll", - test_read_inotify_event_bad_epoll); - test_add_st("/task/read_inotify_event/nodata", - test_read_inotify_event_nodata); - test_add_st("/task/read_inotify_event/eof", - test_read_inotify_event_eof); - test_add_st("/task/read_inotify_event/IN_CLOSE_WRITE", - test_read_inotify_event_IN_CLOSE_WRITE); - test_add_st("/task/read_inotify_event/IN_MOVED_TO", - test_read_inotify_event_IN_MOVED_TO); - test_add_st("/task/read_inotify_event/IN_MOVED_FROM", - test_read_inotify_event_IN_MOVED_FROM); - test_add_st("/task/read_inotify_event/IN_DELETE", - test_read_inotify_event_IN_DELETE); - test_add_st("/task/read_inotify_event/IN_CLOSE_WRITE/badname", - test_read_inotify_event_IN_CLOSE_WRITE_badname); - test_add_st("/task/read_inotify_event/IN_MOVED_TO/badname", - test_read_inotify_event_IN_MOVED_TO_badname); - test_add_st("/task/read_inotify_event/IN_MOVED_FROM/badname", - test_read_inotify_event_IN_MOVED_FROM_badname); - test_add_st("/task/read_inotify_event/IN_DELETE/badname", - test_read_inotify_event_IN_DELETE_badname); - test_add_st("/task/open_and_parse_question/ENOENT", - test_open_and_parse_question_ENOENT); - test_add_st("/task/open_and_parse_question/EIO", - test_open_and_parse_question_EIO); - test_add_st("/task/open_and_parse_question/parse-error", - test_open_and_parse_question_parse_error); - test_add_st("/task/open_and_parse_question/nosocket", - test_open_and_parse_question_nosocket); - test_add_st("/task/open_and_parse_question/badsocket", - test_open_and_parse_question_badsocket); - test_add_st("/task/open_and_parse_question/nopid", - test_open_and_parse_question_nopid); - test_add_st("/task/open_and_parse_question/badpid", - test_open_and_parse_question_badpid); - test_add_st("/task/open_and_parse_question/noexist_pid", - test_open_and_parse_question_noexist_pid); - test_add_st("/task/open_and_parse_question/no-notafter", - test_open_and_parse_question_no_notafter); - test_add_st("/task/open_and_parse_question/bad-notafter", - test_open_and_parse_question_bad_notafter); - test_add_st("/task/open_and_parse_question/notafter-0", - test_open_and_parse_question_notafter_0); - test_add_st("/task/open_and_parse_question/notafter-1", - test_open_and_parse_question_notafter_1); - test_add_st("/task/open_and_parse_question/notafter-1-1", - test_open_and_parse_question_notafter_1_1); - test_add_st("/task/open_and_parse_question/notafter-1-2", - test_open_and_parse_question_notafter_1_2); - test_add_st("/task/open_and_parse_question/equal-notafter", - test_open_and_parse_question_equal_notafter); - test_add_st("/task/open_and_parse_question/late-notafter", - test_open_and_parse_question_late_notafter); - test_add_st("/task/cancel_old_question/0-1-2", - test_cancel_old_question_0_1_2); - test_add_st("/task/cancel_old_question/0-2-1", - test_cancel_old_question_0_2_1); - test_add_st("/task/cancel_old_question/1-2-3", - test_cancel_old_question_1_2_3); - test_add_st("/task/cancel_old_question/1-3-2", - test_cancel_old_question_1_3_2); - test_add_st("/task/cancel_old_question/2-1-3", - test_cancel_old_question_2_1_3); - test_add_st("/task/cancel_old_question/2-3-1", - test_cancel_old_question_2_3_1); - test_add_st("/task/cancel_old_question/3-1-2", - test_cancel_old_question_3_1_2); - test_add_st("/task/cancel_old_question/3-2-1", - test_cancel_old_question_3_2_1); - test_add_st("/task/connect_question_socket/name-too-long", - test_connect_question_socket_name_too_long); - test_add_st("/task/connect_question_socket/connect-fail", - test_connect_question_socket_connect_fail); - test_add_st("/task/connect_question_socket/bad-epoll", - test_connect_question_socket_bad_epoll); - test_add_st("/task/connect_question_socket/usable", - test_connect_question_socket_usable); - test_add_st("/task/send_password_to_socket/client-not-exited", - test_send_password_to_socket_client_not_exited); - test_add_st("/task/send_password_to_socket/password-not-read", - test_send_password_to_socket_password_not_read); - test_add_st("/task/send_password_to_socket/EMSGSIZE", - test_send_password_to_socket_EMSGSIZE); - test_add_st("/task/send_password_to_socket/retry", - test_send_password_to_socket_retry); - test_add_st("/task/send_password_to_socket/bad-epoll", - test_send_password_to_socket_bad_epoll); - test_add_st("/task/send_password_to_socket/null-password", - test_send_password_to_socket_null_password); - test_add_st("/task/send_password_to_socket/empty-password", - test_send_password_to_socket_empty_password); - test_add_st("/task/send_password_to_socket/empty-str-password", - test_send_password_to_socket_empty_str_pass); - test_add_st("/task/send_password_to_socket/text-password", - test_send_password_to_socket_text_password); - test_add_st("/task/send_password_to_socket/binary-password", - test_send_password_to_socket_binary_password); - test_add_st("/task/send_password_to_socket/nuls-in-password", - test_send_password_to_socket_nuls_in_password); - test_add_st("/task-creators/add_existing_questions/ENOENT", - test_add_existing_questions_ENOENT); - test_add_st("/task-creators/add_existing_questions/no-questions", - test_add_existing_questions_no_questions); - test_add_st("/task-creators/add_existing_questions/one-question", - test_add_existing_questions_one_question); - test_add_st("/task-creators/add_existing_questions/two-questions", - test_add_existing_questions_two_questions); - test_add_st("/task-creators/add_existing_questions/non-questions", - test_add_existing_questions_non_questions); - test_add_st("/task-creators/add_existing_questions/both-types", - test_add_existing_questions_both_types); - - return g_test_run() == 0; -} - -static bool should_only_run_tests(int *argc_p, char **argv_p[]){ - GOptionContext *context = g_option_context_new(""); - - g_option_context_set_help_enabled(context, FALSE); - g_option_context_set_ignore_unknown_options(context, TRUE); - - gboolean should_run_tests = FALSE; - GOptionEntry entries[] = { - { "test", 0, 0, G_OPTION_ARG_NONE, - &should_run_tests, "Run tests", NULL }, - { NULL } - }; - g_option_context_add_main_entries(context, entries, NULL); - - GError *error = NULL; - - if(g_option_context_parse(context, argc_p, argv_p, &error) != TRUE){ - g_option_context_free(context); - g_error("Failed to parse options: %s", error->message); - } - - g_option_context_free(context); - return should_run_tests != FALSE; -} - -/* -Local Variables: -run-tests: -(lambda () - (if (not (funcall run-tests-in-test-buffer default-directory)) - (funcall show-test-buffer-in-test-window) - (funcall remove-test-window))) -run-tests-in-test-buffer: -(lambda (dir) - (with-current-buffer (get-buffer-create "*Test*") - (setq buffer-read-only nil - default-directory dir) - (erase-buffer) - (compilation-mode)) - (let ((process-result - (let ((inhibit-read-only t)) - (process-file-shell-command - (funcall get-command-line) nil "*Test*")))) - (and (numberp process-result) - (= process-result 0)))) -get-command-line: -(lambda () - (let* - ((build-directory - (funcall find-build-directory (buffer-file-name))) - (local-build-directory - (if (fboundp 'file-local-name) - (file-local-name build-directory) - (or (file-remote-p build-directory 'localname) - build-directory))) - (command - (file-relative-name (file-name-sans-extension - (buffer-file-name)) build-directory)) - (qbdir (shell-quote-argument local-build-directory)) - (qcmd (shell-quote-argument command))) - (format (concat "cd %s && CFLAGS=-Werror make --silent %s" - " && %s --test --verbose") qbdir qcmd qcmd))) -find-build-directory: -(lambda (try-directory &optional base-directory) - (let ((base-directory (or base-directory try-directory))) - (cond ((equal try-directory "/") base-directory) - ((file-readable-p - (concat (file-name-as-directory try-directory) - "Makefile")) try-directory) - ((funcall find-build-directory - (directory-file-name (file-name-directory - try-directory)) - base-directory))))) -show-test-buffer-in-test-window: -(lambda () - (when (not (get-buffer-window-list "*Test*")) - (setq next-error-last-buffer (get-buffer "*Test*")) - (let* ((side (if (>= (window-width) 146) 'right 'bottom)) - (display-buffer-overriding-action - `((display-buffer-in-side-window) (side . ,side) - (window-height . fit-window-to-buffer) - (window-width . fit-window-to-buffer)))) - (display-buffer "*Test*")))) -remove-test-window: -(lambda () - (let ((test-window (get-buffer-window "*Test*"))) - (if test-window (delete-window test-window)))) -eval: (add-hook 'after-save-hook run-tests 90 t) -End: -*/ === removed file 'dracut-module/password-agent.xml' --- dracut-module/password-agent.xml 2022-04-24 16:54:30 +0000 +++ dracut-module/password-agent.xml 1970-01-01 00:00:00 +0000 @@ -1,467 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2019 - 2020 - Teddy Hogeborn - Björn Påhlsson - - -
- - - &COMMANDNAME; - 8mandos - - - - &COMMANDNAME; - - Run Mandos client as a systemd password agent. - - - - - - &COMMANDNAME; - - - - - - - - - - - - - - - - - - -- - - MANDOS_CLIENT - - OPTIONS - - - - - &COMMANDNAME; - - - - &COMMANDNAME; - - - - - - - &COMMANDNAME; - - - - &COMMANDNAME; - - - - - - - - - DESCRIPTION - - &COMMANDNAME; is a program which is meant to - be a systemd - 1 Password - Agent (See Password - Agents). The aim of this program is therefore to - acquire and then send a password to some other program which - will use the password to unlock the encrypted root disk. - - - This program is not meant to be invoked directly, but can be in - order to test it. - - - - - PURPOSE - - The purpose of this is to enable remote and unattended - rebooting of client host computer with an - encrypted root file system. See for details. - - - - - OPTIONS - - - - - - - Specify a different agent directory. The default is - /run/systemd/ask-password as per the - Password - Agents specification. - - - - - - - - - Specify a different helper directory. The default is - /lib/mandos/plugin-helpers, which - will exist in the initial RAM disk - environment. (This will simply be passed to the - MANDOS_CLIENT program via the - MANDOSPLUGINHELPERDIR environment variable. - See - mandos-client8mandos.) - - - - - - - - - Change real user ID to USERID - when running MANDOS_CLIENT. - The default is 65534. Note: This - must be a number, not a name. - - - - - - - - - Change real group ID to GROUPID - when running MANDOS_CLIENT. - The default is 65534. Note: This - must be a number, not a name. - - - - - - MANDOS_CLIENT - - - This specifies the file name for - mandos-client8mandos. If the - option is given, any - following options are passed to the MANDOS_CLIENT program. The default is - /lib/mandos/plugins.d/mandos-client - (which is the correct location for the initial - RAM disk environment) without any - options. - - - - - - - - - - Gives a help message about options and their meanings. - - - - - - - - - Ignore normal operation; instead only run self-tests. - Adding the option may show more - options possible in combination with - . - - - - - - - - - Gives a short usage message. - - - - - - - - - - Prints the program version. - - - - - - - - OVERVIEW - - - This program, &COMMANDNAME;, will run on the client side in the - initial RAM disk environment, and is - responsible for getting a password from the Mandos client - program itself, and to send that password to whatever is - currently asking for a password using the systemd Password - Agents mechanism. - - To accomplish this, &COMMANDNAME; runs the - mandos-client program (which is the actual - client program communicating with the Mandos server) or, - alternatively, any executable file specified as - MANDOS_CLIENT, and, as soon as a - password is acquired from the - MANDOS_CLIENT program, sends that - password (as per the Password Agents - specification) to all currently unanswered password questions. - - - This program should be started (normally as a systemd service, - which in turn is normally started by a systemd.path - 5 file) as a reaction to - files named ask.xxxx appearing in the agent directory - /run/systemd/ask-password - (or the directory specified by - ). - - - - - EXIT STATUS - - Exit status of this program is zero if no errors were - encountered, and otherwise not. - - - - - ENVIRONMENT - - This program does not use any environment variables itself, it - only passes on its environment to - MANDOS_CLIENT. Also, the - option will affect the - environment variable MANDOSPLUGINHELPERDIR for - MANDOS_CLIENT. - - - - - FILES - - - - /run/systemd/ask-password - - - The default directory to watch for password questions as - per the Password - Agents specification; can be changed by the - option. - - - - - /lib/mandos/plugin-helpers - - - The helper directory as supplied to - MANDOS_CLIENT via the - MANDOSPLUGINHELPERDIR environment - variable; can be changed by the - option. - - - - - - - - - BUGS - - - - - EXAMPLE - - - Normal invocation needs no options: - - - &COMMANDNAME; - - - - - Run an alternative MANDOS_CLIENT - program:: - - - &COMMANDNAME; /usr/local/sbin/alternate - - - - - Use alternative locations for the helper directory and the - Mandos client, and add extra options suitable for running in - the normal file system: - - - - - &COMMANDNAME; --helper-directory=/usr/lib/x86_64-linux-gnu/mandos/plugin-helpers -- /usr/lib/x86_64-linux-gnu/mandos/plugins.d/mandos-client --pubkey=/etc/keys/mandos/pubkey.txt --seckey=/etc/keys/mandos/seckey.txt --tls-pubkey=/etc/keys/mandos/tls-pubkey.pem --tls-privkey=/etc/keys/mandos/tls-privkey.pem - - - - - - Use the default location for - mandos-client - 8mandos, but add many - options to it: - - - - -&COMMANDNAME; -- /lib/mandos/plugins.d/mandos-client --pubkey=/etc/mandos/keys/pubkey.txt --seckey=/etc/mandos/keys/seckey.txt --tls-pubkey=/etc/mandos/keys/tls-pubkey.pem --tls-privkey=/etc/mandos/keys/tls-privkey.pem - - - - - - Only run the self-tests: - - - &COMMANDNAME; --test - - - - - SECURITY - - This program will need to run as the root user in order to read - the agent directory and the ask.xxxx files - there, and will, when starting the Mandos client program, - require the ability to set the real user and - group ids to another user, by default user and group 65534, - which are assumed to be non-privileged. This is done in order - to match the expectations of mandos-client8mandos, which assumes that its executable file is - owned by the root user and also has the set-user-ID bit set (see - execve2). - - - - - SEE ALSO - - intro - 8mandos, - mandos-client - 8mandos, - systemd - 1, - - - - - Password - Agents - - - - The specification for systemd Password - Agent programs, which - &COMMANDNAME; follows. - - - - - - -
- - - - - === 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 2020-02-09 17:57:08 +0000 +++ initramfs-tools-hook 2016-03-19 04:26:32 +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}" @@ -142,10 +140,9 @@ # Get DEVICE from initramfs.conf and other files . /etc/initramfs-tools/initramfs.conf for conf in /etc/initramfs-tools/conf.d/*; do - if [ -n "`basename \"$conf\" \ - | grep '^[[:alnum:]][[:alnum:]\._-]*$' \ - | grep -v '\.dpkg-.*$'`" ]; then - [ -f "${conf}" ] && . "${conf}" + if [ -n `basename \"$conf\" | grep '^[[:alnum:]][[:alnum:]\._-]*$' \ + | grep -v '\.dpkg-.*$'` ]; then + [ -f ${conf} ] && . ${conf} fi done export DEVICE @@ -160,7 +157,7 @@ if [ -x "$hook" ]; then # Copy any files needed by the network hook MANDOSNETHOOKDIR=/etc/mandos/network-hooks.d MODE=files \ - VERBOSITY=0 "$hook" files | while read -r file target; do + VERBOSITY=0 "$hook" files | while read file target; do if [ ! -e "${file}" ]; then echo "WARNING: file ${file} not found, requested by Mandos network hook '${hook##*/}'" >&2 fi @@ -172,8 +169,10 @@ done # Copy and load any modules needed by the network hook MANDOSNETHOOKDIR=/etc/mandos/network-hooks.d MODE=modules \ - VERBOSITY=0 "$hook" modules | while read -r module; do - force_load "$module" + VERBOSITY=0 "$hook" modules | while read module; do + if [ -z "${target}" ]; then + force_load "$module" + fi done fi done @@ -187,11 +186,6 @@ copy_exec /usr/bin/gpgconf fi gpg="`/usr/bin/gpgconf|sed --quiet --expression='s/^gpg:[^:]*://p'`" - gpgagent="`/usr/bin/gpgconf|sed --quiet --expression='s/^gpg-agent:[^:]*://p'`" - # Newer versions of GnuPG 2 requires the gpg-agent binary - if [ -e "$gpgagent" ] && [ ! -e "${DESTDIR}$gpgagent" ]; then - copy_exec "$gpgagent" - fi fi elif dpkg --compare-versions "$libgpgme11_version" ge 1.4.1-0.1; then gpg=/usr/bin/gpg2 @@ -253,8 +247,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 2020-07-04 08:59:37 +0000 +++ initramfs-tools-script 2016-03-02 16:45:38 +0000 @@ -51,10 +51,13 @@ 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 - [ -f "${conf}" ] && . "${conf}" + [ -f ${conf} ] && . ${conf} done if [ -e /conf/param.conf ]; then . /conf/param.conf @@ -102,85 +105,50 @@ 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,key=none,keyscript=/foo/bar/baz +exec 3>/conf/conf.d/cryptroot.mandos +while read options; do + newopts="" + # 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" + ;; + "") : ;; + *) + 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 - fi -elif [ -x /usr/bin/cryptroot-unlock ]; then - # Use setsid if available - if command -v setsid >/dev/null 2>&1; then - setsid /lib/mandos/mandos-to-cryptroot-unlock & - else - /lib/mandos/mandos-to-cryptroot-unlock & - fi + done + IFS="$old_ifs" + unset old_ifs + # If there was no keyscript option, add one. + if [ -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 /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 'initramfs-unpack' --- initramfs-unpack 2019-07-27 10:11:45 +0000 +++ initramfs-unpack 2013-10-13 15:43:42 +0000 @@ -2,23 +2,21 @@ # # Initramfs unpacker - unpacks initramfs images into /tmp # -# Copyright © 2013-2019 Teddy Hogeborn -# Copyright © 2013-2019 Björn Påhlsson +# Copyright © 2013 Teddy Hogeborn +# Copyright © 2013 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 +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # -# Mandos is distributed in the hope that it will be useful, +# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License -# along with Mandos. If not, see +# along with this program. If not, see # . # # Contact the authors at . @@ -43,40 +41,25 @@ if $cpio --quiet --list --file="$imgfile" >/dev/null 2>&1; then # Number of bytes to skip to get to the compressed archive skip=$(($(LANG=C $cpio --io-size=1 --list --file="$imgfile" 2>&1 \ - | sed --quiet \ - --expression='s/^\([0-9]\+\) blocks$/\1/p')+8)) - if [ -x /usr/lib/dracut/skipcpio ]; then - catimg="/usr/lib/dracut/skipcpio $imgfile" - else - catimg="dd if=$imgfile bs=$skip skip=1 status=noxfer" - fi + | sed --quiet --expression='s/^\([0-9]\+\) blocks$/\1/p')+8)) + catimg="dd if=$imgfile bs=$skip skip=1 status=noxfer" else - echo "No microcode detected" catimg="cat -- $imgfile" fi - while :; do - # Determine the compression method - if { $catimg 2>/dev/null | zcat --test >/dev/null 2>&1; - [ ${PIPESTATUS[-1]} -eq 0 ]; }; then - decomp="zcat" - elif { $catimg 2>/dev/null | bzip2 --test >/dev/null 2>&1; - [ ${PIPESTATUS[-1]} -eq 0 ]; }; then - decomp="bzip2 --stdout --decompress" - elif { $catimg 2>/dev/null | lzop --test >/dev/null 2>&1; - [ ${PIPESTATUS[-1]} -eq 0 ]; }; then - decomp="lzop --stdout --decompress" - else - skip=$((${skip}+1)) - echo "Could not determine compression of ${imgfile}; trying to skip ${skip} bytes" >&2 - catimg="dd if=$imgfile bs=$skip skip=1 status=noxfer" - continue - fi - break - done - case "$catimg" in - *skipcpio*) echo "Microcode detected, skipping";; - *) echo "Microcode detected, skipping ${skip} bytes";; - esac + # Determine the compression method + if { $catimg 2>/dev/null | zcat --test >/dev/null 2>&1; + [ ${PIPESTATUS[-1]} -eq 0 ]; }; then + decomp="zcat" + elif { $catimg 2>/dev/null | bzip2 --test >/dev/null 2>&1; + [ ${PIPESTATUS[-1]} -eq 0 ]; }; then + decomp="bzip2 --stdout --decompress" + elif { $catimg 2>/dev/null | lzop --test >/dev/null 2>&1; + [ ${PIPESTATUS[-1]} -eq 0 ]; }; then + decomp="lzop --stdout --decompress" + else + echo "Error: Could not determine type of $imgfile" >&2 + continue + fi $catimg 2>/dev/null | $decomp | ( cd -- "$imgdir" && $cpio --quiet ) if [ ${PIPESTATUS[-1]} -eq 0 ]; then echo "$imgfile unpacked into $imgdir" === modified file 'intro.xml' --- intro.xml 2022-04-24 16:54:30 +0000 +++ intro.xml 2016-03-23 07:11:22 +0000 @@ -1,7 +1,7 @@ + %common; ]> @@ -36,10 +36,6 @@ 2014 2015 2016 - 2017 - 2018 - 2019 - 2020 Teddy Hogeborn Björn Påhlsson @@ -69,10 +65,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. @@ -81,8 +77,6 @@ INTRODUCTION - You know how it is. You’ve heard of it happening. The Man comes and takes away your servers, your friends’ servers, the servers of everybody in the same hosting facility. The servers @@ -132,9 +126,8 @@ So, at boot time, the Mandos client will ask for its encrypted - data over the network, decrypt the data to get the password, use - the password to decrypt the root file system, and the client can - then continue booting. + data over the network, decrypt it to get the password, use it to + decrypt the root file, and continue booting. Now, of course the initial RAM disk image is not on the @@ -146,7 +139,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 @@ -203,8 +196,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. @@ -385,36 +378,7 @@ plugin requirements. - - - SYSTEMD - - More advanced startup systems like systemd1, - already have their own plugin-like mechanisms for allowing - multiple agents to independently retrieve a password and deliver - it to the subsystem requesting a password to unlock the root - file system. On these systems, it would make no sense to run - plugin-runner8mandos, the plugins of - which would largely duplicate the work of (and conflict with) - the existing systems prompting for passwords. - - - As for systemd1 in particular, it has - its own Password - Agents system. Mandos uses this via its - password-agent8mandos program, which is - run instead of plugin-runner8mandos when systemd1 - is used during system startup. - - + BUGS @@ -435,8 +399,6 @@ 8, plugin-runner 8mandos, - password-agent - 8mandos, mandos-client 8mandos, password-prompt === modified file 'legalnotice.xml' --- legalnotice.xml 2017-08-20 16:20:54 +0000 +++ legalnotice.xml 2008-09-06 17:24:58 +0000 @@ -3,26 +3,25 @@ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd"> - This manual page 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. + This manual page is free software: you can redistribute it and/or + modify it under the terms of the GNU General + Public License as published by the Free Software Foundation, + either version 3 of the License, or (at your option) any later + version. + + + + This manual page 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 http://www.gnu.org/licenses/. === modified file 'mandos' --- mandos 2022-04-25 18:46:48 +0000 +++ mandos 2016-03-19 22:00:38 +0000 @@ -1,36 +1,36 @@ -#!/usr/bin/python3 -bI -# -*- coding: utf-8; lexical-binding: t -*- -# +#!/usr/bin/python +# -*- mode: python; coding: utf-8 -*- +# # Mandos server - give out binary blobs to connecting clients. -# +# # This program is partly derived from an example program for an Avahi # service publisher, downloaded from # . This includes the # methods "add", "remove", "server_state_changed", # "entry_group_state_changed", "cleanup", and "activate" in the # "AvahiService" class, and some lines in "main". -# +# # Everything else is -# Copyright © 2008-2022 Teddy Hogeborn -# Copyright © 2008-2022 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 +# Copyright © 2008-2016 Teddy Hogeborn +# Copyright © 2008-2016 Björn Påhlsson +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 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 +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License -# along with Mandos. If not, see . -# +# along with this program. If not, see +# . +# # Contact the authors at . -# +# + from __future__ import (division, absolute_import, print_function, unicode_literals) @@ -39,27 +39,26 @@ except ImportError: pass -import sys -import unittest -import argparse -import logging -import os try: import SocketServer as socketserver except ImportError: import socketserver import socket +import argparse import datetime import errno try: import ConfigParser as configparser except ImportError: import configparser +import sys import re +import os import signal import subprocess import atexit import stat +import logging import logging.handlers import pwd import contextlib @@ -77,12 +76,9 @@ import itertools import collections import codecs -import random -import shlex import dbus import dbus.service -import gi from gi.repository import GLib from dbus.mainloop.glib import DBusGMainLoop import ctypes @@ -90,71 +86,28 @@ import xml.dom.minidom import inspect -if sys.version_info.major == 2: - __metaclass__ = type - str = unicode - input = raw_input - -# Add collections.abc.Callable if it does not exist -try: - collections.abc.Callable -except AttributeError: - class abc: - Callable = collections.Callable - collections.abc = abc - del abc - -# Add shlex.quote if it does not exist -try: - shlex.quote -except AttributeError: - shlex.quote = re.escape - -# Show warnings by default -if not sys.warnoptions: - import warnings - warnings.simplefilter("default") - -# Try to find the value of SO_BINDTODEVICE: -try: - # This is where SO_BINDTODEVICE is in Python 3.3 (or 3.4?) and - # newer, and it is also the most natural place for it: +try: SO_BINDTODEVICE = socket.SO_BINDTODEVICE except AttributeError: try: - # This is where SO_BINDTODEVICE was up to and including Python - # 2.6, and also 3.2: from IN import SO_BINDTODEVICE except ImportError: - # In Python 2.7 it seems to have been removed entirely. - # Try running the C preprocessor: - try: - cc = subprocess.Popen(["cc", "--language=c", "-E", - "/dev/stdin"], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE) - stdout = cc.communicate( - "#include \nSO_BINDTODEVICE\n")[0] - SO_BINDTODEVICE = int(stdout.splitlines()[-1]) - except (OSError, ValueError, IndexError): - # No value found - SO_BINDTODEVICE = None - -if sys.version_info < (3, 2): - configparser.Configparser = configparser.SafeConfigParser - -version = "1.8.15" + SO_BINDTODEVICE = None + +if sys.version_info.major == 2: + str = unicode + +version = "1.7.7" stored_state_file = "clients.pickle" -log = logging.getLogger(os.path.basename(sys.argv[0])) -logging.captureWarnings(True) # Show warnings via the logging system +logger = logging.getLogger() syslogger = None try: if_nametoindex = ctypes.cdll.LoadLibrary( ctypes.util.find_library("c")).if_nametoindex except (OSError, AttributeError): - + def if_nametoindex(interface): "Get an interface index the hard way, i.e. using fcntl()" SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h @@ -183,24 +136,24 @@ def initlogger(debug, level=logging.WARNING): """init logger and add loglevel""" - + global syslogger syslogger = (logging.handlers.SysLogHandler( - facility=logging.handlers.SysLogHandler.LOG_DAEMON, - address="/dev/log")) + facility = logging.handlers.SysLogHandler.LOG_DAEMON, + address = "/dev/log")) syslogger.setFormatter(logging.Formatter - ("Mandos [%(process)d]: %(levelname)s:" - " %(message)s")) - log.addHandler(syslogger) - + ('Mandos [%(process)d]: %(levelname)s:' + ' %(message)s')) + logger.addHandler(syslogger) + if debug: console = logging.StreamHandler() - console.setFormatter(logging.Formatter("%(asctime)s %(name)s" - " [%(process)d]:" - " %(levelname)s:" - " %(message)s")) - log.addHandler(console) - log.setLevel(level) + console.setFormatter(logging.Formatter('%(asctime)s %(name)s' + ' [%(process)d]:' + ' %(levelname)s:' + ' %(message)s')) + logger.addHandler(console) + logger.setLevel(level) class PGPError(Exception): @@ -208,9 +161,9 @@ pass -class PGPEngine: +class PGPEngine(object): """A simple class for OpenPGP symmetric encryption & decryption""" - + def __init__(self): self.tempdir = tempfile.mkdtemp(prefix="mandos-") self.gpg = "gpg" @@ -218,35 +171,33 @@ output = subprocess.check_output(["gpgconf"]) for line in output.splitlines(): name, text, path = line.split(b":") - if name == b"gpg": + if name == "gpg": self.gpg = path break except OSError as e: if e.errno != errno.ENOENT: raise - self.gnupgargs = ["--batch", - "--homedir", self.tempdir, - "--force-mdc", - "--quiet"] - # Only GPG version 1 has the --no-use-agent option. - if self.gpg == b"gpg" or self.gpg.endswith(b"/gpg"): - self.gnupgargs.append("--no-use-agent") - + self.gnupgargs = ['--batch', + '--homedir', self.tempdir, + '--force-mdc', + '--quiet', + '--no-use-agent'] + def __enter__(self): return self - + def __exit__(self, exc_type, exc_value, traceback): self._cleanup() return False - + def __del__(self): self._cleanup() - + def _cleanup(self): if self.tempdir is not None: # Delete contents of tempdir for root, dirs, files in os.walk(self.tempdir, - topdown=False): + topdown = False): for filename in files: os.remove(os.path.join(root, filename)) for dirname in dirs: @@ -254,7 +205,7 @@ # Remove tempdir os.rmdir(self.tempdir) self.tempdir = None - + def password_encode(self, password): # Passphrase can not be empty and can not contain newlines or # NUL bytes. So we prefix it and hex encode it. @@ -265,69 +216,67 @@ .replace(b"\n", b"\\n") .replace(b"\0", b"\\x00")) return encoded - + def encrypt(self, data, password): passphrase = self.password_encode(password) with tempfile.NamedTemporaryFile( dir=self.tempdir) as passfile: passfile.write(passphrase) passfile.flush() - proc = subprocess.Popen([self.gpg, "--symmetric", - "--passphrase-file", + proc = subprocess.Popen([self.gpg, '--symmetric', + '--passphrase-file', passfile.name] + self.gnupgargs, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - ciphertext, err = proc.communicate(input=data) + stdin = subprocess.PIPE, + stdout = subprocess.PIPE, + stderr = subprocess.PIPE) + ciphertext, err = proc.communicate(input = data) if proc.returncode != 0: raise PGPError(err) return ciphertext - + def decrypt(self, data, password): passphrase = self.password_encode(password) with tempfile.NamedTemporaryFile( - dir=self.tempdir) as passfile: + dir = self.tempdir) as passfile: passfile.write(passphrase) passfile.flush() - proc = subprocess.Popen([self.gpg, "--decrypt", - "--passphrase-file", + proc = subprocess.Popen([self.gpg, '--decrypt', + '--passphrase-file', passfile.name] + self.gnupgargs, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - decrypted_plaintext, err = proc.communicate(input=data) + stdin = subprocess.PIPE, + stdout = subprocess.PIPE, + stderr = subprocess.PIPE) + decrypted_plaintext, err = proc.communicate(input = data) if proc.returncode != 0: raise PGPError(err) return decrypted_plaintext - # Pretend that we have an Avahi module -class avahi: - """This isn't so much a class as it is a module-like namespace.""" - IF_UNSPEC = -1 # avahi-common/address.h - PROTO_UNSPEC = -1 # avahi-common/address.h - PROTO_INET = 0 # avahi-common/address.h - PROTO_INET6 = 1 # avahi-common/address.h +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 + PROTO_INET6 = 1 # avahi-common/address.h DBUS_NAME = "org.freedesktop.Avahi" DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup" 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 - ENTRY_GROUP_COLLISION = 3 # avahi-common/defs.h - ENTRY_GROUP_FAILURE = 4 # avahi-common/defs.h - SERVER_INVALID = 0 # avahi-common/defs.h - SERVER_REGISTERING = 1 # avahi-common/defs.h - SERVER_RUNNING = 2 # avahi-common/defs.h - SERVER_COLLISION = 3 # avahi-common/defs.h - SERVER_FAILURE = 4 # avahi-common/defs.h - + ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h + ENTRY_GROUP_COLLISION = 3 # avahi-common/defs.h + ENTRY_GROUP_FAILURE = 4 # avahi-common/defs.h + SERVER_INVALID = 0 # avahi-common/defs.h + SERVER_REGISTERING = 1 # avahi-common/defs.h + 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): def __init__(self, value, *args, **kwargs): @@ -344,14 +293,14 @@ pass -class AvahiService: +class AvahiService(object): """An Avahi (Zeroconf) service. - + Attributes: interface: integer; avahi.IF_UNSPEC or an interface index. Used to optionally bind to the specified interface. - name: string; Example: "Mandos" - type: string; Example: "_mandos._tcp". + name: string; Example: 'Mandos' + type: string; Example: '_mandos._tcp'. See port: integer; what port to announce TXT: list of strings; TXT record for the service @@ -364,18 +313,18 @@ server: D-Bus Server bus: dbus.SystemBus() """ - + def __init__(self, - interface=avahi.IF_UNSPEC, - name=None, - servicetype=None, - port=None, - TXT=None, - domain="", - host="", - max_renames=32768, - protocol=avahi.PROTO_UNSPEC, - bus=None): + interface = avahi.IF_UNSPEC, + name = None, + servicetype = None, + port = None, + TXT = None, + domain = "", + host = "", + max_renames = 32768, + protocol = avahi.PROTO_UNSPEC, + bus = None): self.interface = interface self.name = name self.type = servicetype @@ -390,19 +339,19 @@ self.server = None self.bus = bus self.entry_group_state_changed_match = None - + def rename(self, remove=True): """Derived from the Avahi example code""" if self.rename_count >= self.max_renames: - log.critical("No suitable Zeroconf service name found" - " after %i retries, exiting.", - self.rename_count) + logger.critical("No suitable Zeroconf service name found" + " after %i retries, exiting.", + self.rename_count) raise AvahiServiceError("Too many renames") self.name = str( self.server.GetAlternativeServiceName(self.name)) self.rename_count += 1 - log.info("Changing Zeroconf service name to %r ...", - self.name) + logger.info("Changing Zeroconf service name to %r ...", + self.name) if remove: self.remove() try: @@ -410,13 +359,13 @@ except dbus.exceptions.DBusException as error: if (error.get_dbus_name() == "org.freedesktop.Avahi.CollisionError"): - log.info("Local Zeroconf service name collision.") + logger.info("Local Zeroconf service name collision.") return self.rename(remove=False) else: - log.critical("D-Bus Exception", exc_info=error) + logger.critical("D-Bus Exception", exc_info=error) self.cleanup() os._exit(1) - + def remove(self): """Derived from the Avahi example code""" if self.entry_group_state_changed_match is not None: @@ -424,7 +373,7 @@ self.entry_group_state_changed_match = None if self.group is not None: self.group.Reset() - + def add(self): """Derived from the Avahi example code""" self.remove() @@ -435,9 +384,9 @@ avahi.DBUS_INTERFACE_ENTRY_GROUP) self.entry_group_state_changed_match = ( self.group.connect_to_signal( - "StateChanged", self.entry_group_state_changed)) - log.debug("Adding Zeroconf service '%s' of type '%s' ...", - self.name, self.type) + 'StateChanged', self.entry_group_state_changed)) + logger.debug("Adding Zeroconf service '%s' of type '%s' ...", + self.name, self.type) self.group.AddService( self.interface, self.protocol, @@ -447,21 +396,21 @@ dbus.UInt16(self.port), avahi.string_array_to_txt_array(self.TXT)) self.group.Commit() - + def entry_group_state_changed(self, state, error): """Derived from the Avahi example code""" - log.debug("Avahi entry group state change: %i", state) - + logger.debug("Avahi entry group state change: %i", state) + if state == avahi.ENTRY_GROUP_ESTABLISHED: - log.debug("Zeroconf service established.") + logger.debug("Zeroconf service established.") elif state == avahi.ENTRY_GROUP_COLLISION: - log.info("Zeroconf service name collision.") + logger.info("Zeroconf service name collision.") self.rename() elif state == avahi.ENTRY_GROUP_FAILURE: - log.critical("Avahi: Error in group state changed %s", - str(error)) + logger.critical("Avahi: Error in group state changed %s", + str(error)) raise AvahiGroupError("State changed: {!s}".format(error)) - + def cleanup(self): """Derived from the Avahi example code""" if self.group is not None: @@ -472,10 +421,10 @@ pass self.group = None self.remove() - + def server_state_changed(self, state, error=None): """Derived from the Avahi example code""" - log.debug("Avahi server state change: %i", state) + logger.debug("Avahi server state change: %i", state) bad_states = { avahi.SERVER_INVALID: "Zeroconf server invalid", avahi.SERVER_REGISTERING: None, @@ -485,9 +434,9 @@ if state in bad_states: if bad_states[state] is not None: if error is None: - log.error(bad_states[state]) + logger.error(bad_states[state]) else: - log.error(bad_states[state] + ": %r", error) + logger.error(bad_states[state] + ": %r", error) self.cleanup() elif state == avahi.SERVER_RUNNING: try: @@ -495,18 +444,19 @@ except dbus.exceptions.DBusException as error: if (error.get_dbus_name() == "org.freedesktop.Avahi.CollisionError"): - log.info("Local Zeroconf service name collision.") + logger.info("Local Zeroconf service name" + " collision.") return self.rename(remove=False) else: - log.critical("D-Bus Exception", exc_info=error) + logger.critical("D-Bus Exception", exc_info=error) self.cleanup() os._exit(1) else: if error is None: - log.debug("Unknown state: %r", state) + logger.debug("Unknown state: %r", state) else: - log.debug("Unknown state: %r: %r", state, error) - + logger.debug("Unknown state: %r: %r", state, error) + def activate(self): """Derived from the Avahi example code""" if self.server is None: @@ -523,364 +473,287 @@ class AvahiServiceToSyslog(AvahiService): def rename(self, *args, **kwargs): """Add the new name to the syslog messages""" - ret = super(AvahiServiceToSyslog, self).rename(*args, - **kwargs) + ret = AvahiService.rename(self, *args, **kwargs) syslogger.setFormatter(logging.Formatter( - "Mandos ({}) [%(process)d]: %(levelname)s: %(message)s" + 'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s' .format(self.name))) return ret - # Pretend that we have a GnuTLS module -class gnutls: - """This isn't so much a class as it is a module-like namespace.""" - - library = ctypes.util.find_library("gnutls") - if library is None: - library = ctypes.util.find_library("gnutls-deb0") - _library = ctypes.cdll.LoadLibrary(library) - del library - +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.cdll.LoadLibrary( + ctypes.util.find_library("gnutls")) + _need_version = b"3.3.0" + def __init__(self): + # Need to use class name "GnuTLS" here, since this method is + # called before the assignment to the "gnutls" global variable + # happens. + if GnuTLS.check_version(self._need_version) is None: + raise GnuTLS.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. - + # Constants E_SUCCESS = 0 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 - class _session_int(ctypes.Structure): + class session_int(ctypes.Structure): _fields_ = [] - session_t = ctypes.POINTER(_session_int) - + session_t = ctypes.POINTER(session_int) class certificate_credentials_st(ctypes.Structure): _fields_ = [] certificate_credentials_t = ctypes.POINTER( certificate_credentials_st) certificate_type_t = ctypes.c_int - class datum_t(ctypes.Structure): - _fields_ = [("data", ctypes.POINTER(ctypes.c_ubyte)), - ("size", ctypes.c_uint)] - - class _openpgp_crt_int(ctypes.Structure): + _fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)), + ('size', ctypes.c_uint)] + class openpgp_crt_int(ctypes.Structure): _fields_ = [] - openpgp_crt_t = ctypes.POINTER(_openpgp_crt_int) - openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h + openpgp_crt_t = ctypes.POINTER(openpgp_crt_int) + openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p) credentials_type_t = ctypes.c_int transport_ptr_t = ctypes.c_void_p close_request_t = ctypes.c_int - + # Exceptions class Error(Exception): - def __init__(self, message=None, code=None, args=()): + # 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).decode( - "utf-8", errors="replace") - return super(gnutls.Error, self).__init__( + message = GnuTLS.strerror(code) + return super(GnuTLS.Error, self).__init__( message, *args) - + class CertificateSecurityError(Error): pass - - class PointerTo: - def __init__(self, cls): - self.cls = cls - - def from_param(self, obj): - if not isinstance(obj, self.cls): - raise TypeError("Not of type {}: {!r}" - .format(self.cls.__name__, obj)) - return ctypes.byref(obj.from_param(obj)) - - class CastToVoidPointer: - def __init__(self, cls): - self.cls = cls - - def from_param(self, obj): - if not isinstance(obj, self.cls): - raise TypeError("Not of type {}: {!r}" - .format(self.cls.__name__, obj)) - return ctypes.cast(obj.from_param(obj), ctypes.c_void_p) - - class With_from_param: - @classmethod - def from_param(cls, obj): - return obj._as_parameter_ - + # Classes - class Credentials(With_from_param): + class Credentials(object): def __init__(self): - self._as_parameter_ = gnutls.certificate_credentials_t() - gnutls.certificate_allocate_credentials(self) + self._c_object = gnutls.certificate_credentials_t() + gnutls.certificate_allocate_credentials( + ctypes.byref(self._c_object)) self.type = gnutls.CRD_CERTIFICATE - + def __del__(self): - gnutls.certificate_free_credentials(self) - - class ClientSession(With_from_param): - def __init__(self, socket, credentials=None): - self._as_parameter_ = gnutls.session_t() - gnutls_flags = gnutls.CLIENT - if gnutls.check_version(b"3.5.6"): - gnutls_flags |= gnutls.NO_TICKETS - if gnutls.has_rawpk: - gnutls_flags |= gnutls.ENABLE_RAWPK - gnutls.init(self, gnutls_flags) - del gnutls_flags - gnutls.set_default_priority(self) - gnutls.transport_set_ptr(self, socket.fileno()) - gnutls.handshake_set_private_extensions(self, True) + gnutls.certificate_free_credentials(self._c_object) + + class ClientSession(object): + def __init__(self, socket, credentials = None): + self._c_object = gnutls.session_t() + 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, + True) self.socket = socket if credentials is None: credentials = gnutls.Credentials() - gnutls.credentials_set(self, credentials.type, - credentials) + gnutls.credentials_set(self._c_object, credentials.type, + ctypes.cast(credentials._c_object, + ctypes.c_void_p)) self.credentials = credentials - + def __del__(self): - gnutls.deinit(self) - + gnutls.deinit(self._c_object) + def handshake(self): - return gnutls.handshake(self) - + return gnutls.handshake(self._c_object) + def send(self, data): data = bytes(data) data_len = len(data) while data_len > 0: - data_len -= gnutls.record_send(self, data[-data_len:], + data_len -= gnutls.record_send(self._c_object, + data[-data_len:], data_len) - + def bye(self): - return gnutls.bye(self, gnutls.SHUT_RDWR) - + return gnutls.bye(self._c_object, gnutls.SHUT_RDWR) + # Error handling functions def _error_code(result): """A function to raise exceptions on errors, suitable - for the "restype" attribute on ctypes functions""" - if result >= gnutls.E_SUCCESS: + for the 'restype' attribute on ctypes functions""" + if result >= 0: return result if result == gnutls.E_NO_CERTIFICATE_FOUND: - raise gnutls.CertificateSecurityError(code=result) - raise gnutls.Error(code=result) - - def _retry_on_error(result, func, arguments, - _error_code=_error_code): + raise gnutls.CertificateSecurityError(code = result) + raise gnutls.Error(code = result) + + def _retry_on_error(result, func, arguments): """A function to retry on some errors, suitable - for the "errcheck" attribute on ctypes functions""" - while result < gnutls.E_SUCCESS: + for the 'errcheck' attribute on ctypes functions""" + while result < 0: if result not in (gnutls.E_INTERRUPTED, gnutls.E_AGAIN): return _error_code(result) result = func(*arguments) return result - + # Unless otherwise indicated, the function declarations below are # all from the gnutls/gnutls.h C header file. - + # Functions priority_set_direct = _library.gnutls_priority_set_direct - priority_set_direct.argtypes = [ClientSession, ctypes.c_char_p, + priority_set_direct.argtypes = [session_t, ctypes.c_char_p, ctypes.POINTER(ctypes.c_char_p)] priority_set_direct.restype = _error_code - + init = _library.gnutls_init - init.argtypes = [PointerTo(ClientSession), ctypes.c_int] + init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int] init.restype = _error_code - + set_default_priority = _library.gnutls_set_default_priority - set_default_priority.argtypes = [ClientSession] + set_default_priority.argtypes = [session_t] set_default_priority.restype = _error_code - + record_send = _library.gnutls_record_send - record_send.argtypes = [ClientSession, ctypes.c_void_p, + record_send.argtypes = [session_t, ctypes.c_void_p, ctypes.c_size_t] record_send.restype = ctypes.c_ssize_t record_send.errcheck = _retry_on_error - + certificate_allocate_credentials = ( _library.gnutls_certificate_allocate_credentials) certificate_allocate_credentials.argtypes = [ - PointerTo(Credentials)] + ctypes.POINTER(certificate_credentials_t)] certificate_allocate_credentials.restype = _error_code - + certificate_free_credentials = ( _library.gnutls_certificate_free_credentials) - certificate_free_credentials.argtypes = [Credentials] + certificate_free_credentials.argtypes = [certificate_credentials_t] certificate_free_credentials.restype = None - + handshake_set_private_extensions = ( _library.gnutls_handshake_set_private_extensions) - handshake_set_private_extensions.argtypes = [ClientSession, + handshake_set_private_extensions.argtypes = [session_t, ctypes.c_int] handshake_set_private_extensions.restype = None - + credentials_set = _library.gnutls_credentials_set - credentials_set.argtypes = [ClientSession, credentials_type_t, - CastToVoidPointer(Credentials)] + credentials_set.argtypes = [session_t, credentials_type_t, + ctypes.c_void_p] credentials_set.restype = _error_code - + strerror = _library.gnutls_strerror strerror.argtypes = [ctypes.c_int] strerror.restype = ctypes.c_char_p - + certificate_type_get = _library.gnutls_certificate_type_get - certificate_type_get.argtypes = [ClientSession] + certificate_type_get.argtypes = [session_t] certificate_type_get.restype = _error_code - + certificate_get_peers = _library.gnutls_certificate_get_peers - certificate_get_peers.argtypes = [ClientSession, + certificate_get_peers.argtypes = [session_t, ctypes.POINTER(ctypes.c_uint)] certificate_get_peers.restype = ctypes.POINTER(datum_t) - + global_set_log_level = _library.gnutls_global_set_log_level global_set_log_level.argtypes = [ctypes.c_int] global_set_log_level.restype = None - + global_set_log_function = _library.gnutls_global_set_log_function global_set_log_function.argtypes = [log_func] global_set_log_function.restype = None - + deinit = _library.gnutls_deinit - deinit.argtypes = [ClientSession] + deinit.argtypes = [session_t] deinit.restype = None - + handshake = _library.gnutls_handshake - handshake.argtypes = [ClientSession] - handshake.restype = ctypes.c_int + handshake.argtypes = [session_t] + handshake.restype = _error_code handshake.errcheck = _retry_on_error - + transport_set_ptr = _library.gnutls_transport_set_ptr - transport_set_ptr.argtypes = [ClientSession, transport_ptr_t] + transport_set_ptr.argtypes = [session_t, transport_ptr_t] transport_set_ptr.restype = None - + bye = _library.gnutls_bye - bye.argtypes = [ClientSession, close_request_t] - bye.restype = ctypes.c_int + bye.argtypes = [session_t, close_request_t] + bye.restype = _error_code bye.errcheck = _retry_on_error - + check_version = _library.gnutls_check_version 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(b"3.6.4"): - certificate_type_get2 = _library.gnutls_certificate_type_get2 - certificate_type_get2.argtypes = [ClientSession, 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 func, *args, **kwargs): """This function is meant to be called by multiprocessing.Process - + This function runs func(*args, **kwargs), and writes the resulting return value on the provided multiprocessing.Connection. """ connection.send(func(*args, **kwargs)) connection.close() - -class Client: +class Client(object): """A representation of a client host served by this server. - + Attributes: - approved: bool(); None if not yet approved/disapproved + approved: bool(); 'None' if not yet approved/disapproved approval_delay: datetime.timedelta(); Time to wait for approval approval_duration: datetime.timedelta(); Duration of one approval - checker: multiprocessing.Process(); a running checker process used - to see if the client lives. None if no process is - running. + checker: subprocess.Popen(); a running checker process used + to see if the client lives. + 'None' if no process is running. checker_callback_tag: a GLib event source tag, or None checker_command: string; External command which is run to check if client lives. %() expansions are done at @@ -894,9 +767,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 @@ -918,9 +789,9 @@ disabled, or None server_settings: The server_settings dict from main() """ - + 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") @@ -935,7 +806,7 @@ "approved_by_default": "True", "enabled": "True", } - + @staticmethod def config_parser(config): """Construct a new dict of client settings of this form: @@ -948,19 +819,17 @@ for client_name in config.sections(): section = dict(config.items(client_name)) client = settings[client_name] = {} - + client["host"] = section["host"] # Reformat values from string types to Python types client["approved_by_default"] = config.getboolean( client_name, "approved_by_default") 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: @@ -987,10 +856,10 @@ client["last_approval_request"] = None client["last_checked_ok"] = None client["last_checker_status"] = -2 - + return settings - - def __init__(self, settings, name=None, server_settings=None): + + def __init__(self, settings, name = None, server_settings=None): self.name = name if server_settings is None: server_settings = {} @@ -998,7 +867,7 @@ # adding all client settings for setting, value in settings.items(): setattr(self, setting, value) - + if self.enabled: if not hasattr(self, "last_enabled"): self.last_enabled = datetime.datetime.utcnow() @@ -1008,13 +877,12 @@ else: self.last_enabled = None self.expires = None - - log.debug("Creating client %r", self.name) - log.debug(" Key ID: %s", self.key_id) - log.debug(" Fingerprint: %s", self.fingerprint) + + logger.debug("Creating client %r", self.name) + logger.debug(" Fingerprint: %s", self.fingerprint) self.created = settings.get("created", datetime.datetime.utcnow()) - + # attributes specific for this server instance self.checker = None self.checker_initiator_tag = None @@ -1029,17 +897,17 @@ for attr in self.__dict__.keys() if not attr.startswith("_")] self.client_structure.append("client_structure") - + for name, t in inspect.getmembers( type(self), lambda obj: isinstance(obj, property)): if not name.startswith("_"): self.client_structure.append(name) - + # Send notice to process children that client state has changed def send_changedstate(self): with self.changedstate: self.changedstate.notify_all() - + def enable(self): """Start this client's checker and timeout hooks""" if getattr(self, "enabled", False): @@ -1050,13 +918,13 @@ self.last_enabled = datetime.datetime.utcnow() self.init_checker() self.send_changedstate() - + def disable(self, quiet=True): """Disable this client.""" if not getattr(self, "enabled", False): return False if not quiet: - log.info("Disabling client %s", self.name) + logger.info("Disabling client %s", self.name) if getattr(self, "disable_initiator_tag", None) is not None: GLib.source_remove(self.disable_initiator_tag) self.disable_initiator_tag = None @@ -1070,18 +938,17 @@ self.send_changedstate() # Do not run this again if called by a GLib.timeout_add return False - + def __del__(self): self.disable() - + def init_checker(self): # Schedule a new checker to be started an 'interval' from now, # and every interval from then on. if self.checker_initiator_tag is not None: GLib.source_remove(self.checker_initiator_tag) self.checker_initiator_tag = GLib.timeout_add( - random.randrange(int(self.interval.total_seconds() * 1000 - + 1)), + int(self.interval.total_seconds() * 1000), self.start_checker) # Schedule a disable() when 'timeout' has passed if self.disable_initiator_tag is not None: @@ -1090,39 +957,39 @@ int(self.timeout.total_seconds() * 1000), self.disable) # Also start a new checker *right now*. self.start_checker() - + def checker_callback(self, source, condition, connection, command): """The checker has completed, so take appropriate actions.""" + self.checker_callback_tag = None + self.checker = None # Read return code from connection (see call_pipe) returncode = connection.recv() connection.close() - if self.checker is not None: - self.checker.join() - self.checker_callback_tag = None - self.checker = None - + if returncode >= 0: self.last_checker_status = returncode self.last_checker_signal = None if self.last_checker_status == 0: - log.info("Checker for %(name)s succeeded", vars(self)) + logger.info("Checker for %(name)s succeeded", + vars(self)) self.checked_ok() else: - log.info("Checker for %(name)s failed", vars(self)) + logger.info("Checker for %(name)s failed", vars(self)) else: self.last_checker_status = -1 self.last_checker_signal = -returncode - log.warning("Checker for %(name)s crashed?", vars(self)) + logger.warning("Checker for %(name)s crashed?", + vars(self)) return False - + def checked_ok(self): """Assert that the client has been seen, alive and well.""" self.last_checked_ok = datetime.datetime.utcnow() self.last_checker_status = 0 self.last_checker_signal = None self.bump_timeout() - + def bump_timeout(self, timeout=None): """Bump up the timeout for this client.""" if timeout is None: @@ -1134,13 +1001,13 @@ self.disable_initiator_tag = GLib.timeout_add( int(timeout.total_seconds() * 1000), self.disable) self.expires = datetime.datetime.utcnow() + timeout - + def need_approval(self): self.last_approval_request = datetime.datetime.utcnow() - + def start_checker(self): """Start a new checker subprocess if one is not running. - + If a checker already exists, leave it running and do nothing.""" # The reason for not killing a running checker is that if we @@ -1151,25 +1018,27 @@ # checkers alone, the checker would have to take more time # than 'timeout' for the client to be disabled, which is as it # should be. - + if self.checker is not None and not self.checker.is_alive(): - log.warning("Checker was not alive; joining") + logger.warning("Checker was not alive; joining") self.checker.join() self.checker = None # Start a new checker if needed if self.checker is None: # Escape attributes for the shell escaped_attrs = { - attr: shlex.quote(str(getattr(self, attr))) - for attr in self.runtime_expansions} + attr: re.escape(str(getattr(self, attr))) + for attr in self.runtime_expansions } try: command = self.checker_command % escaped_attrs except TypeError as error: - log.error('Could not format string "%s"', - self.checker_command, exc_info=error) + logger.error('Could not format string "%s"', + self.checker_command, + exc_info=error) return True # Try again later self.current_checker_command = command - log.info("Starting checker %r for %s", command, self.name) + logger.info("Starting checker %r for %s", command, + self.name) # We don't need to redirect stdout and stderr, since # in normal mode, that is already done by daemon(), # and in debug mode we don't want to. (Stdin is @@ -1177,26 +1046,25 @@ # The exception is when not debugging but nevertheless # running in the foreground; use the previously # created wnull. - popen_args = {"close_fds": True, - "shell": True, - "cwd": "/"} + popen_args = { "close_fds": True, + "shell": True, + "cwd": "/" } if (not self.server_settings["debug"] and self.server_settings["foreground"]): popen_args.update({"stdout": wnull, - "stderr": wnull}) - pipe = multiprocessing.Pipe(duplex=False) + "stderr": wnull }) + pipe = multiprocessing.Pipe(duplex = False) self.checker = multiprocessing.Process( - target=call_pipe, - args=(pipe[1], subprocess.call, command), - kwargs=popen_args) + target = call_pipe, + args = (pipe[1], subprocess.call, command), + kwargs = popen_args) self.checker.start() self.checker_callback_tag = GLib.io_add_watch( - GLib.IOChannel.unix_new(pipe[0].fileno()), - GLib.PRIORITY_DEFAULT, GLib.IO_IN, + pipe[0].fileno(), GLib.IO_IN, self.checker_callback, pipe[0], command) # Re-run this periodically if run by GLib.timeout_add return True - + def stop_checker(self): """Force the checker process, if any, to stop.""" if self.checker_callback_tag: @@ -1204,7 +1072,7 @@ self.checker_callback_tag = None if getattr(self, "checker", None) is None: return - log.debug("Stopping checker for %(name)s", vars(self)) + logger.debug("Stopping checker for %(name)s", vars(self)) self.checker.terminate() self.checker = None @@ -1215,10 +1083,10 @@ byte_arrays=False): """Decorators for marking methods of a DBusObjectWithProperties to become properties on the D-Bus. - + The decorated method will be called with no arguments by "Get" and with one argument by "Set". - + The parameters, where they are supported, are the same as dbus.service.method, except there is only "signature", since the type from Get() and the type sent to Set() is the same. @@ -1228,7 +1096,7 @@ if byte_arrays and signature != "ay": raise ValueError("Byte arrays not supported for non-'ay'" " signature {!r}".format(signature)) - + def decorator(func): func._dbus_is_property = True func._dbus_interface = dbus_interface @@ -1237,37 +1105,37 @@ func._dbus_name = func.__name__ if func._dbus_name.endswith("_dbus_property"): func._dbus_name = func._dbus_name[:-14] - func._dbus_get_args_options = {"byte_arrays": byte_arrays} + func._dbus_get_args_options = {'byte_arrays': byte_arrays } return func - + return decorator def dbus_interface_annotations(dbus_interface): """Decorator for marking functions returning interface annotations - + Usage: - + @dbus_interface_annotations("org.example.Interface") def _foo(self): # Function name does not matter return {"org.freedesktop.DBus.Deprecated": "true", "org.freedesktop.DBus.Property.EmitsChangedSignal": "false"} """ - + def decorator(func): func._dbus_is_interface = True func._dbus_interface = dbus_interface func._dbus_name = dbus_interface return func - + return decorator def dbus_annotations(annotations): """Decorator to annotate D-Bus methods, signals or properties Usage: - + @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true", "org.freedesktop.DBus.Property." "EmitsChangedSignal": "false"}) @@ -1275,14 +1143,14 @@ access="r") def Property_dbus_property(self): return dbus.Boolean(False) - + See also the DBusObjectWithAnnotations class. """ - + def decorator(func): func._dbus_annotations = annotations return func - + return decorator @@ -1306,21 +1174,21 @@ class DBusObjectWithAnnotations(dbus.service.Object): """A D-Bus object with annotations. - + Classes inheriting from this can use the dbus_annotations decorator to add annotations to methods or signals. """ - + @staticmethod def _is_dbus_thing(thing): """Returns a function testing if an attribute is a D-Bus thing - + If called like _is_dbus_thing("method") it returns a function suitable for use as predicate to inspect.getmembers(). """ return lambda obj: getattr(obj, "_dbus_is_{}".format(thing), False) - + def _get_all_dbus_things(self, thing): """Returns a generator of (name, attribute) pairs """ @@ -1329,21 +1197,21 @@ for cls in self.__class__.__mro__ for name, athing in inspect.getmembers(cls, self._is_dbus_thing(thing))) - + @dbus.service.method(dbus.INTROSPECTABLE_IFACE, - out_signature="s", - path_keyword="object_path", - connection_keyword="connection") + out_signature = "s", + path_keyword = 'object_path', + connection_keyword = 'connection') def Introspect(self, object_path, connection): """Overloading of standard D-Bus method. - + Inserts annotation tags on methods and signals. """ xmlstring = dbus.service.Object.Introspect(self, object_path, connection) try: document = xml.dom.minidom.parseString(xmlstring) - + for if_tag in document.getElementsByTagName("interface"): # Add annotation tags for typ in ("method", "signal"): @@ -1376,7 +1244,7 @@ if_tag.appendChild(ann_tag) # Fix argument name for the Introspect method itself if (if_tag.getAttribute("name") - == dbus.INTROSPECTABLE_IFACE): + == dbus.INTROSPECTABLE_IFACE): for cn in if_tag.getElementsByTagName("method"): if cn.getAttribute("name") == "Introspect": for arg in cn.getElementsByTagName("arg"): @@ -1388,19 +1256,19 @@ document.unlink() except (AttributeError, xml.dom.DOMException, xml.parsers.expat.ExpatError) as error: - log.error("Failed to override Introspection method", - exc_info=error) + logger.error("Failed to override Introspection method", + exc_info=error) return xmlstring class DBusObjectWithProperties(DBusObjectWithAnnotations): """A D-Bus object with properties. - + Classes inheriting from this can use the dbus_service_property decorator to expose methods as D-Bus properties. It exposes the standard Get(), Set(), and GetAll() methods on the D-Bus. """ - + def _get_dbus_property(self, interface_name, property_name): """Returns a bound method if one exists which is a D-Bus property with the specified name and interface. @@ -1411,11 +1279,11 @@ if (value._dbus_name == property_name and value._dbus_interface == interface_name): return value.__get__(self) - + # No such property raise DBusPropertyNotFound("{}:{}.{}".format( self.dbus_object_path, interface_name, property_name)) - + @classmethod def _get_all_interface_names(cls): """Get a sequence of all interfaces supported by an object""" @@ -1424,7 +1292,7 @@ for x in (inspect.getmro(cls)) for attr in dir(x)) if name is not None) - + @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss", out_signature="v") @@ -1438,7 +1306,7 @@ if not hasattr(value, "variant_level"): return value return type(value)(value, variant_level=value.variant_level+1) - + @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv") def Set(self, interface_name, property_name, value): """Standard D-Bus property Set() method, see D-Bus standard. @@ -1453,16 +1321,17 @@ raise ValueError("Byte arrays not supported for non-" "'ay' signature {!r}" .format(prop._dbus_signature)) - value = dbus.ByteArray(bytes(value)) + value = dbus.ByteArray(b''.join(chr(byte) + for byte in value)) prop(value) - + @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s", out_signature="a{sv}") def GetAll(self, interface_name): """Standard D-Bus property GetAll() method, see D-Bus standard. - + Note: Will not include properties with access="write". """ properties = {} @@ -1479,9 +1348,9 @@ properties[name] = value continue properties[name] = type(value)( - value, variant_level=value.variant_level + 1) + value, variant_level = value.variant_level + 1) return dbus.Dictionary(properties, signature="sv") - + @dbus.service.signal(dbus.PROPERTIES_IFACE, signature="sa{sv}as") def PropertiesChanged(self, interface_name, changed_properties, invalidated_properties): @@ -1489,14 +1358,14 @@ standard. """ pass - + @dbus.service.method(dbus.INTROSPECTABLE_IFACE, out_signature="s", - path_keyword="object_path", - connection_keyword="connection") + path_keyword='object_path', + connection_keyword='connection') def Introspect(self, object_path, connection): """Overloading of standard D-Bus method. - + Inserts property tags and interface annotation tags. """ xmlstring = DBusObjectWithAnnotations.Introspect(self, @@ -1504,14 +1373,14 @@ connection) try: document = xml.dom.minidom.parseString(xmlstring) - + def make_tag(document, name, prop): e = document.createElement("property") e.setAttribute("name", name) e.setAttribute("type", prop._dbus_signature) e.setAttribute("access", prop._dbus_access) return e - + for if_tag in document.getElementsByTagName("interface"): # Add property tags for tag in (make_tag(document, name, prop) @@ -1555,50 +1424,48 @@ document.unlink() except (AttributeError, xml.dom.DOMException, xml.parsers.expat.ExpatError) as error: - log.error("Failed to override Introspection method", - exc_info=error) + logger.error("Failed to override Introspection method", + exc_info=error) return xmlstring - try: dbus.OBJECT_MANAGER_IFACE except AttributeError: dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager" - class DBusObjectWithObjectManager(DBusObjectWithAnnotations): """A D-Bus object with an ObjectManager. - + Classes inheriting from this exposes the standard GetManagedObjects call and the InterfacesAdded and InterfacesRemoved signals on the standard "org.freedesktop.DBus.ObjectManager" interface. - + Note: No signals are sent automatically; they must be sent manually. """ @dbus.service.method(dbus.OBJECT_MANAGER_IFACE, - out_signature="a{oa{sa{sv}}}") + out_signature = "a{oa{sa{sv}}}") def GetManagedObjects(self): """This function must be overridden""" raise NotImplementedError() - + @dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, - signature="oa{sa{sv}}") + signature = "oa{sa{sv}}") def InterfacesAdded(self, object_path, interfaces_and_properties): pass - - @dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature="oas") + + @dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas") def InterfacesRemoved(self, object_path, interfaces): pass - + @dbus.service.method(dbus.INTROSPECTABLE_IFACE, - out_signature="s", - path_keyword="object_path", - connection_keyword="connection") + out_signature = "s", + path_keyword = 'object_path', + connection_keyword = 'connection') def Introspect(self, object_path, connection): """Overloading of standard D-Bus method. - + Override return argument name of GetManagedObjects to be "objpath_interfaces_and_properties" """ @@ -1607,11 +1474,11 @@ connection) try: document = xml.dom.minidom.parseString(xmlstring) - + for if_tag in document.getElementsByTagName("interface"): # Fix argument name for the GetManagedObjects method if (if_tag.getAttribute("name") - == dbus.OBJECT_MANAGER_IFACE): + == dbus.OBJECT_MANAGER_IFACE): for cn in if_tag.getElementsByTagName("method"): if (cn.getAttribute("name") == "GetManagedObjects"): @@ -1626,15 +1493,14 @@ document.unlink() except (AttributeError, xml.dom.DOMException, xml.parsers.expat.ExpatError) as error: - log.error("Failed to override Introspection method", - exc_info=error) + logger.error("Failed to override Introspection method", + exc_info = error) return xmlstring - def datetime_to_dbus(dt, variant_level=0): """Convert a UTC datetime.datetime() to a D-Bus type.""" if dt is None: - return dbus.String("", variant_level=variant_level) + return dbus.String("", variant_level = variant_level) return dbus.String(dt.isoformat(), variant_level=variant_level) @@ -1643,25 +1509,25 @@ dbus.service.Object, it will add alternate D-Bus attributes with interface names according to the "alt_interface_names" mapping. Usage: - + @alternate_dbus_interfaces({"org.example.Interface": "net.example.AlternateInterface"}) class SampleDBusObject(dbus.service.Object): @dbus.service.method("org.example.Interface") def SampleDBusMethod(): pass - + The above "SampleDBusMethod" on "SampleDBusObject" will be reachable via two interfaces: "org.example.Interface" and "net.example.AlternateInterface", the latter of which will have its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to "true", unless "deprecate" is passed with a False value. - + This works for methods and signals, and also for D-Bus properties (from DBusObjectWithProperties) and interfaces (from the dbus_interface_annotations decorator). """ - + def wrapper(cls): for orig_interface_name, alt_interface_name in ( alt_interface_names.items()): @@ -1707,7 +1573,6 @@ attribute._dbus_annotations) except AttributeError: pass - # Define a creator of a function to call both the # original and alternate functions, so both the # original and alternate signals gets sent when @@ -1716,19 +1581,18 @@ """This function is a scope container to pass func1 and func2 to the "call_both" function outside of its arguments""" - + @functools.wraps(func2) def call_both(*args, **kwargs): """This function will emit two D-Bus signals by calling func1 and func2""" func1(*args, **kwargs) func2(*args, **kwargs) - # Make wrapper function look like a D-Bus - # signal + # Make wrapper function look like a D-Bus signal for name, attr in inspect.getmembers(func2): if name.startswith("_dbus_"): setattr(call_both, name, attr) - + return call_both # Create the "call_both" function and add it to # the class @@ -1780,13 +1644,13 @@ (copy_function(attribute))) if deprecate: # Deprecate all alternate interfaces - iname = "_AlternateDBusNames_interface_annotation{}" + iname="_AlternateDBusNames_interface_annotation{}" for interface_name in interface_names: - + @dbus_interface_annotations(interface_name) def func(self): - return {"org.freedesktop.DBus.Deprecated": - "true"} + return { "org.freedesktop.DBus.Deprecated": + "true" } # Find an unused name for aname in (iname.format(i) for i in itertools.count()): @@ -1803,7 +1667,7 @@ cls = type("{}Alternate".format(cls.__name__), (cls, ), attr) return cls - + return wrapper @@ -1811,20 +1675,20 @@ "se.bsnet.fukt.Mandos"}) class ClientDBus(Client, DBusObjectWithProperties): """A Client class using D-Bus - + Attributes: dbus_object_path: dbus.ObjectPath bus: dbus.SystemBus() """ - + runtime_expansions = (Client.runtime_expansions + ("dbus_object_path", )) - + _interface = "se.recompile.Mandos.Client" - + # dbus.service.Object doesn't use super(), so we can't either. - - def __init__(self, bus=None, *args, **kwargs): + + def __init__(self, bus = None, *args, **kwargs): self.bus = bus Client.__init__(self, *args, **kwargs) # Only now, when this client is initialized, can it show up on @@ -1836,7 +1700,7 @@ "/clients/" + client_object_name) DBusObjectWithProperties.__init__(self, self.bus, self.dbus_object_path) - + def notifychangeproperty(transform_func, dbus_name, type_func=lambda x: x, variant_level=1, @@ -1844,7 +1708,7 @@ _interface=_interface): """ Modify a variable so that it's a property which announces its changes to DBus. - + transform_fun: Function that takes a value and a variant_level and transforms it to a D-Bus type. dbus_name: D-Bus name of the variable @@ -1853,7 +1717,7 @@ variant_level: D-Bus variant level. Default: 1 """ attrname = "_{}".format(dbus_name) - + def setter(self, value): if hasattr(self, "dbus_object_path"): if (not hasattr(self, attrname) or @@ -1866,28 +1730,28 @@ else: dbus_value = transform_func( type_func(value), - variant_level=variant_level) + variant_level = variant_level) self.PropertyChanged(dbus.String(dbus_name), dbus_value) self.PropertiesChanged( _interface, - dbus.Dictionary({dbus.String(dbus_name): - dbus_value}), + dbus.Dictionary({ dbus.String(dbus_name): + dbus_value }), dbus.Array()) setattr(self, attrname, value) - + return property(lambda self: getattr(self, attrname), setter) - + expires = notifychangeproperty(datetime_to_dbus, "Expires") approvals_pending = notifychangeproperty(dbus.Boolean, "ApprovalPending", - type_func=bool) + type_func = bool) enabled = notifychangeproperty(dbus.Boolean, "Enabled") last_enabled = notifychangeproperty(datetime_to_dbus, "LastEnabled") checker = notifychangeproperty( dbus.Boolean, "CheckerRunning", - type_func=lambda checker: checker is not None) + type_func = lambda checker: checker is not None) last_checked_ok = notifychangeproperty(datetime_to_dbus, "LastCheckedOK") last_checker_status = notifychangeproperty(dbus.Int16, @@ -1898,26 +1762,26 @@ "ApprovedByDefault") approval_delay = notifychangeproperty( dbus.UInt64, "ApprovalDelay", - type_func=lambda td: td.total_seconds() * 1000) + type_func = lambda td: td.total_seconds() * 1000) approval_duration = notifychangeproperty( dbus.UInt64, "ApprovalDuration", - type_func=lambda td: td.total_seconds() * 1000) + type_func = lambda td: td.total_seconds() * 1000) host = notifychangeproperty(dbus.String, "Host") timeout = notifychangeproperty( dbus.UInt64, "Timeout", - type_func=lambda td: td.total_seconds() * 1000) + type_func = lambda td: td.total_seconds() * 1000) extended_timeout = notifychangeproperty( dbus.UInt64, "ExtendedTimeout", - type_func=lambda td: td.total_seconds() * 1000) + type_func = lambda td: td.total_seconds() * 1000) interval = notifychangeproperty( dbus.UInt64, "Interval", - type_func=lambda td: td.total_seconds() * 1000) + type_func = lambda td: td.total_seconds() * 1000) checker_command = notifychangeproperty(dbus.String, "Checker") secret = notifychangeproperty(dbus.ByteArray, "Secret", invalidate_only=True) - + del notifychangeproperty - + def __del__(self, *args, **kwargs): try: self.remove_from_connection() @@ -1926,7 +1790,7 @@ if hasattr(DBusObjectWithProperties, "__del__"): DBusObjectWithProperties.__del__(self, *args, **kwargs) Client.__del__(self, *args, **kwargs) - + def checker_callback(self, source, condition, connection, command, *args, **kwargs): ret = Client.checker_callback(self, source, condition, @@ -1948,7 +1812,7 @@ | self.last_checker_signal), dbus.String(command)) return ret - + def start_checker(self, *args, **kwargs): old_checker_pid = getattr(self.checker, "pid", None) r = Client.start_checker(self, *args, **kwargs) @@ -1958,42 +1822,42 @@ # Emit D-Bus signal self.CheckerStarted(self.current_checker_command) return r - + def _reset_approved(self): self.approved = None return False - + def approve(self, value=True): self.approved = value GLib.timeout_add(int(self.approval_duration.total_seconds() * 1000), self._reset_approved) self.send_changedstate() - - # D-Bus methods, signals & properties - - # Interfaces - - # Signals - + + ## D-Bus methods, signals & properties + + ## Interfaces + + ## Signals + # CheckerCompleted - signal @dbus.service.signal(_interface, signature="nxs") def CheckerCompleted(self, exitcode, waitstatus, command): "D-Bus signal" pass - + # CheckerStarted - signal @dbus.service.signal(_interface, signature="s") def CheckerStarted(self, command): "D-Bus signal" pass - + # PropertyChanged - signal @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"}) @dbus.service.signal(_interface, signature="sv") def PropertyChanged(self, property, value): "D-Bus signal" pass - + # GotSecret - signal @dbus.service.signal(_interface) def GotSecret(self): @@ -2002,65 +1866,65 @@ server to mandos-client """ pass - + # Rejected - signal @dbus.service.signal(_interface, signature="s") def Rejected(self, reason): "D-Bus signal" pass - + # NeedApproval - signal @dbus.service.signal(_interface, signature="tb") def NeedApproval(self, timeout, default): "D-Bus signal" return self.need_approval() - - # Methods - + + ## Methods + # Approve - method @dbus.service.method(_interface, in_signature="b") def Approve(self, value): self.approve(value) - + # CheckedOK - method @dbus.service.method(_interface) def CheckedOK(self): self.checked_ok() - + # Enable - method @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"}) @dbus.service.method(_interface) def Enable(self): "D-Bus method" self.enable() - + # StartChecker - method @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"}) @dbus.service.method(_interface) def StartChecker(self): "D-Bus method" self.start_checker() - + # Disable - method @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"}) @dbus.service.method(_interface) def Disable(self): "D-Bus method" self.disable() - + # StopChecker - method @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"}) @dbus.service.method(_interface) def StopChecker(self): self.stop_checker() - - # Properties - + + ## Properties + # ApprovalPending - property @dbus_service_property(_interface, signature="b", access="read") def ApprovalPending_dbus_property(self): return dbus.Boolean(bool(self.approvals_pending)) - + # ApprovedByDefault - property @dbus_service_property(_interface, signature="b", @@ -2069,7 +1933,7 @@ if value is None: # get return dbus.Boolean(self.approved_by_default) self.approved_by_default = bool(value) - + # ApprovalDelay - property @dbus_service_property(_interface, signature="t", @@ -2079,7 +1943,7 @@ return dbus.UInt64(self.approval_delay.total_seconds() * 1000) self.approval_delay = datetime.timedelta(0, 0, 0, value) - + # ApprovalDuration - property @dbus_service_property(_interface, signature="t", @@ -2089,28 +1953,21 @@ return dbus.UInt64(self.approval_duration.total_seconds() * 1000) self.approval_duration = datetime.timedelta(0, 0, 0, value) - + # Name - property @dbus_annotations( {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"}) @dbus_service_property(_interface, signature="s", access="read") 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"}) @dbus_service_property(_interface, signature="s", access="read") def Fingerprint_dbus_property(self): return dbus.String(self.fingerprint) - + # Host - property @dbus_service_property(_interface, signature="s", @@ -2119,19 +1976,19 @@ if value is None: # get return dbus.String(self.host) self.host = str(value) - + # Created - property @dbus_annotations( {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"}) @dbus_service_property(_interface, signature="s", access="read") def Created_dbus_property(self): return datetime_to_dbus(self.created) - + # LastEnabled - property @dbus_service_property(_interface, signature="s", access="read") def LastEnabled_dbus_property(self): return datetime_to_dbus(self.last_enabled) - + # Enabled - property @dbus_service_property(_interface, signature="b", @@ -2143,7 +2000,7 @@ self.enable() else: self.disable() - + # LastCheckedOK - property @dbus_service_property(_interface, signature="s", @@ -2153,22 +2010,22 @@ self.checked_ok() return return datetime_to_dbus(self.last_checked_ok) - + # LastCheckerStatus - property @dbus_service_property(_interface, signature="n", access="read") def LastCheckerStatus_dbus_property(self): return dbus.Int16(self.last_checker_status) - + # Expires - property @dbus_service_property(_interface, signature="s", access="read") def Expires_dbus_property(self): return datetime_to_dbus(self.expires) - + # LastApprovalRequest - property @dbus_service_property(_interface, signature="s", access="read") def LastApprovalRequest_dbus_property(self): return datetime_to_dbus(self.last_approval_request) - + # Timeout - property @dbus_service_property(_interface, signature="t", @@ -2193,7 +2050,7 @@ self.disable_initiator_tag = GLib.timeout_add( int((self.expires - now).total_seconds() * 1000), self.disable) - + # ExtendedTimeout - property @dbus_service_property(_interface, signature="t", @@ -2203,7 +2060,7 @@ return dbus.UInt64(self.extended_timeout.total_seconds() * 1000) self.extended_timeout = datetime.timedelta(0, 0, 0, value) - + # Interval - property @dbus_service_property(_interface, signature="t", @@ -2219,8 +2076,8 @@ GLib.source_remove(self.checker_initiator_tag) self.checker_initiator_tag = GLib.timeout_add( value, self.start_checker) - self.start_checker() # Start one now, too - + self.start_checker() # Start one now, too + # Checker - property @dbus_service_property(_interface, signature="s", @@ -2229,7 +2086,7 @@ if value is None: # get return dbus.String(self.checker_command) self.checker_command = str(value) - + # CheckerRunning - property @dbus_service_property(_interface, signature="b", @@ -2241,15 +2098,15 @@ self.start_checker() else: self.stop_checker() - + # ObjectPath - property @dbus_annotations( {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const", "org.freedesktop.DBus.Deprecated": "true"}) @dbus_service_property(_interface, signature="o", access="read") def ObjectPath_dbus_property(self): - return self.dbus_object_path # is already a dbus.ObjectPath - + return self.dbus_object_path # is already a dbus.ObjectPath + # Secret = property @dbus_annotations( {"org.freedesktop.DBus.Property.EmitsChangedSignal": @@ -2260,147 +2117,135 @@ byte_arrays=True) def Secret_dbus_property(self, value): self.secret = bytes(value) - + del _interface -class ProxyClient: - def __init__(self, child_pipe, key_id, fpr, address): +class ProxyClient(object): + 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": + if name == '_pipe': return super(ProxyClient, self).__getattribute__(name) - self._pipe.send(("getattr", name)) + self._pipe.send(('getattr', name)) data = self._pipe.recv() - if data[0] == "data": + if data[0] == 'data': return data[1] - if data[0] == "function": - + if data[0] == 'function': + def func(*args, **kwargs): - self._pipe.send(("funcall", name, args, kwargs)) + self._pipe.send(('funcall', name, args, kwargs)) return self._pipe.recv()[1] - + return func - + def __setattr__(self, name, value): - if name == "_pipe": + if name == '_pipe': return super(ProxyClient, self).__setattr__(name, value) - self._pipe.send(("setattr", name, value)) + self._pipe.send(('setattr', name, value)) class ClientHandler(socketserver.BaseRequestHandler, object): """A class to handle client connections. - + Instantiated once for each connection to handle it. Note: This will run in its own forked process.""" - + def handle(self): with contextlib.closing(self.server.child_pipe) as child_pipe: - log.info("TCP connection from: %s", - str(self.client_address)) - log.debug("Pipe FD: %d", self.server.child_pipe.fileno()) - + logger.info("TCP connection from: %s", + str(self.client_address)) + logger.debug("Pipe FD: %d", + self.server.child_pipe.fileno()) + session = gnutls.ClientSession(self.request) - - # priority = ":".join(("NONE", "+VERS-TLS1.1", - # "+AES-256-CBC", "+SHA1", - # "+COMP-NULL", "+CTYPE-OPENPGP", - # "+DHE-DSS")) + + #priority = ':'.join(("NONE", "+VERS-TLS1.1", + # "+AES-256-CBC", "+SHA1", + # "+COMP-NULL", "+CTYPE-OPENPGP", + # "+DHE-DSS")) # Use a fallback default, since this MUST be set. priority = self.server.gnutls_priority if priority is None: priority = "NORMAL" - gnutls.priority_set_direct(session, - priority.encode("utf-8"), None) - + gnutls.priority_set_direct(session._c_object, priority, + None) + # Start communication using the Mandos protocol # Get protocol number line = self.request.makefile().readline() - log.debug("Protocol version: %r", line) + logger.debug("Protocol version: %r", line) try: if int(line.strip().split()[0]) > 1: raise RuntimeError(line) except (ValueError, IndexError, RuntimeError) as error: - log.error("Unknown protocol version: %s", error) + logger.error("Unknown protocol version: %s", error) return - + # Start GnuTLS connection try: session.handshake() except gnutls.Error as error: - log.warning("Handshake failed: %s", error) + logger.warning("Handshake failed: %s", error) # Do not run session.bye() here: the session is not # established. Just abandon the request. return - log.debug("Handshake succeeded") - + logger.debug("Handshake succeeded") + approval_required = False try: - if gnutls.has_rawpk: - fpr = b"" - try: - key_id = self.key_id( - self.peer_certificate(session)) - except (TypeError, gnutls.Error) as error: - log.warning("Bad certificate: %s", error) - return - log.debug("Key ID: %s", - key_id.decode("utf-8", - errors="replace")) - - else: - key_id = b"" - try: - fpr = self.fingerprint( - self.peer_certificate(session)) - except (TypeError, gnutls.Error) as error: - log.warning("Bad certificate: %s", error) - return - log.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 - + if client.approval_delay: delay = client.approval_delay client.approvals_pending += 1 approval_required = True - + while True: if not client.enabled: - log.info("Client %s is disabled", client.name) + logger.info("Client %s is disabled", + client.name) if self.server.use_dbus: # Emit D-Bus signal client.Rejected("Disabled") return - + if client.approved or not client.approval_delay: - # We are approved or approval is disabled + #We are approved or approval is disabled break elif client.approved is None: - log.info("Client %s needs approval", - client.name) + logger.info("Client %s needs approval", + client.name) if self.server.use_dbus: # Emit D-Bus signal client.NeedApproval( client.approval_delay.total_seconds() * 1000, client.approved_by_default) else: - log.warning("Client %s was not approved", - client.name) + logger.warning("Client %s was not approved", + client.name) if self.server.use_dbus: # Emit D-Bus signal client.Rejected("Denied") return - - # wait until timeout or approved + + #wait until timeout or approved time = datetime.datetime.now() client.changedstate.acquire() client.changedstate.wait(delay.total_seconds()) @@ -2408,9 +2253,9 @@ time2 = datetime.datetime.now() if (time2 - time) >= delay: if not client.approved_by_default: - log.warning("Client %s timed out while" - " waiting for approval", - client.name) + logger.warning("Client %s timed out while" + " waiting for approval", + client.name) if self.server.use_dbus: # Emit D-Bus signal client.Rejected("Approval timed out") @@ -2419,91 +2264,48 @@ break else: delay -= time2 - time - + try: session.send(client.secret) except gnutls.Error as error: - log.warning("gnutls send failed", exc_info=error) + logger.warning("gnutls send failed", + exc_info = error) return - - log.info("Sending secret to %s", client.name) + + logger.info("Sending secret to %s", client.name) # bump the timeout using extended_timeout client.bump_timeout(client.extended_timeout) if self.server.use_dbus: # Emit D-Bus signal client.GotSecret() - + finally: if approval_required: client.approvals_pending -= 1 try: session.bye() except gnutls.Error as error: - log.warning("GnuTLS bye failed", exc_info=error) - + logger.warning("GnuTLS bye failed", + exc_info=error) + @staticmethod def peer_certificate(session): - "Return the peer's certificate as a bytestring" - try: - cert_type = gnutls.certificate_type_get2( - session, gnutls.CTYPE_PEERS) - except AttributeError: - cert_type = gnutls.certificate_type_get(session) - 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: - log.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) cert_list = (gnutls.certificate_get_peers - (session, ctypes.byref(list_size))) + (session._c_object, ctypes.byref(list_size))) if not bool(cert_list) and list_size.value != 0: raise gnutls.Error("error getting peer certificate") if list_size.value == 0: return None cert = cert_list[0] 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" @@ -2524,8 +2326,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() @@ -2541,54 +2342,54 @@ return hex_fpr -class MultiprocessingMixIn: +class MultiprocessingMixIn(object): """Like socketserver.ThreadingMixIn, but with multiprocessing""" - + def sub_process_main(self, request, address): try: self.finish_request(request, address) except Exception: self.handle_error(request, address) self.close_request(request) - + def process_request(self, request, address): """Start a new process to process the request.""" - proc = multiprocessing.Process(target=self.sub_process_main, - args=(request, address)) + proc = multiprocessing.Process(target = self.sub_process_main, + args = (request, address)) proc.start() return proc -class MultiprocessingMixInWithPipe(MultiprocessingMixIn): +class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object): """ adds a pipe to the MixIn """ - + def process_request(self, request, client_address): """Overrides and wraps the original process_request(). - + This function creates a new pipe in self.pipe """ parent_pipe, self.child_pipe = multiprocessing.Pipe() - + proc = MultiprocessingMixIn.process_request(self, request, client_address) self.child_pipe.close() self.add_pipe(parent_pipe, proc) - + def add_pipe(self, parent_pipe, proc): """Dummy function; override as necessary""" raise NotImplementedError() class IPv6_TCPServer(MultiprocessingMixInWithPipe, - socketserver.TCPServer): - """IPv6-capable TCP server. Accepts None as address and/or port - + socketserver.TCPServer, object): + """IPv6-capable TCP server. Accepts 'None' as address and/or port + Attributes: enabled: Boolean; whether this server is activated yet interface: None or a network interface name (string) use_ipv6: Boolean; to use IPv6 or not """ - + def __init__(self, server_address, RequestHandlerClass, interface=None, use_ipv6=True, @@ -2604,13 +2405,12 @@ self.socketfd = socketfd # Save the original socket.socket() function self.socket_socket = socket.socket - # To implement --socket, we monkey patch socket.socket. - # + # # (When socketserver.TCPServer is a new-style class, we # could make self.socket into a property instead of monkey # patching socket.socket.) - # + # # Create a one-time-only replacement for socket.socket() @functools.wraps(socket.socket) def socket_wrapper(*args, **kwargs): @@ -2628,43 +2428,41 @@ # socket_wrapper(), if socketfd was set. socketserver.TCPServer.__init__(self, server_address, RequestHandlerClass) - + def server_bind(self): """This overrides the normal server_bind() function to bind to an interface if one was specified, and also NOT to bind to an address or port if they were not specified.""" - global SO_BINDTODEVICE if self.interface is not None: if SO_BINDTODEVICE is None: - # Fall back to a hard-coded value which seems to be - # common enough. - log.warning("SO_BINDTODEVICE not found, trying 25") - SO_BINDTODEVICE = 25 - try: - self.socket.setsockopt( - socket.SOL_SOCKET, SO_BINDTODEVICE, - (self.interface + "\0").encode("utf-8")) - except socket.error as error: - if error.errno == errno.EPERM: - log.error("No permission to bind to interface %s", - self.interface) - elif error.errno == errno.ENOPROTOOPT: - log.error("SO_BINDTODEVICE not available; cannot" - " bind to interface %s", self.interface) - elif error.errno == errno.ENODEV: - log.error("Interface %s does not exist, cannot" - " bind", self.interface) - else: - raise + logger.error("SO_BINDTODEVICE does not exist;" + " cannot bind to interface %s", + self.interface) + else: + try: + self.socket.setsockopt( + socket.SOL_SOCKET, SO_BINDTODEVICE, + (self.interface + "\0").encode("utf-8")) + except socket.error as error: + if error.errno == errno.EPERM: + logger.error("No permission to bind to" + " interface %s", self.interface) + elif error.errno == errno.ENOPROTOOPT: + logger.error("SO_BINDTODEVICE not available;" + " cannot bind to interface %s", + self.interface) + elif error.errno == errno.ENODEV: + logger.error("Interface %s does not exist," + " cannot bind", self.interface) + else: + raise # Only bind(2) the socket if we really need to. if self.server_address[0] or self.server_address[1]: - if self.server_address[1]: - self.allow_reuse_address = True if not self.server_address[0]: if self.address_family == socket.AF_INET6: - any_address = "::" # in6addr_any + any_address = "::" # in6addr_any else: - any_address = "0.0.0.0" # INADDR_ANY + any_address = "0.0.0.0" # INADDR_ANY self.server_address = (any_address, self.server_address[1]) elif not self.server_address[1]: @@ -2680,15 +2478,15 @@ class MandosServer(IPv6_TCPServer): """Mandos server. - + Attributes: clients: set of Client objects gnutls_priority GnuTLS priority string use_dbus: Boolean; to emit D-Bus signals or not - + Assumes a GLib.MainLoop event loop. """ - + def __init__(self, server_address, RequestHandlerClass, interface=None, use_ipv6=True, @@ -2704,130 +2502,121 @@ self.gnutls_priority = gnutls_priority IPv6_TCPServer.__init__(self, server_address, RequestHandlerClass, - interface=interface, - use_ipv6=use_ipv6, - socketfd=socketfd) - + interface = interface, + use_ipv6 = use_ipv6, + socketfd = socketfd) + def server_activate(self): if self.enabled: return socketserver.TCPServer.server_activate(self) - + def enable(self): self.enabled = True - + def add_pipe(self, parent_pipe, proc): # Call "handle_ipc" for both data and EOF events GLib.io_add_watch( - GLib.IOChannel.unix_new(parent_pipe.fileno()), - GLib.PRIORITY_DEFAULT, GLib.IO_IN | GLib.IO_HUP, + parent_pipe.fileno(), + GLib.IO_IN | GLib.IO_HUP, functools.partial(self.handle_ipc, - parent_pipe=parent_pipe, - proc=proc)) - + parent_pipe = parent_pipe, + proc = proc)) + def handle_ipc(self, source, condition, parent_pipe=None, - proc=None, + proc = None, client_object=None): # error, or the other end of multiprocessing.Pipe has closed if condition & (GLib.IO_ERR | GLib.IO_HUP): # Wait for other process to exit proc.join() return False - + # Read a request from the child request = parent_pipe.recv() command = request[0] - - if command == "init": - key_id = request[1].decode("ascii") - fpr = request[2].decode("ascii") - address = request[3] - + + if command == 'init': + fpr = request[1] + address = request[2] + for c in self.clients.values(): - if key_id == ("E3B0C44298FC1C149AFBF4C8996FB924" - "27AE41E4649B934CA495991B7852B855"): - 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: - log.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 - + GLib.io_add_watch( - GLib.IOChannel.unix_new(parent_pipe.fileno()), - GLib.PRIORITY_DEFAULT, GLib.IO_IN | GLib.IO_HUP, + parent_pipe.fileno(), + GLib.IO_IN | GLib.IO_HUP, functools.partial(self.handle_ipc, - parent_pipe=parent_pipe, - proc=proc, - client_object=client)) + parent_pipe = parent_pipe, + proc = proc, + client_object = client)) parent_pipe.send(True) # remove the old hook in favor of the new above hook on # same fileno return False - if command == "funcall": + if command == 'funcall': funcname = request[1] args = request[2] kwargs = request[3] - - parent_pipe.send(("data", getattr(client_object, + + parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs))) - - if command == "getattr": + + if command == 'getattr': attrname = request[1] if isinstance(client_object.__getattribute__(attrname), - collections.abc.Callable): - parent_pipe.send(("function", )) + collections.Callable): + parent_pipe.send(('function', )) else: parent_pipe.send(( - "data", client_object.__getattribute__(attrname))) - - if command == "setattr": + 'data', client_object.__getattribute__(attrname))) + + if command == 'setattr': attrname = request[1] value = request[2] setattr(client_object, attrname, value) - + return True def rfc3339_duration_to_delta(duration): """Parse an RFC 3339 "duration" and return a datetime.timedelta - - >>> timedelta = datetime.timedelta - >>> rfc3339_duration_to_delta("P7D") == timedelta(7) - True - >>> rfc3339_duration_to_delta("PT60S") == timedelta(0, 60) - True - >>> rfc3339_duration_to_delta("PT60M") == timedelta(0, 3600) - True - >>> rfc3339_duration_to_delta("PT24H") == timedelta(1) - True - >>> rfc3339_duration_to_delta("P1W") == timedelta(7) - True - >>> rfc3339_duration_to_delta("PT5M30S") == timedelta(0, 330) - True - >>> rfc3339_duration_to_delta("P1DT3M20S") == timedelta(1, 200) - True - >>> del timedelta + + >>> rfc3339_duration_to_delta("P7D") + datetime.timedelta(7) + >>> rfc3339_duration_to_delta("PT60S") + datetime.timedelta(0, 60) + >>> rfc3339_duration_to_delta("PT60M") + datetime.timedelta(0, 3600) + >>> rfc3339_duration_to_delta("PT24H") + datetime.timedelta(1) + >>> rfc3339_duration_to_delta("P1W") + datetime.timedelta(7) + >>> rfc3339_duration_to_delta("PT5M30S") + datetime.timedelta(0, 330) + >>> rfc3339_duration_to_delta("P1DT3M20S") + datetime.timedelta(1, 200) """ - + # Parsing an RFC 3339 duration with regular expressions is not # possible - there would have to be multiple places for the same # values, like seconds. The current code, while more esoteric, is # cleaner without depending on a parsing library. If Python had a # built-in library for parsing we would use it, but we'd like to # avoid excessive use of external libraries. - + # New type for defining tokens, syntax, and semantics all-in-one Token = collections.namedtuple("Token", ( "regexp", # To match token; if "value" is not None, must have @@ -2866,14 +2655,11 @@ frozenset((token_year, token_month, token_day, token_time, token_week))) - # Define starting values: - # Value so far - value = datetime.timedelta() + # Define starting values + value = datetime.timedelta() # Value so far found_token = None - # Following valid tokens - followers = frozenset((token_duration, )) - # String left to parse - s = duration + followers = frozenset((token_duration, )) # Following valid tokens + s = duration # String left to parse # Loop until end token is found while found_token is not token_end: # Search for any currently valid tokens @@ -2903,26 +2689,26 @@ def string_to_delta(interval): """Parse a string and return a datetime.timedelta - - >>> string_to_delta("7d") == datetime.timedelta(7) - True - >>> string_to_delta("60s") == datetime.timedelta(0, 60) - True - >>> string_to_delta("60m") == datetime.timedelta(0, 3600) - True - >>> string_to_delta("24h") == datetime.timedelta(1) - True - >>> string_to_delta("1w") == datetime.timedelta(7) - True - >>> string_to_delta("5m 30s") == datetime.timedelta(0, 330) - True + + >>> string_to_delta('7d') + datetime.timedelta(7) + >>> string_to_delta('60s') + datetime.timedelta(0, 60) + >>> string_to_delta('60m') + datetime.timedelta(0, 3600) + >>> string_to_delta('24h') + datetime.timedelta(1) + >>> string_to_delta('1w') + datetime.timedelta(7) + >>> string_to_delta('5m 30s') + datetime.timedelta(0, 330) """ - + try: return rfc3339_duration_to_delta(interval) except ValueError: pass - + timevalue = datetime.timedelta(0) for s in interval.split(): try: @@ -2946,9 +2732,9 @@ return timevalue -def daemon(nochdir=False, noclose=False): +def daemon(nochdir = False, noclose = False): """See daemon(3). Standard BSD Unix function. - + This should really exist as os.daemon, but it doesn't (yet).""" if os.fork(): sys.exit() @@ -2972,13 +2758,13 @@ def main(): - + ################################################################## # Parsing of options, both command line and config file - + parser = argparse.ArgumentParser() parser.add_argument("-v", "--version", action="version", - version="%(prog)s {}".format(version), + version = "%(prog)s {}".format(version), help="show version number and exit") parser.add_argument("-i", "--interface", metavar="IF", help="Bind to interface IF") @@ -3020,42 +2806,41 @@ parser.add_argument("--no-zeroconf", action="store_false", dest="zeroconf", help="Do not use Zeroconf", default=None) - + options = parser.parse_args() - + + if options.check: + import doctest + fail_count, test_count = doctest.testmod() + 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, - "servicename": "Mandos", - "use_dbus": "True", - "use_ipv6": "True", - "debuglevel": "", - "restore": "True", - "socket": "", - "statedir": "/var/lib/mandos", - "foreground": "False", - "zeroconf": "True", - } - del priority - + server_defaults = { "interface": "", + "address": "", + "port": "", + "debug": "False", + "priority": + "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA" + ":+SIGN-DSA-SHA256", + "servicename": "Mandos", + "use_dbus": "True", + "use_ipv6": "True", + "debuglevel": "", + "restore": "True", + "socket": "", + "statedir": "/var/lib/mandos", + "foreground": "False", + "zeroconf": "True", + } + # Parse config file for server-global settings - server_config = configparser.ConfigParser(server_defaults) + server_config = configparser.SafeConfigParser(server_defaults) del server_defaults server_config.read(os.path.join(options.configdir, "mandos.conf")) - # Convert the ConfigParser object to a dict + # Convert the SafeConfigParser object to a dict server_settings = server_config.defaults() # Use the appropriate methods on the non-string config options - for option in ("debug", "use_dbus", "use_ipv6", "restore", - "foreground", "zeroconf"): + for option in ("debug", "use_dbus", "use_ipv6", "foreground"): server_settings[option] = server_config.getboolean("DEFAULT", option) if server_settings["port"]: @@ -3071,7 +2856,7 @@ server_settings["socket"] = os.dup(server_settings ["socket"]) del server_config - + # Override the settings from the config file with command line # options, if set. for option in ("interface", "address", "port", "debug", @@ -3095,14 +2880,14 @@ if server_settings["debug"]: server_settings["foreground"] = True # Now we have our good server settings in "server_settings" - + ################################################################## - + if (not server_settings["zeroconf"] and not (server_settings["port"] or server_settings["socket"] != "")): parser.error("Needs port or socket to work without Zeroconf") - + # For convenience debug = server_settings["debug"] debuglevel = server_settings["debuglevel"] @@ -3112,7 +2897,7 @@ stored_state_file) foreground = server_settings["foreground"] zeroconf = server_settings["zeroconf"] - + if debug: initlogger(debug, logging.DEBUG) else: @@ -3121,21 +2906,22 @@ else: level = getattr(logging, debuglevel.upper()) initlogger(debug, level) - + if server_settings["servicename"] != "Mandos": syslogger.setFormatter( - logging.Formatter("Mandos ({}) [%(process)d]:" - " %(levelname)s: %(message)s".format( + logging.Formatter('Mandos ({}) [%(process)d]:' + ' %(levelname)s: %(message)s'.format( server_settings["servicename"]))) - + # Parse config file with clients - client_config = configparser.ConfigParser(Client.client_defaults) + client_config = configparser.SafeConfigParser(Client + .client_defaults) client_config.read(os.path.join(server_settings["configdir"], "clients.conf")) - + global mandos_dbus_service mandos_dbus_service = None - + socketfd = None if server_settings["socket"] != "": socketfd = server_settings["socket"] @@ -3155,9 +2941,9 @@ try: pidfile = codecs.open(pidfilename, "w", encoding="utf-8") except IOError as e: - log.error("Could not open file %r", pidfilename, - exc_info=e) - + logger.error("Could not open file %r", pidfilename, + exc_info=e) + for name, group in (("_mandos", "_mandos"), ("mandos", "mandos"), ("nobody", "nogroup")): @@ -3173,43 +2959,43 @@ try: os.setgid(gid) os.setuid(uid) - log.debug("Did setuid/setgid to %s:%s", uid, gid) + if debug: + logger.debug("Did setuid/setgid to {}:{}".format(uid, + gid)) except OSError as error: - log.warning("Failed to setuid/setgid to %s:%s: %s", uid, gid, - os.strerror(error.errno)) + logger.warning("Failed to setuid/setgid to {}:{}: {}" + .format(uid, gid, os.strerror(error.errno))) if error.errno != errno.EPERM: raise - + if debug: # Enable all possible GnuTLS debugging - + # "Use a log level over 10 to enable all debugging options." # - GnuTLS manual gnutls.global_set_log_level(11) - + @gnutls.log_func def debug_gnutls(level, string): - log.debug("GnuTLS: %s", - string[:-1].decode("utf-8", errors="replace")) - + logger.debug("GnuTLS: %s", string[:-1]) + gnutls.global_set_log_function(debug_gnutls) - + # Redirect stdin so all checkers get /dev/null null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR) os.dup2(null, sys.stdin.fileno()) if null > 2: os.close(null) - + # Need to fork before connecting to D-Bus if not foreground: # Close all input and output, do double fork, etc. daemon() - - if gi.version_info < (3, 10, 2): - # multiprocessing will use threads, so before we use GLib we - # need to inform GLib that threads will be used. - GLib.threads_init() - + + # multiprocessing will use threads, so before we use GLib we need + # to inform GLib that threads will be used. + GLib.threads_init() + global main_loop # From the Avahi example code DBusGMainLoop(set_as_default=True) @@ -3225,113 +3011,109 @@ "se.bsnet.fukt.Mandos", bus, do_not_queue=True) except dbus.exceptions.DBusException as e: - log.error("Disabling D-Bus:", exc_info=e) + logger.error("Disabling D-Bus:", exc_info=e) use_dbus = False server_settings["use_dbus"] = False tcp_server.use_dbus = False if zeroconf: protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET service = AvahiServiceToSyslog( - name=server_settings["servicename"], - servicetype="_mandos._tcp", - protocol=protocol, - bus=bus) + name = server_settings["servicename"], + servicetype = "_mandos._tcp", + protocol = protocol, + bus = bus) if server_settings["interface"]: service.interface = if_nametoindex( server_settings["interface"].encode("utf-8")) - + global multiprocessing_manager multiprocessing_manager = multiprocessing.Manager() - + client_class = Client if use_dbus: - client_class = functools.partial(ClientDBus, bus=bus) - + client_class = functools.partial(ClientDBus, bus = bus) + client_settings = Client.config_parser(client_config) old_client_settings = {} clients_data = {} - + # This is used to redirect stdout and stderr for checker processes global wnull - wnull = open(os.devnull, "w") # A writable /dev/null + wnull = open(os.devnull, "w") # A writable /dev/null # Only used if server is running in foreground but not in debug # mode if debug or not foreground: wnull.close() - + # Get client data and settings from last running state. if server_settings["restore"]: try: with open(stored_state_path, "rb") as stored_state: - if sys.version_info.major == 2: + if sys.version_info.major == 2: clients_data, old_client_settings = pickle.load( stored_state) else: bytes_clients_data, bytes_old_client_settings = ( - pickle.load(stored_state, encoding="bytes")) - # Fix bytes to strings - # clients_data + pickle.load(stored_state, encoding = "bytes")) + ### Fix bytes to strings + ## clients_data # .keys() - clients_data = {(key.decode("utf-8") - if isinstance(key, bytes) - else key): value - for key, value in - bytes_clients_data.items()} + clients_data = { (key.decode("utf-8") + if isinstance(key, bytes) + else key): value + for key, value in + bytes_clients_data.items() } del bytes_clients_data for key in clients_data: - value = {(k.decode("utf-8") - if isinstance(k, bytes) else k): v - for k, v in - clients_data[key].items()} + value = { (k.decode("utf-8") + if isinstance(k, bytes) else k): v + for k, v in + clients_data[key].items() } clients_data[key] = value # .client_structure value["client_structure"] = [ (s.decode("utf-8") if isinstance(s, bytes) else s) for s in - value["client_structure"]] - # .name, .host, and .checker_command - for k in ("name", "host", "checker_command"): + value["client_structure"] ] + # .name & .host + for k in ("name", "host"): if isinstance(value[k], bytes): value[k] = value[k].decode("utf-8") - if "key_id" not in value: - value["key_id"] = "" - elif "fingerprint" not in value: - value["fingerprint"] = "" - # old_client_settings + ## old_client_settings # .keys() old_client_settings = { (key.decode("utf-8") if isinstance(key, bytes) else key): value for key, value in - bytes_old_client_settings.items()} + bytes_old_client_settings.items() } del bytes_old_client_settings - # .host and .checker_command + # .host for value in old_client_settings.values(): - for attribute in ("host", "checker_command"): - if isinstance(value[attribute], bytes): - value[attribute] = (value[attribute] - .decode("utf-8")) + if isinstance(value["host"], bytes): + value["host"] = (value["host"] + .decode("utf-8")) os.remove(stored_state_path) except IOError as e: if e.errno == errno.ENOENT: - log.warning("Could not load persistent state:" - " %s", os.strerror(e.errno)) + logger.warning("Could not load persistent state:" + " {}".format(os.strerror(e.errno))) else: - log.critical("Could not load persistent state:", - exc_info=e) + logger.critical("Could not load persistent state:", + exc_info=e) raise except EOFError as e: - log.warning("Could not load persistent state: EOFError:", - exc_info=e) - + logger.warning("Could not load persistent state: " + "EOFError:", + exc_info=e) + with PGPEngine() as pgp: for client_name, client in clients_data.items(): # Skip removed clients if client_name not in client_settings: continue - + # Decide which value to use after restoring saved state. # We have three different values: Old config file, # new config file, and saved state. @@ -3348,7 +3130,7 @@ client[name] = value except KeyError: pass - + # Clients who has passed its expire date can still be # enabled if its last checker was successful. A Client # whose checker succeeded before we stored its state is @@ -3357,33 +3139,37 @@ if client["enabled"]: if datetime.datetime.utcnow() >= client["expires"]: if not client["last_checked_ok"]: - log.warning("disabling client %s - Client" - " never performed a successful" - " checker", client_name) + logger.warning( + "disabling client {} - Client never " + "performed a successful checker".format( + client_name)) client["enabled"] = False elif client["last_checker_status"] != 0: - log.warning("disabling client %s - Client" - " last checker failed with error" - " code %s", client_name, - client["last_checker_status"]) + logger.warning( + "disabling client {} - Client last" + " checker failed with error code" + " {}".format( + client_name, + client["last_checker_status"])) client["enabled"] = False else: client["expires"] = ( datetime.datetime.utcnow() + client["timeout"]) - log.debug("Last checker succeeded, keeping %s" - " enabled", client_name) + logger.debug("Last checker succeeded," + " keeping {} enabled".format( + client_name)) try: client["secret"] = pgp.decrypt( client["encrypted_secret"], client_settings[client_name]["secret"]) except PGPError: # If decryption fails, we use secret from new settings - log.debug("Failed to decrypt %s old secret", - client_name) + logger.debug("Failed to decrypt {} old secret".format( + client_name)) client["secret"] = (client_settings[client_name] ["secret"]) - + # Add/remove clients based on new changes made to config for client_name in (set(old_client_settings) - set(client_settings)): @@ -3391,17 +3177,17 @@ for client_name in (set(client_settings) - set(old_client_settings)): clients_data[client_name] = client_settings[client_name] - + # Create all client objects for client_name, client in clients_data.items(): tcp_server.clients[client_name] = client_class( - name=client_name, - settings=client, - server_settings=server_settings) - + name = client_name, + settings = client, + server_settings = server_settings) + if not tcp_server.clients: - log.warning("No clients defined") - + logger.warning("No clients defined") + if not foreground: if pidfile is not None: pid = os.getpid() @@ -3409,44 +3195,44 @@ with pidfile: print(pid, file=pidfile) except IOError: - log.error("Could not write to file %r with PID %d", - pidfilename, pid) + logger.error("Could not write to file %r with PID %d", + pidfilename, pid) del pidfile del pidfilename - + for termsig in (signal.SIGHUP, signal.SIGTERM): GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig, lambda: main_loop.quit() and False) - + if use_dbus: - + @alternate_dbus_interfaces( - {"se.recompile.Mandos": "se.bsnet.fukt.Mandos"}) + { "se.recompile.Mandos": "se.bsnet.fukt.Mandos" }) class MandosDBusService(DBusObjectWithObjectManager): """A D-Bus proxy object""" - + def __init__(self): dbus.service.Object.__init__(self, bus, "/") - + _interface = "se.recompile.Mandos" - + @dbus.service.signal(_interface, signature="o") def ClientAdded(self, objpath): "D-Bus signal" pass - + @dbus.service.signal(_interface, signature="ss") - def ClientNotFound(self, key_id, address): + def ClientNotFound(self, fingerprint, address): "D-Bus signal" pass - + @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"}) @dbus.service.signal(_interface, signature="os") def ClientRemoved(self, objpath, name): "D-Bus signal" pass - + @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"}) @dbus.service.method(_interface, out_signature="ao") @@ -3454,7 +3240,7 @@ "D-Bus method" return dbus.Array(c.dbus_object_path for c in tcp_server.clients.values()) - + @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"}) @dbus.service.method(_interface, @@ -3462,11 +3248,11 @@ def GetAllClientsWithProperties(self): "D-Bus method" return dbus.Dictionary( - {c.dbus_object_path: c.GetAll( + { c.dbus_object_path: c.GetAll( "se.recompile.Mandos.Client") - for c in tcp_server.clients.values()}, + for c in tcp_server.clients.values() }, signature="oa{sv}") - + @dbus.service.method(_interface, in_signature="o") def RemoveClient(self, object_path): "D-Bus method" @@ -3480,21 +3266,21 @@ self.client_removed_signal(c) return raise KeyError(object_path) - + del _interface - + @dbus.service.method(dbus.OBJECT_MANAGER_IFACE, - out_signature="a{oa{sa{sv}}}") + out_signature = "a{oa{sa{sv}}}") def GetManagedObjects(self): """D-Bus method""" return dbus.Dictionary( - {client.dbus_object_path: - dbus.Dictionary( - {interface: client.GetAll(interface) - for interface in - client._get_all_interface_names()}) - for client in tcp_server.clients.values()}) - + { client.dbus_object_path: + dbus.Dictionary( + { interface: client.GetAll(interface) + for interface in + client._get_all_interface_names()}) + for client in tcp_server.clients.values()}) + def client_added_signal(self, client): """Send the new standard signal and the old signal""" if use_dbus: @@ -3502,12 +3288,12 @@ self.InterfacesAdded( client.dbus_object_path, dbus.Dictionary( - {interface: client.GetAll(interface) - for interface in - client._get_all_interface_names()})) + { interface: client.GetAll(interface) + for interface in + client._get_all_interface_names()})) # Old signal self.ClientAdded(client.dbus_object_path) - + def client_removed_signal(self, client): """Send the new standard signal and the old signal""" if use_dbus: @@ -3518,24 +3304,19 @@ # Old signal self.ClientRemoved(client.dbus_object_path, client.name) - + mandos_dbus_service = MandosDBusService() - - # Save modules to variables to exempt the modules from being - # unloaded before the function registered with atexit() is run. - mp = multiprocessing - wn = wnull - + def cleanup(): "Cleanup function; run on exit" if zeroconf: service.cleanup() - - mp.active_children() - wn.close() + + multiprocessing.active_children() + wnull.close() if not (tcp_server.clients or client_settings): return - + # Store client before exiting. Secrets are encrypted with key # based on what config file has. If config file is # removed/edited, old secret will thus be unrecovable. @@ -3546,33 +3327,33 @@ client.encrypted_secret = pgp.encrypt(client.secret, key) client_dict = {} - + # A list of attributes that can not be pickled # + secret. - exclude = {"bus", "changedstate", "secret", - "checker", "server_settings"} + exclude = { "bus", "changedstate", "secret", + "checker", "server_settings" } for name, typ in inspect.getmembers(dbus.service .Object): exclude.add(name) - + client_dict["encrypted_secret"] = (client .encrypted_secret) for attr in client.client_structure: if attr not in exclude: client_dict[attr] = getattr(client, attr) - + clients[client.name] = client_dict del client_settings[client.name]["secret"] - + try: with tempfile.NamedTemporaryFile( - mode="wb", + mode='wb', suffix=".pickle", - prefix="clients-", + prefix='clients-', dir=os.path.dirname(stored_state_path), delete=False) as stored_state: pickle.dump((clients, client_settings), stored_state, - protocol=2) + protocol = 2) tempname = stored_state.name os.rename(tempname, stored_state_path) except (IOError, OSError) as e: @@ -3582,13 +3363,13 @@ except NameError: pass if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST): - log.warning("Could not save persistent state: %s", - os.strerror(e.errno)) + logger.warning("Could not save persistent state: {}" + .format(os.strerror(e.errno))) else: - log.warning("Could not save persistent state:", - exc_info=e) + logger.warning("Could not save persistent state:", + exc_info=e) raise - + # Delete all clients, and settings from config while tcp_server.clients: name, client = tcp_server.clients.popitem() @@ -3600,9 +3381,9 @@ if use_dbus: mandos_dbus_service.client_removed_signal(client) client_settings.clear() - + atexit.register(cleanup) - + for client in tcp_server.clients.values(): if use_dbus: # Emit D-Bus signal for adding @@ -3610,147 +3391,53 @@ # Need to initiate checking of clients if client.enabled: client.init_checker() - + tcp_server.enable() tcp_server.server_activate() - + # Find out what port we got if zeroconf: service.port = tcp_server.socket.getsockname()[1] if use_ipv6: - log.info("Now listening on address %r, port %d, flowinfo %d," - " scope_id %d", *tcp_server.socket.getsockname()) + logger.info("Now listening on address %r, port %d," + " flowinfo %d, scope_id %d", + *tcp_server.socket.getsockname()) else: # IPv4 - log.info("Now listening on address %r, port %d", - *tcp_server.socket.getsockname()) - - # service.interface = tcp_server.socket.getsockname()[3] - + logger.info("Now listening on address %r, port %d", + *tcp_server.socket.getsockname()) + + #service.interface = tcp_server.socket.getsockname()[3] + try: if zeroconf: # From the Avahi example code try: service.activate() except dbus.exceptions.DBusException as error: - log.critical("D-Bus Exception", exc_info=error) + logger.critical("D-Bus Exception", exc_info=error) cleanup() sys.exit(1) # End of Avahi example code - - GLib.io_add_watch( - GLib.IOChannel.unix_new(tcp_server.fileno()), - GLib.PRIORITY_DEFAULT, GLib.IO_IN, - lambda *args, **kwargs: (tcp_server.handle_request - (*args[2:], **kwargs) or True)) - - log.debug("Starting main loop") + + GLib.io_add_watch(tcp_server.fileno(), GLib.IO_IN, + lambda *args, **kwargs: + (tcp_server.handle_request + (*args[2:], **kwargs) or True)) + + logger.debug("Starting main loop") main_loop.run() except AvahiError as error: - log.critical("Avahi Error", exc_info=error) + logger.critical("Avahi Error", exc_info=error) cleanup() sys.exit(1) except KeyboardInterrupt: if debug: print("", file=sys.stderr) - log.debug("Server received KeyboardInterrupt") - log.debug("Server exiting") + logger.debug("Server received KeyboardInterrupt") + logger.debug("Server exiting") # Must run before the D-Bus bus name gets deregistered cleanup() - -def parse_test_args(): - # type: () -> argparse.Namespace - parser = argparse.ArgumentParser(add_help=False) - parser.add_argument("--check", action="store_true") - parser.add_argument("--prefix", ) - args, unknown_args = parser.parse_known_args() - if args.check: - # Remove test options from sys.argv - sys.argv[1:] = unknown_args - return args - -# Add all tests from doctest strings -def load_tests(loader, tests, none): - import doctest - tests.addTests(doctest.DocTestSuite()) - return tests - -if __name__ == "__main__": - options = parse_test_args() - try: - if options.check: - extra_test_prefix = options.prefix - if extra_test_prefix is not None: - if not (unittest.main(argv=[""], exit=False) - .result.wasSuccessful()): - sys.exit(1) - class ExtraTestLoader(unittest.TestLoader): - testMethodPrefix = extra_test_prefix - # Call using ./scriptname --test [--verbose] - unittest.main(argv=[""], testLoader=ExtraTestLoader()) - else: - unittest.main(argv=[""]) - else: - main() - finally: - logging.shutdown() - -# Local Variables: -# run-tests: -# (lambda (&optional extra) -# (if (not (funcall run-tests-in-test-buffer default-directory -# extra)) -# (funcall show-test-buffer-in-test-window) -# (funcall remove-test-window) -# (if extra (message "Extra tests run successfully!")))) -# run-tests-in-test-buffer: -# (lambda (dir &optional extra) -# (with-current-buffer (get-buffer-create "*Test*") -# (setq buffer-read-only nil -# default-directory dir) -# (erase-buffer) -# (compilation-mode)) -# (let ((process-result -# (let ((inhibit-read-only t)) -# (process-file-shell-command -# (funcall get-command-line extra) nil "*Test*")))) -# (and (numberp process-result) -# (= process-result 0)))) -# get-command-line: -# (lambda (&optional extra) -# (let ((quoted-script -# (shell-quote-argument (funcall get-script-name)))) -# (format -# (concat "%s --check" (if extra " --prefix=atest" "")) -# quoted-script))) -# get-script-name: -# (lambda () -# (if (fboundp 'file-local-name) -# (file-local-name (buffer-file-name)) -# (or (file-remote-p (buffer-file-name) 'localname) -# (buffer-file-name)))) -# remove-test-window: -# (lambda () -# (let ((test-window (get-buffer-window "*Test*"))) -# (if test-window (delete-window test-window)))) -# show-test-buffer-in-test-window: -# (lambda () -# (when (not (get-buffer-window-list "*Test*")) -# (setq next-error-last-buffer (get-buffer "*Test*")) -# (let* ((side (if (>= (window-width) 146) 'right 'bottom)) -# (display-buffer-overriding-action -# `((display-buffer-in-side-window) (side . ,side) -# (window-height . fit-window-to-buffer) -# (window-width . fit-window-to-buffer)))) -# (display-buffer "*Test*")))) -# eval: -# (progn -# (let* ((run-extra-tests (lambda () (interactive) -# (funcall run-tests t))) -# (inner-keymap `(keymap (116 . ,run-extra-tests))) ; t -# (outer-keymap `(keymap (3 . ,inner-keymap)))) ; C-c -# (setq minor-mode-overriding-map-alist -# (cons `(run-tests . ,outer-keymap) -# minor-mode-overriding-map-alist))) -# (add-hook 'after-save-hook run-tests 90 t)) -# End: + +if __name__ == '__main__': + main() === modified file 'mandos-clients.conf.xml' --- mandos-clients.conf.xml 2019-02-10 04:20:26 +0000 +++ mandos-clients.conf.xml 2016-03-05 21:42:56 +0000 @@ -3,7 +3,7 @@ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ /etc/mandos/clients.conf"> - + %common; ]> @@ -41,9 +41,6 @@ 2014 2015 2016 - 2017 - 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -187,9 +184,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. @@ -233,23 +230,7 @@ This option sets the OpenPGP fingerprint 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. - - - - - - - - - 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, + through TLS. The string needs to be in hexidecimal form, but spaces or upper/lower case are not significant. @@ -331,9 +312,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 +415,6 @@ created, enabled, expires, - key_id, fingerprint, host, interval, @@ -497,7 +477,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 +499,6 @@ # Client "bar" [bar] -key_id = F90C7A81D72D1EA69A51031A91FF8885F36C8B46D155C8C58709A4C99AE9E361 fingerprint = 3e393aeaefb84c7e89e2f547b3a107558fca3a27 secfile = /etc/mandos/bar-secret timeout = PT15M === modified file 'mandos-ctl' --- mandos-ctl 2022-04-25 18:46:48 +0000 +++ mandos-ctl 2016-03-19 22:00:38 +0000 @@ -1,28 +1,28 @@ -#!/usr/bin/python3 -bbI -# -*- coding: utf-8; lexical-binding: t -*- -# -# Mandos Control - Control or query the Mandos server -# -# Copyright © 2008-2022 Teddy Hogeborn -# Copyright © 2008-2022 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 +#!/usr/bin/python +# -*- mode: python; coding: utf-8 -*- +# +# Mandos Monitor - Control and monitor the Mandos server +# +# Copyright © 2008-2016 Teddy Hogeborn +# Copyright © 2008-2016 Björn Påhlsson +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 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 +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License -# along with Mandos. If not, see . -# +# along with this program. If not, see +# . +# # Contact the authors at . -# +# + from __future__ import (division, absolute_import, print_function, unicode_literals) @@ -32,292 +32,89 @@ pass import sys -import unittest import argparse -import logging -import os import locale import datetime import re +import os import collections -import json -import io -import tempfile -import contextlib +import doctest + +import dbus if sys.version_info.major == 2: - __metaclass__ = type str = unicode - input = raw_input - -class gi: - """Dummy gi module, for the tests""" - class repository: - class GLib: - class Error(Exception): - pass -dbussy = None -ravel = None -dbus_python = None -pydbus = None - -try: - import dbussy - import ravel -except ImportError: - try: - import pydbus - import gi - except ImportError: - import dbus as dbus_python - - -# Show warnings by default -if not sys.warnoptions: - import warnings - warnings.simplefilter("default") - -log = logging.getLogger(os.path.basename(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 - -if sys.version_info.major == 2: - import StringIO - io.StringIO = StringIO.StringIO locale.setlocale(locale.LC_ALL, "") -version = "1.8.15" - - -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: - logging.getLogger("").setLevel(logging.DEBUG) - - if dbussy is not None and ravel is not None: - bus = dbussy_adapter.CachingBus(dbussy, ravel) - elif 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" +} +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.7" + + +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): """Parse an RFC 3339 "duration" and return a datetime.timedelta - - >>> rfc3339_duration_to_delta("P7D") == datetime.timedelta(7) - True - >>> rfc3339_duration_to_delta("PT60S") == datetime.timedelta(0, 60) - True - >>> rfc3339_duration_to_delta("PT60M") == datetime.timedelta(hours=1) - True - >>> # 60 months - >>> rfc3339_duration_to_delta("P60M") == datetime.timedelta(1680) - True - >>> rfc3339_duration_to_delta("PT24H") == datetime.timedelta(1) - True - >>> rfc3339_duration_to_delta("P1W") == datetime.timedelta(7) - True - >>> rfc3339_duration_to_delta("PT5M30S") == datetime.timedelta(0, 330) - True - >>> rfc3339_duration_to_delta("P1DT3M20S") == datetime.timedelta(1, 200) - True - >>> # 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" + + >>> rfc3339_duration_to_delta("P7D") + datetime.timedelta(7) + >>> rfc3339_duration_to_delta("PT60S") + datetime.timedelta(0, 60) + >>> rfc3339_duration_to_delta("PT60M") + datetime.timedelta(0, 3600) + >>> rfc3339_duration_to_delta("PT24H") + datetime.timedelta(1) + >>> rfc3339_duration_to_delta("P1W") + datetime.timedelta(7) + >>> rfc3339_duration_to_delta("PT5M30S") + datetime.timedelta(0, 330) + >>> rfc3339_duration_to_delta("P1DT3M20S") + datetime.timedelta(1, 200) """ - + # Parsing an RFC 3339 duration with regular expressions is not # possible - there would have to be multiple places for the same # values, like seconds. The current code, while more esoteric, is # cleaner without depending on a parsing library. If Python had a # built-in library for parsing we would use it, but we'd like to # avoid excessive use of external libraries. - + # New type for defining tokens, syntax, and semantics all-in-one Token = collections.namedtuple("Token", ( "regexp", # To match token; if "value" is not None, must have @@ -356,14 +153,11 @@ frozenset((token_year, token_month, token_day, token_time, token_week))) - # Define starting values: - # Value so far - value = datetime.timedelta() + # Define starting values + value = datetime.timedelta() # Value so far found_token = None - # Following valid tokens - followers = frozenset((token_duration, )) - # String left to parse - s = duration + followers = frozenset((token_duration, )) # Following valid tokens + s = duration # String left to parse # Loop until end token is found while found_token is not token_end: # Search for any currently valid tokens @@ -385,39 +179,37 @@ 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 - - >>> parse_pre_1_6_1_interval("7d") == datetime.timedelta(days=7) - True - >>> parse_pre_1_6_1_interval("60s") == datetime.timedelta(0, 60) - True - >>> parse_pre_1_6_1_interval("60m") == datetime.timedelta(hours=1) - True - >>> parse_pre_1_6_1_interval("24h") == datetime.timedelta(days=1) - True - >>> parse_pre_1_6_1_interval("1w") == datetime.timedelta(days=7) - True - >>> parse_pre_1_6_1_interval("5m 30s") == datetime.timedelta(0, 330) - True - >>> parse_pre_1_6_1_interval("") == datetime.timedelta(0) - True - >>> # Ignore unknown characters, allow any order and repetitions - >>> parse_pre_1_6_1_interval("2dxy7zz11y3m5m") == datetime.timedelta(2, 480, 18000) - True - +def string_to_delta(interval): + """Parse a string and return a datetime.timedelta + + >>> string_to_delta('7d') + datetime.timedelta(7) + >>> string_to_delta('60s') + datetime.timedelta(0, 60) + >>> string_to_delta('60m') + datetime.timedelta(0, 3600) + >>> string_to_delta('24h') + datetime.timedelta(1) + >>> string_to_delta('1w') + datetime.timedelta(7) + >>> string_to_delta('5m 30s') + datetime.timedelta(0, 330) """ - + + try: + return rfc3339_duration_to_delta(interval) + except ValueError: + pass + value = datetime.timedelta(0) regexp = re.compile(r"(\d+)([dsmhw]?)") - + for num, suffix in regexp.findall(interval): if suffix == "d": value += datetime.timedelta(int(num)) @@ -434,2471 +226,246 @@ 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("-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): - parser.error("--verbose can only be used alone.") - if (has_commands(options, (command.DumpJSON,)) - and (options.verbose or len(options.commands) > 1)): - parser.error("--dump-json can only be used alone.") - if options.all and not has_commands(options): + if options.verbose and has_actions(options): + parser.error("--verbose can only be used alone or with" + " --all.") + 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: - - class SystemBus: - - 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) - - def call_method(self, methodname, busname, objectpath, - interface, *args): - raise NotImplementedError() - - - 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: - - 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: - "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: - 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) - 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 - - -class dbussy_adapter: - class SystemBus(dbus.SystemBus): - """Use DBussy""" - - def __init__(self, dbussy, ravel): - self.dbussy = dbussy - self.ravel = ravel - self.bus = ravel.system_bus() - - @contextlib.contextmanager - def convert_exception(self, exception_class=dbus.Error): - try: - yield - except self.dbussy.DBusError 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)) - iface = proxy_object.get_interface(interface) - method = getattr(iface, methodname) - with self.convert_exception(dbus.Error): - value = method(*args) - # DBussy returns values either as an empty list or as a - # list of one element with the return value - if value: - return self.type_filter(value[0]) - - 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[busname][objectpath] - - def type_filter(self, value): - """Convert the most bothersome types to Python types""" - # A D-Bus Variant value is represented as the Python type - # Tuple[dbussy.DBUS.Signature, Any] - if isinstance(value, tuple): - if (len(value) == 2 - and isinstance(value[0], - self.dbussy.DBUS.Signature)): - return self.type_filter(value[1]) - elif isinstance(value, self.dbussy.DBUS.ObjectPath): - return str(value) - # Also recurse into dictionaries - elif isinstance(value, dict): - return {self.type_filter(key): - self.type_filter(subval) - for key, subval in value.items()} - return value - - def set_property(self, busname, objectpath, interface, key, - value): - proxy_object = self.get_object(busname, objectpath) - log.debug("D-Bus: %s:%s:%s.Set(%r, %r, %r)", busname, - objectpath, self.properties_iface, interface, - key, value) - if key == "Secret": - # DBussy wants a Byte Array to be a sequence of - # values, not a byte string - value = tuple(value) - setattr(proxy_object.get_interface(interface), key, value) - - class MandosBus(SystemBus, dbus.MandosBus): - pass - - class CachingBus(MandosBus): - """A caching layer for dbussy_adapter.MandosBus""" - def __init__(self, *args, **kwargs): - self.object_cache = {} - super(dbussy_adapter.CachingBus, self).__init__(*args, - **kwargs) - def get_object(self, busname, objectpath): - try: - return self.object_cache[(busname, objectpath)] - except KeyError: - new_object = super( - dbussy_adapter.CachingBus, - self).get_object(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: - """A namespace for command classes""" - - class Base: - """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: - 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: - """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: - """mock dbus-python module""" - class exceptions: - """Pseudo-namespace""" - class DBusException(Exception): - pass - class SystemBus: - @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: - 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.exception, 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: - """fake dbus-python module""" - class exceptions: - """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: - """mock dbus-python modules""" - class SystemBus: - @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: - """stub pydbus module""" - class SystemBus: - @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.exception, 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: - """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: - pass - obj = Object() - class pydbus_spy: - class SystemBus: - @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: - class SystemBus: - @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: - """stub pydbus module""" - class SystemBus: - @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_dbussy_adapter_SystemBus(TestCaseWithAssertLogs): - - class dummy_dbussy: - class DBUS: - class ObjectPath(str): - pass - class DBusError(Exception): - pass - - def fake_ravel_func(self, func): - class fake_ravel: - @staticmethod - def system_bus(): - class DBusInterfaceProxy: - @staticmethod - def methodname(*args): - return [func(*args)] - class DBusObject: - @staticmethod - def get_interface(interface): - if interface == "interface": - return DBusInterfaceProxy() - return {"busname": {"objectpath": DBusObject()}} - return fake_ravel - - 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 - fake_ravel = self.fake_ravel_func(func) - bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface", - *method_args) - self.assertIs(ret, expected_method_return) - - def test_call_method_filters_objectpath(self): - def func(): - return method_return - fake_ravel = self.fake_ravel_func(func) - bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel) - method_return = (self.dummy_dbussy.DBUS - .ObjectPath("objectpath")) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - self.assertEqual("objectpath", ret) - self.assertNotIsInstance(ret, - self.dummy_dbussy.DBUS.ObjectPath) - - def test_call_method_filters_objectpaths_in_dict(self): - ObjectPath = self.dummy_dbussy.DBUS.ObjectPath - def func(): - return method_return - fake_ravel = self.fake_ravel_func(func) - bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel) - method_return = { - ObjectPath("objectpath_key_1"): - ObjectPath("objectpath_value_1"), - ObjectPath("objectpath_key_2"): - 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()} - for key, value in ret.items(): - self.assertNotIsInstance(key, ObjectPath) - self.assertNotIsInstance(value, ObjectPath) - self.assertEqual(expected_method_return, ret) - self.assertIsInstance(ret, dict) - - def test_call_method_filters_objectpaths_in_dict_in_dict(self): - ObjectPath = self.dummy_dbussy.DBUS.ObjectPath - def func(): - return method_return - fake_ravel = self.fake_ravel_func(func) - bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel) - method_return = { - ObjectPath("key1"): { - ObjectPath("key11"): ObjectPath("value11"), - ObjectPath("key12"): ObjectPath("value12"), - }, - ObjectPath("key2"): { - ObjectPath("key21"): ObjectPath("value21"), - ObjectPath("key22"): ObjectPath("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) - for key, value in ret.items(): - self.assertIsInstance(value, dict) - self.assertEqual(expected_method_return[key], value) - self.assertNotIsInstance(key, ObjectPath) - for inner_key, inner_value in value.items(): - self.assertIsInstance(value, dict) - self.assertEqual( - expected_method_return[key][inner_key], - inner_value) - self.assertNotIsInstance(key, ObjectPath) - - def test_call_method_filters_objectpaths_in_dict_three_deep(self): - ObjectPath = self.dummy_dbussy.DBUS.ObjectPath - def func(): - return method_return - fake_ravel = self.fake_ravel_func(func) - bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel) - method_return = { - ObjectPath("key1"): { - ObjectPath("key2"): { - ObjectPath("key3"): ObjectPath("value"), - }, - }, - } - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - expected_method_return = {"key1": {"key2": {"key3": "value"}}} - self.assertEqual(expected_method_return, ret) - self.assertIsInstance(ret, dict) - self.assertNotIsInstance(next(iter(ret.keys())), ObjectPath) - self.assertIsInstance(ret["key1"], dict) - self.assertNotIsInstance(next(iter(ret["key1"].keys())), - ObjectPath) - self.assertIsInstance(ret["key1"]["key2"], dict) - self.assertNotIsInstance( - next(iter(ret["key1"]["key2"].keys())), - ObjectPath) - self.assertEqual("value", ret["key1"]["key2"]["key3"]) - self.assertNotIsInstance(ret["key1"]["key2"]["key3"], - self.dummy_dbussy.DBUS.ObjectPath) - - def test_call_method_handles_exception(self): - def func(): - raise self.dummy_dbussy.DBusError() - - fake_ravel = self.fake_ravel_func(func) - bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel) - - with self.assertRaises(dbus.Error) as e: - self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - - self.assertNotIsInstance(e.exception, dbus.ConnectFailed) - - def test_get_object_converts_to_correct_exception(self): - class fake_ravel_raises_exception_on_connect: - @staticmethod - def system_bus(): - class Bus: - @staticmethod - def __getitem__(key): - if key == "objectpath": - raise self.dummy_dbussy.DBusError() - raise Exception(key) - return {"busname": Bus()} - def func(): - raise self.dummy_dbussy.DBusError() - bus = dbussy_adapter.SystemBus( - self.dummy_dbussy, - fake_ravel_raises_exception_on_connect) - with self.assertRaises(dbus.ConnectFailed): - self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - - -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, length=1, - clients=None, **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(length, len(commands)) - for command in commands: - if isinstance(command, command_cls): - break - else: - self.assertIsInstance(command, command_cls) - if clients is not None: - self.assertEqual(clients, options.client) - for key, value in cmd_attrs.items(): - self.assertEqual(value, getattr(command, key)) - - def assert_commands_from_args(self, args, commands, clients=None): - for cmd in commands: - self.assert_command_from_args(args, cmd, - length=len(commands), - clients=clients) - - 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) - - - def test_manual_page_example_1(self): - self.assert_command_from_args("", - command.PrintTable, - clients=[], - verbose=False) - - def test_manual_page_example_2(self): - self.assert_command_from_args( - "--verbose foo1.example.org foo2.example.org".split(), - command.PrintTable, clients=["foo1.example.org", - "foo2.example.org"], - verbose=True) - - def test_manual_page_example_3(self): - self.assert_command_from_args("--enable --all".split(), - command.Enable, - clients=[]) - - def test_manual_page_example_4(self): - self.assert_commands_from_args( - ("--timeout=PT5M --interval=PT1M foo1.example.org" - " foo2.example.org").split(), - [command.SetTimeout, command.SetInterval], - clients=["foo1.example.org", "foo2.example.org"]) - - def test_manual_page_example_5(self): - self.assert_command_from_args("--approve --all".split(), - command.Approve, - clients=[]) - - -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) - self.assertTrue(self.bus.clients) - 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) - self.assertTrue(self.bus.clients) - for clientpath in self.bus.clients: - self.assertIn(("Approve", busname, clientpath, - client_interface, (False,)), - self.bus.calls) - - def test_Remove(self): - busname = "se.recompile.Mandos" - server_path = "/" - server_interface = "se.recompile.Mandos" - orig_clients = self.bus.clients.copy() - command.Remove().run(self.bus.clients, self.bus) - self.assertFalse(self.bus.clients) - for clientpath in orig_clients: - self.assertIn(("RemoveClient", busname, - server_path, 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 - self.assertTrue(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) - self.assertTrue(self.bus.clients) - 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" - def __init__(self, *args, **kwargs): - self.values_to_set = [io.BytesIO(b""), - io.BytesIO(b"secret\0xyzzy\nbar")] - self.values_to_get = [f.getvalue() for f in - self.values_to_set] - super(TestSetSecretCmd, self).__init__(*args, **kwargs) - - -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 parse_test_args(): - # type: () -> argparse.Namespace - parser = argparse.ArgumentParser(add_help=False) - parser.add_argument("--check", action="store_true") - parser.add_argument("--prefix", ) - args, unknown_args = parser.parse_known_args() - if args.check: - # Remove test options from sys.argv - sys.argv[1:] = unknown_args - return args - -# Add all tests from doctest strings -def load_tests(loader, tests, none): - import doctest - tests.addTests(doctest.DocTestSuite()) - return tests + + if options.check: + 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: + keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK", + "Created", "Interval", "Host", "Fingerprint", + "CheckerRunning", "LastEnabled", + "ApprovalPending", "ApprovedByDefault", + "LastApprovalRequest", "ApprovalDelay", + "ApprovalDuration", "Checker", + "ExtendedTimeout") + else: + keywords = defaultkeywords + + 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: + sys.exit(0 if client.Get(client_interface, + "Enabled", + dbus_interface= + dbus.PROPERTIES_IFACE) + else 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__": - options = parse_test_args() - try: - if options.check: - extra_test_prefix = options.prefix - if extra_test_prefix is not None: - if not (unittest.main(argv=[""], exit=False) - .result.wasSuccessful()): - sys.exit(1) - class ExtraTestLoader(unittest.TestLoader): - testMethodPrefix = extra_test_prefix - # Call using ./scriptname --check [--verbose] - unittest.main(argv=[""], testLoader=ExtraTestLoader()) - else: - unittest.main(argv=[""]) - else: - main() - finally: - logging.shutdown() - -# Local Variables: -# run-tests: -# (lambda (&optional extra) -# (if (not (funcall run-tests-in-test-buffer default-directory -# extra)) -# (funcall show-test-buffer-in-test-window) -# (funcall remove-test-window) -# (if extra (message "Extra tests run successfully!")))) -# run-tests-in-test-buffer: -# (lambda (dir &optional extra) -# (with-current-buffer (get-buffer-create "*Test*") -# (setq buffer-read-only nil -# default-directory dir) -# (erase-buffer) -# (compilation-mode)) -# (let ((process-result -# (let ((inhibit-read-only t)) -# (process-file-shell-command -# (funcall get-command-line extra) nil "*Test*")))) -# (and (numberp process-result) -# (= process-result 0)))) -# get-command-line: -# (lambda (&optional extra) -# (let ((quoted-script -# (shell-quote-argument (funcall get-script-name)))) -# (format -# (concat "%s --check" (if extra " --prefix=atest" "")) -# quoted-script))) -# get-script-name: -# (lambda () -# (if (fboundp 'file-local-name) -# (file-local-name (buffer-file-name)) -# (or (file-remote-p (buffer-file-name) 'localname) -# (buffer-file-name)))) -# remove-test-window: -# (lambda () -# (let ((test-window (get-buffer-window "*Test*"))) -# (if test-window (delete-window test-window)))) -# show-test-buffer-in-test-window: -# (lambda () -# (when (not (get-buffer-window-list "*Test*")) -# (setq next-error-last-buffer (get-buffer "*Test*")) -# (let* ((side (if (>= (window-width) 146) 'right 'bottom)) -# (display-buffer-overriding-action -# `((display-buffer-in-side-window) (side . ,side) -# (window-height . fit-window-to-buffer) -# (window-width . fit-window-to-buffer)))) -# (display-buffer "*Test*")))) -# eval: -# (progn -# (let* ((run-extra-tests (lambda () (interactive) -# (funcall run-tests t))) -# (inner-keymap `(keymap (116 . ,run-extra-tests))) ; t -# (outer-keymap `(keymap (3 . ,inner-keymap)))) ; C-c -# (setq minor-mode-overriding-map-alist -# (cons `(run-tests . ,outer-keymap) -# minor-mode-overriding-map-alist))) -# (add-hook 'after-save-hook run-tests 90 t)) -# End: + main() === modified file 'mandos-ctl.xml' --- mandos-ctl.xml 2019-07-29 16:35:53 +0000 +++ mandos-ctl.xml 2016-03-05 21:42:56 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -38,9 +38,6 @@ 2014 2015 2016 - 2017 - 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -55,7 +52,7 @@ &COMMANDNAME; - Control or query the operation of the Mandos server + Control the operation of the Mandos server @@ -63,127 +60,103 @@ &COMMANDNAME; - - - - - - - - - - CLIENT - - - - - &COMMANDNAME; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CLIENT - - - - - &COMMANDNAME; - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - @@ -194,11 +167,22 @@ &COMMANDNAME; + + + + + + + CLIENT + + + + + &COMMANDNAME; - CLIENT @@ -224,10 +208,9 @@ DESCRIPTION - &COMMANDNAME; is a program to control or - query the operation of the Mandos server - mandos8. + &COMMANDNAME; is a program to control the + operation of the Mandos server mandos8. This program can be used to change client settings, approve or @@ -491,16 +474,6 @@ - - - - - Dump client settings as JSON to standard output. - - - - - @@ -512,15 +485,6 @@ - - - - Show debug output; currently, this means show D-Bus calls. - - - - - @@ -557,11 +521,7 @@ EXAMPLE - - To list all clients: @@ -571,7 +531,6 @@ - To list all settings for the clients named foo1.example.org and - To enable all clients: @@ -596,7 +554,6 @@ - To change timeout and interval value for the clients named foo1.example.org and -&COMMANDNAME; --timeout=PT5M --interval=PT1M foo1.example.org foo2.example.org +&COMMANDNAME; --timeout="5m" --interval="1m" foo1.example.org foo2.example.org - - To approve all clients currently waiting for approval: + To approve all clients currently waiting for it: &COMMANDNAME; --approve --all === modified file 'mandos-keygen' --- mandos-keygen 2022-04-25 18:46:48 +0000 +++ mandos-keygen 2016-03-19 22:00:38 +0000 @@ -1,29 +1,27 @@ #!/bin/sh -e # -# Mandos key generator - create new keys for a Mandos client -# -# Copyright © 2008-2019 Teddy Hogeborn -# Copyright © 2008-2019 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 +# Mandos key generator - create a new OpenPGP key for a Mandos client +# +# Copyright © 2008-2016 Teddy Hogeborn +# Copyright © 2008-2016 Björn Påhlsson +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 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 +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with Mandos. If not, see . +# along with this program. If not, see . # # Contact the authors at . # -VERSION="1.8.15" +VERSION="1.7.7" KEYDIR="/etc/keys/mandos" KEYTYPE=RSA @@ -34,7 +32,6 @@ KEYEMAIL="" KEYCOMMENT="" KEYEXPIRE=0 -TLS_KEYTYPE=ed25519 FORCE=no SSH=yes KEYCOMMENT_ORIG="$KEYCOMMENT" @@ -45,8 +42,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 +61,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 +104,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 +119,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,10 +161,8 @@ [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|*) FORCE=0;; esac - if { [ -e "$SECKEYFILE" ] || [ -e "$PUBKEYFILE" ] \ - || [ -e "$TLS_PRIVKEYFILE" ] \ - || [ -e "$TLS_PUBKEYFILE" ]; } \ - && [ "$FORCE" -eq 0 ]; then + if [ \( -e "$SECKEYFILE" -o -e "$PUBKEYFILE" \) \ + -a "$FORCE" -eq 0 ]; then echo "Refusing to overwrite old key files; use --force" >&2 exit 1 fi @@ -187,7 +177,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 +191,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\"; @@ -230,7 +218,6 @@ #Handle: #%pubring pubring.gpg #%secring secring.gpg - %no-protection %commit EOF @@ -243,39 +230,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 +250,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 @@ -331,12 +285,11 @@ esac if [ $SSH -eq 1 ]; then - for ssh_keytype in ecdsa-sha2-nistp256 ed25519 rsa; do + for ssh_keytype in ed25519 rsa; do set +e ssh_fingerprint="`ssh-keyscan -t $ssh_keytype localhost 2>/dev/null`" - err=$? set -e - if [ $err -ne 0 ]; then + if [ $? -ne 0 ]; then ssh_fingerprint="" continue fi @@ -364,31 +317,18 @@ 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 if [ -n "$PASSFILE" ]; then - cat -- "$PASSFILE" + cat "$PASSFILE" else tty --quiet && stty -echo - echo -n "Enter passphrase: " >/dev/tty - read -r first + echo -n "Enter passphrase: " >&2 + read first tty --quiet && echo >&2 - echo -n "Repeat passphrase: " >/dev/tty - read -r second + echo -n "Repeat passphrase: " >&2 + read second if tty --quiet; then echo >&2 stty echo @@ -397,7 +337,7 @@ echo "Passphrase mismatch" >&2 touch "$RINGDIR"/mismatch else - printf "%s" "$first" + echo -n "$first" fi fi | gpg --quiet --batch --no-tty --no-options --enable-dsa2 \ --homedir "$RINGDIR" --trust-model always --armor \ @@ -416,11 +356,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 +379,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-07-18 00:02:43 +0000 +++ mandos-keygen.xml 2016-03-05 21:42:56 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -40,9 +40,6 @@ 2014 2015 2016 - 2017 - 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -128,13 +125,6 @@ - - - - - @@ -188,12 +178,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 +226,8 @@ DIRECTORY - Target directory for key files. Default is /etc/keys/mandos. + Target directory for key files. Default is + /etc/mandos. @@ -249,7 +239,7 @@ TYPE - OpenPGP key type. Default is RSA. + Key type. Default is RSA. @@ -261,7 +251,7 @@ BITS - OpenPGP key length in bits. Default is 4096. + Key length in bits. Default is 4096. @@ -273,7 +263,8 @@ KEYTYPE - OpenPGP subkey type. Default is RSA + Subkey type. Default is RSA (Elgamal + encryption-only). @@ -285,7 +276,7 @@ BITS - OpenPGP subkey length in bits. Default is 4096. + Subkey length in bits. Default is 4096. @@ -329,18 +320,6 @@ - - - - - TLS key type. Default is ed25519 - - - - - @@ -355,17 +334,15 @@ 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.conf8. The host name or the name specified with the option is used for the section header. All other options are ignored, - and no key is created. Note: white space is stripped from - the beginning and from the end of the password; See . + and no key is created. @@ -377,8 +354,7 @@ The same as , but read from - FILE, not the terminal, and - white space is not stripped from the password in any way. + FILE, not the terminal. @@ -405,9 +381,9 @@ OVERVIEW - 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. @@ -445,7 +421,7 @@ - /etc/keys/mandos/seckey.txt + /etc/mandos/seckey.txt OpenPGP secret key file which will be created or @@ -454,7 +430,7 @@ - /etc/keys/mandos/pubkey.txt + /etc/mandos/pubkey.txt OpenPGP public key file which will be created or @@ -463,22 +439,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 @@ -492,13 +452,6 @@ BUGS - - The / option - strips white space from the start and from the end of the - password before using it. If this is a problem, use the - option instead, which does not do - this. - @@ -526,9 +479,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 @@ -536,7 +489,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 2022-04-25 18:46:48 +0000 +++ mandos-monitor 2016-03-19 22:00:38 +0000 @@ -1,42 +1,39 @@ -#!/usr/bin/python3 -bbI +#!/usr/bin/python # -*- mode: python; coding: utf-8 -*- -# +# # Mandos Monitor - Control and monitor the Mandos server -# -# Copyright © 2009-2019 Teddy Hogeborn -# Copyright © 2009-2019 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 +# +# Copyright © 2009-2016 Teddy Hogeborn +# Copyright © 2009-2016 Björn Påhlsson +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 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 +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License -# along with Mandos. If not, see . -# +# along with this program. If not, see +# . +# # Contact the authors at . -# +# + from __future__ import (division, absolute_import, print_function, unicode_literals) - try: from future_builtins import * except ImportError: pass import sys -import logging import os -import warnings + import datetime -import locale import urwid.curses_display import urwid @@ -46,37 +43,27 @@ import dbus +import locale + if sys.version_info.major == 2: - __metaclass__ = type str = unicode - input = raw_input - -# Show warnings by default -if not sys.warnoptions: - warnings.simplefilter("default") - -log = logging.getLogger(os.path.basename(sys.argv[0])) -logging.basicConfig(level="NOTSET", # Show all messages - format="%(message)s") # Show basic log messages - -logging.captureWarnings(True) # Show warnings via the logging system - -locale.setlocale(locale.LC_ALL, "") - -logging.getLogger("dbus.proxies").setLevel(logging.CRITICAL) + +locale.setlocale(locale.LC_ALL, '') + +import logging +logging.getLogger('dbus.proxies').setLevel(logging.CRITICAL) # Some useful constants -domain = "se.recompile" -server_interface = domain + ".Mandos" -client_interface = domain + ".Mandos.Client" -version = "1.8.15" +domain = 'se.recompile' +server_interface = domain + '.Mandos' +client_interface = domain + '.Mandos.Client' +version = "1.7.7" try: dbus.OBJECT_MANAGER_IFACE except AttributeError: dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager" - def isoformat_to_datetime(iso): "Parse an ISO 8601 date string to a datetime.datetime()" if not iso: @@ -90,31 +77,31 @@ int(day), int(hour), int(minute), - int(second), # Whole seconds - int(fraction*1000000)) # Microseconds - - -class MandosClientPropertyCache: + int(second), # Whole seconds + int(fraction*1000000)) # Microseconds + +class MandosClientPropertyCache(object): """This wraps a Mandos Client D-Bus proxy object, caches the properties and calls a hook function when any of them are changed. """ def __init__(self, proxy_object=None, properties=None, **kwargs): - self.proxy = proxy_object # Mandos Client proxy object + self.proxy = proxy_object # Mandos Client proxy object self.properties = dict() if properties is None else properties self.property_changed_match = ( self.proxy.connect_to_signal("PropertiesChanged", self.properties_changed, dbus.PROPERTIES_IFACE, byte_arrays=True)) - + if properties is None: - self.properties.update(self.proxy.GetAll( - client_interface, - dbus_interface=dbus.PROPERTIES_IFACE)) - + self.properties.update( + self.proxy.GetAll(client_interface, + dbus_interface + = dbus.PROPERTIES_IFACE)) + super(MandosClientPropertyCache, self).__init__(**kwargs) - + def properties_changed(self, interface, properties, invalidated): """This is called whenever we get a PropertiesChanged signal It updates the changed properties in the "properties" dict. @@ -122,7 +109,7 @@ # Update properties dict with new value if interface == client_interface: self.properties.update(properties) - + def delete(self): self.property_changed_match.remove() @@ -130,18 +117,20 @@ class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache): """A Mandos Client which is visible on the screen. """ - + def __init__(self, server_proxy_object=None, update_hook=None, - delete_hook=None, **kwargs): + delete_hook=None, logger=None, **kwargs): # Called on update self.update_hook = update_hook # Called on delete self.delete_hook = delete_hook # Mandos Server proxy object self.server_proxy_object = server_proxy_object - + # Logger + self.logger = logger + self._update_timer_callback_tag = None - + # The widget shown normally self._text_widget = urwid.Text("") # The widget shown when we have focus @@ -149,7 +138,7 @@ super(MandosClientWidget, self).__init__(**kwargs) self.update() self.opened = False - + self.match_objects = ( self.proxy.connect_to_signal("CheckerCompleted", self.checker_completed, @@ -171,95 +160,102 @@ self.rejected, client_interface, byte_arrays=True)) - log.debug("Created client %s", self.properties["Name"]) - + self.logger('Created client {}' + .format(self.properties["Name"]), level=0) + def using_timer(self, flag): """Call this method with True or False when timer should be activated or deactivated. """ if flag and self._update_timer_callback_tag is None: # Will update the shown timer value every second - self._update_timer_callback_tag = ( - GLib.timeout_add(1000, - glib_safely(self.update_timer))) + self._update_timer_callback_tag = (GLib.timeout_add + (1000, + self.update_timer)) elif not (flag or self._update_timer_callback_tag is None): GLib.source_remove(self._update_timer_callback_tag) self._update_timer_callback_tag = None - + def checker_completed(self, exitstatus, condition, command): if exitstatus == 0: - log.debug('Checker for client %s (command "%s")' - " succeeded", self.properties["Name"], command) + self.logger('Checker for client {} (command "{}")' + ' succeeded'.format(self.properties["Name"], + command), level=0) self.update() return # Checker failed if os.WIFEXITED(condition): - log.info('Checker for client %s (command "%s") failed' - " with exit code %d", self.properties["Name"], - command, os.WEXITSTATUS(condition)) + self.logger('Checker for client {} (command "{}") failed' + ' with exit code {}' + .format(self.properties["Name"], command, + os.WEXITSTATUS(condition))) elif os.WIFSIGNALED(condition): - log.info('Checker for client %s (command "%s") was' - " killed by signal %d", self.properties["Name"], - command, os.WTERMSIG(condition)) + self.logger('Checker for client {} (command "{}") was' + ' killed by signal {}' + .format(self.properties["Name"], command, + os.WTERMSIG(condition))) self.update() - + def checker_started(self, command): """Server signals that a checker started.""" - log.debug('Client %s started checker "%s"', - self.properties["Name"], command) - + self.logger('Client {} started checker "{}"' + .format(self.properties["Name"], + command), level=0) + def got_secret(self): - log.info("Client %s received its secret", - self.properties["Name"]) - + self.logger('Client {} received its secret' + .format(self.properties["Name"])) + def need_approval(self, timeout, default): if not default: - message = "Client %s needs approval within %f seconds" + message = 'Client {} needs approval within {} seconds' else: - message = "Client %s will get its secret in %f seconds" - log.info(message, self.properties["Name"], timeout/1000) - + message = 'Client {} will get its secret in {} seconds' + self.logger(message.format(self.properties["Name"], + timeout/1000)) + def rejected(self, reason): - log.info("Client %s was rejected; reason: %s", - self.properties["Name"], reason) - + self.logger('Client {} was rejected; reason: {}' + .format(self.properties["Name"], reason)) + def selectable(self): """Make this a "selectable" widget. This overrides the method from urwid.FlowWidget.""" return True - + def rows(self, maxcolrow, focus=False): """How many rows this widget will occupy might depend on whether we have focus or not. This overrides the method from urwid.FlowWidget""" return self.current_widget(focus).rows(maxcolrow, focus=focus) - + def current_widget(self, focus=False): if focus or self.opened: return self._focus_widget return self._widget - + def update(self): "Called when what is visible on the screen should be updated." # How to add standout mode to a style - with_standout = {"normal": "standout", - "bold": "bold-standout", - "underline-blink": - "underline-blink-standout", - "bold-underline-blink": - "bold-underline-blink-standout", - } - + with_standout = { "normal": "standout", + "bold": "bold-standout", + "underline-blink": + "underline-blink-standout", + "bold-underline-blink": + "bold-underline-blink-standout", + } + # Rebuild focus and non-focus widgets using current properties - + # Base part of a client. Name! - base = "{name}: ".format(name=self.properties["Name"]) + base = '{name}: '.format(name=self.properties["Name"]) if not self.properties["Enabled"]: message = "DISABLED" self.using_timer(False) elif self.properties["ApprovalPending"]: - timeout = datetime.timedelta( - milliseconds=self.properties["ApprovalDelay"]) + timeout = datetime.timedelta(milliseconds + = self.properties + ["ApprovalDelay"]) last_approval_request = isoformat_to_datetime( self.properties["LastApprovalRequest"]) if last_approval_request is not None: @@ -281,18 +277,18 @@ timer = datetime.timedelta(0) else: expires = (datetime.datetime.strptime - (expires, "%Y-%m-%dT%H:%M:%S.%f")) + (expires, '%Y-%m-%dT%H:%M:%S.%f')) timer = max(expires - datetime.datetime.utcnow(), datetime.timedelta()) - message = ("A checker has failed! Time until client" - " gets disabled: {}" + message = ('A checker has failed! Time until client' + ' gets disabled: {}' .format(str(timer).rsplit(".", 1)[0])) self.using_timer(True) else: message = "enabled" self.using_timer(False) self._text = "{}{}".format(base, message) - + if not urwid.supports_unicode(): self._text = self._text.encode("ascii", "replace") textlist = [("normal", self._text)] @@ -308,14 +304,14 @@ # Run update hook, if any if self.update_hook is not None: self.update_hook() - + def update_timer(self): """called by GLib. Will indefinitely loop until GLib.source_remove() on tag is called """ self.update() return True # Keep calling this - + def delete(self, **kwargs): if self._update_timer_callback_tag is not None: GLib.source_remove(self._update_timer_callback_tag) @@ -326,31 +322,31 @@ if self.delete_hook is not None: self.delete_hook(self) return super(MandosClientWidget, self).delete(**kwargs) - + def render(self, maxcolrow, focus=False): """Render differently if we have focus. This overrides the method from urwid.FlowWidget""" return self.current_widget(focus).render(maxcolrow, focus=focus) - + def keypress(self, maxcolrow, key): """Handle keys. This overrides the method from urwid.FlowWidget""" if key == "+": self.proxy.Set(client_interface, "Enabled", - dbus.Boolean(True), ignore_reply=True, - dbus_interface=dbus.PROPERTIES_IFACE) + dbus.Boolean(True), ignore_reply = True, + dbus_interface = dbus.PROPERTIES_IFACE) elif key == "-": self.proxy.Set(client_interface, "Enabled", False, - ignore_reply=True, - dbus_interface=dbus.PROPERTIES_IFACE) + ignore_reply = True, + dbus_interface = dbus.PROPERTIES_IFACE) elif key == "a": self.proxy.Approve(dbus.Boolean(True, variant_level=1), - dbus_interface=client_interface, + dbus_interface = client_interface, ignore_reply=True) elif key == "d": self.proxy.Approve(dbus.Boolean(False, variant_level=1), - dbus_interface=client_interface, + dbus_interface = client_interface, ignore_reply=True) elif key == "R" or key == "_" or key == "ctrl k": self.server_proxy_object.RemoveClient(self.proxy @@ -358,14 +354,14 @@ ignore_reply=True) elif key == "s": self.proxy.Set(client_interface, "CheckerRunning", - dbus.Boolean(True), ignore_reply=True, - dbus_interface=dbus.PROPERTIES_IFACE) + dbus.Boolean(True), ignore_reply = True, + dbus_interface = dbus.PROPERTIES_IFACE) elif key == "S": self.proxy.Set(client_interface, "CheckerRunning", - dbus.Boolean(False), ignore_reply=True, - dbus_interface=dbus.PROPERTIES_IFACE) + dbus.Boolean(False), ignore_reply = True, + dbus_interface = dbus.PROPERTIES_IFACE) elif key == "C": - self.proxy.CheckedOK(dbus_interface=client_interface, + self.proxy.CheckedOK(dbus_interface = client_interface, ignore_reply=True) # xxx # elif key == "p" or key == "=": @@ -376,12 +372,12 @@ # self.open() else: return key - + def properties_changed(self, interface, properties, invalidated): """Call self.update() if any properties changed. This overrides the method from MandosClientPropertyCache""" - old_values = {key: self.properties.get(key) - for key in properties.keys()} + old_values = { key: self.properties.get(key) + for key in properties.keys() } super(MandosClientWidget, self).properties_changed( interface, properties, invalidated) if any(old_values[key] != self.properties.get(key) @@ -389,38 +385,27 @@ self.update() -def glib_safely(func, retval=True): - def safe_func(*args, **kwargs): - try: - return func(*args, **kwargs) - except Exception: - log.exception("") - return retval - return safe_func - - class ConstrainedListBox(urwid.ListBox): """Like a normal urwid.ListBox, but will consume all "up" or "down" key presses, thus not allowing any containing widgets to use them as an excuse to shift focus away from this widget. """ def keypress(self, *args, **kwargs): - ret = (super(ConstrainedListBox, self) - .keypress(*args, **kwargs)) + ret = super(ConstrainedListBox, self).keypress(*args, **kwargs) if ret in ("up", "down"): return return ret -class UserInterface: +class UserInterface(object): """This is the entire user interface - the whole screen with boxes, lists of client widgets, etc. """ - def __init__(self, max_log_length=1000): + def __init__(self, max_log_length=1000, log_level=1): DBusGMainLoop(set_as_default=True) - + self.screen = urwid.curses_display.Screen() - + self.screen.register_palette(( ("normal", "default", "default", None), @@ -431,8 +416,7 @@ ("standout", "standout", "default", "standout"), ("bold-underline-blink", - "bold,underline,blink", "default", - "bold,underline,blink"), + "bold,underline,blink", "default", "bold,underline,blink"), ("bold-standout", "bold,standout", "default", "bold,standout"), ("underline-blink-standout", @@ -442,76 +426,93 @@ "bold,underline,blink,standout", "default", "bold,underline,blink,standout"), )) - + if urwid.supports_unicode(): - self.divider = "─" # \u2500 + self.divider = "─" # \u2500 + #self.divider = "━" # \u2501 else: - self.divider = "_" # \u005f - + #self.divider = "-" # \u002d + self.divider = "_" # \u005f + self.screen.start() - + self.size = self.screen.get_cols_rows() - + self.clients = urwid.SimpleListWalker([]) 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 + # We keep a reference to the log widget so we can remove it # from the ListWalker without it getting destroyed self.logbox = ConstrainedListBox(self.log) - + # This keeps track of whether self.uilist currently has # self.logbox in it or not self.log_visible = True self.log_wrap = "any" - - self.loghandler = UILogHandler(self) - + self.rebuild() - self.add_log_line(("bold", - "Mandos Monitor version " + version)) - self.add_log_line(("bold", "q: Quit ?: Help")) - - self.busname = domain + ".Mandos" + self.log_message_raw(("bold", + "Mandos Monitor version " + version)) + self.log_message_raw(("bold", + "q: Quit ?: Help")) + + self.busname = domain + '.Mandos' self.main_loop = GLib.MainLoop() - - def client_not_found(self, key_id, address): - log.info("Client with address %s and key ID %s could" - " not be found", address, key_id) - + + def client_not_found(self, fingerprint, address): + self.log_message("Client with address {} and fingerprint {}" + " could not be found" + .format(address, fingerprint)) + def rebuild(self): """This rebuilds the User Interface. Call this when the widget layout needs to change""" self.uilist = [] - # self.uilist.append(urwid.ListBox(self.clients)) + #self.uilist.append(urwid.ListBox(self.clients)) self.uilist.append(urwid.Frame(ConstrainedListBox(self. clients), - # header=urwid.Divider(), + #header=urwid.Divider(), header=None, - footer=urwid.Divider( - div_char=self.divider))) + footer= + urwid.Divider(div_char= + self.divider))) if self.log_visible: self.uilist.append(self.logbox) self.topwidget = urwid.Pile(self.uilist) - - def add_log_line(self, markup): + + def log_message(self, message, level=1): + """Log message formatted with timestamp""" + if level < self.log_level: + return + timestamp = datetime.datetime.now().isoformat() + self.log_message_raw("{}: {}".format(timestamp, message), + level=level) + + def log_message_raw(self, markup, level=1): + """Add a log message to the log buffer.""" + if level < self.log_level: + return self.log.append(urwid.Text(markup, wrap=self.log_wrap)) - if self.max_log_length: - if len(self.log) > self.max_log_length: - del self.log[0:(len(self.log) - self.max_log_length)] - self.logbox.set_focus(len(self.logbox.body.contents)-1, + if (self.max_log_length + and 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), coming_from="above") self.refresh() - + def toggle_log_display(self): """Toggle visibility of the log buffer.""" self.log_visible = not self.log_visible self.rebuild() - log.debug("Log visibility changed to: %s", self.log_visible) - + self.log_message("Log visibility changed to: {}" + .format(self.log_visible), level=0) + def change_log_display(self): """Change type of log display. Currently, this toggles wrapping of text lines.""" @@ -521,11 +522,12 @@ self.log_wrap = "clip" for textwidget in self.log: textwidget.set_wrap_mode(self.log_wrap) - log.debug("Wrap mode: %s", self.log_wrap) - + self.log_message("Wrap mode: {}".format(self.log_wrap), + level=0) + def find_and_remove_client(self, path, interfaces): """Find a client by its object path and remove it. - + This is connected to the InterfacesRemoved signal from the Mandos server object.""" if client_interface not in interfaces: @@ -535,13 +537,14 @@ client = self.clients_dict[path] except KeyError: # not found? - log.warning("Unknown client %s removed", path) + self.log_message("Unknown client {!r} removed" + .format(path)) return client.delete() - + def add_new_client(self, path, ifs_and_props): """Find a client by its object path and remove it. - + This is connected to the InterfacesAdded signal from the Mandos server object. """ @@ -549,14 +552,21 @@ # Not a Mandos client object; ignore return client_proxy_object = self.bus.get_object(self.busname, path) - self.add_client(MandosClientWidget( - server_proxy_object=self.mandos_serv, - proxy_object=client_proxy_object, - update_hook=self.refresh, - delete_hook=self.remove_client, - properties=dict(ifs_and_props[client_interface])), + self.add_client(MandosClientWidget(server_proxy_object + =self.mandos_serv, + proxy_object + =client_proxy_object, + update_hook + =self.refresh, + delete_hook + =self.remove_client, + logger + =self.log_message, + properties + = dict(ifs_and_props[ + client_interface])), path=path) - + def add_client(self, client, path=None): self.clients.append(client) if path is None: @@ -564,49 +574,47 @@ self.clients_dict[path] = client self.clients.sort(key=lambda c: c.properties["Name"]) self.refresh() - + def remove_client(self, client, path=None): self.clients.remove(client) if path is None: path = client.proxy.object_path del self.clients_dict[path] self.refresh() - + def refresh(self): """Redraw the screen""" canvas = self.topwidget.render(self.size, focus=True) self.screen.draw_screen(self.size, canvas) - + def run(self): """Start the main loop and exit when it's done.""" - log.addHandler(self.loghandler) - self.orig_log_propagate = log.propagate - log.propagate = False - self.orig_log_level = log.level - log.setLevel("INFO") self.bus = dbus.SystemBus() mandos_dbus_objc = self.bus.get_object( self.busname, "/", follow_name_owner_changes=True) - self.mandos_serv = dbus.Interface( - mandos_dbus_objc, dbus_interface=server_interface) + self.mandos_serv = dbus.Interface(mandos_dbus_objc, + dbus_interface + = server_interface) try: mandos_clients = (self.mandos_serv .GetAllClientsWithProperties()) if not mandos_clients: - log.warning("Note: Server has no clients.") + self.log_message_raw(("bold", "Note: Server has no clients.")) except dbus.exceptions.DBusException: - log.warning("Note: No Mandos server running.") + self.log_message_raw(("bold", "Note: No Mandos server running.")) mandos_clients = dbus.Dictionary() - + (self.mandos_serv .connect_to_signal("InterfacesRemoved", self.find_and_remove_client, - dbus_interface=dbus.OBJECT_MANAGER_IFACE, + dbus_interface + = dbus.OBJECT_MANAGER_IFACE, byte_arrays=True)) (self.mandos_serv .connect_to_signal("InterfacesAdded", self.add_new_client, - dbus_interface=dbus.OBJECT_MANAGER_IFACE, + dbus_interface + = dbus.OBJECT_MANAGER_IFACE, byte_arrays=True)) (self.mandos_serv .connect_to_signal("ClientNotFound", @@ -616,50 +624,50 @@ for path, client in mandos_clients.items(): client_proxy_object = self.bus.get_object(self.busname, path) - self.add_client(MandosClientWidget( - server_proxy_object=self.mandos_serv, - proxy_object=client_proxy_object, - properties=client, - update_hook=self.refresh, - delete_hook=self.remove_client), + self.add_client(MandosClientWidget(server_proxy_object + =self.mandos_serv, + proxy_object + =client_proxy_object, + properties=client, + update_hook + =self.refresh, + delete_hook + =self.remove_client, + logger + =self.log_message), path=path) - + self.refresh() - self._input_callback_tag = ( - GLib.io_add_watch( - GLib.IOChannel.unix_new(sys.stdin.fileno()), - GLib.PRIORITY_DEFAULT, GLib.IO_IN, - glib_safely(self.process_input))) + self._input_callback_tag = (GLib.io_add_watch + (sys.stdin.fileno(), + GLib.IO_IN, + self.process_input)) self.main_loop.run() # Main loop has finished, we should close everything now GLib.source_remove(self._input_callback_tag) - with warnings.catch_warnings(): - warnings.simplefilter("ignore", BytesWarning) - self.screen.stop() - + self.screen.stop() + def stop(self): self.main_loop.quit() - log.removeHandler(self.loghandler) - log.propagate = self.orig_log_propagate - + def process_input(self, source, condition): keys = self.screen.get_input() - translations = {"ctrl n": "down", # Emacs - "ctrl p": "up", # Emacs - "ctrl v": "page down", # Emacs - "meta v": "page up", # Emacs - " ": "page down", # less - "f": "page down", # less - "b": "page up", # less - "j": "down", # vi - "k": "up", # vi - } + translations = { "ctrl n": "down", # Emacs + "ctrl p": "up", # Emacs + "ctrl v": "page down", # Emacs + "meta v": "page up", # Emacs + " ": "page down", # less + "f": "page down", # less + "b": "page up", # less + "j": "down", # vi + "k": "up", # vi + } for key in keys: try: key = translations[key] except KeyError: # :-) pass - + if key == "q" or key == "Q": self.stop() break @@ -679,25 +687,26 @@ if not self.log_visible: self.log_visible = True self.rebuild() - self.add_log_line(("bold", - " ".join(("q: Quit", - "?: Help", - "l: Log window toggle", - "TAB: Switch window", - "w: Wrap (log lines)", - "v: Toggle verbose log", - )))) - self.add_log_line(("bold", - " ".join(("Clients:", - "+: Enable", - "-: Disable", - "R: Remove", - "s: Start new checker", - "S: Stop checker", - "C: Checker OK", - "a: Approve", - "d: Deny", - )))) + self.log_message_raw(("bold", + " ". + join(("q: Quit", + "?: Help", + "l: Log window toggle", + "TAB: Switch window", + "w: Wrap (log lines)", + "v: Toggle verbose log", + )))) + self.log_message_raw(("bold", + " " + .join(("Clients:", + "+: Enable", + "-: Disable", + "R: Remove", + "s: Start new checker", + "S: Stop checker", + "C: Checker OK", + "a: Approve", + "d: Deny")))) self.refresh() elif key == "tab": if self.topwidget.get_focus() is self.logbox: @@ -706,58 +715,41 @@ self.topwidget.set_focus(self.logbox) self.refresh() elif key == "v": - if log.level < logging.INFO: - log.setLevel(logging.INFO) - log.info("Verbose mode: Off") + if self.log_level == 0: + self.log_level = 1 + self.log_message("Verbose mode: Off") else: - log.setLevel(logging.NOTSET) - log.info("Verbose mode: On") - # elif (key == "end" or key == "meta >" or key == "G" - # or key == ">"): - # pass # xxx end-of-buffer - # elif (key == "home" or key == "meta <" or key == "g" - # or key == "<"): - # pass # xxx beginning-of-buffer - # elif key == "ctrl e" or key == "$": - # pass # xxx move-end-of-line - # elif key == "ctrl a" or key == "^": - # pass # xxx move-beginning-of-line - # elif key == "ctrl b" or key == "meta (" or key == "h": - # pass # xxx left - # elif key == "ctrl f" or key == "meta )" or key == "l": - # pass # xxx right - # elif key == "a": - # pass # scroll up log - # elif key == "z": - # pass # scroll down log + self.log_level = 0 + self.log_message("Verbose mode: On") + #elif (key == "end" or key == "meta >" or key == "G" + # or key == ">"): + # pass # xxx end-of-buffer + #elif (key == "home" or key == "meta <" or key == "g" + # or key == "<"): + # pass # xxx beginning-of-buffer + #elif key == "ctrl e" or key == "$": + # pass # xxx move-end-of-line + #elif key == "ctrl a" or key == "^": + # pass # xxx move-beginning-of-line + #elif key == "ctrl b" or key == "meta (" or key == "h": + # pass # xxx left + #elif key == "ctrl f" or key == "meta )" or key == "l": + # pass # xxx right + #elif key == "a": + # pass # scroll up log + #elif key == "z": + # pass # scroll down log elif self.topwidget.selectable(): self.topwidget.keypress(self.size, key) self.refresh() return True - -class UILogHandler(logging.Handler): - def __init__(self, ui, *args, **kwargs): - self.ui = ui - super(UILogHandler, self).__init__(*args, **kwargs) - self.setFormatter( - logging.Formatter("%(asctime)s: %(message)s")) - def emit(self, record): - msg = self.format(record) - if record.levelno > logging.INFO: - msg = ("bold", msg) - self.ui.add_log_line(msg) - - ui = UserInterface() try: ui.run() except KeyboardInterrupt: - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", "", BytesWarning) - ui.screen.stop() -except Exception: - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", "", BytesWarning) - ui.screen.stop() + ui.screen.stop() +except Exception as e: + ui.log_message(str(e)) + ui.screen.stop() raise === modified file 'mandos-monitor.xml' --- mandos-monitor.xml 2019-02-10 04:20:26 +0000 +++ mandos-monitor.xml 2016-03-05 21:46:00 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -38,9 +38,6 @@ 2014 2015 2016 - 2017 - 2018 - 2019 Teddy Hogeborn Björn Påhlsson === modified file 'mandos-options.xml' --- mandos-options.xml 2019-07-25 21:42:40 +0000 +++ mandos-options.xml 2015-07-20 03:03:33 +0000 @@ -48,12 +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 2020-07-04 11:58:52 +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-2019 Teddy Hogeborn -# Copyright © 2018-2019 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 command -v 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-06-20 18:54:10 +0000 +++ mandos.conf.xml 2016-03-05 21:42:56 +0000 @@ -3,7 +3,7 @@ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ /etc/mandos/mandos.conf"> - + %common; ]> @@ -41,9 +41,6 @@ 2014 2015 2016 - 2017 - 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -227,11 +224,11 @@ [DEFAULT] # A configuration example -interface = enp1s0 +interface = eth0 address = fe80::aede:48ff:fe71:f6f2 port = 1025 debug = True -priority = SECURE128:!CTYPE-X.509:+CTYPE-RAWPK:!RSA:!VERS-ALL:+VERS-TLS1.3:%PROFILE_ULTRA +priority = SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA servicename = Daena use_dbus = False use_ipv6 = True === modified file 'mandos.lsm' --- mandos.lsm 2022-04-25 18:46:48 +0000 +++ mandos.lsm 2016-03-23 07:11:22 +0000 @@ -1,7 +1,7 @@ Begin4 Title: Mandos -Version: 1.8.15 -Entered-date: 2022-04-25 +Version: 1.7.7 +Entered-date: 2016-03-19 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 - 239K mandos_1.8.15.orig.tar.gz + 172K mandos_1.7.7.orig.tar.gz Alternate-site: ftp://ftp.recompile.se/pub/mandos - 239K mandos_1.8.15.orig.tar.gz + 172K mandos_1.7.7.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.service' --- mandos.service 2020-02-07 20:53:34 +0000 +++ mandos.service 2016-03-13 00:37:02 +0000 @@ -8,14 +8,13 @@ ## If the server is configured to not use ZeroConf, these two lines ## become unnecessary and should be removed or commented out. After=avahi-daemon.service -Requisite=avahi-daemon.service +RequisiteOverridable=avahi-daemon.service [Service] ## If the server's D-Bus interface is disabled, the "BusName" setting ## should be removed or commented out. BusName=se.recompile.Mandos -EnvironmentFile=/etc/default/mandos -ExecStart=/usr/sbin/mandos --foreground $DAEMON_ARGS +ExecStart=/usr/sbin/mandos --foreground Restart=always KillMode=mixed ## Using socket activation won't work, because systemd always does @@ -29,8 +28,6 @@ ProtectSystem=full ProtectHome=yes CapabilityBoundingSet=CAP_KILL CAP_SETGID CAP_SETUID CAP_DAC_OVERRIDE CAP_NET_RAW -ProtectKernelTunables=yes -ProtectControlGroups=yes [Install] WantedBy=multi-user.target === modified file 'mandos.xml' --- mandos.xml 2022-04-23 23:25:49 +0000 +++ mandos.xml 2016-03-05 21:42:56 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -40,9 +40,6 @@ 2014 2015 2016 - 2017 - 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -136,8 +133,8 @@ DESCRIPTION &COMMANDNAME; is a server daemon which - handles incoming requests for passwords for a pre-defined list - of client host computers. For an introduction, see + handles incoming request for passwords for a pre-defined list of + client host computers. For an introduction, see intro 8mandos. The Mandos server uses Zeroconf to announce itself on the local network, and uses @@ -362,11 +359,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 +389,7 @@ - Public key (part of TLS handshake) + OpenPGP public key (part of TLS handshake) -> @@ -587,6 +584,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 +644,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 @@ -695,7 +696,7 @@ - Avahi + Avahi @@ -706,13 +707,13 @@ - GnuTLS + GnuTLS 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. @@ -739,7 +740,7 @@ The clients use IPv6 link-local addresses, which are - immediately usable since a link-local address is + immediately usable since a link-local addresses is automatically assigned to a network interfaces when it is brought up. @@ -771,28 +772,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 'network-hooks.d/bridge' --- network-hooks.d/bridge 2018-02-08 10:23:55 +0000 +++ network-hooks.d/bridge 2012-06-13 22:06:57 +0000 @@ -6,8 +6,8 @@ # configuration file(s) should be copied into the # /etc/mandos/network-hooks.d directory. # -# Copyright © 2012-2018 Teddy Hogeborn -# Copyright © 2012-2018 Björn Påhlsson +# Copyright © 2012 Teddy Hogeborn +# Copyright © 2012 Björn Påhlsson # # Copying and distribution of this file, with or without modification, # are permitted in any medium without royalty provided the copyright @@ -29,7 +29,7 @@ . "$CONFIG" fi -if [ -z "$BRIDGE" ] || [ -z "$PORT_ADDRESSES" ]; then +if [ -z "$BRIDGE" -o -z "$PORT_ADDRESSES" ]; then exit fi === modified file 'network-hooks.d/openvpn' --- network-hooks.d/openvpn 2018-02-08 10:23:55 +0000 +++ network-hooks.d/openvpn 2012-06-13 22:06:57 +0000 @@ -6,8 +6,8 @@ # configuration file(s) should be copied into the # /etc/mandos/network-hooks.d directory. # -# Copyright © 2012-2018 Teddy Hogeborn -# Copyright © 2012-2018 Björn Påhlsson +# Copyright © 2012 Teddy Hogeborn +# Copyright © 2012 Björn Påhlsson # # Copying and distribution of this file, with or without modification, # are permitted in any medium without royalty provided the copyright === modified file 'network-hooks.d/wireless' --- network-hooks.d/wireless 2018-02-08 10:23:55 +0000 +++ network-hooks.d/wireless 2012-06-13 22:06:57 +0000 @@ -6,8 +6,8 @@ # configuration file(s) should be copied into the # /etc/mandos/network-hooks.d directory. # -# Copyright © 2012-2018 Teddy Hogeborn -# Copyright © 2012-2018 Björn Påhlsson +# Copyright © 2012 Teddy Hogeborn +# Copyright © 2012 Björn Påhlsson # # Copying and distribution of this file, with or without modification, # are permitted in any medium without royalty provided the copyright === 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 2022-04-24 16:54:30 +0000 +++ plugin-helpers/mandos-client-iprouteadddel.c 2016-02-28 14:22:10 +0000 @@ -2,80 +2,72 @@ /* * iprouteadddel - Add or delete direct route to a local IP address * - * Copyright © 2015-2018, 2021-2022 Teddy Hogeborn - * Copyright © 2015-2018, 2021-2022 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 + * Copyright © 2015-2016 Teddy Hogeborn + * Copyright © 2015-2016 Björn Påhlsson + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with Mandos. If not, see . + * along with this program. If not, see + * . * * Contact the authors at . */ -#define _GNU_SOURCE /* program_invocation_short_name */ +#define _GNU_SOURCE /* asprintf(), + program_invocation_short_name */ #include /* bool, false, true */ -#include /* argp_program_version, - argp_program_bug_address, - struct argp_option, - struct argp_state, ARGP_KEY_ARG, +#include /* fprintf(), stderr, FILE, vfprintf */ +#include /* program_invocation_short_name, + errno, perror(), EINVAL, ENOMEM */ +#include /* va_list, va_start */ +#include /* EXIT_SUCCESS */ +#include /* struct argp_option, error_t, struct + argp_state, ARGP_KEY_ARG, argp_usage(), ARGP_KEY_END, ARGP_ERR_UNKNOWN, struct argp, - argp_parse(), ARGP_IN_ORDER */ -#include /* errno, - program_invocation_short_name, - error_t, EINVAL, ENOMEM */ -#include /* fprintf(), stderr, perror(), FILE, - vfprintf() */ -#include /* va_list, va_start(), vfprintf() */ -#include /* EXIT_SUCCESS */ + argp_parse() */ +#include /* EX_USAGE, EX_OSERR */ +#include /* sa_family_t, AF_INET6, AF_INET */ +#include /* PRIdMAX, intmax_t */ + #include /* struct nl_addr, nl_addr_parse(), nl_geterror(), - nl_addr_get_family(), NLM_F_EXCL, + nl_addr_get_family(), nl_addr_put() */ -#include /* NULL */ -#include /* struct rtnl_route, - struct rtnl_nexthop, NETLINK_ROUTE, - rtnl_route_alloc(), - rtnl_route_set_family(), - rtnl_route_set_protocol(), - RTPROT_BOOT, - rtnl_route_set_scope(), - RT_SCOPE_LINK, - rtnl_route_set_type(), RTN_UNICAST, - rtnl_route_set_dst(), - rtnl_route_set_table(), - RT_TABLE_MAIN, - rtnl_route_nh_alloc(), - rtnl_route_nh_set_ifindex(), - rtnl_route_add_nexthop(), - rtnl_route_add(), - rtnl_route_delete(), - rtnl_route_put(), - rtnl_route_nh_free() */ +#include /* struct rtnl_route, + struct rtnl_nexthop, + rtnl_route_alloc(), + rtnl_route_set_family(), + rtnl_route_set_protocol(), + RTPROT_BOOT, + rtnl_route_set_scope(), + RT_SCOPE_LINK, + rtnl_route_set_type(), + RTN_UNICAST, + rtnl_route_set_dst(), + rtnl_route_set_table(), + RT_TABLE_MAIN, + rtnl_route_nh_alloc(), + rtnl_route_nh_set_ifindex(), + rtnl_route_add_nexthop(), + rtnl_route_add(), + rtnl_route_delete(), + rtnl_route_put(), + rtnl_route_nh_free() */ #include /* struct nl_sock, nl_socket_alloc(), nl_connect(), nl_socket_free() */ -#include /* strcasecmp() */ -#include /* AF_UNSPEC, AF_INET6, AF_INET */ -#include /* EX_USAGE, EX_OSERR */ -#include /* struct rtnl_link, - rtnl_link_get_kernel(), +#include /* rtnl_link_get_kernel(), rtnl_link_get_ifindex(), rtnl_link_put() */ -#include /* sa_family_t */ -#include /* PRIdMAX, intmax_t */ -#include /* uint8_t */ - bool debug = false; const char *argp_program_version = "mandos-client-iprouteadddel " VERSION; @@ -93,7 +85,7 @@ __attribute__((format (gnu_printf, 2, 3), nonnull)) int fprintf_plus(FILE *stream, const char *format, ...){ va_list ap; - va_start(ap, format); + va_start (ap, format); fprintf(stream, "Mandos plugin helper %s: ", program_invocation_short_name); @@ -251,7 +243,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 2022-04-24 16:54:30 +0000 +++ plugin-runner.c 2016-03-17 21:14:12 +0000 @@ -2,90 +2,75 @@ /* * Mandos plugin runner - Run Mandos plugins * - * Copyright © 2008-2022 Teddy Hogeborn - * Copyright © 2008-2022 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 + * Copyright © 2008-2016 Teddy Hogeborn + * Copyright © 2008-2016 Björn Påhlsson + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with Mandos. If not, see . + * along with this program. If not, see + * . * * Contact the authors at . */ -#define _GNU_SOURCE /* strchrnul(), TEMP_FAILURE_RETRY(), - getline(), asprintf(), O_CLOEXEC, - scandirat(), pipe2() */ -#include /* argp_program_version, - argp_program_bug_address, - struct argp_option, - struct argp_state, argp_error(), - ARGP_NO_EXIT, argp_state_help, - ARGP_HELP_STD_HELP, - ARGP_HELP_USAGE, ARGP_HELP_EXIT_OK, - ARGP_KEY_ARG, ARGP_ERR_UNKNOWN, - struct argp, argp_parse(), - ARGP_IN_ORDER, ARGP_NO_HELP */ -#include /* bool, false, true */ -#include /* pid_t, sig_atomic_t, uid_t, gid_t, - getuid(), setgid(), setuid() */ +#define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), getline(), + O_CLOEXEC, pipe2() */ #include /* size_t, NULL */ -#include /* or, and, not */ -#include /* strcmp(), strdup(), strchrnul(), - strncmp(), strlen(), strcpy(), - strsep(), strchr(), strsignal() */ -#include /* malloc(), free(), reallocarray(), - realloc(), EXIT_SUCCESS */ -#include /* errno, EINTR, ENOMEM, ECHILD, - error_t, EINVAL, EMFILE, ENFILE, - ENOENT, ESRCH */ -#include /* SIZE_MAX */ -#define _GNU_SOURCE /* strchrnul(), TEMP_FAILURE_RETRY(), - getline(), asprintf(), O_CLOEXEC, - scandirat(), pipe2() */ -#include /* TEMP_FAILURE_RETRY(), ssize_t, - write(), STDOUT_FILENO, uid_t, - gid_t, getuid(), fchown(), close(), - symlink(), setgid(), setuid(), - faccessat(), X_OK, pipe(), pipe2(), - fork(), _exit(), dup2(), fexecve(), - read(), getpass() */ +#include /* malloc(), exit(), EXIT_SUCCESS, + realloc() */ +#include /* bool, true, false */ +#include /* fileno(), fprintf(), + stderr, STDOUT_FILENO, fclose() */ +#include /* fstat(), struct stat, waitpid(), + WIFEXITED(), WEXITSTATUS(), wait(), + pid_t, uid_t, gid_t, getuid(), + getgid() */ +#include /* fd_set, select(), FD_ZERO(), + FD_SET(), FD_ISSET(), FD_CLR */ +#include /* wait(), waitpid(), WIFEXITED(), + WEXITSTATUS(), WTERMSIG() */ +#include /* struct stat, fstat(), S_ISREG() */ +#include /* and, or, not */ +#include /* struct dirent, scandirat() */ +#include /* fcntl(), F_GETFD, F_SETFD, + FD_CLOEXEC, write(), STDOUT_FILENO, + struct stat, fstat(), close(), + setgid(), setuid(), S_ISREG(), + faccessat() pipe2(), fork(), + _exit(), dup2(), fexecve(), read() + */ #include /* fcntl(), F_GETFD, F_SETFD, - FD_CLOEXEC, open(), O_RDONLY, - O_CLOEXEC, openat() */ -#include /* waitpid(), WNOHANG, WIFEXITED(), - WEXITSTATUS(), WIFSIGNALED(), - WTERMSIG(), wait() */ -#include /* error() */ -#include /* FILE, fprintf(), fopen(), - getline(), fclose(), EOF, - asprintf(), stderr */ -#include /* struct dirent, scandirat(), - alphasort() */ -#include /* struct stat, fstat(), S_ISDIR(), - lstat(), S_ISREG() */ -#include /* fd_set, FD_ZERO(), FD_SETSIZE, - FD_SET(), select(), FD_CLR(), - FD_ISSET() */ -#include /* struct sigaction, SA_NOCLDSTOP, - sigemptyset(), sigaddset(), - SIGCHLD, sigprocmask(), SIG_BLOCK, - SIG_UNBLOCK, kill(), SIGTERM */ + FD_CLOEXEC, openat(), scandirat(), + pipe2() */ +#include /* strsep, strlen(), strsignal(), + strcmp(), strncmp() */ +#include /* errno */ +#include /* struct argp_option, struct + argp_state, struct argp, + argp_parse(), ARGP_ERR_UNKNOWN, + ARGP_KEY_END, ARGP_KEY_ARG, + error_t */ +#include /* struct sigaction, sigemptyset(), + sigaddset(), sigaction(), + sigprocmask(), SIG_BLOCK, SIGCHLD, + SIG_UNBLOCK, kill(), sig_atomic_t + */ +#include /* errno, EBADF */ +#include /* intmax_t, PRIdMAX, strtoimax() */ #include /* EX_OSERR, EX_USAGE, EX_IOERR, EX_CONFIG, EX_UNAVAILABLE, EX_OK */ -#include /* intmax_t, strtoimax(), PRIdMAX */ -#include /* fnmatch(), FNM_FILE_NAME, - FNM_PERIOD, FNM_NOMATCH */ +#include /* errno */ +#include /* error() */ +#include /* fnmatch() */ #define BUFFER_SIZE 256 @@ -193,19 +178,8 @@ /* Resize the pointed-to array to hold one more pointer */ char **new_array = NULL; do { -#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 26) - new_array = reallocarray(*array, (size_t)((*len) + 2), - sizeof(char *)); -#else - if(((size_t)((*len) + 2)) > (SIZE_MAX / sizeof(char *))){ - /* overflow */ - new_array = NULL; - errno = ENOMEM; - } else { - new_array = realloc(*array, (size_t)((*len) + 2) - * sizeof(char *)); - } -#endif + new_array = realloc(*array, sizeof(char *) + * (size_t) ((*len) + 2)); } while(new_array == NULL and errno == EINTR); /* Malloc check */ if(new_array == NULL){ @@ -338,10 +312,9 @@ __attribute__((nonnull)) static void free_plugin(plugin *plugin_node){ - for(char **arg = (plugin_node->argv)+1; *arg != NULL; arg++){ + 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); @@ -590,12 +563,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); @@ -611,11 +582,6 @@ if(arg[0] == '\0'){ break; } -#if __GNUC__ >= 7 - __attribute__((fallthrough)); -#else - /* FALLTHROUGH */ -#endif default: return ARGP_ERR_UNKNOWN; } @@ -733,22 +699,10 @@ custom_argc += 1; { -#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 26) - char **new_argv = reallocarray(custom_argv, - (size_t)custom_argc + 1, - sizeof(char *)); -#else - char **new_argv = NULL; - if(((size_t)custom_argc + 1) > (SIZE_MAX / sizeof(char *))){ - /* overflow */ - errno = ENOMEM; - } else { - new_argv = realloc(custom_argv, ((size_t)custom_argc + 1) - * sizeof(char *)); - } -#endif + char **new_argv = realloc(custom_argv, sizeof(char *) + * ((size_t)custom_argc + 1)); if(new_argv == NULL){ - error(0, errno, "reallocarray"); + error(0, errno, "realloc"); exitstatus = EX_OSERR; free(new_arg); free(org_line); @@ -838,7 +792,7 @@ } if(debug){ - for(plugin *p = plugin_list; p != NULL; p = p->next){ + for(plugin *p = plugin_list; p != NULL; p=p->next){ fprintf(stderr, "Plugin: %s has %d arguments\n", p->name ? p->name : "Global", p->argc - 1); for(char **a = p->argv; *a != NULL; a++){ @@ -853,7 +807,7 @@ if(getuid() == 0){ /* Work around Debian bug #633582: - */ + */ int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY); if(plugindir_fd == -1){ if(errno != ENOENT){ @@ -873,15 +827,6 @@ } close(plugindir_fd); } - - /* Work around Debian bug #981302 - */ - if(lstat("/dev/fd", &st) != 0 and errno == ENOENT){ - ret = symlink("/proc/self/fd", "/dev/fd"); - if(ret == -1){ - error(0, errno, "Failed to create /dev/fd symlink"); - } - } } /* Lower permissions */ @@ -1146,12 +1091,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-07-25 22:44:36 +0000 +++ plugin-runner.xml 2016-03-17 21:18:37 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -40,9 +40,6 @@ 2014 2015 2016 - 2017 - 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -628,14 +625,14 @@ Read a different configuration file, run plugins from a different directory, specify an alternate plugin helper - directory and add four options to the + directory and add two options to the mandos-client 8mandos plugin: -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 2021-02-03 08:33:43 +0000 +++ plugins.d/askpass-fifo.c 2016-02-28 14:22:10 +0000 @@ -2,52 +2,47 @@ /* * Askpass-FIFO - Read a password from a FIFO and output it * - * Copyright © 2008-2019, 2021 Teddy Hogeborn - * Copyright © 2008-2019, 2021 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 + * Copyright © 2008-2016 Teddy Hogeborn + * Copyright © 2008-2016 Björn Påhlsson + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with Mandos. If not, see . + * along with this program. If not, see + * . * * Contact the authors at . */ -#define _GNU_SOURCE /* vasprintf(), - program_invocation_short_name */ -#include /* uid_t, gid_t, getuid(), getgid(), - setgid(), setuid() */ -#include /* uid_t, gid_t, ssize_t, getuid(), - getgid(), setgid(), setuid(), - read(), close(), write(), - STDOUT_FILENO */ -#include /* va_list, va_start(), vfprintf() */ -#include /* vasprintf(), fprintf(), stderr, - vfprintf() */ -#include /* program_invocation_short_name, - errno, EACCES, ENOTDIR, ELOOP, +#define _GNU_SOURCE /* TEMP_FAILURE_RETRY() */ +#include /* uid_t, gid_t, ssize_t */ +#include /* mkfifo(), S_IRUSR, S_IWUSR */ +#include /* and */ +#include /* errno, EACCES, ENOTDIR, ELOOP, ENAMETOOLONG, ENOSPC, EROFS, ENOENT, EEXIST, EFAULT, EMFILE, ENFILE, ENOMEM, EBADF, EINVAL, EIO, EISDIR, EFBIG */ -#include /* strerror() */ #include /* error() */ -#include /* free(), realloc(), EXIT_SUCCESS */ -#include /* mkfifo(), S_IRUSR, S_IWUSR */ -#include /* EX_OSFILE, EX_OSERR, +#include /* fprintf(), vfprintf(), + vasprintf() */ +#include /* EXIT_FAILURE, NULL, size_t, free(), + realloc(), EXIT_SUCCESS */ +#include /* open(), O_RDONLY */ +#include /* read(), close(), write(), + STDOUT_FILENO */ +#include /* EX_OSERR, EX_OSFILE, EX_UNAVAILABLE, EX_IOERR */ -#include /* open(), O_RDONLY */ -#include /* NULL, size_t */ +#include /* strerror() */ +#include /* va_list, va_start(), ... */ uid_t uid = 65534; gid_t gid = 65534; @@ -69,16 +64,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); } @@ -100,17 +89,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 2016-03-05 21:42:56 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -40,9 +40,6 @@ 2014 2015 2016 - 2017 - 2018 - 2019 Teddy Hogeborn Björn Påhlsson === modified file 'plugins.d/mandos-client.c' --- plugins.d/mandos-client.c 2022-04-24 16:54:30 +0000 +++ plugins.d/mandos-client.c 2016-03-05 20:11:10 +0000 @@ -9,23 +9,22 @@ * "browse_callback", and parts of "main". * * Everything else is - * Copyright © 2008-2022 Teddy Hogeborn - * Copyright © 2008-2022 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 + * Copyright © 2008-2016 Teddy Hogeborn + * Copyright © 2008-2016 Björn Påhlsson + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with Mandos. If not, see . + * along with this program. If not, see + * . * * Contact the authors at . */ @@ -38,103 +37,68 @@ #define _FILE_OFFSET_BITS 64 #endif /* not _FILE_OFFSET_BITS */ -#define _GNU_SOURCE /* program_invocation_short_name, - TEMP_FAILURE_RETRY(), O_CLOEXEC, - scandirat(), asprintf() */ +#define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), asprintf() */ + +#include /* fprintf(), stderr, fwrite(), + stdout, ferror() */ +#include /* uint16_t, uint32_t, intptr_t */ +#include /* NULL, size_t, ssize_t */ +#include /* free(), EXIT_SUCCESS, srand(), + strtof(), abort() */ #include /* bool, false, true */ -#include /* argp_program_version, - argp_program_bug_address, - struct argp_option, - struct argp_state, argp_error(), - argp_state_help, - ARGP_HELP_STD_HELP, - ARGP_HELP_EXIT_ERR, - ARGP_HELP_EXIT_OK, ARGP_HELP_USAGE, - argp_err_exit_status, - ARGP_ERR_UNKNOWN, struct argp, - argp_parse(), ARGP_IN_ORDER, - ARGP_NO_HELP */ -#include /* NULL, size_t */ -#include /* uid_t, gid_t, sig_atomic_t, - seteuid(), setuid(), pid_t, - setgid(), getuid(), getgid() */ -#include /* uid_t, gid_t, TEMP_FAILURE_RETRY(), - seteuid(), setuid(), close(), - ssize_t, read(), fork(), setgid(), - _exit(), dup2(), STDIN_FILENO, - STDERR_FILENO, STDOUT_FILENO, - fexecve(), write(), getuid(), - getgid(), fchown(), symlink(), - sleep(), unlinkat(), pause() */ -#include /* in_port_t, struct sockaddr_in6, - sa_family_t, struct sockaddr_in, - htons(), IN6_IS_ADDR_LINKLOCAL, - INET_ADDRSTRLEN, INET6_ADDRSTRLEN, - ntohl(), IPPROTO_IP */ -#include /* struct timespec, clock_gettime(), - CLOCK_MONOTONIC, time_t, struct tm, - gmtime_r(), clock_settime(), - CLOCK_REALTIME, nanosleep() */ -#include /* errno, - program_invocation_short_name, - EINTR, EINVAL, ENETUNREACH, +#include /* strcmp(), strlen(), strerror(), + asprintf(), strncpy() */ +#include /* ioctl */ +#include /* socket(), inet_pton(), sockaddr, + sockaddr_in6, PF_INET6, + SOCK_STREAM, uid_t, gid_t, open(), + opendir(), DIR */ +#include /* open(), S_ISREG */ +#include /* socket(), struct sockaddr_in6, + inet_pton(), connect(), + getnameinfo() */ +#include /* open(), unlinkat(), AT_REMOVEDIR */ +#include /* opendir(), struct dirent, readdir() + */ +#include /* PRIu16, PRIdMAX, intmax_t, + strtoimax() */ +#include /* perror(), errno, EINTR, EINVAL, + EAI_SYSTEM, ENETUNREACH, EHOSTUNREACH, ECONNREFUSED, EPROTO, - EIO, ENOENT, ENXIO, error_t, - ENOMEM, EISDIR, ENOTEMPTY */ -#include /* fprintf(), stderr, perror(), FILE, - vfprintf(), off_t, SEEK_SET, - stdout, fwrite(), ferror(), - fflush(), asprintf() */ -#include /* va_list, va_start(), vfprintf() */ -#include /* realloc(), free(), malloc(), - getenv(), EXIT_FAILURE, setenv(), - EXIT_SUCCESS, strtof(), strtod(), - srand(), mkdtemp(), abort() */ -#include /* strdup(), strcmp(), strlen(), - strerror(), strncpy(), strspn(), - memcpy(), strrchr(), strchr(), - strsignal() */ -#include /* open(), O_RDONLY, O_DIRECTORY, - O_PATH, O_CLOEXEC, openat(), - O_NOFOLLOW, AT_REMOVEDIR */ -#include /* or, and, not */ -#include /* struct stat, fstat(), fstatat(), - S_ISREG(), S_IXUSR, S_IXGRP, - S_IXOTH, lstat() */ -#include /* IF_NAMESIZE, if_indextoname(), - if_nametoindex(), SIOCGIFFLAGS, - IFF_LOOPBACK, IFF_POINTOPOINT, - IFF_BROADCAST, IFF_NOARP, IFF_UP, - IFF_RUNNING, SIOCSIFFLAGS */ -#include /* EX_NOPERM, EX_OSERR, - EX_UNAVAILABLE, EX_USAGE */ + EIO, ENOENT, ENXIO, ENOMEM, EISDIR, + ENOTEMPTY, + program_invocation_short_name */ +#include /* nanosleep(), time(), sleep() */ +#include /* ioctl, ifreq, SIOCGIFFLAGS, IFF_UP, + SIOCSIFFLAGS, if_indextoname(), + if_nametoindex(), IF_NAMESIZE */ +#include /* IN6_IS_ADDR_LINKLOCAL, + INET_ADDRSTRLEN, INET6_ADDRSTRLEN + */ +#include /* close(), SEEK_SET, off_t, write(), + getuid(), getgid(), seteuid(), + setgid(), pause(), _exit(), + unlinkat() */ +#include /* inet_pton(), htons() */ +#include /* not, or, and */ +#include /* struct argp_option, error_t, struct + argp_state, struct argp, + argp_parse(), ARGP_KEY_ARG, + ARGP_KEY_END, ARGP_ERR_UNKNOWN */ +#include /* sigemptyset(), sigaddset(), + sigaction(), SIGTERM, sig_atomic_t, + raise() */ +#include /* EX_OSERR, EX_USAGE, EX_UNAVAILABLE, + EX_NOHOST, EX_IOERR, EX_PROTOCOL */ +#include /* waitpid(), WIFEXITED(), + WEXITSTATUS(), WTERMSIG() */ #include /* setgroups() */ -#include /* waitpid(), WIFEXITED(), - WEXITSTATUS(), WIFSIGNALED(), - WTERMSIG() */ -#include /* kill(), SIGTERM, struct sigaction, - SIG_DFL, sigemptyset(), - sigaddset(), SIGINT, SIGHUP, - SIG_IGN, raise() */ -#include /* struct sockaddr_storage, AF_INET6, - PF_INET6, AF_INET, PF_INET, - socket(), SOCK_STREAM, - SOCK_CLOEXEC, struct sockaddr, - connect(), SOCK_DGRAM */ -#include /* argz_next(), argz_add_sep(), - argz_delete(), argz_stringify(), - argz_add(), argz_count() */ -#include /* PRIuMAX, uintmax_t, uint32_t, - PRIdMAX, PRIu16, intmax_t, - strtoimax() */ -#include /* inet_pton() */ -#include /* uint32_t, intptr_t, uint16_t */ +#include /* argz_add_sep(), argz_next(), + argz_delete(), argz_append(), + argz_stringify(), argz_add(), + argz_count() */ #include /* getnameinfo(), NI_NUMERICHOST, EAI_SYSTEM, gai_strerror() */ -#include /* ioctl() */ -#include /* struct dirent, scandirat(), - alphasort(), scandir() */ -#include /* INT_MAX */ #ifdef __linux__ #include /* klogctl() */ @@ -153,30 +117,26 @@ /* GnuTLS */ #include /* All GnuTLS types, constants and - functions: gnutls_*, GNUTLS_* */ -#if GNUTLS_VERSION_NUMBER < 0x030600 + functions: + gnutls_* + init_gnutls_session(), + GNUTLS_* */ #include /* gnutls_certificate_set_openpgp_key_file(), GNUTLS_OPENPGP_FMT_BASE64 */ -#elif GNUTLS_VERSION_NUMBER >= 0x030606 -#include /* GNUTLS_PKCS_PLAIN, - GNUTLS_PKCS_NULL_PASSWORD */ -#endif /* GPGME */ #include /* All GPGME types, constants and functions: - gpgme_*, GPG_ERR_NO_*, - GPGME_IMPORT_* - GPGME_PROTOCOL_OpenPGP */ + gpgme_* + GPGME_PROTOCOL_OpenPGP, + GPG_ERR_NO_* */ #define BUFFER_SIZE 256 #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; @@ -310,58 +270,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. */ @@ -387,55 +295,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"); - } - if(clock_settime(CLOCK_REALTIME, &keystat.st_mtim) != 0){ - perror_plus("clock_settime"); - } - 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", @@ -449,81 +308,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){ @@ -570,8 +354,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; } @@ -613,7 +398,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; @@ -632,23 +418,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; } } } @@ -736,6 +523,7 @@ const char *dhparamsfilename, mandos_context *mc){ int ret; + unsigned int uret; if(debug){ fprintf_plus(stderr, "Initializing GnuTLS\n"); @@ -758,34 +546,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)); @@ -839,10 +611,6 @@ } params.size += (unsigned int)bytes_read; } - ret = close(dhpfile); - if(ret == -1){ - perror_plus("close"); - } if(params.data == NULL){ dhparamsfilename = NULL; } @@ -857,12 +625,10 @@ safer_gnutls_strerror(ret)); dhparamsfilename = NULL; } - free(params.data); } while(false); } 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 }; @@ -948,7 +714,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){ @@ -966,22 +732,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; @@ -998,14 +761,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; } @@ -1059,6 +815,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, @@ -1103,7 +911,6 @@ ret = setgid(0); if(ret == -1){ perror_plus("setgid"); - close(devnull); _exit(EX_NOPERM); } /* Reset supplementary groups */ @@ -1111,19 +918,18 @@ ret = setgroups(0, NULL); if(ret == -1){ perror_plus("setgroups"); - close(devnull); _exit(EX_NOPERM); } } ret = dup2(devnull, STDIN_FILENO); if(ret == -1){ perror_plus("dup2(devnull, STDIN_FILENO)"); - close(devnull); _exit(EX_OSERR); } ret = close(devnull); if(ret == -1){ perror_plus("close"); + _exit(EX_OSERR); } ret = dup2(STDERR_FILENO, STDOUT_FILENO); if(ret == -1){ @@ -1164,13 +970,8 @@ } if(pid == -1){ perror_plus("fork"); - close(devnull); return false; } - ret = close(devnull); - if(ret == -1){ - perror_plus("close"); - } int status; pid_t pret = -1; errno = 0; @@ -1276,9 +1077,8 @@ bool match = false; { char *interface = NULL; - while((interface = argz_next(mc->interfaces, - mc->interfaces_size, - interface))){ + while((interface=argz_next(mc->interfaces, mc->interfaces_size, + interface))){ if(if_nametoindex(interface) == (unsigned int)if_index){ match = true; break; @@ -1437,7 +1237,7 @@ with an explicit route added with the server's address. Avahi bug reference: - https://lists.freedesktop.org/archives/avahi/2010-February/001833.html + http://lists.freedesktop.org/archives/avahi/2010-February/001833.html https://bugs.debian.org/587961 */ if(debug){ @@ -1623,7 +1423,6 @@ &decrypted_buffer, mc); if(decrypted_buffer_size >= 0){ - clearerr(stdout); written = 0; while(written < (size_t) decrypted_buffer_size){ if(quit_now){ @@ -1645,16 +1444,6 @@ } written += (size_t)ret; } - ret = fflush(stdout); - if(ret != 0){ - int e = errno; - if(debug){ - fprintf_plus(stderr, "Error writing encrypted data: %s\n", - strerror(errno)); - } - errno = e; - goto mandos_end; - } retval = 0; } } @@ -1691,6 +1480,7 @@ return retval; } +__attribute__((nonnull)) static void resolve_callback(AvahiSServiceResolver *r, AvahiIfIndex interface, AvahiProtocol proto, @@ -1851,18 +1641,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; } @@ -2129,20 +1909,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){ @@ -2404,7 +2183,7 @@ /* Sleep checking until interface is running. Check every 0.25s, up to total time of delay */ - for(int i = 0; i < delay * 4; i++){ + for(int i=0; i < delay * 4; i++){ if(interface_is_running(interface)){ break; } @@ -2497,16 +2276,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; @@ -2523,10 +2294,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; @@ -2580,23 +2347,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" @@ -2658,16 +2409,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); @@ -2708,11 +2449,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); @@ -2745,6 +2484,9 @@ } { + /* Work around Debian bug #633582: + */ + /* Re-raise privileges */ ret = raise_privileges(); if(ret != 0){ @@ -2753,9 +2495,6 @@ } else { struct stat st; - /* Work around Debian bug #633582: - */ - if(strcmp(seckey, PATHDIR "/" SECKEY) == 0){ int seckey_fd = open(seckey, O_RDONLY); if(seckey_fd == -1){ @@ -2820,15 +2559,6 @@ } } - /* Work around Debian bug #981302 - */ - if(lstat("/dev/fd", &st) != 0 and errno == ENOENT){ - ret = symlink("/proc/self/fd", "/dev/fd"); - if(ret == -1){ - perror_plus("Failed to create /dev/fd symlink"); - } - } - /* Lower privileges */ ret = lower_privileges(); if(ret != 0){ @@ -3038,13 +2768,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; @@ -3222,13 +2946,7 @@ end: if(debug){ - if(signal_received){ - fprintf_plus(stderr, "%s exiting due to signal %d: %s\n", - argv[0], signal_received, - strsignal(signal_received)); - } else { - fprintf_plus(stderr, "%s exiting\n", argv[0]); - } + fprintf_plus(stderr, "%s exiting\n", argv[0]); } /* Cleanup things */ @@ -3286,9 +3004,9 @@ /* Take down the network interfaces which were brought up */ { char *interface = NULL; - while((interface = argz_next(interfaces_to_take_down, - interfaces_to_take_down_size, - interface))){ + while((interface=argz_next(interfaces_to_take_down, + interfaces_to_take_down_size, + interface))){ ret = take_down_interface(interface); if(ret != 0){ errno = ret; @@ -3323,7 +3041,6 @@ | O_PATH)); if(dir_fd == -1){ perror_plus("open"); - return; } int numentries = scandirat(dir_fd, ".", &direntries, notdotentries, alphasort); @@ -3346,7 +3063,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)); } @@ -3356,6 +3073,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-07-27 10:11:45 +0000 +++ plugins.d/mandos-client.xml 2016-03-05 21:42:56 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -40,9 +40,6 @@ 2014 2015 2016 - 2017 - 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -96,20 +93,6 @@ FILE - - - - - - - - - - @@ -169,10 +152,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 +305,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 +320,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. @@ -525,8 +479,8 @@ This environment variable will be assumed to contain the directory containing any helper executables. The use and - nature of these helper executables, if any, is purposely - not documented. + nature of these helper executables, if any, is + purposefully not documented. @@ -726,20 +680,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 +727,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 +747,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 @@ -816,9 +756,9 @@ SECURITY - This program assumes that it is set-uid to root, and will switch - back to the original (and presumably non-privileged) user and - group after bringing up the network interface. + This program is set-uid to root, but will switch back to the + original (and presumably non-privileged) user and group after + bringing up the network interface. To use this program for its intended purpose (see 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. @@ -891,7 +831,7 @@ - Avahi + Avahi @@ -902,19 +842,20 @@ - GnuTLS + GnuTLS 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. - GPGME @@ -958,12 +899,12 @@ - RFC 5246: The Transport Layer Security (TLS) - Protocol Version 1.2 + RFC 4346: The Transport Layer Security (TLS) + Protocol Version 1.1 - TLS 1.2 is the protocol implemented by GnuTLS. + TLS 1.1 is the protocol implemented by GnuTLS. @@ -980,28 +921,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 + RFC 5081: 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 2022-04-24 16:54:30 +0000 +++ plugins.d/password-prompt.c 2016-02-28 14:22:10 +0000 @@ -2,73 +2,66 @@ /* * Password-prompt - Read a password from the terminal and print it * - * Copyright © 2008-2019, 2021-2022 Teddy Hogeborn - * Copyright © 2008-2019, 2021-2022 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 + * Copyright © 2008-2016 Teddy Hogeborn + * Copyright © 2008-2016 Björn Påhlsson + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with Mandos. If not, see . + * along with this program. If not, see + * . * * Contact the authors at . */ -#define _GNU_SOURCE /* vasprintf(), - program_invocation_short_name, - asprintf(), getline() */ -#include /* sig_atomic_t, pid_t */ +#define _GNU_SOURCE /* getline(), asprintf() */ + +#include /* struct termios, tcsetattr(), + TCSAFLUSH, tcgetattr(), ECHO */ +#include /* struct termios, tcsetattr(), + STDIN_FILENO, TCSAFLUSH, + tcgetattr(), ECHO, readlink() */ +#include /* sig_atomic_t, raise(), struct + sigaction, sigemptyset(), + sigaction(), sigaddset(), SIGINT, + SIGQUIT, SIGHUP, SIGTERM, + raise() */ +#include /* NULL, size_t, ssize_t */ +#include /* ssize_t, struct dirent, pid_t, + ssize_t, open() */ +#include /* EXIT_SUCCESS, EXIT_FAILURE, + getenv(), free() */ +#include /* scandir(), alphasort() */ +#include /* fprintf(), stderr, getline(), + stdin, feof(), fputc(), vfprintf(), + vasprintf() */ +#include /* errno, EBADF, ENOTTY, EINVAL, + EFAULT, EFBIG, EIO, ENOSPC, EINTR + */ +#include /* error() */ +#include /* or, not */ #include /* bool, false, true */ -#include /* argp_program_version, - argp_program_bug_address, - struct argp_option, - struct argp_state, argp_state_help, - ARGP_HELP_STD_HELP, - ARGP_HELP_EXIT_ERR, - ARGP_HELP_EXIT_OK, ARGP_HELP_USAGE, - argp_err_exit_status, - ARGP_ERR_UNKNOWN, argp_parse(), - ARGP_IN_ORDER, ARGP_NO_HELP */ -#include /* va_list, va_start(), vfprintf() */ -#include /* vasprintf(), fprintf(), stderr, - vfprintf(), asprintf(), getline(), - stdin, feof(), clearerr(), - fputc() */ -#include /* program_invocation_short_name, - errno, ENOENT, error_t, ENOMEM, - EINVAL, EBADF, ENOTTY, EFAULT, - EFBIG, EIO, ENOSPC, EINTR */ -#include /* strerror(), strrchr(), strcmp() */ -#include /* error() */ -#include /* free(), realloc(), EXIT_SUCCESS, - EXIT_FAILURE, getenv() */ -#include /* access(), R_OK, ssize_t, close(), - read(), STDIN_FILENO, write(), - STDOUT_FILENO */ -#include /* struct dirent, scandir(), - alphasort() */ -#include /* uintmax_t, strtoumax() */ -#include /* or, and, not */ -#include /* open(), O_RDONLY */ -#include /* NULL, size_t */ -#include /* struct termios, tcgetattr(), - tcflag_t, ECHO, tcsetattr(), - TCSAFLUSH */ -#include /* struct sigaction, sigemptyset(), - sigaddset(), SIGINT, SIGHUP, - SIGTERM, SIG_IGN, SIG_DFL, - raise() */ -#include /* EX_OSERR, EX_USAGE, EX_UNAVAILABLE, - EX_IOERR, EX_OSFILE, EX_OK */ +#include /* strtoumax() */ +#include /* struct stat, lstat(), open() */ +#include /* strlen, rindex, memcmp, strerror() + */ +#include /* struct argp_option, struct + argp_state, struct argp, + argp_parse(), error_t, + ARGP_KEY_ARG, ARGP_KEY_END, + ARGP_ERR_UNKNOWN */ +#include /* EX_SOFTWARE, EX_OSERR, + EX_UNAVAILABLE, EX_IOERR, EX_OK */ +#include /* open() */ +#include /* va_list, va_start(), ... */ volatile sig_atomic_t quit_now = 0; int signal_received; @@ -116,10 +109,6 @@ from the terminal. Password-prompt will exit if it detects plymouth since plymouth performs the same functionality. */ - if(access("/run/plymouth/pid", R_OK) == 0){ - return true; - } - __attribute__((nonnull)) int is_plymouth(const struct dirent *proc_entry){ int ret; @@ -226,12 +215,6 @@ if(ret == -1){ error_plus(1, errno, "scandir"); } - { - int i = ret; - while(i--){ - free(direntries[i]); - } - } free(direntries); return ret > 0; } @@ -244,7 +227,6 @@ struct termios t_new, t_old; char *buffer = NULL; char *prefix = NULL; - char *prompt = NULL; int status = EXIT_SUCCESS; struct sigaction old_action, new_action = { .sa_handler = termination_handler, @@ -254,9 +236,6 @@ { .name = "prefix", .key = 'p', .arg = "PREFIX", .flags = 0, .doc = "Prefix shown before the prompt", .group = 2 }, - { .name = "prompt", .key = 129, - .arg = "PROMPT", .flags = 0, - .doc = "The prompt to show", .group = 2 }, { .name = "debug", .key = 128, .doc = "Debug mode", .group = 3 }, /* @@ -275,15 +254,12 @@ error_t parse_opt (int key, char *arg, struct argp_state *state){ errno = 0; switch (key){ - case 'p': /* --prefix */ + case 'p': prefix = arg; break; - case 128: /* --debug */ + case 128: debug = true; break; - case 129: /* --prompt */ - prompt = arg; - break; /* * These reproduce what we would get without ARGP_NO_HELP */ @@ -291,11 +267,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); @@ -444,9 +418,7 @@ if(prefix){ fprintf(stderr, "%s ", prefix); } - if(prompt != NULL){ - fprintf(stderr, "%s: ", prompt); - } else { + { const char *cryptsource = getenv("CRYPTTAB_SOURCE"); const char *crypttarget = getenv("CRYPTTAB_NAME"); /* Before cryptsetup 1.1.0~rc2 */ @@ -527,23 +499,19 @@ } if(sret < 0){ int e = errno; - if(errno != EINTR){ - if(not feof(stdin)){ - error_plus(0, errno, "getline"); - switch(e){ - case EBADF: - status = EX_UNAVAILABLE; - break; - case EIO: - case EINVAL: - default: - status = EX_IOERR; - break; - } - break; - } else { - clearerr(stdin); + if(errno != EINTR and not feof(stdin)){ + error_plus(0, errno, "getline"); + switch(e){ + case EBADF: + status = EX_UNAVAILABLE; + break; + case EIO: + case EINVAL: + default: + status = EX_IOERR; + break; } + break; } } /* if(sret == 0), then the only sensible thing to do is to retry === modified file 'plugins.d/password-prompt.xml' --- plugins.d/password-prompt.xml 2019-07-27 10:11:45 +0000 +++ plugins.d/password-prompt.xml 2016-03-05 21:42:56 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -40,9 +40,6 @@ 2014 2015 2016 - 2017 - 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -69,9 +66,6 @@ >PREFIX - - - @@ -113,15 +107,6 @@ wrapper, although actual use of that function is not guaranteed or implied. - - This program tries to detect if a Plymouth daemon - (plymouthd8) - is running, by looking for a - /run/plymouth/pid file or a process named - plymouthd. If it is detected, - this process will immediately exit without doing anything. - @@ -150,18 +135,6 @@ - - - - The password prompt. Using this option will make this - program ignore the CRYPTTAB_SOURCE and - CRYPTTAB_NAME environment variables. - - - - - @@ -221,8 +194,7 @@ CRYPTTAB_NAME - If set, and if the option is not - used, these environment variables will be assumed to + If set, these environment variables will be assumed to contain the source device name and the target device mapper name, respectively, and will be shown as part of the prompt. @@ -230,13 +202,22 @@ These variables will normally be inherited from plugin-runner - 8mandos, which might - have in turn inherited them from its calling process. + 8mandos, which will + normally have inherited them from + /scripts/local-top/cryptroot in the + initial RAM disk environment, which will + have set them from parsing kernel arguments and + /conf/conf.d/cryptroot (also in the + initial RAM disk environment), which in turn will have been + created when the initial RAM disk image was created by + /usr/share/initramfs-tools/hooks/cryptroot, by + extracting the information of the root file system from + /etc/crypttab. This behavior is meant to exactly mirror the behavior of - askpass, the default password prompter - from initramfs-tools. + askpass, the default password prompter. @@ -317,13 +298,13 @@ SEE ALSO intro - 8mandos, + 8mandos + crypttab + 5 mandos-client - 8mandos, + 8mandos plugin-runner 8mandos, - plymouthd - 8 === modified file 'plugins.d/plymouth.c' --- plugins.d/plymouth.c 2022-04-24 16:54:30 +0000 +++ plugins.d/plymouth.c 2016-03-17 20:40:55 +0000 @@ -2,87 +2,63 @@ /* * Plymouth - Read a password from Plymouth and output it * - * Copyright © 2010-2022 Teddy Hogeborn - * Copyright © 2010-2022 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 + * Copyright © 2010-2016 Teddy Hogeborn + * Copyright © 2010-2016 Björn Påhlsson + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with Mandos. If not, see . + * along with this program. If not, see + * . * * Contact the authors at . */ -#define _GNU_SOURCE /* program_invocation_short_name, - vasprintf(), asprintf(), - TEMP_FAILURE_RETRY() */ -#include /* sig_atomic_t, pid_t, setuid(), - geteuid(), setsid() */ -#include /* argp_program_version, - argp_program_bug_address, - struct argp_option, - struct argp_state, - ARGP_ERR_UNKNOWN, struct argp, - argp_parse(), ARGP_IN_ORDER */ -#include /* NULL, size_t */ +#define _GNU_SOURCE /* asprintf(), TEMP_FAILURE_RETRY() */ +#include /* sig_atomic_t, struct sigaction, + sigemptyset(), sigaddset(), SIGINT, + SIGHUP, SIGTERM, sigaction(), + kill(), SIG_IGN */ #include /* bool, false, true */ -#include /* FILE, fprintf(), vfprintf(), - vasprintf(), stderr, asprintf(), - fopen(), fscanf(), fclose(), - sscanf() */ -#include /* va_list, va_start(), vfprintf() */ -#include /* program_invocation_short_name, - errno, ENOMEM, EINTR, ENOENT, - error_t, EINVAL */ -#include /* strerror(), strdup(), memcmp() */ +#include /* open(), O_RDONLY */ +#include /* and, or, not*/ +#include /* size_t, ssize_t, pid_t, struct + dirent, waitpid() */ +#include /* waitpid() */ +#include /* NULL */ +#include /* strchr(), memcmp() */ +#include /* asprintf(), perror(), fopen(), + fscanf(), vasprintf(), fprintf(), + vfprintf() */ +#include /* close(), readlink(), read(), + fork(), setsid(), chdir(), dup2(), + STDERR_FILENO, execv(), access() */ +#include /* free(), EXIT_FAILURE, realloc(), + EXIT_SUCCESS, malloc(), _exit(), + getenv() */ +#include /* scandir(), alphasort() */ +#include /* intmax_t, strtoumax(), SCNuMAX */ +#include /* struct stat, lstat() */ +#include /* EX_OSERR, EX_UNAVAILABLE */ #include /* error() */ -#include /* free(), getenv(), malloc(), - reallocarray(), realloc(), - EXIT_FAILURE, EXIT_SUCCESS */ -#include /* TEMP_FAILURE_RETRY(), setuid(), - geteuid(), setsid(), chdir(), - dup2(), STDERR_FILENO, - STDOUT_FILENO, fork(), _exit(), - execv(), ssize_t, readlink(), - close(), read(), access(), X_OK */ -#include /* kill(), SIGTERM, struct sigaction, - sigemptyset(), SIGINT, SIGHUP, - sigaddset(), SIG_IGN */ -#include /* waitpid(), WIFEXITED(), - WEXITSTATUS(), WIFSIGNALED(), - WTERMSIG() */ -#include /* not, and, or */ -#include /* EX_OSERR, EX_USAGE, - EX_UNAVAILABLE */ -#include /* SIZE_MAX */ -#include /* struct dirent, scandir(), - alphasort() */ -#include /* uintmax_t, strtoumax(), SCNuMAX, - PRIuMAX */ -#include /* struct stat, lstat(), S_ISLNK() */ -#include /* open(), O_RDONLY */ +#include /* TEMP_FAILURE_RETRY */ #include /* argz_count(), argz_extract() */ +#include /* va_list, va_start(), ... */ sig_atomic_t interrupted_by_signal = 0; -const char *argp_program_version = "plymouth " VERSION; -const char *argp_program_bug_address = ""; /* Used by Ubuntu 11.04 (Natty Narwahl) */ -const char plymouth_old_old_pid[] = "/dev/.initramfs/plymouth.pid"; +const char plymouth_old_pid[] = "/dev/.initramfs/plymouth.pid"; /* Used by Ubuntu 11.10 (Oneiric Ocelot) */ -const char plymouth_old_pid[] = "/run/initramfs/plymouth.pid"; -/* Used by Debian 9 (stretch) */ -const char plymouth_pid[] = "/run/plymouth/pid"; +const char plymouth_pid[] = "/run/initramfs/plymouth.pid"; const char plymouth_path[] = "/bin/plymouth"; const char plymouthd_path[] = "/sbin/plymouthd"; @@ -90,7 +66,6 @@ "--mode=boot", "--attach-to-session", NULL }; -bool debug = false; static void termination_handler(__attribute__((unused))int signum){ if(interrupted_by_signal){ @@ -99,14 +74,6 @@ interrupted_by_signal = 1; } -__attribute__((format (gnu_printf, 2, 3), nonnull)) -int fprintf_plus(FILE *stream, const char *format, ...){ - va_list ap; - va_start (ap, format); - fprintf(stream, "Mandos plugin %s: ", program_invocation_short_name); - return vfprintf(stream, format, ap); -} - /* Function to use when printing errors */ __attribute__((format (gnu_printf, 3, 4))) void error_plus(int status, int errnum, const char *formatstring, @@ -189,18 +156,11 @@ __attribute__((nonnull (2, 3))) bool exec_and_wait(pid_t *pid_return, const char *path, - const char * const * const argv, bool interruptable, + const char * const *argv, bool interruptable, bool daemonize){ int status; int ret; pid_t pid; - if(debug){ - for(const char * const *arg = argv; *arg != NULL; arg++){ - fprintf_plus(stderr, "exec_and_wait arg: %s\n", *arg); - } - fprintf_plus(stderr, "exec_and_wait end of args\n"); - } - pid = fork(); if(pid == -1){ error_plus(0, errno, "fork"); @@ -221,21 +181,10 @@ } char **tmp; int i = 0; - for (; argv[i] != NULL; i++){ -#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 26) - tmp = reallocarray(new_argv, ((size_t)i + 2), - sizeof(const char *)); -#else - if(((size_t)i + 2) > (SIZE_MAX / sizeof(const char *))){ - /* overflow */ - tmp = NULL; - errno = ENOMEM; - } else { - tmp = realloc(new_argv, ((size_t)i + 2) * sizeof(const char *)); - } -#endif + for (; argv[i]!=NULL; i++){ + tmp = realloc(new_argv, sizeof(const char *) * ((size_t)i + 2)); if(tmp == NULL){ - error_plus(0, errno, "reallocarray"); + error_plus(0, errno, "realloc"); free(new_argv); _exit(EX_OSERR); } @@ -257,24 +206,12 @@ and ((not interrupted_by_signal) or (not interruptable))); if(interrupted_by_signal and interruptable){ - if(debug){ - fprintf_plus(stderr, "Interrupted by signal\n"); - } return false; } if(ret == -1){ error_plus(0, errno, "waitpid"); return false; } - if(debug){ - if(WIFEXITED(status)){ - fprintf_plus(stderr, "exec_and_wait exited: %d\n", - WEXITSTATUS(status)); - } else if(WIFSIGNALED(status)) { - fprintf_plus(stderr, "exec_and_wait signaled: %d\n", - WTERMSIG(status)); - } - } if(WIFEXITED(status) and (WEXITSTATUS(status) == 0)){ return true; } @@ -344,18 +281,7 @@ } /* Try the old pid file location */ if(proc_id == 0){ - pidfile = fopen(plymouth_old_pid, "r"); - if(pidfile != NULL){ - ret = fscanf(pidfile, "%" SCNuMAX, &proc_id); - if(ret != 1){ - proc_id = 0; - } - fclose(pidfile); - } - } - /* Try the old old pid file location */ - if(proc_id == 0){ - pidfile = fopen(plymouth_old_old_pid, "r"); + pidfile = fopen(plymouth_pid, "r"); if(pidfile != NULL){ ret = fscanf(pidfile, "%" SCNuMAX, &proc_id); if(ret != 1){ @@ -372,14 +298,9 @@ error_plus(0, errno, "scandir"); } if(ret > 0){ - for(int i = ret-1; i >= 0; i--){ - if(proc_id == 0){ - ret = sscanf(direntries[i]->d_name, "%" SCNuMAX, &proc_id); - if(ret < 0){ - error_plus(0, errno, "sscanf"); - } - } - free(direntries[i]); + ret = sscanf(direntries[0]->d_name, "%" SCNuMAX, &proc_id); + if(ret < 0){ + error_plus(0, errno, "sscanf"); } } /* scandir might preallocate for this variable (man page unclear). @@ -395,7 +316,7 @@ return 0; } -char **getargv(pid_t pid){ +const char * const * getargv(pid_t pid){ int cl_fd; char *cmdline_filename; ssize_t sret; @@ -462,74 +383,22 @@ return NULL; } argz_extract(cmdline, cmdline_len, argv); /* Create argv */ - return argv; + return (const char * const *)argv; } int main(__attribute__((unused))int argc, __attribute__((unused))char **argv){ - char *prompt = NULL; + char *prompt; char *prompt_arg; pid_t plymouth_command_pid; int ret; bool bret; - { - struct argp_option options[] = { - { .name = "prompt", .key = 128, .arg = "PROMPT", - .doc = "The prompt to show" }, - { .name = "debug", .key = 129, - .doc = "Debug mode" }, - { .name = NULL } - }; - - __attribute__((nonnull(3))) - error_t parse_opt (int key, char *arg, __attribute__((unused)) - struct argp_state *state){ - errno = 0; - switch (key){ - case 128: /* --prompt */ - prompt = arg; - if(debug){ - fprintf_plus(stderr, "Custom prompt \"%s\"\n", prompt); - } - break; - case 129: /* --debug */ - debug = true; - break; - default: - return ARGP_ERR_UNKNOWN; - } - return errno; - } - - struct argp argp = { .options = options, .parser = parse_opt, - .args_doc = "", - .doc = "Mandos plymouth -- Read and" - " output a password" }; - ret = argp_parse(&argp, argc, argv, ARGP_IN_ORDER, NULL, NULL); - switch(ret){ - case 0: - break; - case ENOMEM: - default: - errno = ret; - error_plus(0, errno, "argp_parse"); - return EX_OSERR; - case EINVAL: - error_plus(0, errno, "argp_parse"); - return EX_USAGE; - } - } - /* test -x /bin/plymouth */ ret = access(plymouth_path, X_OK); if(ret == -1){ /* Plymouth is probably not installed. Don't print an error message, just exit. */ - if(debug){ - fprintf_plus(stderr, "Plymouth (%s) not found\n", - plymouth_path); - } exit(EX_UNAVAILABLE); } @@ -569,27 +438,17 @@ } /* Plymouth is probably not running. Don't print an error message, just exit. */ - if(debug){ - fprintf_plus(stderr, "Plymouth not running\n"); - } exit(EX_UNAVAILABLE); } - if(prompt != NULL){ - ret = asprintf(&prompt_arg, "--prompt=%s", prompt); - } else { - char *made_prompt = makeprompt(); - ret = asprintf(&prompt_arg, "--prompt=%s", made_prompt); - free(made_prompt); - } + prompt = makeprompt(); + ret = asprintf(&prompt_arg, "--prompt=%s", prompt); + free(prompt); if(ret == -1){ error_plus(EX_OSERR, errno, "asprintf"); } /* plymouth ask-for-password --prompt="$prompt" */ - if(debug){ - fprintf_plus(stderr, "Prompting for password via Plymouth\n"); - } bret = exec_and_wait(&plymouth_command_pid, plymouth_path, (const char *[]) { plymouth_path, "ask-for-password", @@ -605,10 +464,11 @@ } kill_and_wait(plymouth_command_pid); - char **plymouthd_argv = NULL; + const char * const *plymouthd_argv; pid_t pid = get_pid(); if(pid == 0){ error_plus(0, 0, "plymouthd pid not found"); + plymouthd_argv = plymouthd_default_argv; } else { plymouthd_argv = getargv(pid); } @@ -617,21 +477,10 @@ { plymouth_path, "quit", NULL }, false, false); if(not bret){ - if(plymouthd_argv != NULL){ - free(*plymouthd_argv); - free(plymouthd_argv); - } exit(EXIT_FAILURE); } - bret = exec_and_wait(NULL, plymouthd_path, - (plymouthd_argv != NULL) - ? (const char * const *)plymouthd_argv - : plymouthd_default_argv, + bret = exec_and_wait(NULL, plymouthd_path, plymouthd_argv, false, true); - if(plymouthd_argv != NULL){ - free(*plymouthd_argv); - free(plymouthd_argv); - } if(not bret){ exit(EXIT_FAILURE); } === modified file 'plugins.d/plymouth.xml' --- plugins.d/plymouth.xml 2019-07-27 10:11:45 +0000 +++ plugins.d/plymouth.xml 2016-03-05 21:42:56 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -38,9 +38,6 @@ 2014 2015 2016 - 2017 - 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -61,28 +58,6 @@ &COMMANDNAME; - - - - - - - &COMMANDNAME; - - - - - - - &COMMANDNAME; - - - - &COMMANDNAME; - - - - @@ -124,68 +99,8 @@ OPTIONS - This program is commonly not invoked from the command line; it - is normally started by the Mandos - plugin runner, see plugin-runner8mandos - . Any command line options this program accepts - are therefore normally provided by the plugin runner, and not - directly. + This program takes no options. - - - - - - - The password prompt. Note that using this option will - make this program ignore the cryptsource - and crypttarget environment variables. - - - - - - - - - Enable debug mode. This will enable a lot of output to - standard error about what the program is doing. The - program will still perform all other functions normally. - - - - - - - - - - Gives a help message about options and their meanings. - - - - - - - - - Gives a short usage message. - - - - - - - - - - Prints the program version. - - - - @@ -207,8 +122,7 @@ crypttarget - If set, and if the option is not - used, these environment variables will be assumed to + If set, these environment variables will be assumed to contain the source device name and the target device mapper name, respectively, and will be shown as part of the prompt. @@ -216,13 +130,22 @@ These variables will normally be inherited from plugin-runner - 8mandos, which might - have in turn inherited them from its calling process. + 8mandos, which will + normally have inherited them from + /scripts/local-top/cryptroot in the + initial RAM disk environment, which will + have set them from parsing kernel arguments and + /conf/conf.d/cryptroot (also in the + initial RAM disk environment), which in turn will have been + created when the initial RAM disk image was created by + /usr/share/initramfs-tools/hooks/cryptroot, by + extracting the information of the root file system from + /etc/crypttab. This behavior is meant to exactly mirror the behavior of - askpass, the default password prompter - from initramfs-tools. + askpass, the default password prompter. @@ -295,20 +218,12 @@ - Normal invocation needs no options: + This program takes no options. &COMMANDNAME; - - - Show a different prompt. - - - &COMMANDNAME; --prompt=Password - - @@ -354,6 +269,8 @@ intro 8mandos, + crypttab + 5, plugin-runner 8mandos, proc === modified file 'plugins.d/splashy.c' --- plugins.d/splashy.c 2022-04-24 16:54:30 +0000 +++ plugins.d/splashy.c 2016-02-28 14:22:10 +0000 @@ -2,66 +2,60 @@ /* * Splashy - Read a password from splashy and output it * - * Copyright © 2008-2018, 2021-2022 Teddy Hogeborn - * Copyright © 2008-2018, 2021-2022 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 + * Copyright © 2008-2016 Teddy Hogeborn + * Copyright © 2008-2016 Björn Påhlsson + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with Mandos. If not, see . + * along with this program. If not, see + * . * * Contact the authors at . */ -#define _GNU_SOURCE /* vasprintf(), - program_invocation_short_name, - asprintf(), TEMP_FAILURE_RETRY() */ -#include /* sig_atomic_t, pid_t, setuid(), - geteuid(), setsid() */ -#include /* va_list, va_start(), vfprintf() */ -#include /* vasprintf(), fprintf(), stderr, - vfprintf(), asprintf() */ -#include /* program_invocation_short_name, - errno, EACCES, ENOTDIR, ELOOP, +#define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), asprintf() */ +#include /* sig_atomic_t, struct sigaction, + sigemptyset(), sigaddset(), SIGINT, + SIGHUP, SIGTERM, sigaction, + SIG_IGN, kill(), SIGKILL */ +#include /* NULL */ +#include /* getenv() */ +#include /* asprintf(), vasprintf(), vprintf(), + fprintf() */ +#include /* EXIT_FAILURE, free(), + EXIT_SUCCESS */ +#include /* pid_t, DIR, struct dirent, + ssize_t */ +#include /* opendir(), readdir(), closedir() */ +#include /* intmax_t, strtoimax() */ +#include /* struct stat, lstat(), S_ISLNK */ +#include /* not, or, and */ +#include /* readlink(), fork(), execl(), + sleep(), dup2() STDERR_FILENO, + STDOUT_FILENO, _exit(), + pause() */ +#include /* memcmp(), strerror() */ +#include /* errno, EACCES, ENOTDIR, ELOOP, ENOENT, ENAMETOOLONG, EMFILE, ENFILE, ENOMEM, ENOEXEC, EINVAL, E2BIG, EFAULT, EIO, ETXTBSY, EISDIR, ELIBBAD, EPERM, EINTR, ECHILD */ -#include /* strerror(), memcmp() */ #include /* error() */ -#include /* free(), EXIT_FAILURE, getenv(), - EXIT_SUCCESS, abort() */ -#include /* NULL */ -#include /* DIR, opendir(), struct dirent, - readdir(), closedir() */ +#include /* waitpid(), WIFEXITED(), + WEXITSTATUS() */ #include /* EX_OSERR, EX_OSFILE, EX_UNAVAILABLE */ -#include /* intmax_t, strtoimax() */ -#include /* or, not, and */ -#include /* ssize_t, readlink(), fork(), - execl(), _exit(), - TEMP_FAILURE_RETRY(), sleep(), - setuid(), geteuid(), setsid(), - chdir(), dup2(), STDERR_FILENO, - STDOUT_FILENO, pause() */ -#include /* struct stat, lstat(), S_ISLNK() */ -#include /* struct sigaction, sigemptyset(), - sigaddset(), SIGINT, SIGHUP, - SIGTERM, SIG_IGN, kill(), SIGKILL, - SIG_DFL, raise() */ -#include /* waitpid(), WIFEXITED(), - WEXITSTATUS() */ +#include /* va_list, va_start(), ... */ sig_atomic_t interrupted_by_signal = 0; int signal_received; === modified file 'plugins.d/splashy.xml' --- plugins.d/splashy.xml 2019-07-27 10:11:45 +0000 +++ plugins.d/splashy.xml 2016-03-05 21:42:56 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -40,9 +40,6 @@ 2014 2015 2016 - 2017 - 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -135,8 +132,18 @@ These variables will normally be inherited from plugin-runner - 8mandos, which might - have in turn inherited them from its calling process. + 8mandos, which will + normally have inherited them from + /scripts/local-top/cryptroot in the + initial RAM disk environment, which will + have set them from parsing kernel arguments and + /conf/conf.d/cryptroot (also in the + initial RAM disk environment), which in turn will have been + created when the initial RAM disk image was created by + /usr/share/initramfs-tools/hooks/cryptroot, by + extracting the information of the root file system from + /etc/crypttab. This behavior is meant to exactly mirror the behavior of @@ -266,6 +273,8 @@ intro 8mandos, + crypttab + 5, plugin-runner 8mandos, proc === modified file 'plugins.d/usplash.c' --- plugins.d/usplash.c 2022-04-24 16:54:30 +0000 +++ plugins.d/usplash.c 2016-02-28 14:22:10 +0000 @@ -2,63 +2,56 @@ /* * Usplash - Read a password from usplash and output it * - * Copyright © 2008-2018, 2021-2022 Teddy Hogeborn - * Copyright © 2008-2018, 2021-2022 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 + * Copyright © 2008-2016 Teddy Hogeborn + * Copyright © 2008-2016 Björn Påhlsson + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with Mandos. If not, see . + * along with this program. If not, see + * . * * Contact the authors at . */ -#define _GNU_SOURCE /* vasprintf(), - program_invocation_short_name, - asprintf(), TEMP_FAILURE_RETRY() */ -#include /* sig_atomic_t, pid_t, setuid(), - geteuid(), setsid() */ -#include /* va_list, va_start(), vfprintf() */ -#include /* vasprintf(), fprintf(), stderr, - vfprintf(), asprintf() */ -#include /* program_invocation_short_name, - errno, ENOENT, EINTR */ -#include /* strerror(), strlen(), memcmp() */ -#include /* error() */ -#include /* free(), getenv(), realloc(), - EXIT_FAILURE, EXIT_SUCCESS, - malloc(), abort() */ +#define _GNU_SOURCE /* asprintf(), TEMP_FAILURE_RETRY() */ +#include /* sig_atomic_t, struct sigaction, + sigemptyset(), sigaddset(), SIGINT, + SIGHUP, SIGTERM, sigaction(), + SIG_IGN, kill(), SIGKILL */ #include /* bool, false, true */ #include /* open(), O_WRONLY, O_RDONLY */ -#include /* size_t, NULL */ -#include /* close(), ssize_t, write(), - readlink(), read(), STDOUT_FILENO, - sleep(), fork(), setuid(), - geteuid(), setsid(), chdir(), - _exit(), dup2(), STDERR_FILENO, - execv(), TEMP_FAILURE_RETRY(), - pause() */ -#include /* DIR, opendir(), struct dirent, - readdir(), closedir() */ +#include /* and, or, not*/ +#include /* errno, EINTR */ +#include +#include /* size_t, ssize_t, pid_t, DIR, struct + dirent */ +#include /* NULL */ +#include /* strlen(), memcmp(), strerror() */ +#include /* asprintf(), vasprintf(), vprintf(), + fprintf() */ +#include /* close(), write(), readlink(), + read(), STDOUT_FILENO, sleep(), + fork(), setuid(), geteuid(), + setsid(), chdir(), dup2(), + STDERR_FILENO, execv() */ +#include /* free(), EXIT_FAILURE, realloc(), + EXIT_SUCCESS, malloc(), _exit(), + getenv() */ +#include /* opendir(), readdir(), closedir() */ #include /* intmax_t, strtoimax() */ -#include /* or, not, and */ -#include /* struct stat, lstat(), S_ISLNK() */ +#include /* struct stat, lstat(), S_ISLNK */ #include /* EX_OSERR, EX_UNAVAILABLE */ -#include /* struct sigaction, sigemptyset(), - sigaddset(), SIGINT, SIGHUP, - SIGTERM, SIG_IGN, kill(), SIGKILL, - SIG_DFL, raise() */ #include /* argz_count(), argz_extract() */ +#include /* va_list, va_start(), ... */ sig_atomic_t interrupted_by_signal = 0; int signal_received; === modified file 'plugins.d/usplash.xml' --- plugins.d/usplash.xml 2019-07-27 10:11:45 +0000 +++ plugins.d/usplash.xml 2016-03-05 21:42:56 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -40,9 +40,6 @@ 2014 2015 2016 - 2017 - 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -135,8 +132,18 @@ These variables will normally be inherited from plugin-runner - 8mandos, which might - have in turn inherited them from its calling process. + 8mandos, which will + normally have inherited them from + /scripts/local-top/cryptroot in the + initial RAM disk environment, which will + have set them from parsing kernel arguments and + /conf/conf.d/cryptroot (also in the + initial RAM disk environment), which in turn will have been + created when the initial RAM disk image was created by + /usr/share/initramfs-tools/hooks/cryptroot, by + extracting the information of the root file system from + /etc/crypttab. This behavior is meant to exactly mirror the behavior of @@ -280,6 +287,8 @@ intro 8mandos, + crypttab + 5, fifo 7, plugin-runner === removed file 'sysusers.d-mandos.conf' --- sysusers.d-mandos.conf 2019-08-18 00:42:22 +0000 +++ sysusers.d-mandos.conf 1970-01-01 00:00:00 +0000 @@ -1,3 +0,0 @@ -# This file will be installed as mandos.conf and/or mandos-client.conf -# in the /usr/lib/sysusers.d directory. See sysusers.d(5) -u _mandos - "Mandos password system"