=== modified file '.bzrignore' --- .bzrignore 2019-06-06 19:21:43 +0000 +++ .bzrignore 2008-10-03 09:32:30 +0000 @@ -2,8 +2,9 @@ *.8 *.8mandos confdir +debian/po/messages.mo +debian/po/templates.pot keydir -statedir man plugin-runner plugins.d/askpass-fifo @@ -11,6 +12,3 @@ plugins.d/password-prompt plugins.d/splashy plugins.d/usplash -plugins.d/plymouth -plugin-helpers/mandos-client-iprouteadddel -.tramp_history === removed file 'DBUS-API' --- DBUS-API 2019-02-10 04:20:26 +0000 +++ DBUS-API 1970-01-01 00:00:00 +0000 @@ -1,155 +0,0 @@ - -*- mode: org; coding: utf-8 -*- - - Mandos Server D-Bus Interface - -This file documents the D-Bus interface to the Mandos server. - -* Bus: System bus - Bus name: "se.recompile.Mandos" - - -* Object Paths: - - | Path | Object | - |-----------------------+-------------------| - | "/" | The Mandos Server | - - (To get a list of paths to client objects, use the standard D-Bus - org.freedesktop.DBus.ObjectManager interface, which the server - object supports.) - - -* Mandos Server Interface: - Interface name: "se.recompile.Mandos" - -** Methods: -*** RemoveClient(o: ObjectPath) → nothing - 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 - is represented as a string of hexadecimal digits. The address is - an IPv4 or IPv6 address in its normal string format. - - -* Mandos Client Interface: - Interface name: "se.recompile.Mandos.Client" - -** Methods -*** Approve(b: Approve) → nothing - Approve or deny a connected client waiting for approval. If - denied, a client will not be sent its secret. - -*** CheckedOK() → nothing - Assert that this client has been checked and found to be alive. - This will restart the timeout before disabling this client. See - also the "LastCheckedOK" property. - -** Properties - - Note: Many of these properties directly correspond to a setting in - "clients.conf", in which case they are fully documented in - mandos-clients.conf(5). - - | 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 | - | Checker | s | Read/Write | checker | - | CheckerRunning (c) | b | Read/Write | N/A | - | Created (d) | s | Read | N/A | - | Enabled (e) | b | Read/Write | N/A | - | Expires (f) | s | Read | N/A | - | ExtendedTimeout (a) | t | Read/Write | extended_timeout | - | Fingerprint | s | Read | fingerprint | - | KeyID | s | Read | key_id | - | Host | s | Read/Write | host | - | Interval (a) | t | Read/Write | interval | - | LastApprovalRequest (g) | s | Read | N/A | - | LastCheckedOK (h) | s | Read/Write | N/A | - | LastCheckerStatus (i) | n | Read | N/A | - | LastEnabled (j) | s | Read | N/A | - | Name | s | Read | (Section name) | - | Secret (k) | ay | Write | secret (or secfile) | - | Timeout (a) | t | Read/Write | timeout | - - a) Represented as milliseconds. - - b) An approval is currently pending. - - c) Changing this property can either start a new checker or abort a - running one. - - d) The creation time of this client object, as an RFC 3339 string. - - e) Changing this property enables or disables a client. - - f) The date and time this client will be disabled, as an RFC 3339 - string, or an empty string if this is not scheduled. - - g) The date and time of the last approval request, as an RFC 3339 - string, or an empty string if this has not happened. - - h) The date and time a checker was last successful, as an RFC 3339 - string, or an empty string if this has not happened. Setting - this property is equivalent to calling CheckedOK(), i.e. the - current time is set, regardless of the string sent. Please - always use an empty string when setting this property, to allow - for possible future expansion. - - i) The exit status of the last checker, -1 if it did not exit - cleanly, -2 if a checker has not yet returned. - - j) The date and time this client was last enabled, as an RFC 3339 - string, or an empty string if this has not happened. - - k) A raw byte array, not hexadecimal digits. - -** Signals -*** CheckerCompleted(n: Exitcode, x: Waitstatus, s: Command) - A checker (Command) has completed. Exitcode is either the exit - code or -1 for abnormal exit. In any case, the full Waitstatus - (as from wait(2)) is also available. - -*** CheckerStarted(s: Command) - A checker command (Command) has just been started. - -*** GotSecret() - This client has been sent its secret. - -*** NeedApproval(t: Timeout, b: ApprovedByDefault) - This client will be approved or denied in exactly Timeout - milliseconds, depending on ApprovedByDefault. Approve() can now - usefully be called on this client object. - -*** Rejected(s: Reason) - This client was not given its secret for a specified Reason. - -* Copyright - - Copyright © 2010-2019 Teddy Hogeborn - Copyright © 2010-2019 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 - WITHOUT 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 . - - -#+STARTUP: showall === modified file 'INSTALL' --- INSTALL 2019-07-15 15:19:42 +0000 +++ INSTALL 2009-02-15 09:28:06 +0000 @@ -4,16 +4,15 @@ ** Operating System - Debian 8.0 "jessie" or Ubuntu 15.10 "Wily Werewolf" (or later). + Debian 5.0 "lenny" or Ubuntu 8.04 "Hardy Heron". This is mostly for the support scripts which make sure that the client is installed and started in the initial RAM disk environment - and that the initial RAM file system image file is automatically - made unreadable. The server and client programs themselves *could* - be run in other distributions, but they *are* specific to GNU/Linux - systems, and are not written with portabillity to other Unixes in - mind. - + and that the initrd.img file is automatically made unreadable. The + server and client programs themselves *could* be run in other + distributions, but they *are* specific to GNU/Linux systems, and + are not intended to be portable to other Unixes. + ** Libraries The following libraries and packages are needed. (It is possible @@ -28,7 +27,7 @@ + DocBook 4.5 http://www.docbook.org/ Note: DocBook 5.0 is not compatible. + DocBook XSL stylesheets 1.71.0 - http://wiki.docbook.org/DocBookXslStylesheets + http://wiki.docbook.org/topic/DocBookXslStylesheets Package names: docbook docbook-xsl @@ -36,44 +35,34 @@ To build just the documentation, run the command "make doc". Then the manual page "mandos.8", for example, can be read by running "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) + + GnuTLS 2.4 http://www.gnu.org/software/gnutls/ + Avahi 0.6.16 http://www.avahi.org/ - + Python 2.7 https://www.python.org/ - + dbus-python 0.82.4 https://dbus.freedesktop.org/doc/dbus-python/ - + PyGObject 3.7.1 https://wiki.gnome.org/Projects/PyGObject - + pkg-config https://www.freedesktop.org/wiki/Software/pkg-config/ - + Urwid 1.0.1 http://urwid.org/ - (Only needed by the "mandos-monitor" tool.) + + Python 2.5 http://www.python.org/ + + Python-GnuTLS 1.1.5 http://pypi.python.org/pypi/python-gnutls/ + + dbus-python 0.82.4 http://dbus.freedesktop.org/doc/dbus-python/ + + python-ctypes 1.0.0 http://pypi.python.org/pypi/ctypes + + PyGObject 2.14.2 http://library.gnome.org/devel/pygobject/ Strongly recommended: - + fping 2.4b2-to-ipv6 http://www.fping.org/ - + ssh-keyscan from OpenSSH http://www.openssh.com/ + + fping 2.4b2-to-ipv6 http://www.fping.com/ Package names: - avahi-daemon python python-dbus python-gi python-urwid pkg-config - fping ssh-client - + python-gnutls avahi-daemon python python-avahi python-dbus + python-ctypes python-gobject + *** Mandos Client - + GNU C Library 2.17 https://gnu.org/software/libc/ + initramfs-tools 0.85i - https://tracker.debian.org/pkg/initramfs-tools - + GnuTLS 3.3 https://www.gnutls.org/ - (but not 3.6.0 or later, until 3.6.6 which works) - + Avahi 0.6.16 http://www.avahi.org/ - + GnuPG 1.4.9 https://www.gnupg.org/ - + GPGME 1.1.6 https://www.gnupg.org/related_software/gpgme/ - + pkg-config https://www.freedesktop.org/wiki/Software/pkg-config/ - + libnl-route 3 https://www.infradead.org/~tgr/libnl/ - - Strongly recommended: - + OpenSSH http://www.openssh.com/ + http://packages.qa.debian.org/i/initramfs-tools.html + + GnuTLS 2.4 http://www.gnu.org/software/gnutls/ + + Avahi 0.6.16 http://www.avahi.org/ + + GnuPG 1.4.9 http://www.gnupg.org/ + + GPGME 1.1.6 http://www.gnupg.org/related_software/gpgme/ Package names: - initramfs-tools libgnutls-dev gnutls-bin libavahi-core-dev gnupg - libgpgme11-dev pkg-config ssh libnl-route-3-dev + initramfs-tools libgnutls-dev libavahi-core-dev gnupg + libgpgme11-dev * Installing the Mandos server @@ -109,40 +98,36 @@ and append this to the file "/etc/mandos/clients.conf" *on the server computer*. - 4. Configure the client to use any special configuration needed for - your local system. Note: This is not necessary if the server is - present on the same wired local network as the client. If you do - make changes to /etc/mandos/plugin-runner.conf, the initrd.img + 4. Configure the client to use the correct network interface. The + default is "eth0", and if this needs to be adjusted, it will be + necessary to edit /etc/mandos/plugin-runner.conf to uncomment and + change the line there. If that file is changed, the initrd.img file must be updated, possibly using the following command: # 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 Ubuntu: sudo service mandos start + For Ubuntu: sudo invoke-rc.d mandos start At this point, it is possible to verify that the correct password will be received by the client by running the command: # /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. After this, the client computer should be able to reboot without needing a password entered on the console, as long as it does not - take more than five minutes to reboot. + take more than an hour to reboot. * Further customizations You may want to tighten or loosen the timeouts in the server configuration files; see mandos.conf(5) and mandos-clients.conf(5). - If IPsec is not used and SSH is not installed, it is suggested that - a more cryptographically secure checker program is used and - configured, since, without IPsec, ping packets can be faked. - -#+STARTUP: showall + If IPsec is not used, it is suggested that a more cryptographically + secure checker program is used and configured, since without IPsec + ping packets can be faked. === modified file 'Makefile' --- Makefile 2019-07-15 15:19:42 +0000 +++ Makefile 2009-11-19 18:31:28 +0000 @@ -1,37 +1,18 @@ -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 \ - -Wsuggest-attribute=noreturn -Wfloat-equal -Wundef -Wshadow \ +WARN=-O -Wall -Wformat=2 -Winit-self -Wmissing-include-dirs \ + -Wswitch-default -Wswitch-enum -Wunused-parameter \ + -Wstrict-aliasing=1 -Wextra -Wfloat-equal -Wundef -Wshadow \ -Wunsafe-loop-optimizations -Wpointer-arith \ -Wbad-function-cast -Wcast-qual -Wcast-align -Wwrite-strings \ - -Wconversion -Wlogical-op -Waggregate-return \ - -Wstrict-prototypes -Wold-style-definition \ - -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))) -# -ALL_SANITIZE_OPTIONS:=-fsanitize=leak -fsanitize=undefined \ - -fsanitize=shift -fsanitize=integer-divide-by-zero \ - -fsanitize=unreachable -fsanitize=vla-bound -fsanitize=null \ - -fsanitize=return -fsanitize=signed-integer-overflow \ - -fsanitize=bounds -fsanitize=alignment \ - -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=2 -fstack-protector-all -fPIC -LINK_FORTIFY_LD:=-z relro -z now -LINK_FORTIFY:= + -Wconversion -Wstrict-prototypes -Wold-style-definition \ + -Wpacked -Wnested-externs -Winline -Wvolatile-register-var +# -Wunreachable-code +#DEBUG=-ggdb3 +# For info about _FORTIFY_SOURCE, see +# +# and . +FORTIFY=-D_FORTIFY_SOURCE=2 -fstack-protector-all -fPIC +LINK_FORTIFY_LD=-z relro -z now +LINK_FORTIFY= # If BROKEN_PIE is set, do not build with -pie ifndef BROKEN_PIE @@ -39,63 +20,41 @@ LINK_FORTIFY += -pie endif #COVERAGE=--coverage -OPTIMIZE:=-Os -fno-strict-aliasing -LANGUAGE:=-std=gnu11 -htmldir:=man -version:=1.8.4 -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))) +OPTIMIZE=-Os +LANGUAGE=-std=gnu99 +htmldir=man +version=1.0.14 +SED=sed ## 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 -# 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 ## ## 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 -STATEDIR:=$(DESTDIR)/var/lib/mandos -LIBDIR:=$(shell \ - for d in \ - "/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"; \ - break; \ - fi; \ - done) +PREFIX=$(DESTDIR)/usr +CONFDIR=$(DESTDIR)/etc/mandos +KEYDIR=$(DESTDIR)/etc/keys/mandos +MANDIR=$(PREFIX)/share/man +INITRAMFSTOOLS=$(DESTDIR)/usr/share/initramfs-tools ## -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 gpgme-config --cflags; getconf LFS_CFLAGS) -GPGME_LIBS:=$(shell 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) # Do not change these two -CFLAGS+=$(WARN) $(DEBUG) $(FORTIFY) $(COVERAGE) \ - $(OPTIMIZE) $(LANGUAGE) -DVERSION='"$(version)"' -LDFLAGS+=-Xlinker --as-needed $(COVERAGE) $(LINK_FORTIFY) $(strip \ - ) $(foreach flag,$(LINK_FORTIFY_LD),-Xlinker $(flag)) +CFLAGS=$(WARN) $(DEBUG) $(FORTIFY) $(COVERAGE) $(OPTIMIZE) \ + $(LANGUAGE) $(GNUTLS_CFLAGS) $(AVAHI_CFLAGS) $(GPGME_CFLAGS) \ + -DVERSION='"$(version)"' +LDFLAGS=$(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 \ @@ -104,12 +63,11 @@ --param make.single.year.ranges 1 \ --param man.output.quietly 1 \ --param man.authors.section.enabled 0 \ - /usr/share/xml/docbook/stylesheet/nwalsh/manpages/docbook.xsl \ + /usr/share/xml/docbook/stylesheet/nwalsh/manpages/docbook.xsl \ $(notdir $<); \ - if locale --all 2>/dev/null | grep --regexp='^en_US\.utf8$$' \ - && type man 2>/dev/null; then LANG=en_US.UTF-8 MANWIDTH=80 \ - man --warnings --encoding=UTF-8 --local-file $(notdir $@); \ - fi >/dev/null) + $(MANPOST) $(notdir $@)) +# DocBook-to-man post-processing to fix a '\n' escape bug +MANPOST=$(SED) --in-place --expression='s,\\\\en,\\en,g;s,\\n,\\en,g' DOCBOOKTOHTML=$(strip xsltproc --nonet --xinclude \ --param make.year.ranges 1 \ @@ -121,25 +79,22 @@ /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.d/usplash plugins.d/splashy plugins.d/askpass-fifo \ - plugins.d/plymouth -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 \ +PLUGINS=plugins.d/password-prompt plugins.d/mandos-client \ + plugins.d/usplash plugins.d/splashy plugins.d/askpass-fifo +CPROGS=plugin-runner $(PLUGINS) +PROGS=mandos mandos-keygen mandos-ctl $(CPROGS) +DOCS=mandos.8 plugin-runner.8mandos mandos-keygen.8 \ 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)) + plugins.d/password-prompt.8mandos mandos.conf.5 \ + plugins.d/usplash.8mandos plugins.d/splashy.8mandos \ + plugins.d/askpass-fifo.8mandos mandos-clients.conf.5 + +htmldocs=$(addsuffix .xhtml,$(DOCS)) + +objects=$(addsuffix .o,$(CPROGS)) all: $(PROGS) mandos.lsm @@ -162,11 +117,6 @@ %.8mandos.xhtml: %.xml common.ent legalnotice.xml $(DOCBOOKTOHTML) -intro.8mandos: intro.xml common.ent legalnotice.xml - $(DOCBOOKTOMAN) -intro.8mandos.xhtml: intro.xml common.ent legalnotice.xml - $(DOCBOOKTOHTML) - mandos.8: mandos.xml common.ent mandos-options.xml overview.xml \ legalnotice.xml $(DOCBOOKTOMAN) @@ -181,20 +131,6 @@ legalnotice.xml $(DOCBOOKTOHTML) -mandos-monitor.8: mandos-monitor.xml common.ent overview.xml \ - legalnotice.xml - $(DOCBOOKTOMAN) -mandos-monitor.8.xhtml: mandos-monitor.xml common.ent overview.xml \ - legalnotice.xml - $(DOCBOOKTOHTML) - -mandos-ctl.8: mandos-ctl.xml common.ent overview.xml \ - legalnotice.xml - $(DOCBOOKTOMAN) -mandos-ctl.8.xhtml: mandos-ctl.xml common.ent overview.xml \ - legalnotice.xml - $(DOCBOOKTOHTML) - mandos.conf.5: mandos.conf.xml common.ent mandos-options.xml \ legalnotice.xml $(DOCBOOKTOMAN) @@ -241,11 +177,6 @@ --expression='s/^\(version = "\)[^"]*"$$/\1$(version)"/' \ $@) -mandos-monitor: Makefile - $(strip $(SED) --in-place \ - --expression='s/^\(version = "\)[^"]*"$$/\1$(version)"/' \ - $@) - mandos.lsm: Makefile $(strip $(SED) --in-place \ --expression='s/^\(Version:\).*/\1\t$(version)/' \ @@ -257,23 +188,13 @@ --expression='s/\(mandos_\)[0-9.]\+\(\.orig\.tar\.gz\)/\1$(version)\2/' \ $@) -# Need to add the GnuTLS, Avahi and GPGME libraries plugins.d/mandos-client: plugins.d/mandos-client.c - $(LINK.c) $^ $(GNUTLS_CFLAGS) $(AVAHI_CFLAGS) $(strip\ - ) $(GPGME_CFLAGS) $(GNUTLS_LIBS) $(strip\ - ) $(AVAHI_LIBS) $(GPGME_LIBS) $(LOADLIBES) $(strip\ - ) $(LDLIBS) -o $@ - -# Need to add the libnl-route library -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 + $(LINK.c) $(GNUTLS_LIBS) $(AVAHI_LIBS) $(GPGME_LIBS) $(strip\ + ) $(COMMON) $^ $(LOADLIBES) $(LDLIBS) -o $@ + +.PHONY : all doc html clean distclean run-client run-server install \ + install-server install-client uninstall uninstall-server \ + uninstall-client purge purge-server purge-client clean: -rm --force $(CPROGS) $(objects) $(htmldocs) $(DOCS) core @@ -281,60 +202,36 @@ distclean: clean mostlyclean: clean maintainer-clean: clean - -rm --force --recursive keydir confdir statedir + -rm --force --recursive keydir confdir -check: all +check: all ./mandos --check - ./mandos-ctl --check - ./mandos-keygen --version - ./plugin-runner --version - ./plugin-helpers/mandos-client-iprouteadddel --version # Run the client with a local config and key -run-client: all keydir/seckey.txt keydir/pubkey.txt keydir/tls-privkey.pem keydir/tls-pubkey.pem - @echo "###################################################################" - @echo "# The following error messages are harmless and can be safely #" - @echo "# ignored: #" - @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 "# #" - @echo "# (The messages are caused by not running as root, but you should #" - @echo "# NOT run \"make run-client\" as root unless you also unpacked and #" - @echo "# compiled Mandos as root, which is also NOT recommended.) #" - @echo "###################################################################" -# We set GNOME_KEYRING_CONTROL to block pam_gnome_keyring +run-client: all keydir/seckey.txt keydir/pubkey.txt ./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 \ - --env-for=mandos-client:GNOME_KEYRING_CONTROL= \ + --options-for=mandos-client:--seckey=keydir/seckey.txt,--pubkey=keydir/pubkey.txt \ $(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 # Run the server with a local config -run-server: confdir/mandos.conf confdir/clients.conf statedir - ./mandos --debug --no-dbus --configdir=confdir \ - --statedir=statedir $(SERVERARGS) +run-server: confdir/mandos.conf confdir/clients.conf + ./mandos --debug --no-dbus --configdir=confdir $(SERVERARGS) # Used by run-server 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 - ./mandos-keygen --dir keydir --password --no-ssh >> $@ -statedir: - install --directory statedir + ./mandos-keygen --dir keydir --password >> $@ install: install-server install-client-nokey @@ -345,32 +242,13 @@ install-server: doc install --directory $(CONFDIR) - if install --directory --mode=u=rwx --owner=$(USER) \ - --group=$(GROUP) $(STATEDIR); then \ - :; \ - elif install --directory --mode=u=rwx $(STATEDIR); then \ - chown -- $(USER):$(GROUP) $(STATEDIR) || :; \ - fi - if [ "$(TMPFILES)" != "$(DESTDIR)" -a -d "$(TMPFILES)" ]; then \ - install --mode=u=rw,go=r tmpfiles.d-mandos.conf \ - $(TMPFILES)/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 - install --mode=u=rwx,go=rx --target-directory=$(PREFIX)/sbin \ - mandos-monitor install --mode=u=rw,go=r --target-directory=$(CONFDIR) \ mandos.conf install --mode=u=rw --target-directory=$(CONFDIR) \ clients.conf - install --mode=u=rw,go=r dbus-mandos.conf \ - $(DESTDIR)/etc/dbus-1/system.d/mandos.conf install --mode=u=rwx,go=rx init.d-mandos \ $(DESTDIR)/etc/init.d/mandos - if [ "$(SYSTEMD)" != "$(DESTDIR)" -a -d "$(SYSTEMD)" ]; then \ - install --mode=u=rw,go=r mandos.service $(SYSTEMD); \ - fi install --mode=u=rw,go=r default-mandos \ $(DESTDIR)/etc/default/mandos if [ -z $(DESTDIR) ]; then \ @@ -378,83 +256,59 @@ fi gzip --best --to-stdout mandos.8 \ > $(MANDIR)/man8/mandos.8.gz - gzip --best --to-stdout mandos-monitor.8 \ - > $(MANDIR)/man8/mandos-monitor.8.gz - gzip --best --to-stdout mandos-ctl.8 \ - > $(MANDIR)/man8/mandos-ctl.8.gz gzip --best --to-stdout mandos.conf.5 \ > $(MANDIR)/man5/mandos.conf.5.gz gzip --best --to-stdout mandos-clients.conf.5 \ > $(MANDIR)/man5/mandos-clients.conf.5.gz - gzip --best --to-stdout intro.8mandos \ - > $(MANDIR)/man8/intro.8mandos.gz install-client-nokey: all doc - install --directory $(LIBDIR)/mandos $(CONFDIR) + install --directory $(PREFIX)/lib/mandos $(CONFDIR) install --directory --mode=u=rwx $(KEYDIR) \ - $(LIBDIR)/mandos/plugins.d \ - $(LIBDIR)/mandos/plugin-helpers - if [ "$(CONFDIR)" != "$(LIBDIR)/mandos" ]; then \ + $(PREFIX)/lib/mandos/plugins.d + if [ "$(CONFDIR)" != "$(PREFIX)/lib/mandos" ]; then \ install --mode=u=rwx \ - --directory "$(CONFDIR)/plugins.d" \ - "$(CONFDIR)/plugin-helpers"; \ + --directory "$(CONFDIR)/plugins.d"; \ fi - install --mode=u=rwx,go=rx --directory \ - "$(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)/lib/mandos plugin-runner install --mode=u=rwx,go=rx --target-directory=$(PREFIX)/sbin \ mandos-keygen install --mode=u=rwx,go=rx \ - --target-directory=$(LIBDIR)/mandos/plugins.d \ + --target-directory=$(PREFIX)/lib/mandos/plugins.d \ plugins.d/password-prompt install --mode=u=rwxs,go=rx \ - --target-directory=$(LIBDIR)/mandos/plugins.d \ + --target-directory=$(PREFIX)/lib/mandos/plugins.d \ plugins.d/mandos-client install --mode=u=rwxs,go=rx \ - --target-directory=$(LIBDIR)/mandos/plugins.d \ + --target-directory=$(PREFIX)/lib/mandos/plugins.d \ plugins.d/usplash install --mode=u=rwxs,go=rx \ - --target-directory=$(LIBDIR)/mandos/plugins.d \ + --target-directory=$(PREFIX)/lib/mandos/plugins.d \ plugins.d/splashy install --mode=u=rwxs,go=rx \ - --target-directory=$(LIBDIR)/mandos/plugins.d \ + --target-directory=$(PREFIX)/lib/mandos/plugins.d \ plugins.d/askpass-fifo - install --mode=u=rwxs,go=rx \ - --target-directory=$(LIBDIR)/mandos/plugins.d \ - plugins.d/plymouth - install --mode=u=rwx,go=rx \ - --target-directory=$(LIBDIR)/mandos/plugin-helpers \ - plugin-helpers/mandos-client-iprouteadddel install initramfs-tools-hook \ $(INITRAMFSTOOLS)/hooks/mandos - install --mode=u=rw,go=r initramfs-tools-conf \ - $(INITRAMFSTOOLS)/conf.d/mandos-conf - install --mode=u=rw,go=r initramfs-tools-conf-hook \ - $(INITRAMFSTOOLS)/conf-hooks.d/zz-mandos + install --mode=u=rw,go=r initramfs-tools-hook-conf \ + $(INITRAMFSTOOLS)/conf-hooks.d/mandos install initramfs-tools-script \ $(INITRAMFSTOOLS)/scripts/init-premount/mandos - install initramfs-tools-script-stop \ - $(INITRAMFSTOOLS)/scripts/local-premount/mandos install --mode=u=rw,go=r plugin-runner.conf $(CONFDIR) gzip --best --to-stdout mandos-keygen.8 \ > $(MANDIR)/man8/mandos-keygen.8.gz gzip --best --to-stdout plugin-runner.8mandos \ > $(MANDIR)/man8/plugin-runner.8mandos.gz + gzip --best --to-stdout plugins.d/password-prompt.8mandos \ + > $(MANDIR)/man8/password-prompt.8mandos.gz gzip --best --to-stdout plugins.d/mandos-client.8mandos \ > $(MANDIR)/man8/mandos-client.8mandos.gz - gzip --best --to-stdout plugins.d/password-prompt.8mandos \ - > $(MANDIR)/man8/password-prompt.8mandos.gz gzip --best --to-stdout plugins.d/usplash.8mandos \ > $(MANDIR)/man8/usplash.8mandos.gz gzip --best --to-stdout plugins.d/splashy.8mandos \ > $(MANDIR)/man8/splashy.8mandos.gz gzip --best --to-stdout plugins.d/askpass-fifo.8mandos \ > $(MANDIR)/man8/askpass-fifo.8mandos.gz - gzip --best --to-stdout plugins.d/plymouth.8mandos \ - > $(MANDIR)/man8/plymouth.8mandos.gz install-client: install-client-nokey # Post-installation stuff @@ -466,11 +320,7 @@ uninstall-server: -rm --force $(PREFIX)/sbin/mandos \ - $(PREFIX)/sbin/mandos-ctl \ - $(PREFIX)/sbin/mandos-monitor \ $(MANDIR)/man8/mandos.8.gz \ - $(MANDIR)/man8/mandos-monitor.8.gz \ - $(MANDIR)/man8/mandos-ctl.8.gz \ $(MANDIR)/man5/mandos.conf.5.gz \ $(MANDIR)/man5/mandos-clients.conf.5.gz update-rc.d -f mandos remove @@ -482,43 +332,37 @@ ! grep --regexp='^ *[^ #].*keyscript=[^,=]*/mandos/' \ $(DESTDIR)/etc/crypttab -rm --force $(PREFIX)/sbin/mandos-keygen \ - $(LIBDIR)/mandos/plugin-runner \ - $(LIBDIR)/mandos/plugins.d/password-prompt \ - $(LIBDIR)/mandos/plugins.d/mandos-client \ - $(LIBDIR)/mandos/plugins.d/usplash \ - $(LIBDIR)/mandos/plugins.d/splashy \ - $(LIBDIR)/mandos/plugins.d/askpass-fifo \ - $(LIBDIR)/mandos/plugins.d/plymouth \ + $(PREFIX)/lib/mandos/plugin-runner \ + $(PREFIX)/lib/mandos/plugins.d/password-prompt \ + $(PREFIX)/lib/mandos/plugins.d/mandos-client \ + $(PREFIX)/lib/mandos/plugins.d/usplash \ + $(PREFIX)/lib/mandos/plugins.d/splashy \ + $(PREFIX)/lib/mandos/plugins.d/askpass-fifo \ $(INITRAMFSTOOLS)/hooks/mandos \ $(INITRAMFSTOOLS)/conf-hooks.d/mandos \ $(INITRAMFSTOOLS)/scripts/init-premount/mandos \ + $(MANDIR)/man8/plugin-runner.8mandos.gz \ $(MANDIR)/man8/mandos-keygen.8.gz \ - $(MANDIR)/man8/plugin-runner.8mandos.gz \ - $(MANDIR)/man8/mandos-client.8mandos.gz $(MANDIR)/man8/password-prompt.8mandos.gz \ $(MANDIR)/man8/usplash.8mandos.gz \ $(MANDIR)/man8/splashy.8mandos.gz \ $(MANDIR)/man8/askpass-fifo.8mandos.gz \ - $(MANDIR)/man8/plymouth.8mandos.gz \ - -rmdir $(LIBDIR)/mandos/plugins.d $(CONFDIR)/plugins.d \ - $(LIBDIR)/mandos $(CONFDIR) $(KEYDIR) + $(MANDIR)/man8/mandos-client.8mandos.gz + -rmdir $(PREFIX)/lib/mandos/plugins.d $(CONFDIR)/plugins.d \ + $(PREFIX)/lib/mandos $(CONFDIR) $(KEYDIR) update-initramfs -k all -u purge: purge-server purge-client purge-server: uninstall-server -rm --force $(CONFDIR)/mandos.conf $(CONFDIR)/clients.conf \ - $(DESTDIR)/etc/dbus-1/system.d/mandos.conf $(DESTDIR)/etc/default/mandos \ $(DESTDIR)/etc/init.d/mandos \ - $(SYSTEMD)/mandos.service \ - $(DESTDIR)/run/mandos.pid \ $(DESTDIR)/var/run/mandos.pid -rmdir $(CONFDIR) purge-client: uninstall-client - -shred --remove $(KEYDIR)/seckey.txt $(KEYDIR)/tls-privkey.pem + -shred --remove $(KEYDIR)/seckey.txt -rm --force $(CONFDIR)/plugin-runner.conf \ - $(KEYDIR)/pubkey.txt $(KEYDIR)/seckey.txt \ - $(KEYDIR)/tls-pubkey.txt $(KEYDIR)/tls-privkey.txt + $(KEYDIR)/pubkey.txt $(KEYDIR)/seckey.txt -rmdir $(KEYDIR) $(CONFDIR)/plugins.d $(CONFDIR) === modified file 'NEWS' --- NEWS 2019-04-09 20:09:51 +0000 +++ NEWS 2009-10-26 21:16:16 +0000 @@ -1,419 +1,6 @@ This NEWS file records noteworthy changes, very tersely. See the manual for detailed information. -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 - -Version 1.7.6 (2016-03-13) -* Server -** Fix bug where stopping server would time out -** Make server runnable with Python 3 - -Version 1.7.5 (2016-03-08) -* Server -** Fix security restrictions in systemd service file. -** Work around bug where stopping server would time out - -Version 1.7.4 (2016-03-05) -* Client -** Bug fix: Tolerate errors from configure_networking (Debian Bug - #816513) -** Compilation: Only use sanitizing options which work with the - compiler used when building. This should fix compilation with GCC - 4.9 on mips, mipsel, and s390x. -* Server -** Add extra security restrictions in systemd service file. - -Version 1.7.3 (2016-02-29) -* Client -** Bug fix: Remove new type of keyring directory user by GnuPG 2.1. -** Bug fix: Remove "nonnull" attribute from a function argument, which - would otherwise generate a spurious runtime warning. - -Version 1.7.2 (2016-02-28) -* Server -** Stop using python-gnutls library; it was not updated to GnuTLS 3.3. -** Bug fix: Only send D-Bus signal ClientRemoved if using D-Bus. -** Use GnuPG 2 if available. -* Client -** Compile with various sanitizing flags. - -Version 1.7.1 (2015-10-24) -* Client -** Bug fix: Can now really find Mandos server even if the server has - an IPv6 address on a network other than the one which the Mandos - server is on. - -Version 1.7.0 (2015-08-10) -* Server -** Bug fix: Handle local Zeroconf service name collisions better. -** Bug fix: Finally fix "ERROR: Child process vanished" bug. -** Bug fix: Fix systemd service file to start server correctly. -** Bug fix: Be compatible with old 2048-bit DSA keys. -** The D-Bus API now provides the standard D-Bus ObjectManager - interface, and deprecates older functionality. See the DBUS-API - file for the currently recommended API. Note: the original API - still works, but is deprecated. -* Client -** Can now find Mandos server even if the server has an IPv6 address - on a network without IPv6 Router Advertisment (like if the Mandos - client itself is the router, or there is an IPv6 router advertising - a network other than the one which the Mandos server is on.) -** Use a better value than 1024 for the default number of DH bits. - This better value is either provided by a DH parameters file (see - below) or an appropriate number of DH bits is determined based on - the PGP key. -** Bug fix: mandos-keygen now generates correct output for the - "Checker" variable even if the SSH server on the Mandos client has - multiple SSH key types. -** Can now use pre-generated Diffie-Hellman parameters from a file. - -Version 1.6.9 (2014-10-05) -* Server -** Changed to emit standard D-Bus signal when D-Bus properties change. - (The old signal is still emitted too, but marked as deprecated.) - -Version 1.6.8 (2014-08-06) -* Client -** Bug fix: mandos-keygen now generates working SSH checker commands. -* Server -** Bug fix: "mandos-monitor" now really redraws screen on Ctrl-L. -** Now requires Python 2.7. - -Version 1.6.7 (2014-07-17) -* Client -** Bug fix: Now compatible with GPGME 1.5.0. -** Bug fix: Fixed minor memory leaks. -* Server -** "mandos-monitor" now has verbose logging, toggleable with "v". - -Version 1.6.6 (2014-07-13) -* Client -** If client host has an SSH server, "mandos-keygen --password" now - outputs "checker" option which uses "ssh-keyscan"; this is more - secure than the default "fping" checker. -** Bug fix: allow "." in network hook names, to match documentation. -** Better error messages. -* Server -** New --no-zeroconf option. -** Bug fix: Fix --servicename option, broken since 1.6.4. -** Bug fix: Fix --socket option work for --socket=0. - -Version 1.6.5 (2014-05-11) -* Client -** Work around bug in GnuPG -** Give better error messages when run without sufficient privileges -** Only warn if workaround for Debian bug #633582 was necessary and - failed, not if it failed and was unnecessary. - -Version 1.6.4 (2014-02-16) -* Server -** Very minor fix to self-test code. - -Version 1.6.3 (2014-01-21) -* Server -** Add systemd support. -** For PID file, fall back to /var/run if /run does not exist. -* Client -** Moved files from /usr/lib/mandos to whatever the architecture - specifies, like /usr/lib/x86_64-linux-gnu/mandos or - /usr/lib64/mandos. - -Version 1.6.2 (2013-10-24) -* Server -** PID file moved from /var/run to /run. -** Bug fix: Handle long secrets when saving client state. -** Bug fix: Use more magic in the GnuTLS priority string to handle - both old DSA/ELG 2048-bit keys and new RSA/RSA 4096-bit keys. -* Client -** mandos-keygen: Bug fix: now generate RSA keys which GnuTLS can use. - Bug fix: Output passphrase prompts even when - redirecting standard output. - -Version 1.6.1 (2013-10-13) -* Server -** All client options for time intervals now also take an RFC 3339 - duration. The same for all options to mandos-ctl. -** Bug fix: Handle fast checkers (like ":") correctly. -** Bug fix: Don't print output from checkers when running in - foreground. -** Bug fix: Do not fail when client is removed from clients.conf but - saved settings remain. -** Bug fix: mandos-monitor now displays standout (reverse video) again - using new version of Urwid. -** Bug fix: Make boolean options work from the config file again. -** Bug fix: Make --no-ipv6 work again. -** New default priority string to be slightly more compatible with - older versions of GnuTLS. -* Client -** Bug fix: Fix bashism in mandos-keygen. -** Default key and subkey types are now RSA and RSA, respectively. - Also, new default key size is 4096 bits. - -Version 1.6.0 (2012-06-18) -* Server -** Takes new --foreground option -** Init script supports new "status" action. -* Client -** Now uses all interfaces by default; the --interface option can - still be used to restrict it, and the argument to --interface (as - well as the $DEVICE environment variable for the network hooks) is - now a comma-separated list of interfaces to use. - -Version 1.5.5 (2012-06-01) -* Server -** Server takes new --socket option - -Version 1.5.4 (2012-05-20) -* Server -** Bug fix: Regression fix: Make non-zero approval timeout values work. -** Bug fix: Regression fix: Allow changing the Timeout D-Bus property. -** Fall back to not bind to an interface if an invalid interface name - is given. -** Removed support for undocumented feature of using plain "%%s" in - "checker" client option. -** Old D-Bus interface are now marked as deprecated. -** mandos-monitor: Bug fix: show approval timers correctly. -** mandos-ctl: Show "Extended Timeout" correctly, not as milliseconds. - -Version 1.5.3 (2012-01-15) -* Server -** Add D-Bus property se.recompile.Client.LastCheckerStatus and use it - in mandos-monitor. -* Client -** Fix bugs in the example "bridge" network hook. - -Version 1.5.2 (2012-01-08) -* Server -** Removed D-Bus signal se.recompile.Mandos.NewRequest() added in - 1.5.0. It was buggy and was of questionable utility. - -Version 1.5.1 (2012-01-01) -* Server -** Include intro(8mandos) manual page, missing since migration from - README file in version 1.4.0. - -Version 1.5.0 (2012-01-01) -* Client -** Network hooks. The Mandos client can now run custom scripts to take - up a network interface before the client is run. Three example - scripts are provided: "wireless", "openvpn", and "bridge". - To facilitate this, the client now prefers network interfaces which - are up (if any) over all other interfaces. -* Server -** Persistent state. Client state is now saved between server - restarts. -** clients.conf file can now contain "enabled" setting for clients. -** Bug fix: Fix rare crash bug. -** Bug fix: Send corrent D-Bus type in PropertyChanged for - "ApprovalDelay", "ApprovalDuration", "Timeout", and - "ExtendedTimeout". -** mandos-ctl: Bare numbers as arguments are taken to be milliseconds. -** Bug fix: mandos-ctl --secret option now works. -** New D-Bus signal: se.recompile.Mandos.NewRequest(s). - -Version 1.4.1 (2011-10-15) -* Server -** Make D-Bus properties settable again, and handle checkers - for disabled clients correctly. -* Miscellaneous fixes to "pedantic" Lintian warnings - -Version 1.4.0 (2011-10-09) -* README file migrated to manual page intro(8mandos). -* Client: -** Fixed warning about "rmdir: Directory not empty". -* Server: -** Default values changed: timeout 5 minutes, interval 2 minutes. -** Clients gets an expiration extension when receiving a password, - controlled by new "extended_timeout" setting. -** New domain name: "fukt.bsnet.se" changes to "recompile.se". This - also affects the D-Bus bus and interface names (old names still - work). Users should start using the new names immediately. -** New D-Bus Client object properties "Expires" and "ExtendedTimeout"; - see DBUS-API for details. - -Version 1.3.1 (2011-07-27) -* Client: -** Client now retries all Mandos servers periodically. -** Work around Debian bug #633582 - fixes "Permission denied" problem. - -Version 1.3.0 (2011-03-08) -* Server: -** Updated for Python 2.6. -* Client: -** Bug fix: Make the password-prompt plugin not conflict with - Plymouth. -** Bug fix: Bug fix: update initramfs also when purging package. - -Version 1.2.3 (2010-10-11) -* Server: -** Bug fix: Expose D-Bus API also in non-debug mode. - -Version 1.2.2 (2010-10-07) -* Client: -** splashy: Minor fix to compile with non-Linux kernels. - -Version 1.2.1 (2010-10-02) -* Server: -** mandos-monitor(8): Documentation bug fix: Key for removing client - is "R", not "r". - -Version 1.2 (2010-09-28) -* Client: -** New "plymouth" plugin to ask for a password using the Plymouth - graphical boot system. -** The Mandos client now automatically chooses a network interface if - the DEVICE setting in /etc/initramfs-tools/initramfs.conf is set to - the empty string. This is also the new default instead of "eth0". -** The Mandos client --connect option now loops indefinitely until a - password is received from the specified server. -** Bug fix: Quote directory correctly in mandos-keygen with --password -** Bug fix: don't use "echo -e" in mandos-keygen; unsupported by dash. -* Server: -** Terminology change: clients are now "ENABLED" or "DISABLED", not - "valid" or "invalid". -** New D-Bus API; see the file "DBUS-API". -** New control utilities using the new D-Bus API: - + mandos-ctl A command-line based utility - + mandos-monitor A text-based GUI interface -** New feature: manual interactive approval or denying of clients on a - case-by-case basis. -** New --debuglevel option to control logging -** Will not write PID file if --debug is passed -** Bug fix: Avoid race conditions with short "interval" values or - fast checkers. -** Bug fix: Don't try to bind to a network interface when none is - specified - Version 1.0.14 (2009-10-25) Enable building without -pie and -fPIE if BROKEN_PIE is set. === modified file 'README' --- README 2016-03-23 07:11:22 +0000 +++ README 2009-11-03 00:12:35 +0000 @@ -1,11 +1,180 @@ -Please see: https://www.recompile.se/mandos/man/intro.8mandos - -This information previously in this file has been moved to the -intro(8mandos) manual page. Go to the above URL, or install the -Mandos server and run this command: - - man 8mandos intro - -In short, this is the Mandos system; it allows computers to have -encrypted root file systems and at the same time be capable of remote -and/or unattended reboots. +-*- org -*- + +* Mandos + - Have your cake and eat it too! + + 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 of their + neighbors, and their neighbors’ friends. The servers of people who + owe them money. And like *that*, they’re gone. And you doubt + you’ll ever see them again. + + That is why your servers have encrypted root file systems. However, + there’s a downside. There’s no going around it: rebooting is a + pain. Dragging out that rarely-used keyboard and screen and + unraveling cables behind your servers to plug them in to type in + that password is messy, especially if you have many servers. There + are some people who do clever things like using serial line consoles + and daisy-chain it to the next server, and keep all the servers + connected in a ring with serial cables, which will work, if your + servers are physically close enough. There are also other + out-of-band management solutions, but with *all* these, you still + have to be on hand and manually type in the password at boot time. + Otherwise the server just sits there, waiting for a password. + + Wouldn’t it be great if you could have the security of encrypted + root file systems and still have servers that could boot up + automatically if there was a short power outage while you were + asleep? That you could reboot at will, without having someone run + over to the server to type in the password? + + Well, with Mandos, you (almost) can! The gain in convenience will + only be offset by a small loss in security. The setup is as + follows: + + The server will still have its encrypted root file system. The + password to this file system will be stored on another computer + (henceforth known as the Mandos server) on the same local network. + The password will *not* be stored in plaintext, but encrypted with + OpenPGP. To decrypt this password, a key is needed. This key (the + Mandos client key) will not be stored there, but back on the + original server (henceforth known as the Mandos client) in the + initial RAM disk image. Oh, and all network Mandos client/server + communications will be encrypted, using TLS (SSL). + + So, at boot time, the Mandos client will ask for its encrypted 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 encrypted + root file system, so anyone who had physical access could take the + Mandos client computer offline and read the disk with their own + tools to get the authentication keys used by a client. *But*, by + then the Mandos server should notice that the original server has + been offline for too 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 + + (The encrypted keys on the Mandos server is on its normal file + system, so those are safe, provided the root file system of *that* + server is encrypted.) + +* FAQ - couldn’t the security be defeated by... + +** Grabbing the Mandos client key from the initrd *really quickly*? + This, as mentioned above, is the only real weak point. But if you + set the timing values tight enough, this will be really difficult + to do. An attacker would have to physically disassemble the client + computer, extract the key from the initial RAM disk image, and then + connect to a *still online* Mandos server to get the encrypted key, + and do all this *before* the Mandos server timeout kicks in and the + Mandos server refuses to give out the key to anyone. + + Now, as the typical procedure seems to be to barge in and turn off + and grab *all* computers, to maybe look at them months later, this + is not likely. If someone does that, the whole system *will* lock + itself up completely, since Mandos servers are no longer running. + + For sophisticated attackers who *could* do the clever thing, *and* + had physical access to the server for enough time, it would be + simpler to get a key for an encrypted file system by using hardware + memory scanners and reading it right off the memory bus. + +** Replay attacks? + Nope, the network stuff is all done over TLS, which provides + protection against that. + +** Man-in-the-middle? + No. The server only gives out the passwords to clients which have + *in the TLS handshake* proven that they do indeed hold the OpenPGP + private key corresponding to that client. + +** Physically grabbing the Mandos server computer? + You could protect *that* computer the old-fashioned way, with a + must-type-in-the-password-at-boot method. Or you could have two + computers be the Mandos server for each other. + + Multiple Mandos servers can coexist on a network without any + trouble. They do not clash, and clients will try all available + servers. This means that if just one reboots then the other can + bring it back up, but if both reboot at the same time they will + stay down until someone types in the password on one of them. + +** Faking ping replies? + The default for the server is to use "fping", the replies to which + could be faked to eliminate the timeout. But this could easily be + changed to any shell command, with any security measures you like. + It could, for instance, be changed to an SSH command with strict + keychecking, which could not be faked. Or IPsec could be used for + the ping packets, making them secure. + +* Security Summary + So, in summary: The only weakness in the Mandos system is from + people who have: + 1. The power to come in and physically take your servers, *and* + 2. The cunning and patience to do it carefully, one at a time, and + *quickly*, faking Mandos client/server responses for each one + before the timeout. + + While there are some who may be threatened by people who have *both* + these attributes, they do not, probably, constitute the majority. + + If you *do* face such opponents, you must figure that they could + just as well open your servers and read the file system keys right + off the memory by running wires to the memory bus. + + What Mandos is designed to protect against is *not* such determined, + focused, and competent attacks, but against the early morning knock + on your door and the sudden absence of all the servers in your + server room. Which it does nicely. + +* The Plugin System + In the early designs, the mandos-client(8mandos) program (which + retrieves a password from the Mandos server) also prompted for a + password on the terminal, in case a Mandos server could not be + found. Other ways of retrieving a password could easily be + envisoned, but this multiplicity of purpose was seen to be too + complex to be a viable way to continue. Instead, the original + program was separated into mandos-client(8mandos) and + password-prompt(8mandos), and a plugin-runner(8mandos) exist to run + them both in parallel, allowing the first successful plugin to + provide the password. This opened up for any number of additional + plugins to run, all competing to be the first to find a password and + provide it to the plugin runner. + + Three additional plugins are provided: + * usplash(8mandos) + This prompts for a password when using usplash(8). + * splashy(8mandos) + This prompts for a password when using splashy(8). + * askpass-fifo(8mandos) + To provide compatibility with the "askpass" program from + cryptsetup, this plugin listens to the same FIFO as askpass would + do. + + More plugins can easily be written and added by the system + administrator; see the section called "WRITING PLUGINS" in + plugin-runner(8mandos) to learn the plugin requirements. + +* Copyright + + Copyright © 2008,2009 Teddy Hogeborn + Copyright © 2008,2009 Björn Påhlsson + +** License: + + 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 this program. If not, see + . === modified file 'TODO' --- TODO 2019-04-09 19:41:53 +0000 +++ TODO 2010-06-19 00:37:04 +0000 @@ -1,124 +1,130 @@ -*- org -*- -* Testing -** python-nemu - -* mandos-applet +* _attribute_((nonnull)) * mandos-client -** TODO A --server option which only adds to the server list. - (Unlike --connect, which implicitly disables zeroconf.) +** TODO [#B] use scandir(3) instead of readdir(3) +** TODO [#B] Prefix all debug output with "Mandos plugin " + program_invocation_short_name +** TODO use error() instead of perror() +** TODO [#B] Retry a server which has a non-definite reply: +*** A closed connection during the TLS handshake +*** A TCP timeout ** 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)) -** TODO [#A] Detect partial writes to stdout and exit with EX_TEMPFAIL -** TODO [#B] Use reallocarray() with GNU LibC 2.29 or later. +** TODO [#A] Retry --connect forever * splashy ** TODO [#B] use scandir(3) instead of readdir(3) -** TODO [#A] Detect partial writes to stdout and exit with EX_TEMPFAIL +** TODO [#B] Prefix all debug output with "Mandos plugin " + program_invocation_short_name +** TODO [#B] use error() instead of perror() -* usplash (Deprecated) -** TODO [#B] Make it work again +* usplash +** TODO [#A] Make it work again ** TODO [#B] use scandir(3) instead of readdir(3) -** TODO [#A] Detect partial writes to stdout and exit with EX_TEMPFAIL +** TODO [#B] Prefix all debug output with "Mandos plugin " + program_invocation_short_name +** TODO [#B] use error() instead of perror() * askpass-fifo -** TODO [#A] Detect partial writes to stdout and exit with EX_TEMPFAIL +** TODO [#B] Prefix all debug output with "Mandos plugin " + program_invocation_short_name +** TODO [#B] use error() instead of perror() +** TODO [#B] Drop privileges after opening FIFO. * password-prompt +** TODO [#B] Prefix all debug output with "Mandos plugin " + program_invocation_short_name +** TODO [#B] use error() instead of perror() ** TODO [#B] lock stdin (with flock()?) -** TODO [#A] Detect partial writes to stdout and exit with EX_TEMPFAIL - -* plymouth -** TODO [#A] Detect partial writes to stdout and exit with EX_TEMPFAIL -** TODO [#B] Use reallocarray() with GNU LibC 2.29 or later. * TODO [#B] passdev * plugin-runner -** TODO handle printing for errors for plugins -*** Hook up stderr of plugins, buffer them, and prepend "Mandos Plugin [plugin name]" +** TODO [#B] use scandir(3) instead of readdir(3) ** 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 +** TODO [#B] use error() instead of perror() +** TODO [$B] Use openat() and readdir64() + http://udrepper.livejournal.com/19395.html * mandos (server) -** 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() ** TODO [#B] Log level :BUGS: +** TODO Persistent state :BUGS: + /var/lib/mandos/* *** TODO /etc/mandos/clients.d/*.conf Watch this directory and add/remove/update clients? ** TODO [#C] config for TXT record -** TODO Log level dbus option - SetLogLevel D-Bus call +** TODO Log level option + syslogger.setLevel(logging.WARNING) + + SetLogLevel D-Bus call +** TODO Implement --foreground :BUGS: + [[info:standards:Option%20Table][Table of Long Options]] +** TODO Implement --socket + [[info:standards:Option%20Table][Table of Long Options]] +** TODO Date+time on console log messages :BUGS: + Is this the default? ** TODO [#C] DBusServiceObjectUsingSuper ** 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 +** TODO [#B] By-client countdown on secrets given +** TODO [#B] Fix problem with fsck taking a really long time + Whenever a client successfully gets a secret it could get a + one-time timeout boost to allow for an fsck-incurred delay +** TODO [#A] Delay before client receives key + This would give an operator opportunity to cancel the request if + desired. +** TODO [#A] Client manual approval mode + A client needs manual approval on the server before it gets the + secret +** TODO [#B] Support RFC 3339 time duration syntax +** More D-Bus methods +*** NeedsApproval(50, True) -> timeout, default approve + Default approval is configurable, but True by default + + Approval(True) -> approve sending saved + + Approval(False) -> Close client connection immediately +*** NeedsPassword(50) - Timeout, default disapprove + SetPass(u"gazonk", True) -> Approval, persistent - + Approve(False) -> Close client connection immediately + + Approval(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 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 Secret Service API? - https://standards.freedesktop.org/secret-service/ -** TODO Remove D-Bus interfaces with old domain name :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" -** 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() +** TODO [#C] systemd/launchd + http://0pointer.de/blog/projects/systemd.html + +* mandos.xml +** [[file:mandos.xml::XXX][Document D-Bus interface]] + Remove mention of lack of such interface in BUGS section +** Add mandos contact info in manual pages + +* TODO [#A] Provide and install /etc/dbus-1/system.d/mandos.conf * mandos-ctl -** TODO Remove old string_to_delta format :2: +*** Handle "no D-Bus server" and/or "no Mandos server found" better +*** [#B] --dump option +** TODO Support RFC 3339 time duration syntax * TODO mandos-dispatch Listens for specified D-Bus signals and spawns shell commands with arguments. * mandos-monitor -** TODO --servicename :BUGS: -** TODO help should be toggleable ** Urwid client data displayer Better view of client data in the listing *** Properties popup -** Print a nice "We are sorry" message, save stack trace to log. * mandos-keygen +** TODO Loop until passwords match when run interactively ** TODO "--secfile" option Using the "secfile" option instead of "secret" ** TODO [#B] "--test" option For testing decryption before rebooting. +* Makefile +** TODO Add "--Xlinker --as-needed" + http://udrepper.livejournal.com/19395.html +** TODO [#C] Implement DEB_BUILD_OPTIONS + http://www.debian.org/doc/debian-policy/ch-source.html#s-debianrules-options + * Package ** /usr/share/initramfs-tools/hooks/mandos *** 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] /etc/bash_completion.d/mandos From XML sources directly? -* Side Stuff -** TODO Locate which package moves the other bin/sh when busybox is deactivated -** TODO contact owner of package, and ask them to have that shell static in position regardless of busybox - -* [[http://www.undeadly.org/cgi?action=article&sid=20110530221728][OpenBSD]] - #+STARTUP: showall === removed file 'bugs.xml' --- bugs.xml 2016-03-05 21:42:56 +0000 +++ bugs.xml 1970-01-01 00:00:00 +0000 @@ -1,11 +0,0 @@ - - - - Please report bugs to the Mandos development mailing list: - mandos-dev@recompile.se (subscription required). - Note that this list is public. The developers can be reached - privately at mandos@recompile.se (OpenPGP key - fingerprint 153A 37F1 0BBA 0435 987F 2C4A 7223 2973 CA34 - C2C4 for encrypted mail). - === modified file 'clients.conf' --- clients.conf 2019-02-09 23:34:15 +0000 +++ clients.conf 2009-12-25 23:13:47 +0000 @@ -4,43 +4,23 @@ # How long until a client is disabled and not be allowed to get the # data this server holds. -;timeout = PT5M +;timeout = 1h # How often to run the checker to confirm that a client is still up. # Note: a new checker will not be started if an old one is still # 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. -;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. -;extended_timeout = PT15M +;interval = 5m # What command to run as "the checker". ;checker = fping -q -- %%(host)s -# Whether to approve a client by default after the approval delay. -;approved_by_default = True - -# How long to wait for approval. -;approval_delay = PT0S - -# How long one approval will last. -;approval_duration = PT1S - -# Whether this client is enabled by default -;enabled = True - ;#### ;# Example client ;[foo] ; -;# TLS public key ID -;key_id = f33fcbed11ed5e03073f6a55b86ffe92af0e24c045fb6e3b40547b3dc0c030ed -; ;# OpenPGP key fingerprint ;fingerprint = 7788 2722 5BA7 DE53 9C5A 7CFA 59CF F7CD BD9A 5920 ; @@ -71,23 +51,15 @@ ;#### ;# 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 ; ;# Parameters from the [DEFAULT] section can be overridden per client. -;interval = PT1M -; -;# This client requires manual approval before it receives its secret. -;approved_by_default = False -;# Require approval within 30 seconds. -;approval_delay = PT30S +;interval = 5m ;#### === modified file 'common.ent' --- common.ent 2019-04-09 20:09:51 +0000 +++ common.ent 2009-10-26 21:16:16 +0000 @@ -1,3 +1,3 @@ - + === modified file 'dbus-mandos.conf' --- dbus-mandos.conf 2011-10-02 19:18:24 +0000 +++ dbus-mandos.conf 2009-11-09 07:35:16 +0000 @@ -7,18 +7,12 @@ - - - - - - === modified file 'debian/changelog' --- debian/changelog 2019-04-09 20:09:51 +0000 +++ debian/changelog 2009-10-26 21:16:16 +0000 @@ -1,683 +1,3 @@ -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. - * debian/mandos-client.postinst (configure): If older version, fix - permissions on plugin helper directory. Also fix permissions on - plugin helper local override directory (/etc/mandos/plugin-helpers), - but only if not listed by "dpkg-statoverride". - * debian/rules (override_dh_fixperms-arch): Exclude plugin helper - directory from dh_fixperms. - * debian/mandos.postinst (configure): Fix state directory permissions, - but only if not listed by "dpkg-statoverride". - * debian/mandos-client.lintian-overrides: Do not warn about permissions - on plugin helper directory. - * debian/mandos.dirs (usr/lib/tmpfiles.d): Added. - - -- Teddy Hogeborn Sat, 19 Mar 2016 22:58:49 +0100 - -mandos (1.7.6-1) unstable; urgency=medium - - * New upstream release. - * debian/control (Source: mandos/Build-Depends-Indep): Remove - "python-avahi". - (Source: mandos/Build-Depends-Indep): Change "python-gi | - python-gobject" to "python-gi"; i.e. remove "python-gobject". - - -- Teddy Hogeborn Sun, 13 Mar 2016 22:58:23 +0100 - -mandos (1.7.5-1) unstable; urgency=high - - * New upstream release. - * debian/mandos.postinst (configure): If old version was 1.7.4-1 or - 1.7.4-1~bpo8+1, fix situation where clients.pickle file is owned by - root. - - -- Teddy Hogeborn Tue, 08 Mar 2016 01:09:55 +0100 - -mandos (1.7.4-1) unstable; urgency=medium - - * New upstream release. - * initramfs-tools-script: Fix "Call to configure_network in initramfs - script broken due to set -e" by surrounding call by "set +x" and "set - -e" (Closes: #816513) - * debian/control: (Source: mandos/Build-Depends-Indep): Change - "python-gobject | python-gi" to "python-gi | python-gobject" - (Package: mandos/Depends): - '' - - - -- Teddy Hogeborn Sat, 05 Mar 2016 23:10:07 +0100 - -mandos (1.7.3-1) unstable; urgency=medium - - * New upstream release. - - -- Teddy Hogeborn Mon, 29 Feb 2016 22:26:38 +0100 - -mandos (1.7.2-1) unstable; urgency=medium - - * New upstream release. - * Fix "Uses unneeded and obsolete version specific python packages" - by removing version-specific dependencies (Closes: #811159) - * debian/control (Source: mandos/Build-Depends): Add (>= 3.3.0) to - "libgnutls28-dev" and "gnutls-dev". - (Source: mandos/Build-Depends-Indep): Remove "python2.7-gnutls", - "python2.7", "python2.7-dbus", "python2.7-avahi", and - "python2.7-gobject"; replace with "python (>= 2.7), python (<< 3)", - "python-dbus", "python-avahi", "python-gobject | python-gi". - (Package: mandos/Depends): Remove "python-gnutls" and - "python2.7-gnutls", add "libgnutls28-dev (>= 3.3.0) | libgnutls30 (>= - 3.3.0)". Add "python (<< 3)". Remove "python2.7-dbus", - "python2.7-avahi", "python2.7-gobject", and "python2.7-urwid". - Replace "python-gobject" with "python-gobject | python-gi" and "gnupg - (<< 2)" with "gnupg". - (Package: mandos-client/Depends): Replace - "gnupg (<< 2)" with "gnupg". - (Source: mandos/Standards-Version): Change to 3.9.7. - * debian/copyright (Copyright): Update copyright year. - - -- Teddy Hogeborn Sun, 28 Feb 2016 16:09:01 +0100 - -mandos (1.7.1-2) unstable; urgency=medium - - * debian/control (Package: mandos/Depends): Fix "Please drop versioned - dependency on initscripts package" by removing initscripts dependency - (Closes: #804967) - * debian/rules (override_dh_fixperms) Fix "FTBFS when built with - dpkg-buildpackage -A (No such file or directory)" by splitting into - "override_dh_fixperms-arch" and "override_dh_fixperms-indep". - (Closes: #806073) - - -- Teddy Hogeborn Sat, 05 Dec 2015 02:27:40 +0100 - -mandos (1.7.1-1) unstable; urgency=medium - - * New upstream release. - - -- Teddy Hogeborn Sat, 24 Oct 2015 19:43:40 +0200 - -mandos (1.7.0-1) unstable; urgency=medium - - * New upstream release. - * debian/control (Standards-Version): Updated to "3.9.6". - (Build-Depends): Add "libnl-route-3-dev". - (Package: mandos-client/Recommends): Added "gnutls-bin | openssl" for - the generating of DH parameters. - * debian/mandos-client.README.Debian: Update example command line to use - new MANDOSPLUGINHELPERDIR environment variable. Also document the new - dhparams.pem file. - * debian/mandos-client.postinst: Create DH parameters file. - * debian/mandos.prerm: Don't run init script, use only invoke-rc.d. - * debian/mandos-client.postinst: Don't use absolute paths to commands. - * debian/mandos-client.postrm: Don't use absolute paths to commands. - Also remove dhparams.pem file. - * debian/copyright (Copyright): Update copyright year. - * Upstream changed systemd service file to implicitly be of - "Type=dbus". (Closes: #786845) - - -- Teddy Hogeborn Mon, 10 Aug 2015 22:00:29 +0200 - -mandos (1.6.9-1) unstable; urgency=medium - - * New upstream release. - * debian/control (Build-Depends): Fix "still uses GnutLS 2.x" by - changing from "libgnutls-dev" to "libgnutls28-dev | gnutls-dev" - (Closes: #762349) - - -- Teddy Hogeborn Sun, 05 Oct 2014 22:05:06 +0200 - -mandos (1.6.8-1) unstable; urgency=medium - - * New upstream release. - * debian/control (Source: mandos/Build-Depends-Indep): Since upstream - now requires Python 2.7, depend on exactly the python2.7 package and - all the Python 2.7 versions of the python modules. - (Package: mandos/Depends): - '' - but still depend on python (>=2.7) - and the generic versions of the Python modules; this is for mandos-ctl - and mandos-monitor, both of which are compatible with Python 3, and - use #!/usr/bin/python. - - -- Teddy Hogeborn Wed, 06 Aug 2014 22:55:24 +0200 - -mandos (1.6.7-1) unstable; urgency=medium - - * New upstream release. - - -- Teddy Hogeborn Thu, 17 Jul 2014 05:22:45 +0200 - -mandos (1.6.6-1) unstable; urgency=medium - - * New upstream release. - * debian/mandos.postinst: Fix typo in comment. - * debian/control (mandos/Recommends): Changed to "ssh-client | fping". - (mandos-client/Recommends): New; set to "ssh". - - -- Teddy Hogeborn Sun, 13 Jul 2014 22:49:21 +0200 - -mandos (1.6.5-3) unstable; urgency=medium - - * debian/control (mandos-client/Depends): Add "dpkg-dev (>=1.16.0)"; - initramfs-tools-hook runs "dpkg-architecture -qDEB_HOST_MULTIARCH". - (Closes: #750221) - - -- Teddy Hogeborn Fri, 06 Jun 2014 04:27:15 +0200 - -mandos (1.6.5-2) unstable; urgency=medium - - * debian/rules (override_dh_auto_test-arch): New; does nothing. Fixes - FTBFS for build-indep. - - -- Teddy Hogeborn Tue, 13 May 2014 08:08:31 +0200 - -mandos (1.6.5-1) unstable; urgency=medium - - * New upstream release. - * debian/copyright: Change year to "2014". - * debian/control (Build-Depends, Build-Depends-Indep): Moved build - dependencies of "mandos" package to "Build-Depends-Indep". - * debian/upstream/signing-key.asc: New; upstream source public key. - * debian/control (Standards-Version): Updated to "3.9.5". - * debian/control (mandos/Depends): Remove the dependency on - "avahi-daemon (>= 0.6.31-3) | systemd-sysv". It is unnecessary - since we have a workaround in debian/mandos.postinst anyway. - - -- Teddy Hogeborn Sun, 11 May 2014 22:16:33 +0200 - -mandos (1.6.4-1) unstable; urgency=medium - - * New upstream release. - * debian/control (Build-Depends): Add Python dependencies to - successfully run self-tests. - * debian/copyright: GPLv3 now has its own license file - use it. - * debian/watch: Set PGP signature URL. - - -- Teddy Hogeborn Sun, 16 Feb 2014 14:09:25 +0100 - -mandos (1.6.3-1) unstable; urgency=low - - * New upstream release. - * debian/control (Build-Depends): Added "systemd". - * debian/mandos.dirs (lib/systemd/system): New. - * debian/mandos-client.README.Debian: Refer to architecture libdir. - * debian/control (mandos/Depends): Add "avahi-daemon (>= 0.6.31-3) | - systemd-sysv". - * debian/mandos.postinst: If avahi-daemon is version 0.6.31-2 or older, - edit /etc/init.d script headers Required-Start - and Required-Stop to have "avahi" instead of - "avahi-daemon", before insserv(8) sees it. - * debian/mandos-client.lintian-overrides: Libdir changes. - * debian/rules (override_dh_fixperms): - '' - - - -- Teddy Hogeborn Tue, 21 Jan 2014 22:01:30 +0100 - -mandos (1.6.2-1) unstable; urgency=low - - * New upstream release. - * debian/compat: Changed to "9". - * debian/control (Build-Depends): Changed debhelper version to (>= 9). - (Standards-Version): Updated to "3.9.4". - (DM-Upload-Allowed): Removed. - (mandos/Depends): Add "initscripts (>= 2.88dsf-13.3)" to be able to - use the "/run" directory (for mandos.pid). - * debian/copyright (Copyright): Update year. - * Fix "Mandos/gnutls fails to establish connection, "an algorithm that - is not enabled was negotiated"" fixed by upstream. (Closes: #702120) - - -- Teddy Hogeborn Thu, 24 Oct 2013 22:33:40 +0200 - -mandos (1.6.1-1) unstable; urgency=low - - * New upstream release. - * debian/control (mandos/Depends): No longer depends on - python-gnupginterface, but does - depend on gnupg (<< 2). - (Build-Depends): Depend on debhelper 8.9.7 for using "override-*-arch" - and "override-*-indep" targets in debian/rules. - * debian/mandos-client.README: Update Linux documentation link. - * debian/rules: Completely rewritten to use debhelper v7. - * initramfs-tools-hook: Bug fix: Make sure the right version of GnuPG is - copied into the initramfs image. Always assume that GPGME is used to - avoid searching for it since the path might not be /usr/lib. Thanks - to Félix Sipma for the initial bug report, - and also thanks to Dick Middleton for some more - debugging. (Closes: #721903) - * Fix "bashism in /bin/sh script" fixed by upstream. (Closes: #690639) - - -- Teddy Hogeborn Sun, 13 Oct 2013 19:03:23 +0200 - -mandos (1.6.0-1) unstable; urgency=low - - * New upstream release. - * debian/copyright (Copyright): Join the two lines to a single line. - * debian/mandos-client.README.Debian: Update to refer to the new - location of the example network hooks, and the new feature of using - all network interfaces. - * debian/mandos-client.docs (network-hooks.d): Removed. - * debian/mandos-client.examples (network-hooks.d): New. - * debian/rules (binary-common): Added "dh_installexamples". - (binary-common/dh_fixperms): Exclude new location of - "network-hooks.d". - - -- Teddy Hogeborn Mon, 18 Jun 2012 00:15:23 +0200 - -mandos (1.5.5-1) unstable; urgency=low - - * New upstream release. - * debian/copyright (Format): Updated to - "http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/". - * debian/control (Build-Depends): Removed "man, locales-all". - - -- Teddy Hogeborn Fri, 01 Jun 2012 20:30:41 +0200 - -mandos (1.5.4-1) unstable; urgency=low - - * New upstream release. - - -- Teddy Hogeborn Sun, 20 May 2012 15:38:34 +0200 - -mandos (1.5.3-1.2) unstable; urgency=low - - * Non-maintainer upload. - * Set Architecture to linux-any. (Closes: #647670) - - -- Robert Millan Sun, 22 Apr 2012 16:22:01 +0200 - -mandos (1.5.3-1.1) unstable; urgency=low - - * Non-maintainer upload. - * Fix "mandos FTBFS on buildds": add build-dependency on locales-all and - pass LC_ALL to dh_auto_build to make sure we have and use the en_US.UTF-8 - locale for manpage creation. - (Closes: #656178) - - -- gregor herrmann Tue, 31 Jan 2012 17:56:05 +0100 - -mandos (1.5.3-1) unstable; urgency=low - - * New upstream release. - - -- Teddy Hogeborn Sun, 15 Jan 2012 22:05:54 +0100 - -mandos (1.5.2-1) unstable; urgency=low - - * New upstream release. - - -- Teddy Hogeborn Sun, 08 Jan 2012 11:17:20 +0100 - -mandos (1.5.1-1) unstable; urgency=low - - * New upstream release. - - -- Teddy Hogeborn Sun, 01 Jan 2012 21:53:31 +0100 - -mandos (1.5.0-1) unstable; urgency=low - - * New upstream release. - * debian/control (mandos-client/Depends): Added "initramfs-tools". - * debian/mandos-client.README.Debian: Corrected mail address and adjust - wording. - * debian/rules (binary-common): Exclude new nework-hooks.d directory - from dh_fixperms. - * debian/mandos-client.README.Debian: Document network hook facility. - * debian/mandos-client.docs (network-hooks.d): Added. - * debian/mandos.dirs (var/lib/mandos): Added. - * debian/mandos.postinst: Fix ownership of /var/lib/mandos. - * debian/control (mandos/Depends): Added "python-gnupginterface". - - -- Teddy Hogeborn Sun, 01 Jan 2012 05:58:11 +0100 - -mandos (1.4.1-1) unstable; urgency=low - - * New upstream release. - * debian/control (Build-Depends): Added "man". - * debian/control (Conflicts): Changed to "Breaks:". - * debian/copyright: Updated format. - * debian/mandos-client.postinst: Use "set -e" instead of "#!/bin/sh -e". - * debian/mandos-client.postrm: - '' - - * debian/mandos.postinst: - '' - - * debian/mandos.prerm: Consistent magic. - - -- Björn Påhlsson Sat, 15 Oct 2011 18:18:52 +0200 - -mandos (1.4.0-1) unstable; urgency=low - - * New upstream release. - * Fix "FTBFS with binutils-gold": Added "-Xlinker --as-needed" to - LDFLAGS in Makefile. (Closes: #632145) - * Fix "/run transition: uses obsolete /dev/.initramfs": Try both old and - new PID file locations. (Closes: #643554) - * debian/source/local-options: New; contains "--single-debian-patch". - * debian/control (Standards-Version): Upgraded to "3.9.2". - (DM-Upload-Allowed): New; set to "yes". - * debian/control: Changed domain from "fukt.bsnet.se" to "recompile.se". - * debian/copyright: - '' - - * debian/mandos-client.README.Debian: - '' - - * debian/mandos.README.Debian: - '' - - * debian/watch: - '' - - * debian/control (mandos/Description): Fix language to placate lintian. - - -- Teddy Hogeborn Sun, 09 Oct 2011 19:15:08 +0200 - -mandos (1.3.1-1) unstable; urgency=low - - * New upstream release. - * Conflict with correct version of dropbear. - * New version uses argparse; depend on python (<=2.7) | python-argparse. - - -- Teddy Hogeborn Wed, 27 Jul 2011 19:47:17 +0200 - -mandos (1.3.0-1) unstable; urgency=low - - * New upstream release. - * debian/control (mandos): Depend on Python 2.6, remove dependency on - python-multiprocessing. - (mandos-client): Conflict with dropbear (<< 0.52-5). - * debian/mandos-client.postrm (purge): Bug fix: update initramfs also on - purge. - * debian/mandos-client.lintian-overrides: Added plugins.d/plymouth. - - -- Teddy Hogeborn Tue, 08 Mar 2011 20:22:57 +0100 - -mandos (1.2.3-1) experimental; urgency=low - - * New upstream release. - - -- Teddy Hogeborn Mon, 11 Oct 2010 19:37:31 +0200 - -mandos (1.2.2-1) experimental; urgency=low - - * New upstream release. - * plugins.d/splashy.c: Only use ELIBBAD if defined. (Closes: #599256) - - -- Teddy Hogeborn Thu, 07 Oct 2010 20:27:54 +0200 - -mandos (1.2.1-3) experimental; urgency=low - - * debian/changelog: Include entry for NMU of version 1.0.14-1.1. - - -- Teddy Hogeborn Tue, 05 Oct 2010 20:58:38 +0200 - -mandos (1.2.1-2) unstable; urgency=low - - * debian/source/format: New; contains "3.0 (quilt)". Really. - - -- Björn Påhlsson Sat, 02 Oct 2010 19:46:59 +0200 - -mandos (1.2.1-1) unstable; urgency=low - - * New upstream release. - * debian/source/format: New; contains "3.0 (quilt)". - - -- Björn Påhlsson Sat, 02 Oct 2010 19:03:58 +0200 - -mandos (1.2-1) unstable; urgency=low - - * New upstream release. - * Makefile (LINK_FORTIFY_LD): Remove "-fPIE". (Closes: #557076) - * debian/control: Add gnupg dependency to "mandos-client" and removed it - from "mandos". Added dependency on "python-urwid" "mandos" since the - new "mandos-monitor" utility needs it, and on "python (>=2.6) | - python-multiprocessing" since the Mandos server now uses it. - * debian/rules: Set BROKEN_PIE on mips and mipsel if a known buggy - version of binutils is used. - * debian/mandos.docs: Also install "/usr/share/doc/mandos/DBUS-API". - * debian/mandos.dirs: Added "etc/dbus-1/system.d". - * debian/mandos-client.README.Debian: Update info about DEVICE setting - of initramfs.conf. - * debian/mandos-client.README.Debian: Remove warning about --connect not - looping, since it now does. - - -- Teddy Hogeborn Tue, 28 Sep 2010 20:46:11 +0200 - -mandos (1.0.14-1.1) unstable; urgency=low - - * Non-maintainer upload. - * Rebuild against libavahi-core-dev (>= 0.6.26-1). - - -- Michael Biebl Mon, 12 Jul 2010 16:34:34 +0200 - mandos (1.0.14-1) unstable; urgency=low (HIGH on mips and mipsel) * New upstream release. === modified file 'debian/compat' --- debian/compat 2018-02-06 20:03:50 +0000 +++ debian/compat 2008-09-17 00:34:09 +0000 @@ -1,1 +1,1 @@ -10 +7 === modified file 'debian/control' --- debian/control 2019-07-15 01:59:36 +0000 +++ debian/control 2010-01-01 15:17:12 +0000 @@ -1,34 +1,23 @@ Source: mandos Section: admin -Priority: optional -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, - libgnutls28-dev (>= 3.3.0), - libgnutls28-dev (>= 3.6.6) | libgnutls28-dev (<< 3.6.0), - xsltproc, pkg-config, libnl-route-3-dev -Build-Depends-Indep: systemd, python (>= 2.7), python (<< 3), - python-dbus, python-gi -Standards-Version: 4.3.0 -Vcs-Bzr: https://ftp.recompile.se/pub/mandos/trunk -Vcs-Browser: https://bzr.recompile.se/loggerhead/mandos/trunk/files -Homepage: https://www.recompile.se/mandos -Rules-Requires-Root: binary-targets -Testsuite: autopkgtest +Priority: extra +Maintainer: Mandos Maintainers +Uploaders: Teddy Hogeborn , + Björn Påhlsson +Build-Depends: debhelper (>= 7), docbook-xml, docbook-xsl, + libavahi-core-dev, libgpgme11-dev, libgnutls-dev, xsltproc, + pkg-config +Standards-Version: 3.8.3 +Vcs-Bzr: http://ftp.fukt.bsnet.se/pub/mandos/trunk +Vcs-Browser: http://bzr.fukt.bsnet.se/loggerhead/mandos/trunk/files +Homepage: http://www.fukt.bsnet.se/mandos Package: mandos Architecture: all -Depends: ${misc:Depends}, python (>= 2.7), python (<< 3), - libgnutls30 (>= 3.3.0), - libgnutls30 (>= 3.6.6) | libgnutls30 (<< 3.6.0), - python-dbus, python-gi, avahi-daemon, adduser, python-urwid, - gnupg2 | gnupg, systemd-sysv | lsb-base (>= 3.0-6), - debconf (>= 1.5.5) | debconf-2.0 -Recommends: ssh-client | fping -Suggests: libc6-dev | libc-dev, c-compiler -Description: server giving encrypted passwords to Mandos clients +Depends: ${misc:Depends}, python (>=2.5), python-gnutls, python-dbus, + python-avahi, python-gobject, avahi-daemon, adduser +Recommends: fping +Description: a 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 same time be capable of remote and/or unattended reboots. @@ -36,22 +25,17 @@ The computers run a small client program in the initial RAM disk environment which will communicate with a server over a network. All network communication is encrypted using TLS. - The clients are identified by the server using a TLS public + The clients are identified by the server using an OpenPGP key; each client has one unique to it. The server sends the clients an encrypted password. The encrypted password is - decrypted by the clients using an OpenPGP key, and the + decrypted by the clients using the same OpenPGP key, and the password is then used to unlock the root file system, whereupon the computers can continue booting normally. Package: mandos-client -Architecture: linux-any -Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, - cryptsetup (<< 2:2.0.3-1) | cryptsetup-initramfs, - initramfs-tools (>= 0.99), dpkg-dev (>=1.16.0), - gnutls-bin (>= 3.6.6) | libgnutls30 (<< 3.6.0), - debconf (>= 1.5.5) | debconf-2.0 -Recommends: ssh -Breaks: dropbear (<= 0.53.1-1) +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, cryptsetup, + gnupg (<< 2) Enhances: cryptsetup Description: do unattended reboots with an encrypted root file system This is the client part of the Mandos system, which allows @@ -61,9 +45,9 @@ The computers run a small client program in the initial RAM disk environment which will communicate with a server over a network. All network communication is encrypted using TLS. - The clients are identified by the server using a TLS public + The clients are identified by the server using an OpenPGP key; each client has one unique to it. The server sends the clients an encrypted password. The encrypted password is - decrypted by the clients using an OpenPGP key, and the + decrypted by the clients using the same OpenPGP key, and the password is then used to unlock the root file system, whereupon the computers can continue booting normally. === modified file 'debian/copyright' --- debian/copyright 2019-02-10 04:20:26 +0000 +++ debian/copyright 2009-01-04 21:54:55 +0000 @@ -1,26 +1,26 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Format-Specification: + http://wiki.debian.org/Proposals/CopyrightFormat?action=recall&rev=233 Upstream-Name: Mandos -Upstream-Contact: Mandos -Source: +Upstream-Maintainer: Mandos Maintainers +Upstream-Source: Files: * -Copyright: Copyright © 2008-2019 Teddy Hogeborn - Copyright © 2008-2019 Björn Påhlsson +Copyright: Copyright © 2008,2009 Teddy Hogeborn +Copyright: Copyright © 2008,2009 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". + License can be found in "/usr/share/common-licenses/GPL". === modified file 'debian/mandos-client.README.Debian' --- debian/mandos-client.README.Debian 2019-06-20 18:54:10 +0000 +++ debian/mandos-client.README.Debian 2009-09-08 06:28:20 +0000 @@ -1,6 +1,22 @@ -This file documents the next steps to take after installation of the -Debian package, and also contain some notes specific to the Debian -packaging which are not also in the manual. +* Choose the Client Network Interface + + You MUST make sure that the correct network interface is specified + in the DEVICE setting in the "/etc/initramfs-tools/initramfs.conf" + file. *If* this is changed, it will be necessary to update the + initrd image by running the command + + update-initramfs -k all -u + + The device can be overridden at boot time on the Linux kernel + command line using the sixth colon-separated field of the "ip=" + option; for exact syntax, read the documentation in the file + "/usr/share/doc/linux-doc-*/Documentation/filesystems/nfsroot.txt", + available in the "linux-doc-*" package. + + Note that since this network interface is used in the initial RAM + disk environment, the network interface *must* exist at that stage. + Thus, the interface can *not* be a pseudo-interface such as "br0" or + "tun0"; instead, a real interface (such as "eth0") must be used. * Adding a Client Password to the Server @@ -20,53 +36,14 @@ is possible to verify that the correct password will be received by this client by running the command, on the client: - MANDOSPLUGINHELPERDIR=/usr/lib/$(dpkg-architecture \ - -qDEB_HOST_MULTIARCH)/mandos/plugin-helpers \ - /usr/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH \ - )/mandos/plugins.d/mandos-client \ + /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. There it can be verified to be the correct password, before rebooting. -* Emergency Escape - - If it ever should be necessary, the Mandos client can be temporarily - prevented from running at startup by passing the parameter - "mandos=off" to the kernel. - -* Specifying a Client Network Interface - - At boot time the network interfaces to use will by default be - 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 - setting is changed, it will be necessary to update the initrd image - by running this command: - - update-initramfs -k all -u - - The device can also be overridden at boot time on the Linux kernel - command line using the sixth colon-separated field of the "ip=" - option; for exact syntax, read the documentation in the file - "/usr/share/doc/linux-doc-*/Documentation/filesystems/nfs/nfsroot.txt", - available in the "linux-doc-*" package. - - 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". - * User-Supplied Plugins Any plugins found in "/etc/mandos/plugins.d" will override and add @@ -83,29 +60,28 @@ Mandos client will be the new default way for getting a password for the root file system when booting. +* Emergency Escape + + If it ever should be necessary, the Mandos client can be temporarily + prevented from running at startup by passing the parameter + "mandos=off" to the kernel. + * Non-local Connection (Not Using ZeroConf) If the "ip=" kernel command line option is used to specify a complete IP address and device name, as noted above, it then becomes possible to specify a specific IP address and port to connect to, instead of using ZeroConf. The syntax for doing this is - "mandos=connect::" on the kernel command - line. - - For very advanced users, it is possible to specify simply + "mandos=connect::". + + Warning: this will cause the client to make exactly one attempt at + connecting, and then fail if it does not succeed. + + 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 work, "--options-for=mandos-client:--connect=
:" needs to be manually added to the file "/etc/mandos/plugin-runner.conf". -* Diffie-Hellman Parameters - - On installation, a file with Diffie-Hellman parameters, - /etc/keys/mandos/dhparams.pem, will be generated and automatically - installed into the initital RAM disk image and also used by the - Mandos Client on boot. If different parameters are needed for - policy or other reasons, simply replace the existing dhparams.pem - file and update the initital RAM disk image. - - -- Teddy Hogeborn , Thu, 20 Jun 2019 20:28:25 +0200 + -- Teddy Hogeborn , Tue, 8 Sep 2009 08:25:58 +0200 === modified file 'debian/mandos-client.dirs' --- debian/mandos-client.dirs 2019-04-09 19:33:36 +0000 +++ debian/mandos-client.dirs 2009-02-07 04:50:39 +0000 @@ -1,7 +1,5 @@ usr/share/man/man8 usr/sbin usr/share/initramfs-tools/hooks -usr/share/initramfs-tools/conf.d usr/share/initramfs-tools/conf-hooks.d usr/share/initramfs-tools/scripts/init-premount -usr/share/initramfs-tools/scripts/local-premount === removed file 'debian/mandos-client.examples' --- debian/mandos-client.examples 2012-06-01 21:48:12 +0000 +++ debian/mandos-client.examples 1970-01-01 00:00:00 +0000 @@ -1,1 +0,0 @@ -network-hooks.d === modified file 'debian/mandos-client.lintian-overrides' --- debian/mandos-client.lintian-overrides 2019-02-10 03:50:20 +0000 +++ debian/mandos-client.lintian-overrides 2009-01-18 06:41:57 +0000 @@ -2,14 +2,12 @@ # mandos-client binary: non-standard-dir-perm etc/keys/mandos/ 0700 != 0755 -# The directory /usr/lib//mandos/plugins.d contains setuid -# binaries which are not meant to be run outside an initial RAM disk +# The directory /usr/lib/mandos/plugins.d contains setuid 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 +mandos-client binary: non-standard-dir-perm usr/lib/mandos/plugins.d/ 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 @@ -17,27 +15,13 @@ # system, but in an initial RAM disk environment. Here they are # protected from non-root access by the directory permissions, above. # -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 +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 # 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. +# /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 - -# It is a really long line -mandos-client binary: manpage-has-errors-from-man usr/share/man/man8/plugin-runner.8mandos.gz *: warning *: can't break line === modified file 'debian/mandos-client.postinst' --- debian/mandos-client.postinst 2019-02-10 10:39:26 +0000 +++ debian/mandos-client.postinst 2009-05-24 23:36:15 +0000 @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/sh -e # This script can be called in the following ways: # # After the package was installed: @@ -15,14 +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() { - update-initramfs -u -k all + if [ -x /usr/sbin/update-initramfs ]; then + update-initramfs -u -k all + fi if dpkg --compare-versions "$2" lt-nl "1.0.10-1"; then # Make old initrd.img files unreadable too, in case they were @@ -52,123 +50,22 @@ 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 +# Create client key pair +create_key(){ + if [ -r /etc/keys/mandos/pubkey.txt \ + -a -r /etc/keys/mandos/seckey.txt ]; then + return 0 + fi + if [ -x /usr/sbin/mandos-keygen ]; 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_dh_params(){ - if [ -r /etc/keys/mandos/dhparams.pem ]; then - return 0 - fi - # Create a Diffe-Hellman parameters file - DHFILE="`mktemp -t mandos-client-dh-parameters.XXXXXXXXXX.pem`" - # First try certtool from GnuTLS - if ! certtool --generate-dh-params --sec-param high \ - --outfile "$DHFILE"; then - # Otherwise try OpenSSL - if ! openssl genpkey -genparam -algorithm DH -out "$DHFILE" \ - -pkeyopt dh_paramgen_prime_len:3072; then - # None of the commands succeded; give up - rm -- "$DHFILE" - return 1 - fi - fi - sed --in-place --expression='0,/^-----BEGIN DH PARAMETERS-----$/d' \ - "$DHFILE" - sed --in-place --expression='1i-----BEGIN DH PARAMETERS-----' \ - "$DHFILE" - cp --archive "$DHFILE" /etc/keys/mandos/dhparams.pem - rm -- "$DHFILE" + fi } case "$1" in configure) add_mandos_user "$@" - create_keys "$@" - create_dh_params "$@" || : + create_key "$@" update_initramfs "$@" - if dpkg --compare-versions "$2" lt-nl "1.7.10-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 - chmod u=rwx,go= -- "$PLUGINHELPERDIR" - fi - if ! dpkg-statoverride --list /etc/mandos/plugin-helpers \ - >/dev/null 2>&1; then - chmod u=rwx,go= -- /etc/mandos/plugin-helpers - fi - fi ;; abort-upgrade|abort-deconfigure|abort-remove) ;; === modified file 'debian/mandos-client.postrm' --- debian/mandos-client.postrm 2019-02-09 23:23:26 +0000 +++ debian/mandos-client.postrm 2009-01-18 00:16:57 +0000 @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/sh -e # This script can be called in the following ways: # # After the package was removed: @@ -26,12 +26,13 @@ # If preinst fails during upgrade: # abort-upgrade -set -e # Update the initial RAM file system image update_initramfs() { - update-initramfs -u -k all || : + if [ -x /usr/sbin/update-initramfs ]; then + update-initramfs -u -k all + fi } case "$1" in @@ -43,11 +44,7 @@ shred --remove /etc/keys/mandos/seckey.txt 2>/dev/null || : 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 + /etc/keys/mandos/seckey.txt 2>/dev/null ;; upgrade|failed-upgrade|disappear|abort-install|abort-upgrade) ;; === removed file 'debian/mandos-client.templates' --- debian/mandos-client.templates 2019-02-10 03:50:20 +0000 +++ debian/mandos-client.templates 1970-01-01 00:00:00 +0000 @@ -1,10 +0,0 @@ -Template: mandos-client/key_id -Type: note -Description: New client option "${key_id}" is REQUIRED on server - A new "key_id" client option is REQUIRED in the server's clients.conf file, otherwise this computer most likely will not reboot unattended. This option: - . - ${key_id} - . - must be added (all on one line!) on the Mandos server host, in the file /etc/mandos/clients.conf, right before the "fingerprint" option for this Mandos client. You must edit that file on that server and add this option. - . - With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP keys as TLS session keys. A new TLS key pair has been generated and will be used as identification, but the key ID of the public key needs to be added to the server, since this will now be used to identify the client to the server. === modified file 'debian/mandos.README.Debian' --- debian/mandos.README.Debian 2011-10-05 16:00:56 +0000 +++ debian/mandos.README.Debian 2009-09-08 06:28:20 +0000 @@ -1,10 +1,10 @@ The Mandos server is useless without at least one configured client in /etc/mandos/clients.conf. To create one, install the "mandos-client" -package on a client computer, and, on the client, run the command +package on a client computer, and run the command # mandos-keygen --password -to get a config file stanza. Append the output of that command to the -file "/etc/mandos/clients.conf" on the Mandos server computer. +there to get a config file stanza. Append the output of that command +to the file "/etc/mandos/clients.conf" on the Mandos server. - -- Teddy Hogeborn , Wed, 5 Oct 2011 17:51:22 +0200 + -- Teddy Hogeborn , Tue, 8 Sep 2009 06:57:45 +0200 === modified file 'debian/mandos.dirs' --- debian/mandos.dirs 2016-03-19 12:10:15 +0000 +++ debian/mandos.dirs 2008-09-17 00:34:09 +0000 @@ -2,8 +2,4 @@ usr/share/man/man8 etc/init.d etc/default -etc/dbus-1/system.d usr/sbin -var/lib/mandos -lib/systemd/system -usr/lib/tmpfiles.d === modified file 'debian/mandos.docs' --- debian/mandos.docs 2010-09-12 03:00:40 +0000 +++ debian/mandos.docs 2008-10-18 11:17:22 +0000 @@ -1,4 +1,3 @@ NEWS README TODO -DBUS-API === modified file 'debian/mandos.lintian-overrides' --- debian/mandos.lintian-overrides 2019-02-10 03:50:20 +0000 +++ debian/mandos.lintian-overrides 2008-10-01 15:29:01 +0000 @@ -2,12 +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 -mandos: init.d-script-needs-depends-on-lsb-base etc/init.d/mandos (line 46) - -# The debconf templates is only used for displaying information -# detected in the postinst, not for saving answers to questions, so we -# don't need a .config file. -mandos binary: no-debconf-config - -# The notice displayed from the postinst script really is critical -mandos binary: postinst-uses-db-input === modified file 'debian/mandos.postinst' --- debian/mandos.postinst 2019-02-10 08:41:14 +0000 +++ debian/mandos.postinst 2009-05-24 23:28:04 +0000 @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/sh -e # This script can be called in the following ways: # # After the package was installed: @@ -15,10 +15,6 @@ # If prerm fails during replacement due to conflict: # abort-remove in-favour -. /usr/share/debconf/confmodule - -set -e - case "$1" in configure) # Rename old "mandos" user and group @@ -36,73 +32,18 @@ --home /nonexistent --no-create-home --group \ --disabled-password --gecos "Mandos password system" \ _mandos - elif dpkg --compare-versions "$2" eq 1.7.4-1 \ - || dpkg --compare-versions "$2" eq "1.7.4-1~bpo8+1" - then - start=no - if ! [ -f /var/lib/mandos/clients.pickle ]; then - invoke-rc.d mandos stop - start=yes - fi - chown _mandos:_mandos /var/lib/mandos/clients.pickle \ - 2>/dev/null || : - if [ "$start" = yes ]; then - invoke-rc.d mandos start - fi - fi - if ! dpkg-statoverride --list "/var/lib/mandos" >/dev/null \ - 2>&1; then - 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) ;; - + *) echo "$0 called with unknown argument '$1'" 1>&2 exit 1 ;; esac -# Avahi version 0.6.31-2 and older provides "avahi" (instead of -# "avahi-daemon") in its /etc/init.d script header. To make -# insserv(8) happy, we edit our /etc/init.d script header to contain -# the correct string before the code added by dh_installinit calls -# update.rc-d, which calls insserv. -avahi_version="`dpkg-query --showformat='${Version}' --show avahi-daemon`" -if dpkg --compare-versions "$avahi_version" le 0.6.31-2; then - sed --in-place --expression='/^### BEGIN INIT INFO$/,/^### END INIT INFO$/s/^\(# Required-\(Stop\|Start\):.*avahi\)-daemon\>/\1/g' /etc/init.d/mandos -fi - #DEBHELPER# exit 0 === modified file 'debian/mandos.prerm' --- debian/mandos.prerm 2015-07-12 01:57:54 +0000 +++ debian/mandos.prerm 2009-01-18 00:16:57 +0000 @@ -1,4 +1,4 @@ -#!/bin/sh +#! /bin/sh # prerm script for mandos # # see: dh_installdeb(1) @@ -17,7 +17,13 @@ case "$1" in remove|deconfigure) - invoke-rc.d mandos stop || : + if [ -x /etc/init.d/mandos ]; then + if [ -x /usr/sbin/invoke-rc.d ]; then + invoke-rc.d mandos stop + else + /etc/init.d/mandos stop + fi + fi ;; upgrade|failed-upgrade) ;; === removed file 'debian/mandos.templates' --- debian/mandos.templates 2019-07-14 22:50:47 +0000 +++ debian/mandos.templates 1970-01-01 00:00:00 +0000 @@ -1,19 +0,0 @@ -Template: mandos/key_id -Type: note -Description: New client option "key_id" is REQUIRED on server - A new "key_id" client option is REQUIRED in the clients.conf file, otherwise the client most likely will not reboot unattended. This option: - . - key_id = - . - must be added in the file /etc/mandos/clients.conf, right before the "fingerprint" option, for each Mandos client. You must edit that file and add this option for all clients. To see the correct key ID for each client, run this command (on each client): - . - mandos-keygen -F/dev/null|grep ^key_id - . - Note: the 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 === modified file 'debian/rules' --- debian/rules 2019-04-09 22:31:23 +0000 +++ debian/rules 2009-11-11 00:25:22 +0000 @@ -1,61 +1,106 @@ #!/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 $@ - -override_dh_auto_build-arch: - LC_ALL=en_US.utf8 dh_auto_build -- all doc - -override_dh_auto_build-indep: - LC_ALL=en_US.utf8 dh_auto_build -- doc - -override_dh_installinit-indep: +# Sample debian/rules that uses debhelper. +# +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. +# +# Modified to make a template file for a multi-binary package with separated +# build-arch and build-indep targets by Bill Allombert 2001 + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +# This has to be exported to make some magic below work. +export DH_OPTIONS + +# -pie was broken briefly on the mips and mipsel architectures, see +# +BINUTILS_V := $(shell dpkg-query --showformat='$${Version}' \ + --show binutils) +ifeq (yes,$(shell dpkg --compare-versions $(BINUTILS_V) lt 2.20-3 \ + && dpkg --compare-versions $(BINUTILS_V) ge 2.19.1-1 \ + && echo yes)) + ifneq (,$(strip $(findstring :$(DEB_HOST_ARCH):,:mips:mipsel:) \ + $(findstring :$(DEB_BUILD_ARCH):,:mips:mipsel:))) + BROKEN_PIE := yes + export BROKEN_PIE + endif +endif + +configure: configure-stamp +configure-stamp: + dh_testdir + touch configure-stamp + +build: build-arch build-indep + +build-arch: build-arch-stamp +build-arch-stamp: configure-stamp + dh_auto_build -- all doc + touch $@ + +build-indep: build-indep-stamp +build-indep-stamp: configure-stamp + dh_auto_build -- doc + touch $@ + +clean: + dh_testdir + dh_testroot + rm -f build-arch-stamp build-indep-stamp configure-stamp + dh_auto_clean + dh_clean + +install: install-indep install-arch +install-indep: + dh_testdir + dh_testroot + dh_prep + dh_installdirs --indep + $(MAKE) DESTDIR=$(CURDIR)/debian/mandos install-server + dh_lintian dh_installinit --onlyscripts \ --update-rcd-params="defaults 25 15" - -override_dh_auto_install-indep: - $(MAKE) DESTDIR=$(CURDIR)/debian/mandos install-server - -override_dh_auto_install-arch: - $(MAKE) DESTDIR=$(CURDIR)/debian/mandos-client \ - install-client-nokey - -override_dh_fixperms-arch: + dh_install --indep + +install-arch: + dh_testdir + dh_testroot + dh_prep + dh_installdirs --same-arch + $(MAKE) DESTDIR=$(CURDIR)/debian/mandos-client install-client-nokey + dh_lintian + dh_install --same-arch + +binary-common: + dh_testdir + dh_testroot + dh_installchangelogs + dh_installdocs + dh_link + dh_strip + dh_compress dh_fixperms --exclude etc/keys/mandos \ + --exclude etc/mandos/clients.conf \ --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/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" - -override_dh_fixperms-indep: - 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 + --exclude usr/lib/mandos/plugins.d + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +# Build architecture independant packages using the common target. +binary-indep: build-indep install-indep + $(MAKE) -f debian/rules DH_OPTIONS=--indep binary-common + +# Build architecture dependant packages using the common target. +binary-arch: build-arch install-arch + $(MAKE) -f debian/rules DH_OPTIONS=--same-arch binary-common + +binary: binary-arch binary-indep + +.PHONY: build clean binary-indep binary-arch binary install \ + install-indep install-arch configure === removed directory 'debian/source' === removed file 'debian/source/format' --- debian/source/format 2010-10-02 17:41:05 +0000 +++ debian/source/format 1970-01-01 00:00:00 +0000 @@ -1,1 +0,0 @@ -3.0 (quilt) === removed file 'debian/source/local-options' --- debian/source/local-options 2011-10-08 21:13:46 +0000 +++ debian/source/local-options 1970-01-01 00:00:00 +0000 @@ -1,1 +0,0 @@ ---single-debian-patch === removed directory 'debian/tests' === removed file 'debian/tests/control' --- debian/tests/control 2019-07-15 01:59:36 +0000 +++ debian/tests/control 1970-01-01 00:00:00 +0000 @@ -1,24 +0,0 @@ -Test-Command: /usr/sbin/mandos --check -Restrictions: superficial -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 === removed directory 'debian/upstream' === removed file 'debian/upstream/signing-key.asc' --- debian/upstream/signing-key.asc 2014-03-28 22:32:21 +0000 +++ debian/upstream/signing-key.asc 1970-01-01 00:00:00 +0000 @@ -1,52 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1.4.12 (GNU/Linux) - -mQINBFJQOFYBEACoWsEGlOxVWFUAxOxdd3GDLaqEKkKihJwLp102Ks7JKMd9friR -7+OZuo3U0gdqLU9q1jPJn36J1QbaUTOvcaKtZp+QpUoYJ2OaGtlOY5ML8LSoC0rZ -MIzGYTtvriwpU/YplLNGPl/90KsB2VqjrY1l1he5M8zziWDlPdJxwg8GFvmPWoif -6oo+1iCswL5IdQ6c5MVO53zYu0cgyUSazLsVD5Xzy59lefgtaDydahJpPycf5aEQ -DAoC9fZt2mgG3FLIUCZdXIhZdOJGCMdjLThBnJXYgGbG4rbGLNlI4W/uA5aqa4ME -WYSAcCyX3ucKY/LkXRtC+z5s05e7tZ3Z+uAJy1eDsbhDXgZERye7a/zPWx1tAlzQ -E80Oltjh1uXWjQORyx99a0jK87zjm49YjhYw1ZN6Z0HfSaws4Yj2QOzp9t4B3l7f -DIUYoWBfHW7mseQeQ+t3TwQU5gjFCNu7oDeATqi5A5MksXN0+BcksterbGRBhEyp -CybIEyrZE033jIs407Ool4Kv10cnjc8oy609BXex/dxwcvVr2vQHle4NPUZd+Xhg -zC+9Z4jFwE0M/EPvtyieA/DWQse+TZ5itDGMYDub/GJfv1U61ANOgPIbTEF7iSa9 -5nWmq7zyUy/txmABka842Kt0Vp6ayoKcF8EIXCaDrVfPnXj+JlKf3c2u6wARAQAB -tCxNYW5kb3MgTWFpbnRhaW5lciBUZWFtIDxtYW5kb3NAcmVjb21waWxlLnNlPokC -PQQTAQgAJwUCUlA4VgIbAwUJCWYBgAULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAAK -CRByIylzyjTCxETXEACi56jCV9lJNSBbTp0Iet4X/i7Mx0Z8UkFFa3l7o0i4jFQj -CIBrWECDlcxqZziii2dgh7L0ma93vB3rfjfCWeYLcEQw43MFBsd4dHuobrLXTcqU -7n0Zmc8BsXwk5B25CnEYgvlbWX9BCYtxHGRcZzQrqOFjCMKatq0EIIVuWaz5yuCU -V2rEgnr+veTd/rBOE9ez6Ju6xH11Teob5G7pMM0YPKHtZG/J3rvWPw4BDM6Tc60B -G0sTDZNgkrGWxuB8YaLIwVWzliQK/17Jv/0alajyA3cWLWMkcK9Yhi/einzdMRoD -IjnbtoHcSC9g6i0VGelwnMpHlFTwXriXEBSttULarK3iKE4tOv9nxMAEwicqlw14 -X96PPgz6ogtJG7FiwZfy1CQ81Uby+YuIhHZx3ZEyR1TFq70e98EgCuvjMZWMIhSy -gB5Vssfq7c2lXQjltV3ujhK1PD+7/iHlL7t4QRxPDN8fbMS2VPfAdtnWS1K48d5J -D/jP1LrGWS81HIaX8GFVLVw+jSQEu9cn3TFiZxK/4MMsITmlouJdtZWmQ/otMSMl -wiCCZp3dGpRXMmaqR1N8V0nMKshM8mci7bD92ubd/t6cR/G6l+VIp45WyFONvtde -F3ccfmfrKJuroMDfHxPxMf58EroAuWwzJKCPRH4JmwDvSSQoIIAGNL1lOKp6wrkC -DQRSUDhWARAAzN7pbpAu7XLNPODotV/N+JaCFvNAIqTcr9PrbhxiKFCDs9/IExwP -sGENL9GZd1DfoGEgxQ8j3l8VGw9VSeUoN7uMY2NwwbXTilAFkn/S8xnr2zQDRZ+n -EeFSq9MMFxj4Kt1TqVYDbO8vmFfOT3gRCRHeJ+pn4yJeSPau/ndNrmbQ1/Z6vaUG -yfo931ottx7SXZwkHA6jJVFT9rbHTyx9tzOqMKDJiMrx8qKaHpE9B45oHNR0WJJJ -75zoDVuOZ6wAxXZuqBFu2lKPqDTZeawfzcu5qplrm1RPgSOjz6w1A41HBLqGe+v9 -7Twx5wfNMgnKC0V2wUe0xR6hQHQlyZoCwcGyrasiu+v/joZ1p66SSWKjs+LGjoe4 -Lwh6VjZU2x+irVBjcgIoRWf3k4JAef0nGYsm0cFjAnwXac+/CYxVt+7Y4+HG8wPB -oUNZkW+bvdHQouxEClxnccIEgX/AkriJTYDQ4q3tkl2HVE/51R4pdQVLer2a5ov+ -Jwk44DdqzYstMsvqu+iD48hXzADg6HFvofkpct15h463pMaJf99uVVM9ZDNQ6B34 -l3tPX9ZDkIDl6n9dE2Jkaxx1yNVhPXpeMf4EzL+CEdUErVStB66lUkw+tNkuZyVT -ZTQel9h0196+CNSiqAaL8+ZZdbjKKfzlcB4Qnd897XzMfsFQ7mzJ9QsAEQEAAYkC -JQQYAQgADwUCUlA4VgIbDAUJCWYBgAAKCRByIylzyjTCxFmlEACBTOg5NqX63d8D -mwk4smlFPppQBIduxZaMG9HsLcPi3VKTG9Zg6WI6rEdr/4MnoINsudLsEbrQLgRH -2q1Zs+HqIIP5H2/sYHmswyokYB10zKB6gNUUg/GSlcAcrelsHVKx5B8kccWGT5gk -Wo/X0BGMUTOvQ6lJ6YNo1idcQ2ZjsyfZoz3G8JS7/EXN//jAZf+017yj8WsAS7hw -JRFMy7VET4g00JcBoNOAMP7PkozimZ2OwwsggJSYWkR1RaU2tKR1VmDF8R6UxuEd -BJzwFmz+wNC1Kq+FoSaRNsrKEmzLnfV9unDnF2z7Lc4LqOysXdzOk9zTBPur0gd2 -Lh5H/g5rTAMQBARqXfvIwiTtrBGgil8JW8e4Bc0LQUuHAE7x9gMRil+OtkQrCRk9 -0LWXVS+K0tvvruE4EDtCGiS5046+BEI3aYsp4hNzjHADq0TJeCYjNg9kY0CjxcEq -cfuMoUbQ0MkARGuBbykCdlylfTrkxrj/dPhr49lctY3H+Pj6F4fMDM4TP6UTGA8k -993RRNYhkDWSxIp6G7RJpBZobHN+eHQ3r8A4tWdYb4Fvd2lvwEDjUFT9uD6WAff4 -8A1hM2uSy91UYBOPrIjqYdRFKJc9rThYdXH2T6SiRMYtZMrEKhqPffB/i9mqVBlD -6vKRsaQikZujRdP9Dkf0mLmJ7LANWw== -=9Noe ------END PGP PUBLIC KEY BLOCK----- === modified file 'debian/watch' --- debian/watch 2019-02-11 05:15:24 +0000 +++ debian/watch 2009-01-15 02:52:02 +0000 @@ -1,3 +1,2 @@ -version=4 -opts=pgpmode=auto \ - https://ftp.recompile.se/pub/@PACKAGE@/@PACKAGE@@ANY_VERSION@\.orig@ARCHIVE_EXT@ +version=3 +ftp://ftp.fukt.bsnet.se/pub/mandos/mandos[-_]([^\s]+?)(?:\.orig)?\.tar\.(?:gz|bz2|7z) === modified file 'init.d-mandos' --- init.d-mandos 2018-02-10 13:23:58 +0000 +++ init.d-mandos 2009-09-16 23:28:39 +0000 @@ -1,16 +1,19 @@ #! /bin/sh ### BEGIN INIT INFO # Provides: mandos -# Required-Start: $remote_fs $syslog avahi-daemon -# Required-Stop: $remote_fs $syslog avahi-daemon +# Required-Start: $remote_fs $syslog avahi +# Required-Stop: $remote_fs $syslog avahi # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Mandos server -# Description: Server of encrypted passwords to Mandos clients +# Description: Gives encrypted passwords to Mandos clients ### END INIT INFO -# Author: Teddy Hogeborn -# Author: Björn Påhlsson +# 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" @@ -20,11 +23,7 @@ NAME=mandos DAEMON=/usr/sbin/$NAME DAEMON_ARGS="" -if [ -d /run/. ]; then - PIDFILE=/run/$NAME.pid -else - PIDFILE=/var/run/$NAME.pid -fi +PIDFILE=/var/run/$NAME.pid SCRIPTNAME=/etc/init.d/$NAME # Exit if the package is not installed @@ -41,8 +40,7 @@ . /lib/init/vars.sh # Define LSB log_* functions. -# Depend on lsb-base (>= 3.2-14) to ensure that this file is present -# and status_of_proc is working. +# Depend on lsb-base (>= 3.0-6) to ensure that this file is present. . /lib/lsb/init-functions # @@ -120,9 +118,6 @@ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; - status) - status_of_proc "$DAEMON" "$NAME" -p "$PIDFILE" && exit 0 || exit $? - ;; #reload|force-reload) # # If do_reload() is not implemented then leave this commented out @@ -149,14 +144,14 @@ esac ;; *) - # Failed to stop + # Failed to stop log_end_msg 1 ;; esac ;; *) #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 - echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 + echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2 exit 3 ;; esac === removed file 'initramfs-tools-conf' --- initramfs-tools-conf 2018-08-19 14:06:55 +0000 +++ initramfs-tools-conf 1970-01-01 00:00:00 +0000 @@ -1,17 +0,0 @@ -# -*- shell-script -*- - -# Since the initramfs image will contain key files, we need to -# restrict permissions on it by setting UMASK here. -# -# The proper place to set UMASK is (according to -# /etc/cryptsetup-initramfs/conf-hook), in -# /etc/initramfs-tools/initramfs.conf, which we shouldn't edit. The -# corresponding directory for drop-in files from packages is -# /usr/share/initramfs-tools/conf.d, and this file will be installed -# there as "mandos-conf". -# -# This setting of UMASK will have unfortunate unintended side effects -# on the files *inside* the initramfs, but these are later fixed by -# "initramfs-tools-hook", installed as -# "/usr/share/initramfs-tools/hooks/mandos". -UMASK=0027 === removed file 'initramfs-tools-conf-hook' --- initramfs-tools-conf-hook 2019-04-09 19:33:36 +0000 +++ initramfs-tools-conf-hook 1970-01-01 00:00:00 +0000 @@ -1,14 +0,0 @@ -# -*- shell-script -*- - -# The UMASK is set by the file "initramfs-tools-conf" (which is copied -# to /usr/share/initramfs-tools/conf.d/mandos-conf on installation) -# since there, as described therein, is the proper place to do that. -# However, it is possible for other packages to override the UMASK in -# any file in /usr/share/initramfs-tools/conf-hooks.d. Therefore, -# this file ("initramfs-tools-conf-hook") will be installed as -# "zz-mandos" in that directory to make sure UMASK is set correctly. - -# For more information on the effects of setting UMASK, see the -# aforementioned /usr/share/initramfs-tools/conf.d/mandos-conf file. - -UMASK=0027 === modified file 'initramfs-tools-hook' --- initramfs-tools-hook 2018-08-19 14:06:55 +0000 +++ initramfs-tools-hook 2009-10-22 00:05:01 +0000 @@ -3,7 +3,7 @@ # This script will be run by 'mkinitramfs' when it creates the image. # Its job is to decide which files to install, then install them into # the staging area, where the initramfs is being created. This -# happens when a new 'linux-image' package is installed, or when an +# happens when a new 'linux-image' package is installed, or when the # administrator runs 'update-initramfs' by hand to update an initramfs # image. @@ -29,15 +29,13 @@ . /usr/share/initramfs-tools/hook-functions -for d 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 "$d"/mandos ]; then - libdir="$d" +for d in /usr /usr/local; do + if [ -d "$d"/lib/mandos ]; then + prefix="$d" break fi done -if [ -z "$libdir" ]; then +if [ -z "$prefix" ]; then # Mandos not found exit 1 fi @@ -70,25 +68,20 @@ CONFDIR="/conf/conf.d/mandos" MANDOSDIR="/lib/mandos" PLUGINDIR="${MANDOSDIR}/plugins.d" -PLUGINHELPERDIR="${MANDOSDIR}/plugin-helpers" -HOOKDIR="${MANDOSDIR}/network-hooks.d" # Make directories install --directory --mode=u=rwx,go=rx "${DESTDIR}${CONFDIR}" \ - "${DESTDIR}${MANDOSDIR}" "${DESTDIR}${HOOKDIR}" + "${DESTDIR}${MANDOSDIR}" install --owner=${mandos_user} --group=${mandos_group} --directory \ - --mode=u=rwx "${DESTDIR}${PLUGINDIR}" \ - "${DESTDIR}${PLUGINHELPERDIR}" - -copy_exec "$libdir"/mandos/mandos-to-cryptroot-unlock "${MANDOSDIR}" + --mode=u=rwx "${DESTDIR}${PLUGINDIR}" # Copy the Mandos plugin runner -copy_exec "$libdir"/mandos/plugin-runner "${MANDOSDIR}" +copy_exec "$prefix"/lib/mandos/plugin-runner "${MANDOSDIR}" # Copy the plugins # Copy the packaged plugins -for file in "$libdir"/mandos/plugins.d/*; do +for file in "$prefix"/lib/mandos/plugins.d/*; do base="`basename \"$file\"`" # Is this plugin overridden? if [ -e "/etc/mandos/plugins.d/$base" ]; then @@ -102,21 +95,6 @@ esac done -# Copy the packaged plugin helpers -for file in "$libdir"/mandos/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) - : ;; - "*") : ;; - *) copy_exec "$file" "${PLUGINHELPERDIR}" ;; - esac -done - # Copy any user-supplied plugins for file in /etc/mandos/plugins.d/*; do base="`basename \"$file\"`" @@ -128,78 +106,12 @@ 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) - : ;; - "*") : ;; - *) copy_exec "$file" "${PLUGINHELPERDIR}" ;; - esac -done - -# 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}" - fi -done -export DEVICE - -# Copy network hooks -for hook in /etc/mandos/network-hooks.d/*; do - case "`basename \"$hook\"`" in - "*") continue ;; - *[!A-Za-z0-9_.-]*) continue ;; - *) test -d "$hook" || copy_exec "$hook" "${HOOKDIR}" ;; - 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 -r file target; do - if [ ! -e "${file}" ]; then - echo "WARNING: file ${file} not found, requested by Mandos network hook '${hook##*/}'" >&2 - fi - if [ -z "${target}" ]; then - copy_exec "$file" - else - copy_exec "$file" "$target" - fi - 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" - done - fi -done - -# GPGME needs GnuPG -gpg=/usr/bin/gpg -libgpgme11_version="`dpkg-query --showformat='${Version}' --show libgpgme11`" -if dpkg --compare-versions "$libgpgme11_version" ge 1.5.0-0.1; then - if [ -e /usr/bin/gpgconf ]; then - if [ ! -e "${DESTDIR}/usr/bin/gpgconf" ]; then - 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 -fi -if [ ! -e "${DESTDIR}$gpg" ]; then - copy_exec "$gpg" -fi -unset gpg -unset libgpgme11_version +# GPGME needs /usr/bin/gpg +if [ ! -e "${DESTDIR}/usr/bin/gpg" \ + -a -n "`ls \"${DESTDIR}\"/usr/lib/libgpgme.so* \ + 2>/dev/null`" ]; then + copy_exec /usr/bin/gpg +fi # Config files for file in /etc/mandos/plugin-runner.conf; do @@ -219,29 +131,15 @@ "${DESTDIR}${CONFDIR}/plugin-runner.conf" fi -# Key files +# Key files for file in "$keydir"/*; do if [ -d "$file" ]; then continue fi - case "$file" in - *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) - : ;; - "*") : ;; - *) - cp --archive --sparse=always "$file" \ - "${DESTDIR}${CONFDIR}" - chown ${mandos_user}:${mandos_group} \ - "${DESTDIR}${CONFDIR}/`basename \"$file\"`" - ;; - esac + cp --archive --sparse=always "$file" "${DESTDIR}${CONFDIR}" + chown ${mandos_user}:${mandos_group} \ + "${DESTDIR}${CONFDIR}/`basename \"$file\"`" done -# Use Diffie-Hellman parameters file if available -if [ -e "${DESTDIR}${CONFDIR}"/dhparams.pem ]; then - sed --in-place \ - --expression="1i--options-for=mandos-client:--dh-params=${CONFDIR}/dhparams.pem" \ - "${DESTDIR}/${CONFDIR}/plugin-runner.conf" -fi # /lib/mandos/plugin-runner will drop priviliges, but needs access to # its plugin directory and its config file. However, since almost all @@ -252,8 +150,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 @@ -271,7 +169,7 @@ done for dir in "${DESTDIR}"/lib* "${DESTDIR}"/usr/lib*; do if [ -d "$dir" ]; then - find "$dir" \! -perm -u+rw,g+r -prune -or \! -type l -print0 \ - | xargs --null --no-run-if-empty chmod a+rX -- + find "$dir" \! -perm -u+rw,g+r -prune -or -print0 \ + | xargs --null --no-run-if-empty chmod a+rX fi done === added file 'initramfs-tools-hook-conf' --- initramfs-tools-hook-conf 1970-01-01 00:00:00 +0000 +++ initramfs-tools-hook-conf 2009-05-17 00:50:09 +0000 @@ -0,0 +1,13 @@ +# -*- shell-script -*- + +# if mkinitramfs is started by mkinitramfs-kpkg, mkinitramfs-kpkg has +# already touched the initrd file with umask 022 before we had a +# chance to affect it. We cannot allow a readable initrd file, +# therefore we must fix this now. +if [ -e "${outfile}" ] \ + && [ `stat --format=%s "${outfile}"` -eq 0 ]; then + rm "${outfile}" + (umask 027; touch "${outfile}") +fi + +UMASK=027 === modified file 'initramfs-tools-script' --- initramfs-tools-script 2018-08-19 01:35:11 +0000 +++ initramfs-tools-script 2009-09-16 23:28:39 +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 @@ -91,9 +94,7 @@ # If we are connecting directly, run "configure_networking" (from # /scripts/functions); it needs IPOPTS and DEVICE if [ "${connect+set}" = set ]; then - set +e # Required by library functions configure_networking - set -e if [ -n "$connect" ]; then cat <<-EOF >>/conf/conf.d/mandos/plugin-runner.conf @@ -102,80 +103,49 @@ 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 + +# 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 + 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 -elif [ -x /usr/bin/cryptroot-unlock ]; then - setsid /lib/mandos/mandos-to-cryptroot-unlock & + newopts="${newopts#,}" + echo "$newopts" >&3 +done < /conf/conf.d/cryptroot +exec 3>&- + +# If we need to, replace the old cryptroot file with the new file. +if [ "$replace_cryptroot" = yes ]; then + mv /conf/conf.d/cryptroot /conf/conf.d/cryptroot.mandos-old + mv /conf/conf.d/cryptroot.mandos /conf/conf.d/cryptroot +else + rm /conf/conf.d/cryptroot.mandos fi === removed file 'initramfs-tools-script-stop' --- initramfs-tools-script-stop 2018-08-19 14:58:40 +0000 +++ initramfs-tools-script-stop 1970-01-01 00:00:00 +0000 @@ -1,65 +0,0 @@ -#!/bin/sh -e -# -# Script to wait for plugin-runner to exit before continuing boot -# -# Copyright © 2018 Teddy Hogeborn -# Copyright © 2018 Björn Påhlsson -# -# This file is part of Mandos. -# -# Mandos is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Mandos is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Mandos. If not, see . -# -# Contact the authors at . -# -# This script will run in the initrd environment at boot and remove -# the file keeping the dummy plugin running, forcing plugin-runner to -# exit if it is still running. - -# This script should be installed as -# "/usr/share/initramfs-tools/scripts/local-premount/mandos" which will -# eventually be "/scripts/local-premount/mandos" in the initrd.img -# file. - -PREREQ="" -prereqs() -{ - echo "$PREREQ" -} - -case $1 in -prereqs) - prereqs - exit 0 - ;; -esac - -. /scripts/functions - -pid=$(cat /run/mandos-plugin-runner.pid 2>/dev/null) - -# If the dummy plugin is running, removing this file should force the -# dummy plugin to exit successfully, thereby making plugin-runner shut -# down all its other plugins and then exit itself. -rm -f /run/mandos-keep-running >/dev/null 2>&1 - -# Wait for exit of plugin-runner, if still running -if [ -n "$pid" ]; then - while :; do - case "$(readlink /proc/"$pid"/exe 2>/dev/null)" in - */plugin-runner) sleep 1;; - *) break;; - esac - done - rm -f /run/mandos-plugin-runner.pid >/dev/null 2>&1 -fi === removed file 'initramfs-unpack' --- initramfs-unpack 2018-02-08 10:23:55 +0000 +++ initramfs-unpack 1970-01-01 00:00:00 +0000 @@ -1,69 +0,0 @@ -#!/bin/bash -# -# Initramfs unpacker - unpacks initramfs images into /tmp -# -# Copyright © 2013-2018 Teddy Hogeborn -# Copyright © 2013-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 . - -cpio="cpio --extract --make-directories --unconditional --preserve-modification-time" - -if [ -z "$*" ]; then - set -- /boot/initrd.img-* -fi - -for imgfile in "$@"; do - if ! [ -f "$imgfile" ]; then - echo "Error: Not an existing file: $imgfile" >&2 - continue - fi - imgdir="${TMPDIR:-/tmp}/${imgfile##*/}" - if [ -d "$imgdir" ]; then - rm --recursive -- "$imgdir" - fi - mkdir --parents "$imgdir" - # Does this image contain microcode? - 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)) - catimg="dd if=$imgfile bs=$skip skip=1 status=noxfer" - else - catimg="cat -- $imgfile" - fi - # 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" - fi -done === removed file 'intro.xml' --- intro.xml 2019-04-10 20:33:13 +0000 +++ intro.xml 1970-01-01 00:00:00 +0000 @@ -1,441 +0,0 @@ - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 - Teddy Hogeborn - Björn Påhlsson - - -
- - - intro - 8mandos - - - - intro - - Introduction to the Mandos system - - - - - DESCRIPTION - - This is the the Mandos system, which allows computers to have - encrypted root file systems and at the same time be capable of - remote and/or unattended reboots. - - - 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 - 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. - - - - - 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 - of their neighbors, and their neighbors’ friends. The servers - of people who owe them money. And like - that, they’re gone. And you doubt you’ll - ever see them again. - - - That is why your servers have encrypted root file systems. - However, there’s a downside. There’s no going around it: - rebooting is a pain. Dragging out that rarely-used keyboard and - screen and unraveling cables behind your servers to plug them in - to type in that password is messy, especially if you have many - servers. There are some people who do clever things like using - serial line consoles and daisy-chain it to the next server, and - keep all the servers connected in a ring with serial cables, - which will work, if your servers are physically close enough. - There are also other out-of-band management solutions, but with - all these, you still have to be on hand and - manually type in the password at boot time. Otherwise the - server just sits there, waiting for a password. - - - Wouldn’t it be great if you could have the security of encrypted - root file systems and still have servers that could boot up - automatically if there was a short power outage while you were - asleep? That you could reboot at will, without having someone - run over to the server to type in the password? - - - Well, with Mandos, you (almost) can! The gain in convenience - will only be offset by a small loss in security. The setup is - as follows: - - - The server will still have its encrypted root file system. The - password to this file system will be stored on another computer - (henceforth known as the Mandos server) on the same local - network. The password will not be stored - in plaintext, but encrypted with OpenPGP. To decrypt this - password, a key is needed. This key (the Mandos client key) - will not be stored there, but back on the original server - (henceforth known as the Mandos client) in the initial RAM disk - image. Oh, and all network Mandos client/server communications - will be encrypted, using TLS (SSL). - - - 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. - - - Now, of course the initial RAM disk image is not on the - encrypted root file system, so anyone who had physical access - could take the Mandos client computer offline and read the disk - with their own tools to get the authentication keys used by a - client. But, by then the Mandos server - should notice that the original server has been offline for too - 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. - - - (The encrypted keys on the Mandos server is on its normal file - system, so those are safe, provided the root file system of - that server is encrypted.) - - - - - FREQUENTLY ASKED QUESTIONS - - Couldn’t the security be defeated by… - - - Grabbing the Mandos client key from the - initrd <emphasis>really quickly</emphasis>? - - This, as mentioned above, is the only real weak point. But if - you set the timing values tight enough, this will be really - difficult to do. An attacker would have to physically - disassemble the client computer, extract the key from the - initial RAM disk image, and then connect to a still - online Mandos server to get the encrypted key, and do - all this before the Mandos server timeout - kicks in and the Mandos server refuses to give out the key to - anyone. - - - Now, as the typical procedure seems to be to barge in and turn - off and grab all computers, to maybe look - at them months later, this is not likely. If someone does that, - the whole system will lock itself up - completely, since Mandos servers are no longer running. - - - For sophisticated attackers who could do - the clever thing, and had physical access - to the server for enough time, it would be simpler to get a key - for an encrypted file system by using hardware memory scanners - and reading it right off the memory bus. - - - - - Replay attacks? - - Nope, the network stuff is all done over TLS, which provides - protection against that. - - - - - Man-in-the-middle? - - 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. - - - - - How about sniffing the network traffic and decrypting it - later by physically grabbing the Mandos client and using its - key? - - We only use PFS (Perfect Forward Security) - key exchange algorithms in TLS, which protects against this. - - - - - Physically grabbing the Mandos server computer? - - You could protect that computer the - old-fashioned way, with a must-type-in-the-password-at-boot - method. Or you could have two computers be the Mandos server - for each other. - - - Multiple Mandos servers can coexist on a network without any - trouble. They do not clash, and clients will try all - available servers. This means that if just one reboots then - the other can bring it back up, but if both reboot at the same - time they will stay down until someone types in the password - on one of them. - - - - - Faking checker results? - - If the Mandos client does not have an SSH server, the default - is for the Mandos server to use - fping, the replies to which - could be faked to eliminate the timeout. But this could - easily be changed to any shell command, with any security - measures you like. If the Mandos client - has an SSH server, the default - configuration (as generated by - mandos-keygen with the - option) is for the Mandos server - to use an ssh-keyscan command with strict - keychecking, which can not be faked. Alternatively, IPsec - could be used for the ping packets, making them secure. - - - - - - SECURITY - - So, in summary: The only weakness in the Mandos system is from - people who have: - - - - - The power to come in and physically take your servers, - and - - - - - The cunning and patience to do it carefully, one at a time, - and quickly, faking Mandos - client/server responses for each one before the timeout. - - - - - While there are some who may be threatened by people who have - both these attributes, they do not, - probably, constitute the majority. - - - If you do face such opponents, you must - figure that they could just as well open your servers and read - the file system keys right off the memory by running wires to - the memory bus. - - - What Mandos is designed to protect against is - not such determined, focused, and competent - attacks, but against the early morning knock on your door and - the sudden absence of all the servers in your server room. - Which it does nicely. - - - - - PLUGINS - - In the early designs, the - mandos-client8mandos program (which - retrieves a password from the Mandos server) also prompted for a - password on the terminal, in case a Mandos server could not be - found. Other ways of retrieving a password could easily be - envisoned, but this multiplicity of purpose was seen to be too - complex to be a viable way to continue. Instead, the original - program was separated into mandos-client8mandos and password-prompt8mandos, and a plugin-runner8mandos exist to run them both in parallel, allowing - the first successful plugin to provide the password. This - opened up for any number of additional plugins to run, all - competing to be the first to find a password and provide it to - the plugin runner. - - - Four additional plugins are provided: - - - - - plymouth - 8mandos - - - - This prompts for a password when using - plymouth8. - - - - - - usplash - 8mandos - - - - This prompts for a password when using - usplash8. - - - - - - splashy - 8mandos - - - - This prompts for a password when using - splashy8. - - - - - - askpass-fifo - 8mandos - - - - To provide compatibility with the "askpass" program from - cryptsetup, this plugin listens to the same FIFO as - askpass would do. - - - - - - More plugins can easily be written and added by the system - administrator; see the section called "WRITING PLUGINS" in - plugin-runner - 8mandos to learn the - plugin requirements. - - - - - BUGS - - - - - SEE ALSO - - mandos - 8, - mandos.conf - 5, - mandos-clients.conf - 5, - mandos-ctl - 8, - mandos-monitor - 8, - plugin-runner - 8mandos, - mandos-client - 8mandos, - password-prompt - 8mandos, - plymouth - 8mandos, - usplash - 8mandos, - splashy - 8mandos, - askpass-fifo - 8mandos, - mandos-keygen - 8 - - - - - Mandos - - - - The Mandos home page. - - - - - -
- - - - - === 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 2019-07-14 23:39:53 +0000 +++ mandos 2010-09-05 20:19:02 +0000 @@ -1,57 +1,50 @@ #!/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-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 +# Copyright © 2008,2009 Teddy Hogeborn +# Copyright © 2008,2009 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 . -# -# Contact the authors at . -# - -from __future__ import (division, absolute_import, print_function, - unicode_literals) - -try: - from future_builtins import * -except ImportError: - pass - -try: - import SocketServer as socketserver -except ImportError: - import socketserver +# along with this program. If not, see +# . +# +# Contact the authors at . +# + +from __future__ import division, with_statement, absolute_import + +import SocketServer as socketserver import socket -import argparse +import optparse import datetime import errno -try: - import ConfigParser as configparser -except ImportError: - import configparser +import gnutls.crypto +import gnutls.connection +import gnutls.errors +import gnutls.library.functions +import gnutls.library.constants +import gnutls.library.types +import ConfigParser as configparser import sys import re import os @@ -66,264 +59,68 @@ import struct import fcntl import functools -try: - import cPickle as pickle -except ImportError: - import pickle -import multiprocessing -import types -import binascii -import tempfile -import itertools -import collections -import codecs +import cPickle as pickle +import select import dbus import dbus.service -from gi.repository import GLib +import gobject +import avahi from dbus.mainloop.glib import DBusGMainLoop import ctypes import ctypes.util import xml.dom.minidom import inspect -# 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: 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.major == 2: - str = unicode - -version = "1.8.4" -stored_state_file = "clients.pickle" - -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 - with contextlib.closing(socket.socket()) as s: - ifreq = fcntl.ioctl(s, SIOCGIFINDEX, - struct.pack(b"16s16x", interface)) - interface_index = struct.unpack("I", ifreq[16:20])[0] - return interface_index - - -def copy_function(func): - """Make a copy of a function""" - if sys.version_info.major == 2: - return types.FunctionType(func.func_code, - func.func_globals, - func.func_name, - func.func_defaults, - func.func_closure) - else: - return types.FunctionType(func.__code__, - func.__globals__, - func.__name__, - func.__defaults__, - func.__closure__) - - -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")) - syslogger.setFormatter(logging.Formatter - ('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')) - logger.addHandler(console) - logger.setLevel(level) - - -class PGPError(Exception): - """Exception if encryption/decryption fails""" - pass - - -class PGPEngine(object): - """A simple class for OpenPGP symmetric encryption & decryption""" - - def __init__(self): - self.tempdir = tempfile.mkdtemp(prefix="mandos-") - self.gpg = "gpg" - try: - output = subprocess.check_output(["gpgconf"]) - for line in output.splitlines(): - name, text, path = line.split(b":") - 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 == "gpg" or self.gpg.endswith("/gpg"): - self.gnupgargs.append("--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): - for filename in files: - os.remove(os.path.join(root, filename)) - for dirname in dirs: - os.rmdir(os.path.join(root, dirname)) - # 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. - encoded = b"mandos" + binascii.hexlify(password) - if len(encoded) > 2048: - # GnuPG can't handle long passwords, so encode differently - encoded = (b"mandos" + password.replace(b"\\", b"\\\\") - .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', - passfile.name] - + self.gnupgargs, - 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: - passfile.write(passphrase) - passfile.flush() - 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) - if proc.returncode != 0: - raise PGPError(err) - return decrypted_plaintext - - -# Pretend that we have an Avahi module -class avahi(object): - """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 - 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): - 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 - + SO_BINDTODEVICE = None + + +version = "1.0.14" + +logger = logging.Logger(u'mandos') +syslogger = (logging.handlers.SysLogHandler + (facility = logging.handlers.SysLogHandler.LOG_DAEMON, + address = "/dev/log")) +syslogger.setFormatter(logging.Formatter + (u'Mandos [%(process)d]: %(levelname)s:' + u' %(message)s')) +logger.addHandler(syslogger) + +console = logging.StreamHandler() +console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:' + u' %(levelname)s:' + u' %(message)s')) +logger.addHandler(console) class AvahiError(Exception): def __init__(self, value, *args, **kwargs): self.value = value - return super(AvahiError, self).__init__(value, *args, - **kwargs) - + super(AvahiError, self).__init__(value, *args, **kwargs) + def __unicode__(self): + return unicode(repr(self.value)) class AvahiServiceError(AvahiError): pass - class AvahiGroupError(AvahiError): pass 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'. - See + name: string; Example: u'Mandos' + type: string; Example: u'_mandos._tcp'. + See port: integer; what port to announce TXT: list of strings; TXT record for the service domain: string; Domain to publish on, default to .local if empty. @@ -335,18 +132,10 @@ 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): + def __init__(self, interface = avahi.IF_UNSPEC, name = None, + servicetype = None, port = None, TXT = None, + domain = u"", host = u"", max_renames = 32768, + protocol = avahi.PROTO_UNSPEC, bus = None): self.interface = interface self.name = name self.type = servicetype @@ -360,54 +149,38 @@ self.group = None # our entry group self.server = None self.bus = bus - self.entry_group_state_changed_match = None - - def rename(self, remove=True): + def rename(self): """Derived from the Avahi example code""" if self.rename_count >= self.max_renames: - logger.critical("No suitable Zeroconf service name found" - " after %i retries, exiting.", + logger.critical(u"No suitable Zeroconf service name found" + u" after %i retries, exiting.", self.rename_count) - raise AvahiServiceError("Too many renames") - self.name = str( - self.server.GetAlternativeServiceName(self.name)) + raise AvahiServiceError(u"Too many renames") + self.name = self.server.GetAlternativeServiceName(self.name) + logger.info(u"Changing Zeroconf service name to %r ...", + unicode(self.name)) + syslogger.setFormatter(logging.Formatter + (u'Mandos (%s) [%%(process)d]:' + u' %%(levelname)s: %%(message)s' + % self.name)) + self.remove() + self.add() self.rename_count += 1 - logger.info("Changing Zeroconf service name to %r ...", - self.name) - if remove: - self.remove() - try: - self.add() - except dbus.exceptions.DBusException as error: - if (error.get_dbus_name() - == "org.freedesktop.Avahi.CollisionError"): - logger.info("Local Zeroconf service name collision.") - return self.rename(remove=False) - else: - 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: - self.entry_group_state_changed_match.remove() - 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() if self.group is None: self.group = dbus.Interface( self.bus.get_object(avahi.DBUS_NAME, self.server.EntryGroupNew()), avahi.DBUS_INTERFACE_ENTRY_GROUP) - self.entry_group_state_changed_match = ( - self.group.connect_to_signal( - 'StateChanged', self.entry_group_state_changed)) - logger.debug("Adding Zeroconf service '%s' of type '%s' ...", + self.group.connect_to_signal('StateChanged', + self + .entry_group_state_changed) + logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...", self.name, self.type) self.group.AddService( self.interface, @@ -418,821 +191,329 @@ 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""" - logger.debug("Avahi entry group state change: %i", state) - + logger.debug(u"Avahi entry group state change: %i", state) + if state == avahi.ENTRY_GROUP_ESTABLISHED: - logger.debug("Zeroconf service established.") + logger.debug(u"Zeroconf service established.") elif state == avahi.ENTRY_GROUP_COLLISION: - logger.info("Zeroconf service name collision.") + logger.warning(u"Zeroconf service name collision.") self.rename() elif state == avahi.ENTRY_GROUP_FAILURE: - logger.critical("Avahi: Error in group state changed %s", - str(error)) - raise AvahiGroupError("State changed: {!s}".format(error)) - + logger.critical(u"Avahi: Error in group state changed %s", + unicode(error)) + raise AvahiGroupError(u"State changed: %s" + % unicode(error)) def cleanup(self): """Derived from the Avahi example code""" if self.group is not None: - try: - self.group.Free() - except (dbus.exceptions.UnknownMethodException, - dbus.exceptions.DBusException): - pass + self.group.Free() self.group = None - self.remove() - - def server_state_changed(self, state, error=None): + def server_state_changed(self, state): """Derived from the Avahi example code""" - logger.debug("Avahi server state change: %i", state) - bad_states = { - avahi.SERVER_INVALID: "Zeroconf server invalid", - avahi.SERVER_REGISTERING: None, - avahi.SERVER_COLLISION: "Zeroconf server name collision", - avahi.SERVER_FAILURE: "Zeroconf server failure", - } - if state in bad_states: - if bad_states[state] is not None: - if error is None: - logger.error(bad_states[state]) - else: - logger.error(bad_states[state] + ": %r", error) - self.cleanup() + logger.debug(u"Avahi server state change: %i", state) + if state == avahi.SERVER_COLLISION: + logger.error(u"Zeroconf server name collision") + self.remove() elif state == avahi.SERVER_RUNNING: - try: - self.add() - except dbus.exceptions.DBusException as error: - if (error.get_dbus_name() - == "org.freedesktop.Avahi.CollisionError"): - logger.info("Local Zeroconf service name" - " collision.") - return self.rename(remove=False) - else: - logger.critical("D-Bus Exception", exc_info=error) - self.cleanup() - os._exit(1) - else: - if error is None: - logger.debug("Unknown state: %r", state) - else: - logger.debug("Unknown state: %r: %r", state, error) - + self.add() def activate(self): """Derived from the Avahi example code""" if self.server is None: self.server = dbus.Interface( self.bus.get_object(avahi.DBUS_NAME, - avahi.DBUS_PATH_SERVER, - follow_name_owner_changes=True), + avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER) - self.server.connect_to_signal("StateChanged", - self.server_state_changed) + self.server.connect_to_signal(u"StateChanged", + self.server_state_changed) self.server_state_changed(self.server.GetState()) -class AvahiServiceToSyslog(AvahiService): - def rename(self, *args, **kwargs): - """Add the new name to the syslog messages""" - ret = super(AvahiServiceToSyslog, self).rename(*args, **kwargs) - syslogger.setFormatter(logging.Formatter( - 'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s' - .format(self.name))) - return ret - - -# Pretend that we have a GnuTLS module -class gnutls(object): - """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 - - # 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): - _fields_ = [] - 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_ = [] - 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=()): - # Default usage is by a message string, but if a return - # code is passed, convert it to a string with - # gnutls.strerror() - self.code = code - if message is None and code is not None: - message = gnutls.strerror(code) - return super(gnutls.Error, self).__init__( - message, *args) - - class CertificateSecurityError(Error): - pass - - # Classes - class Credentials(object): - def __init__(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._c_object) - - class ClientSession(object): - def __init__(self, socket, credentials=None): - self._c_object = gnutls.session_t() - gnutls_flags = gnutls.CLIENT - if gnutls.check_version("3.5.6"): - gnutls_flags |= gnutls.NO_TICKETS - if gnutls.has_rawpk: - gnutls_flags |= gnutls.ENABLE_RAWPK - gnutls.init(ctypes.byref(self._c_object), gnutls_flags) - del gnutls_flags - gnutls.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._c_object, credentials.type, - ctypes.cast(credentials._c_object, - ctypes.c_void_p)) - self.credentials = credentials - - def __del__(self): - gnutls.deinit(self._c_object) - - def 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._c_object, - data[-data_len:], - data_len) - - def bye(self): - 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 >= 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): - """A function to retry on some errors, suitable - 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 = [session_t, ctypes.c_char_p, - ctypes.POINTER(ctypes.c_char_p)] - priority_set_direct.restype = _error_code - - init = _library.gnutls_init - 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 = [session_t] - set_default_priority.restype = _error_code - - record_send = _library.gnutls_record_send - 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 = [ - ctypes.POINTER(certificate_credentials_t)] - certificate_allocate_credentials.restype = _error_code - - certificate_free_credentials = ( - _library.gnutls_certificate_free_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 = [session_t, - ctypes.c_int] - handshake_set_private_extensions.restype = None - - credentials_set = _library.gnutls_credentials_set - 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 = [session_t] - certificate_type_get.restype = _error_code - - certificate_get_peers = _library.gnutls_certificate_get_peers - 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 = [session_t] - deinit.restype = None - - handshake = _library.gnutls_handshake - 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 = [session_t, transport_ptr_t] - transport_set_ptr.restype = None - - bye = _library.gnutls_bye - 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("3.6.4"): - certificate_type_get2 = _library.gnutls_certificate_type_get2 - certificate_type_get2.argtypes = [session_t, ctypes.c_int] - certificate_type_get2.restype = _error_code - - # Remove non-public functions - del _error_code, _retry_on_error - - -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(object): """A representation of a client host served by this server. - + Attributes: - 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 + name: string; from the config file, used in log messages and + D-Bus identifiers + fingerprint: string (40 or 32 hexadecimal digits); used to + uniquely identify the client + secret: bytestring; sent verbatim (over TLS) to client + host: string; available for use by the checker command + created: datetime.datetime(); (UTC) object creation + last_enabled: datetime.datetime(); (UTC) + enabled: bool() + last_checked_ok: datetime.datetime(); (UTC) or None + timeout: datetime.timedelta(); How long from last_checked_ok + until this client is disabled + interval: datetime.timedelta(); How often to start a new checker + disable_hook: If set, called by disable() as disable_hook(self) 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 + checker_initiator_tag: a gobject event source tag, or None + disable_initiator_tag: - '' - + checker_callback_tag: - '' - + checker_command: string; External command which is run to check if + client lives. %() expansions are done at runtime with vars(self) as dict, so that for instance %(name)s can be used in the command. - checker_initiator_tag: a GLib event source tag, or None - created: datetime.datetime(); (UTC) object creation - client_structure: Object describing what attributes a client has - and is used for storing the client at exit current_checker_command: string; current running checker_command - 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 - 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 - last_checked_ok: datetime.datetime(); (UTC) or None - last_checker_status: integer between 0 and 255 reflecting exit - status of last checker. -1 reflects crashed - checker, -2 means no checker completed yet. - last_checker_signal: The signal which killed the last checker, if - last_checker_status is -1 - last_enabled: datetime.datetime(); (UTC) or None - name: string; from the config file, used in log messages and - D-Bus identifiers - secret: bytestring; sent verbatim (over TLS) to client - timeout: datetime.timedelta(); How long from last_checked_ok - until this client is disabled - extended_timeout: extra long timeout when secret has been sent - runtime_expansions: Allowed attributes for runtime expansion. - expires: datetime.datetime(); time (UTC) when a client will be - disabled, or None - server_settings: The server_settings dict from main() """ - - runtime_expansions = ("approval_delay", "approval_duration", - "created", "enabled", "expires", "key_id", - "fingerprint", "host", "interval", - "last_approval_request", "last_checked_ok", - "last_enabled", "name", "timeout") - client_defaults = { - "timeout": "PT5M", - "extended_timeout": "PT15M", - "interval": "PT2M", - "checker": "fping -q -- %%(host)s", - "host": "", - "approval_delay": "PT0S", - "approval_duration": "PT1S", - "approved_by_default": "True", - "enabled": "True", - } - + @staticmethod - def config_parser(config): - """Construct a new dict of client settings of this form: - { client_name: {setting_name: value, ...}, ...} - with exceptions for any special settings as defined above. - NOTE: Must be a pure function. Must return the same result - value given the same arguments. - """ - settings = {} - 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(" ", "")) - client["fingerprint"] = (section["fingerprint"].upper() - .replace(" ", "")) - if "secret" in section: - client["secret"] = codecs.decode(section["secret"] - .encode("utf-8"), - "base64") - elif "secfile" in section: - with open(os.path.expanduser(os.path.expandvars - (section["secfile"])), - "rb") as secfile: - client["secret"] = secfile.read() - else: - raise TypeError("No secret or secfile for section {}" - .format(section)) - client["timeout"] = string_to_delta(section["timeout"]) - client["extended_timeout"] = string_to_delta( - section["extended_timeout"]) - client["interval"] = string_to_delta(section["interval"]) - client["approval_delay"] = string_to_delta( - section["approval_delay"]) - client["approval_duration"] = string_to_delta( - section["approval_duration"]) - client["checker_command"] = section["checker"] - 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 _timedelta_to_milliseconds(td): + "Convert a datetime.timedelta() to milliseconds" + return ((td.days * 24 * 60 * 60 * 1000) + + (td.seconds * 1000) + + (td.microseconds // 1000)) + + def timeout_milliseconds(self): + "Return the 'timeout' attribute in milliseconds" + return self._timedelta_to_milliseconds(self.timeout) + + def interval_milliseconds(self): + "Return the 'interval' attribute in milliseconds" + return self._timedelta_to_milliseconds(self.interval) + + def __init__(self, name = None, disable_hook=None, config=None): + """Note: the 'checker' key in 'config' sets the + 'checker_command' attribute and *not* the 'checker' + attribute.""" self.name = name - if server_settings is None: - server_settings = {} - self.server_settings = server_settings - # 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() - if not hasattr(self, "expires"): - self.expires = (datetime.datetime.utcnow() - + self.timeout) + if config is None: + config = {} + logger.debug(u"Creating client %r", self.name) + # Uppercase and remove spaces from fingerprint for later + # comparison purposes with return value from the fingerprint() + # function + self.fingerprint = (config[u"fingerprint"].upper() + .replace(u" ", u"")) + logger.debug(u" Fingerprint: %s", self.fingerprint) + if u"secret" in config: + self.secret = config[u"secret"].decode(u"base64") + elif u"secfile" in config: + with open(os.path.expanduser(os.path.expandvars + (config[u"secfile"])), + "rb") as secfile: + self.secret = secfile.read() else: - self.last_enabled = None - self.expires = None - - logger.debug("Creating client %r", self.name) - logger.debug(" Key ID: %s", self.key_id) - logger.debug(" Fingerprint: %s", self.fingerprint) - self.created = settings.get("created", - datetime.datetime.utcnow()) - - # attributes specific for this server instance + raise TypeError(u"No secret or secfile for client %s" + % self.name) + self.host = config.get(u"host", u"") + self.created = datetime.datetime.utcnow() + self.enabled = False + self.last_enabled = None + self.last_checked_ok = None + self.timeout = string_to_delta(config[u"timeout"]) + self.interval = string_to_delta(config[u"interval"]) + self.disable_hook = disable_hook self.checker = None self.checker_initiator_tag = None self.disable_initiator_tag = None self.checker_callback_tag = None + self.checker_command = config[u"checker"] self.current_checker_command = None - self.approved = None - self.approvals_pending = 0 - self.changedstate = multiprocessing_manager.Condition( - multiprocessing_manager.Lock()) - self.client_structure = [attr - 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() - + self.last_connect = None + def enable(self): """Start this client's checker and timeout hooks""" - if getattr(self, "enabled", False): + if getattr(self, u"enabled", False): # Already enabled return - self.expires = datetime.datetime.utcnow() + self.timeout - self.enabled = True self.last_enabled = datetime.datetime.utcnow() - self.init_checker() - self.send_changedstate() - + # Schedule a new checker to be started an 'interval' from now, + # and every interval from then on. + self.checker_initiator_tag = (gobject.timeout_add + (self.interval_milliseconds(), + self.start_checker)) + # Schedule a disable() when 'timeout' has passed + self.disable_initiator_tag = (gobject.timeout_add + (self.timeout_milliseconds(), + self.disable)) + self.enabled = True + # Also start a new checker *right now*. + self.start_checker() + def disable(self, quiet=True): """Disable this client.""" if not getattr(self, "enabled", False): return False if not quiet: - logger.info("Disabling client %s", self.name) - if getattr(self, "disable_initiator_tag", None) is not None: - GLib.source_remove(self.disable_initiator_tag) + logger.info(u"Disabling client %s", self.name) + if getattr(self, u"disable_initiator_tag", False): + gobject.source_remove(self.disable_initiator_tag) self.disable_initiator_tag = None - self.expires = None - if getattr(self, "checker_initiator_tag", None) is not None: - GLib.source_remove(self.checker_initiator_tag) + if getattr(self, u"checker_initiator_tag", False): + gobject.source_remove(self.checker_initiator_tag) self.checker_initiator_tag = None self.stop_checker() + if self.disable_hook: + self.disable_hook(self) self.enabled = False - if not quiet: - self.send_changedstate() - # Do not run this again if called by a GLib.timeout_add + # Do not run this again if called by a gobject.timeout_add return False - + def __del__(self): + self.disable_hook = None 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( - int(self.interval.total_seconds() * 1000), - self.start_checker) - # Schedule a disable() when 'timeout' has passed - if self.disable_initiator_tag is not None: - GLib.source_remove(self.disable_initiator_tag) - self.disable_initiator_tag = GLib.timeout_add( - 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): + + def checker_callback(self, pid, condition, 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 returncode >= 0: - self.last_checker_status = returncode - self.last_checker_signal = None - if self.last_checker_status == 0: - logger.info("Checker for %(name)s succeeded", + if os.WIFEXITED(condition): + exitstatus = os.WEXITSTATUS(condition) + if exitstatus == 0: + logger.info(u"Checker for %(name)s succeeded", vars(self)) self.checked_ok() else: - logger.info("Checker for %(name)s failed", vars(self)) + logger.info(u"Checker for %(name)s failed", + vars(self)) else: - self.last_checker_status = -1 - self.last_checker_signal = -returncode - logger.warning("Checker for %(name)s crashed?", + logger.warning(u"Checker for %(name)s crashed?", vars(self)) - return False - + def checked_ok(self): - """Assert that the client has been seen, alive and well.""" + """Bump up the timeout for this client. + + This should only be called when 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: - timeout = self.timeout - if self.disable_initiator_tag is not None: - GLib.source_remove(self.disable_initiator_tag) - self.disable_initiator_tag = None - if getattr(self, "enabled", False): - 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() - + gobject.source_remove(self.disable_initiator_tag) + self.disable_initiator_tag = (gobject.timeout_add + (self.timeout_milliseconds(), + self.disable)) + 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 - # did that, and if a checker (for some reason) started running - # slowly and taking more than 'interval' time, then the client - # would inevitably timeout, since no checker would get a - # chance to run to completion. If we instead leave running + # did that, then if a checker (for some reason) started + # running slowly and taking more than 'interval' time, the + # client would inevitably timeout, since no checker would get + # a chance to run to completion. If we instead leave running # 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(): - logger.warning("Checker was not alive; joining") - self.checker.join() - self.checker = None + + # If a checker exists, make sure it is not a zombie + try: + pid, status = os.waitpid(self.checker.pid, os.WNOHANG) + except (AttributeError, OSError), error: + if (isinstance(error, OSError) + and error.errno != errno.ECHILD): + raise error + else: + if pid: + logger.warning(u"Checker was a zombie") + gobject.source_remove(self.checker_callback_tag) + self.checker_callback(pid, status, + self.current_checker_command) # Start a new checker if needed if self.checker is None: - # Escape attributes for the shell - escaped_attrs = { - attr: re.escape(str(getattr(self, attr))) - for attr in self.runtime_expansions} try: - command = self.checker_command % escaped_attrs - except TypeError as error: - logger.error('Could not format string "%s"', - self.checker_command, - exc_info=error) - return True # Try again later + # In case checker_command has exactly one % operator + command = self.checker_command % self.host + except TypeError: + # Escape attributes for the shell + escaped_attrs = dict((key, + re.escape(unicode(str(val), + errors= + u'replace'))) + for key, val in + vars(self).iteritems()) + try: + command = self.checker_command % escaped_attrs + except TypeError, error: + logger.error(u'Could not format string "%s":' + u' %s', self.checker_command, error) + return True # Try again later self.current_checker_command = command - 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 - # always replaced by /dev/null.) - # 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": "/"} - if (not self.server_settings["debug"] - and self.server_settings["foreground"]): - popen_args.update({"stdout": wnull, - "stderr": wnull}) - pipe = multiprocessing.Pipe(duplex=False) - self.checker = multiprocessing.Process( - target=call_pipe, - args=(pipe[1], subprocess.call, command), - kwargs=popen_args) - self.checker.start() - self.checker_callback_tag = GLib.io_add_watch( - pipe[0].fileno(), GLib.IO_IN, - self.checker_callback, pipe[0], command) - # Re-run this periodically if run by GLib.timeout_add + try: + logger.info(u"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 + # always replaced by /dev/null.) + self.checker = subprocess.Popen(command, + close_fds=True, + shell=True, cwd=u"/") + self.checker_callback_tag = (gobject.child_watch_add + (self.checker.pid, + self.checker_callback, + data=command)) + # The checker may have completed before the gobject + # watch was added. Check for this. + pid, status = os.waitpid(self.checker.pid, os.WNOHANG) + if pid: + gobject.source_remove(self.checker_callback_tag) + self.checker_callback(pid, status, command) + except OSError, error: + logger.error(u"Failed to start subprocess: %s", + error) + # Re-run this periodically if run by gobject.timeout_add return True - + def stop_checker(self): """Force the checker process, if any, to stop.""" if self.checker_callback_tag: - GLib.source_remove(self.checker_callback_tag) + gobject.source_remove(self.checker_callback_tag) self.checker_callback_tag = None - if getattr(self, "checker", None) is None: + if getattr(self, u"checker", None) is None: return - logger.debug("Stopping checker for %(name)s", vars(self)) - self.checker.terminate() + logger.debug(u"Stopping checker for %(name)s", vars(self)) + try: + os.kill(self.checker.pid, signal.SIGTERM) + #time.sleep(0.5) + #if self.checker.poll() is None: + # os.kill(self.checker.pid, signal.SIGKILL) + except OSError, error: + if error.errno != errno.ESRCH: # No such process + raise self.checker = None -def dbus_service_property(dbus_interface, - signature="v", - access="readwrite", - byte_arrays=False): +def dbus_service_property(dbus_interface, signature=u"v", + access=u"readwrite", 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. """ # Encoding deeply encoded byte arrays is not supported yet by the # "Set" method, so we fail early here: - if byte_arrays and signature != "ay": - raise ValueError("Byte arrays not supported for non-'ay'" - " signature {!r}".format(signature)) - + if byte_arrays and signature != u"ay": + raise ValueError(u"Byte arrays not supported for non-'ay'" + u" signature %r" % signature) def decorator(func): func._dbus_is_property = True func._dbus_interface = dbus_interface func._dbus_signature = signature func._dbus_access = access func._dbus_name = func.__name__ - if func._dbus_name.endswith("_dbus_property"): + if func._dbus_name.endswith(u"_dbus_property"): func._dbus_name = func._dbus_name[:-14] - 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"}) - @dbus_service_property("org.example.Interface", signature="b", - 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 - + func._dbus_get_args_options = {u'byte_arrays': byte_arrays } + return func return decorator class DBusPropertyException(dbus.exceptions.DBusException): """A base class for D-Bus property-related exceptions """ - pass + def __unicode__(self): + return unicode(str(self)) class DBusPropertyAccessException(DBusPropertyException): @@ -1247,1375 +528,716 @@ pass -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 - """ - return ((getattr(athing.__get__(self), "_dbus_name", name), - athing.__get__(self)) - 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') - 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"): - for tag in if_tag.getElementsByTagName(typ): - annots = dict() - for name, prop in (self. - _get_all_dbus_things(typ)): - if (name == tag.getAttribute("name") - and prop._dbus_interface - == if_tag.getAttribute("name")): - annots.update(getattr( - prop, "_dbus_annotations", {})) - for name, value in annots.items(): - ann_tag = document.createElement( - "annotation") - ann_tag.setAttribute("name", name) - ann_tag.setAttribute("value", value) - tag.appendChild(ann_tag) - # Add interface annotation tags - for annotation, value in dict( - itertools.chain.from_iterable( - annotations().items() - for name, annotations - in self._get_all_dbus_things("interface") - if name == if_tag.getAttribute("name") - )).items(): - ann_tag = document.createElement("annotation") - ann_tag.setAttribute("name", annotation) - ann_tag.setAttribute("value", value) - if_tag.appendChild(ann_tag) - # Fix argument name for the Introspect method itself - if (if_tag.getAttribute("name") - == dbus.INTROSPECTABLE_IFACE): - for cn in if_tag.getElementsByTagName("method"): - if cn.getAttribute("name") == "Introspect": - for arg in cn.getElementsByTagName("arg"): - if (arg.getAttribute("direction") - == "out"): - arg.setAttribute("name", - "xml_data") - xmlstring = document.toxml("utf-8") - document.unlink() - except (AttributeError, xml.dom.DOMException, - xml.parsers.expat.ExpatError) as error: - logger.error("Failed to override Introspection method", - exc_info=error) - return xmlstring - - -class DBusObjectWithProperties(DBusObjectWithAnnotations): +class DBusObjectWithProperties(dbus.service.Object): """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. """ - + + @staticmethod + def _is_dbus_property(obj): + return getattr(obj, u"_dbus_is_property", False) + + def _get_all_dbus_properties(self): + """Returns a generator of (name, attribute) pairs + """ + return ((prop._dbus_name, prop) + for name, prop in + inspect.getmembers(self, self._is_dbus_property)) + 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. """ - for cls in self.__class__.__mro__: - for name, value in inspect.getmembers( - cls, self._is_dbus_thing("property")): - if (value._dbus_name == property_name - and value._dbus_interface == interface_name): - return value.__get__(self) - + for name in (property_name, + property_name + u"_dbus_property"): + prop = getattr(self, name, None) + if (prop is None + or not self._is_dbus_property(prop) + or prop._dbus_name != property_name + or (interface_name and prop._dbus_interface + and interface_name != prop._dbus_interface)): + continue + return prop # 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""" - return (name for name in set(getattr(getattr(x, attr), - "_dbus_interface", None) - 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") + raise DBusPropertyNotFound(self.dbus_object_path + u":" + + interface_name + u"." + + property_name) + + @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss", + out_signature=u"v") def Get(self, interface_name, property_name): """Standard D-Bus property Get() method, see D-Bus standard. """ prop = self._get_dbus_property(interface_name, property_name) - if prop._dbus_access == "write": + if prop._dbus_access == u"write": raise DBusPropertyAccessException(property_name) value = prop() - if not hasattr(value, "variant_level"): + if not hasattr(value, u"variant_level"): return value return type(value)(value, variant_level=value.variant_level+1) - - @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv") + + @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv") def Set(self, interface_name, property_name, value): """Standard D-Bus property Set() method, see D-Bus standard. """ prop = self._get_dbus_property(interface_name, property_name) - if prop._dbus_access == "read": + if prop._dbus_access == u"read": raise DBusPropertyAccessException(property_name) - if prop._dbus_get_args_options["byte_arrays"]: + if prop._dbus_get_args_options[u"byte_arrays"]: # The byte_arrays option is not supported yet on # signatures other than "ay". - if prop._dbus_signature != "ay": - raise ValueError("Byte arrays not supported for non-" - "'ay' signature {!r}" - .format(prop._dbus_signature)) - value = dbus.ByteArray(b''.join(chr(byte) - for byte in value)) + if prop._dbus_signature != u"ay": + raise ValueError + value = dbus.ByteArray(''.join(unichr(byte) + for byte in value)) prop(value) - - @dbus.service.method(dbus.PROPERTIES_IFACE, - in_signature="s", - out_signature="a{sv}") + + @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s", + out_signature=u"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 = {} - for name, prop in self._get_all_dbus_things("property"): + all = {} + for name, prop in self._get_all_dbus_properties(): if (interface_name and interface_name != prop._dbus_interface): # Interface non-empty but did not match continue # Ignore write-only properties - if prop._dbus_access == "write": + if prop._dbus_access == u"write": continue value = prop() - if not hasattr(value, "variant_level"): - properties[name] = value + if not hasattr(value, u"variant_level"): + all[name] = value continue - properties[name] = type(value)( - 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): - """Standard D-Bus PropertiesChanged() signal, see D-Bus - standard. - """ - pass - + all[name] = type(value)(value, variant_level= + value.variant_level+1) + return dbus.Dictionary(all, signature=u"sv") + @dbus.service.method(dbus.INTROSPECTABLE_IFACE, - out_signature="s", + out_signature=u"s", 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. + """Standard D-Bus method, overloaded to insert property tags. """ - xmlstring = DBusObjectWithAnnotations.Introspect(self, - object_path, - connection) + xmlstring = dbus.service.Object.Introspect(self, object_path, + 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) + e = document.createElement(u"property") + e.setAttribute(u"name", name) + e.setAttribute(u"type", prop._dbus_signature) + e.setAttribute(u"access", prop._dbus_access) return e - - for if_tag in document.getElementsByTagName("interface"): - # Add property tags + for if_tag in document.getElementsByTagName(u"interface"): for tag in (make_tag(document, name, prop) for name, prop - in self._get_all_dbus_things("property") + in self._get_all_dbus_properties() if prop._dbus_interface - == if_tag.getAttribute("name")): + == if_tag.getAttribute(u"name")): if_tag.appendChild(tag) - # Add annotation tags for properties - for tag in if_tag.getElementsByTagName("property"): - annots = dict() - for name, prop in self._get_all_dbus_things( - "property"): - if (name == tag.getAttribute("name") - and prop._dbus_interface - == if_tag.getAttribute("name")): - annots.update(getattr( - prop, "_dbus_annotations", {})) - for name, value in annots.items(): - ann_tag = document.createElement( - "annotation") - ann_tag.setAttribute("name", name) - ann_tag.setAttribute("value", value) - tag.appendChild(ann_tag) # Add the names to the return values for the # "org.freedesktop.DBus.Properties" methods - if (if_tag.getAttribute("name") - == "org.freedesktop.DBus.Properties"): - for cn in if_tag.getElementsByTagName("method"): - if cn.getAttribute("name") == "Get": - for arg in cn.getElementsByTagName("arg"): - if (arg.getAttribute("direction") - == "out"): - arg.setAttribute("name", "value") - elif cn.getAttribute("name") == "GetAll": - for arg in cn.getElementsByTagName("arg"): - if (arg.getAttribute("direction") - == "out"): - arg.setAttribute("name", "props") - xmlstring = document.toxml("utf-8") - document.unlink() - except (AttributeError, xml.dom.DOMException, - xml.parsers.expat.ExpatError) as 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}}}") - def GetManagedObjects(self): - """This function must be overridden""" - raise NotImplementedError() - - @dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, - signature="oa{sa{sv}}") - def InterfacesAdded(self, object_path, interfaces_and_properties): - pass - - @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') - def Introspect(self, object_path, connection): - """Overloading of standard D-Bus method. - - Override return argument name of GetManagedObjects to be - "objpath_interfaces_and_properties" - """ - xmlstring = DBusObjectWithAnnotations.Introspect(self, - object_path, - 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): - for cn in if_tag.getElementsByTagName("method"): - if (cn.getAttribute("name") - == "GetManagedObjects"): - for arg in cn.getElementsByTagName("arg"): - if (arg.getAttribute("direction") - == "out"): - arg.setAttribute( - "name", - "objpath_interfaces" - "_and_properties") - xmlstring = document.toxml("utf-8") - document.unlink() - except (AttributeError, xml.dom.DOMException, - xml.parsers.expat.ExpatError) as 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(dt.isoformat(), variant_level=variant_level) - - -def alternate_dbus_interfaces(alt_interface_names, deprecate=True): - """A class decorator; applied to a subclass of - 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()): - attr = {} - interface_names = set() - # Go though all attributes of the class - for attrname, attribute in inspect.getmembers(cls): - # Ignore non-D-Bus attributes, and D-Bus attributes - # with the wrong interface name - if (not hasattr(attribute, "_dbus_interface") - or not attribute._dbus_interface.startswith( - orig_interface_name)): - continue - # Create an alternate D-Bus interface name based on - # the current name - alt_interface = attribute._dbus_interface.replace( - orig_interface_name, alt_interface_name) - interface_names.add(alt_interface) - # Is this a D-Bus signal? - if getattr(attribute, "_dbus_is_signal", False): - # Extract the original non-method undecorated - # function by black magic - if sys.version_info.major == 2: - nonmethod_func = (dict( - zip(attribute.func_code.co_freevars, - attribute.__closure__)) - ["func"].cell_contents) - else: - nonmethod_func = (dict( - zip(attribute.__code__.co_freevars, - attribute.__closure__)) - ["func"].cell_contents) - # Create a new, but exactly alike, function - # object, and decorate it to be a new D-Bus signal - # with the alternate D-Bus interface name - new_function = copy_function(nonmethod_func) - new_function = (dbus.service.signal( - alt_interface, - attribute._dbus_signature)(new_function)) - # Copy annotations, if any - try: - new_function._dbus_annotations = dict( - 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 - # the function is called - def fixscope(func1, func2): - """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 - 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 - attr[attrname] = fixscope(attribute, new_function) - # Is this a D-Bus method? - elif getattr(attribute, "_dbus_is_method", False): - # Create a new, but exactly alike, function - # object. Decorate it to be a new D-Bus method - # with the alternate D-Bus interface name. Add it - # to the class. - attr[attrname] = ( - dbus.service.method( - alt_interface, - attribute._dbus_in_signature, - attribute._dbus_out_signature) - (copy_function(attribute))) - # Copy annotations, if any - try: - attr[attrname]._dbus_annotations = dict( - attribute._dbus_annotations) - except AttributeError: - pass - # Is this a D-Bus property? - elif getattr(attribute, "_dbus_is_property", False): - # Create a new, but exactly alike, function - # object, and decorate it to be a new D-Bus - # property with the alternate D-Bus interface - # name. Add it to the class. - attr[attrname] = (dbus_service_property( - alt_interface, attribute._dbus_signature, - attribute._dbus_access, - attribute._dbus_get_args_options - ["byte_arrays"]) - (copy_function(attribute))) - # Copy annotations, if any - try: - attr[attrname]._dbus_annotations = dict( - attribute._dbus_annotations) - except AttributeError: - pass - # Is this a D-Bus interface? - elif getattr(attribute, "_dbus_is_interface", False): - # Create a new, but exactly alike, function - # object. Decorate it to be a new D-Bus interface - # with the alternate D-Bus interface name. Add it - # to the class. - attr[attrname] = ( - dbus_interface_annotations(alt_interface) - (copy_function(attribute))) - if deprecate: - # Deprecate all alternate interfaces - iname = "_AlternateDBusNames_interface_annotation{}" - for interface_name in interface_names: - - @dbus_interface_annotations(interface_name) - def func(self): - return {"org.freedesktop.DBus.Deprecated": - "true"} - # Find an unused name - for aname in (iname.format(i) - for i in itertools.count()): - if aname not in attr: - attr[aname] = func - break - if interface_names: - # Replace the class with a new subclass of it with - # methods, signals, etc. as created above. - if sys.version_info.major == 2: - cls = type(b"{}Alternate".format(cls.__name__), - (cls, ), attr) - else: - cls = type("{}Alternate".format(cls.__name__), - (cls, ), attr) - return cls - - return wrapper - - -@alternate_dbus_interfaces({"se.recompile.Mandos": - "se.bsnet.fukt.Mandos"}) + if (if_tag.getAttribute(u"name") + == u"org.freedesktop.DBus.Properties"): + for cn in if_tag.getElementsByTagName(u"method"): + if cn.getAttribute(u"name") == u"Get": + for arg in cn.getElementsByTagName(u"arg"): + if (arg.getAttribute(u"direction") + == u"out"): + arg.setAttribute(u"name", u"value") + elif cn.getAttribute(u"name") == u"GetAll": + for arg in cn.getElementsByTagName(u"arg"): + if (arg.getAttribute(u"direction") + == u"out"): + arg.setAttribute(u"name", u"props") + xmlstring = document.toxml(u"utf-8") + document.unlink() + except (AttributeError, xml.dom.DOMException, + xml.parsers.expat.ExpatError), error: + logger.error(u"Failed to override Introspection method", + error) + return xmlstring + + 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 # the D-Bus - client_object_name = str(self.name).translate( - {ord("."): ord("_"), - ord("-"): ord("_")}) - self.dbus_object_path = dbus.ObjectPath( - "/clients/" + client_object_name) + self.dbus_object_path = (dbus.ObjectPath + (u"/clients/" + + self.name.replace(u".", u"_"))) DBusObjectWithProperties.__init__(self, self.bus, self.dbus_object_path) - - def notifychangeproperty(transform_func, dbus_name, - type_func=lambda x: x, - variant_level=1, - invalidate_only=False, - _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 - type_func: Function that transform the value before sending it - to the D-Bus. Default: no transform - 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 - type_func(getattr(self, attrname, None)) - != type_func(value)): - if invalidate_only: - self.PropertiesChanged( - _interface, dbus.Dictionary(), - dbus.Array((dbus_name, ))) - else: - dbus_value = transform_func( - type_func(value), - variant_level=variant_level) - self.PropertyChanged(dbus.String(dbus_name), - dbus_value) - self.PropertiesChanged( - _interface, - 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) - 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) - last_checked_ok = notifychangeproperty(datetime_to_dbus, - "LastCheckedOK") - last_checker_status = notifychangeproperty(dbus.Int16, - "LastCheckerStatus") - last_approval_request = notifychangeproperty( - datetime_to_dbus, "LastApprovalRequest") - approved_by_default = notifychangeproperty(dbus.Boolean, - "ApprovedByDefault") - approval_delay = notifychangeproperty( - dbus.UInt64, "ApprovalDelay", - type_func=lambda td: td.total_seconds() * 1000) - approval_duration = notifychangeproperty( - dbus.UInt64, "ApprovalDuration", - 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) - extended_timeout = notifychangeproperty( - dbus.UInt64, "ExtendedTimeout", - type_func=lambda td: td.total_seconds() * 1000) - interval = notifychangeproperty( - dbus.UInt64, "Interval", - type_func=lambda td: td.total_seconds() * 1000) - checker_command = notifychangeproperty(dbus.String, "Checker") - secret = notifychangeproperty(dbus.ByteArray, "Secret", - invalidate_only=True) - - del notifychangeproperty - + + @staticmethod + def _datetime_to_dbus(dt, variant_level=0): + """Convert a UTC datetime.datetime() to a D-Bus type.""" + return dbus.String(dt.isoformat(), + variant_level=variant_level) + + def enable(self): + oldstate = getattr(self, u"enabled", False) + r = Client.enable(self) + if oldstate != self.enabled: + # Emit D-Bus signals + self.PropertyChanged(dbus.String(u"enabled"), + dbus.Boolean(True, variant_level=1)) + self.PropertyChanged( + dbus.String(u"last_enabled"), + self._datetime_to_dbus(self.last_enabled, + variant_level=1)) + return r + + def disable(self, quiet = False): + oldstate = getattr(self, u"enabled", False) + r = Client.disable(self, quiet=quiet) + if not quiet and oldstate != self.enabled: + # Emit D-Bus signal + self.PropertyChanged(dbus.String(u"enabled"), + dbus.Boolean(False, variant_level=1)) + return r + def __del__(self, *args, **kwargs): try: self.remove_from_connection() except LookupError: pass - if hasattr(DBusObjectWithProperties, "__del__"): + if hasattr(DBusObjectWithProperties, u"__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, - connection, command, *args, - **kwargs) - exitstatus = self.last_checker_status - if exitstatus >= 0: + + def checker_callback(self, pid, condition, command, + *args, **kwargs): + self.checker_callback_tag = None + self.checker = None + # Emit D-Bus signal + self.PropertyChanged(dbus.String(u"checker_running"), + dbus.Boolean(False, variant_level=1)) + if os.WIFEXITED(condition): + exitstatus = os.WEXITSTATUS(condition) # Emit D-Bus signal self.CheckerCompleted(dbus.Int16(exitstatus), - # This is specific to GNU libC - dbus.Int64(exitstatus << 8), + dbus.Int64(condition), dbus.String(command)) else: # Emit D-Bus signal self.CheckerCompleted(dbus.Int16(-1), - dbus.Int64( - # This is specific to GNU libC - (exitstatus << 8) - | self.last_checker_signal), + dbus.Int64(condition), dbus.String(command)) - return ret - + + return Client.checker_callback(self, pid, condition, command, + *args, **kwargs) + + def checked_ok(self, *args, **kwargs): + r = Client.checked_ok(self, *args, **kwargs) + # Emit D-Bus signal + self.PropertyChanged( + dbus.String(u"last_checked_ok"), + (self._datetime_to_dbus(self.last_checked_ok, + variant_level=1))) + return r + def start_checker(self, *args, **kwargs): - old_checker_pid = getattr(self.checker, "pid", None) + old_checker = self.checker + if self.checker is not None: + old_checker_pid = self.checker.pid + else: + old_checker_pid = None r = Client.start_checker(self, *args, **kwargs) # Only if new checker process was started if (self.checker is not None and old_checker_pid != self.checker.pid): # 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 - + self.PropertyChanged( + dbus.String(u"checker_running"), + dbus.Boolean(True, variant_level=1)) + return r + + def stop_checker(self, *args, **kwargs): + old_checker = getattr(self, u"checker", None) + r = Client.stop_checker(self, *args, **kwargs) + if (old_checker is not None + and getattr(self, u"checker", None) is None): + self.PropertyChanged(dbus.String(u"checker_running"), + dbus.Boolean(False, variant_level=1)) + return r + + ## D-Bus methods, signals & properties + _interface = u"se.bsnet.fukt.Mandos.Client" + + ## Signals + # CheckerCompleted - signal - @dbus.service.signal(_interface, signature="nxs") + @dbus.service.signal(_interface, signature=u"nxs") def CheckerCompleted(self, exitcode, waitstatus, command): "D-Bus signal" pass - + # CheckerStarted - signal - @dbus.service.signal(_interface, signature="s") + @dbus.service.signal(_interface, signature=u"s") def CheckerStarted(self, command): "D-Bus signal" pass - + # PropertyChanged - signal - @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"}) - @dbus.service.signal(_interface, signature="sv") + @dbus.service.signal(_interface, signature=u"sv") def PropertyChanged(self, property, value): "D-Bus signal" pass - + # GotSecret - signal @dbus.service.signal(_interface) def GotSecret(self): - """D-Bus signal - Is sent after a successful transfer of secret from the Mandos - server to mandos-client - """ + "D-Bus signal" pass - + # Rejected - signal - @dbus.service.signal(_interface, signature="s") - def Rejected(self, reason): + @dbus.service.signal(_interface) + def Rejected(self): "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 - - # Approve - method - @dbus.service.method(_interface, in_signature="b") - def Approve(self, value): - self.approve(value) - + + ## Methods + # CheckedOK - method @dbus.service.method(_interface) def CheckedOK(self): - self.checked_ok() - + return 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 - - # 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", - access="readwrite") - def ApprovedByDefault_dbus_property(self, value=None): - 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", - access="readwrite") - def ApprovalDelay_dbus_property(self, value=None): - if value is None: # get - 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", - access="readwrite") - def ApprovalDuration_dbus_property(self, value=None): - if value is None: # get - 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): + + ## Properties + + # name - property + @dbus_service_property(_interface, signature=u"s", access=u"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): + + # fingerprint - property + @dbus_service_property(_interface, signature=u"s", access=u"read") + def fingerprint_dbus_property(self): return dbus.String(self.fingerprint) - - # Host - property - @dbus_service_property(_interface, - signature="s", - access="readwrite") - def Host_dbus_property(self, value=None): + + # host - property + @dbus_service_property(_interface, signature=u"s", + access=u"readwrite") + def host_dbus_property(self, value=None): 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", - access="readwrite") - def Enabled_dbus_property(self, value=None): + self.host = value + # Emit D-Bus signal + self.PropertyChanged(dbus.String(u"host"), + dbus.String(value, variant_level=1)) + + # created - property + @dbus_service_property(_interface, signature=u"s", access=u"read") + def created_dbus_property(self): + return dbus.String(self._datetime_to_dbus(self.created)) + + # last_enabled - property + @dbus_service_property(_interface, signature=u"s", access=u"read") + def last_enabled_dbus_property(self): + if self.last_enabled is None: + return dbus.String(u"") + return dbus.String(self._datetime_to_dbus(self.last_enabled)) + + # enabled - property + @dbus_service_property(_interface, signature=u"b", + access=u"readwrite") + def enabled_dbus_property(self, value=None): if value is None: # get return dbus.Boolean(self.enabled) if value: self.enable() else: self.disable() - - # LastCheckedOK - property - @dbus_service_property(_interface, - signature="s", - access="readwrite") - def LastCheckedOK_dbus_property(self, value=None): + + # last_checked_ok - property + @dbus_service_property(_interface, signature=u"s", + access=u"readwrite") + def last_checked_ok_dbus_property(self, value=None): if value is not None: 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", - access="readwrite") - def Timeout_dbus_property(self, value=None): + if self.last_checked_ok is None: + return dbus.String(u"") + return dbus.String(self._datetime_to_dbus(self + .last_checked_ok)) + + # timeout - property + @dbus_service_property(_interface, signature=u"t", + access=u"readwrite") + def timeout_dbus_property(self, value=None): if value is None: # get - return dbus.UInt64(self.timeout.total_seconds() * 1000) - old_timeout = self.timeout + return dbus.UInt64(self.timeout_milliseconds()) self.timeout = datetime.timedelta(0, 0, 0, value) - # Reschedule disabling - if self.enabled: - now = datetime.datetime.utcnow() - self.expires += self.timeout - old_timeout - if self.expires <= now: - # The timeout has passed - self.disable() - else: - if (getattr(self, "disable_initiator_tag", None) - is None): - return - GLib.source_remove(self.disable_initiator_tag) - self.disable_initiator_tag = GLib.timeout_add( - int((self.expires - now).total_seconds() * 1000), - self.disable) - - # ExtendedTimeout - property - @dbus_service_property(_interface, - signature="t", - access="readwrite") - def ExtendedTimeout_dbus_property(self, value=None): - if value is None: # get - 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", - access="readwrite") - def Interval_dbus_property(self, value=None): - if value is None: # get - return dbus.UInt64(self.interval.total_seconds() * 1000) + # Emit D-Bus signal + self.PropertyChanged(dbus.String(u"timeout"), + dbus.UInt64(value, variant_level=1)) + if getattr(self, u"disable_initiator_tag", None) is None: + return + # Reschedule timeout + gobject.source_remove(self.disable_initiator_tag) + self.disable_initiator_tag = None + time_to_die = (self. + _timedelta_to_milliseconds((self + .last_checked_ok + + self.timeout) + - datetime.datetime + .utcnow())) + if time_to_die <= 0: + # The timeout has passed + self.disable() + else: + self.disable_initiator_tag = (gobject.timeout_add + (time_to_die, self.disable)) + + # interval - property + @dbus_service_property(_interface, signature=u"t", + access=u"readwrite") + def interval_dbus_property(self, value=None): + if value is None: # get + return dbus.UInt64(self.interval_milliseconds()) self.interval = datetime.timedelta(0, 0, 0, value) - if getattr(self, "checker_initiator_tag", None) is None: + # Emit D-Bus signal + self.PropertyChanged(dbus.String(u"interval"), + dbus.UInt64(value, variant_level=1)) + if getattr(self, u"checker_initiator_tag", None) is None: return - if self.enabled: - # Reschedule checker run - 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 + # Reschedule checker run + gobject.source_remove(self.checker_initiator_tag) + self.checker_initiator_tag = (gobject.timeout_add + (value, self.start_checker)) + self.start_checker() # Start one now, too - # Checker - property - @dbus_service_property(_interface, - signature="s", - access="readwrite") - def Checker_dbus_property(self, value=None): + # checker - property + @dbus_service_property(_interface, signature=u"s", + access=u"readwrite") + def checker_dbus_property(self, value=None): if value is None: # get return dbus.String(self.checker_command) - self.checker_command = str(value) - - # CheckerRunning - property - @dbus_service_property(_interface, - signature="b", - access="readwrite") - def CheckerRunning_dbus_property(self, value=None): + self.checker_command = value + # Emit D-Bus signal + self.PropertyChanged(dbus.String(u"checker"), + dbus.String(self.checker_command, + variant_level=1)) + + # checker_running - property + @dbus_service_property(_interface, signature=u"b", + access=u"readwrite") + def checker_running_dbus_property(self, value=None): if value is None: # get return dbus.Boolean(self.checker is not None) if value: 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 - - # Secret = property - @dbus_annotations( - {"org.freedesktop.DBus.Property.EmitsChangedSignal": - "invalidates"}) - @dbus_service_property(_interface, - signature="ay", - access="write", - byte_arrays=True) - def Secret_dbus_property(self, value): - self.secret = bytes(value) - + + # object_path - property + @dbus_service_property(_interface, signature=u"o", access=u"read") + def object_path_dbus_property(self): + return self.dbus_object_path # is already a dbus.ObjectPath + + # secret = property + @dbus_service_property(_interface, signature=u"ay", + access=u"write", byte_arrays=True) + def secret_dbus_property(self, value): + self.secret = str(value) + del _interface -class ProxyClient(object): - def __init__(self, child_pipe, key_id, fpr, address): - self._pipe = child_pipe - self._pipe.send(('init', key_id, fpr, address)) - if not self._pipe.recv(): - raise KeyError(key_id or fpr) - - def __getattribute__(self, name): - if name == '_pipe': - return super(ProxyClient, self).__getattribute__(name) - self._pipe.send(('getattr', name)) - data = self._pipe.recv() - if data[0] == 'data': - return data[1] - if data[0] == 'function': - - def func(*args, **kwargs): - self._pipe.send(('funcall', name, args, kwargs)) - return self._pipe.recv()[1] - - return func - - def __setattr__(self, name, value): - if name == '_pipe': - return super(ProxyClient, self).__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: - 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")) + logger.info(u"TCP connection from: %s", + unicode(self.client_address)) + logger.debug(u"IPC Pipe FD: %d", + self.server.child_pipe[1].fileno()) + # Open IPC pipe to parent process + with contextlib.nested(self.server.child_pipe[1], + self.server.parent_pipe[0] + ) as (ipc, ipc_return): + session = (gnutls.connection + .ClientSession(self.request, + gnutls.connection + .X509Credentials())) + + # Note: gnutls.connection.X509Credentials is really a + # generic GnuTLS certificate credentials object so long as + # no X.509 keys are added to it. Therefore, we can use it + # here despite using OpenPGP certificates. + + #priority = u':'.join((u"NONE", u"+VERS-TLS1.1", + # u"+AES-256-CBC", u"+SHA1", + # u"+COMP-NULL", u"+CTYPE-OPENPGP", + # u"+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._c_object, - priority.encode("utf-8"), - None) - + priority = u"NORMAL" + (gnutls.library.functions + .gnutls_priority_set_direct(session._c_object, + priority, None)) + # Start communication using the Mandos protocol # Get protocol number line = self.request.makefile().readline() - logger.debug("Protocol version: %r", line) + logger.debug(u"Protocol version: %r", line) try: if int(line.strip().split()[0]) > 1: - raise RuntimeError(line) - except (ValueError, IndexError, RuntimeError) as error: - logger.error("Unknown protocol version: %s", error) + raise RuntimeError + except (ValueError, IndexError, RuntimeError), error: + logger.error(u"Unknown protocol version: %s", error) return - + # Start GnuTLS connection try: session.handshake() - except gnutls.Error as error: - logger.warning("Handshake failed: %s", error) + except gnutls.errors.GNUTLSError, error: + logger.warning(u"Handshake failed: %s", error) # Do not run session.bye() here: the session is not # established. Just abandon the request. return - logger.debug("Handshake succeeded") - - approval_required = False + logger.debug(u"Handshake succeeded") try: - if gnutls.has_rawpk: - fpr = "" - try: - key_id = self.key_id( - self.peer_certificate(session)) - except (TypeError, gnutls.Error) as error: - logger.warning("Bad certificate: %s", error) - return - logger.debug("Key ID: %s", key_id) - - else: - key_id = "" - try: - fpr = self.fingerprint( - self.peer_certificate(session)) - except (TypeError, gnutls.Error) as error: - logger.warning("Bad certificate: %s", error) - return - logger.debug("Fingerprint: %s", fpr) - try: - client = ProxyClient(child_pipe, key_id, fpr, - self.client_address) - except KeyError: + fpr = self.fingerprint(self.peer_certificate + (session)) + except (TypeError, gnutls.errors.GNUTLSError), error: + logger.warning(u"Bad certificate: %s", error) return - - if client.approval_delay: - delay = client.approval_delay - client.approvals_pending += 1 - approval_required = True - - while True: - if not client.enabled: - 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 + logger.debug(u"Fingerprint: %s", fpr) + + for c in self.server.clients: + if c.fingerprint == fpr: + client = c break - elif client.approved is None: - 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: - 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 - time = datetime.datetime.now() - client.changedstate.acquire() - client.changedstate.wait(delay.total_seconds()) - client.changedstate.release() - time2 = datetime.datetime.now() - if (time2 - time) >= delay: - if not client.approved_by_default: - 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") - return - else: - break - else: - delay -= time2 - time - - try: - session.send(client.secret) - except gnutls.Error as error: - logger.warning("gnutls send failed", - exc_info=error) - return - - 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() - + else: + ipc.write(u"NOTFOUND %s %s\n" + % (fpr, unicode(self.client_address))) + return + + class ClientProxy(object): + """Client proxy object. Not for calling methods.""" + def __init__(self, client): + self.client = client + def __getattr__(self, name): + if name.startswith("ipc_"): + def tempfunc(): + ipc.write("%s %s\n" % (name[4:].upper(), + self.client.name)) + return tempfunc + if not hasattr(self.client, name): + raise AttributeError + ipc.write(u"GETATTR %s %s\n" + % (name, self.client.fingerprint)) + return pickle.load(ipc_return) + clientproxy = ClientProxy(client) + # Have to check if client.enabled, since it is + # possible that the client was disabled since the + # GnuTLS session was established. + if not clientproxy.enabled: + clientproxy.ipc_disabled() + return + + clientproxy.ipc_sending() + sent_size = 0 + while sent_size < len(client.secret): + sent = session.send(client.secret[sent_size:]) + logger.debug(u"Sent: %d, remaining: %d", + sent, len(client.secret) + - (sent_size + sent)) + sent_size += sent finally: - if approval_required: - client.approvals_pending -= 1 - try: - session.bye() - except gnutls.Error as error: - logger.warning("GnuTLS bye failed", - exc_info=error) - + session.bye() + @staticmethod def peer_certificate(session): - "Return the peer's certificate as a bytestring" - try: - cert_type = gnutls.certificate_type_get2(session._c_object, - gnutls.CTYPE_PEERS) - except AttributeError: - cert_type = gnutls.certificate_type_get(session._c_object) - if gnutls.has_rawpk: - valid_cert_types = frozenset((gnutls.CRT_RAWPK,)) - else: - valid_cert_types = frozenset((gnutls.CRT_OPENPGP,)) - # If not a valid certificate type... - if cert_type not in valid_cert_types: - logger.info("Cert type %r not in %r", cert_type, - valid_cert_types) - # ...return invalid data - return b"" + "Return the peer's OpenPGP certificate as a bytestring" + # If not an OpenPGP certificate... + if (gnutls.library.functions + .gnutls_certificate_type_get(session._c_object) + != gnutls.library.constants.GNUTLS_CRT_OPENPGP): + # ...do the normal thing + return session.peer_certificate list_size = ctypes.c_uint(1) - cert_list = (gnutls.certificate_get_peers + cert_list = (gnutls.library.functions + .gnutls_certificate_get_peers (session._c_object, ctypes.byref(list_size))) if not bool(cert_list) and list_size.value != 0: - raise gnutls.Error("error getting peer certificate") + raise gnutls.errors.GNUTLSError(u"error getting peer" + u" 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" # New GnuTLS "datum" with the OpenPGP public key - datum = gnutls.datum_t( - ctypes.cast(ctypes.c_char_p(openpgp), - ctypes.POINTER(ctypes.c_ubyte)), - ctypes.c_uint(len(openpgp))) + datum = (gnutls.library.types + .gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp), + ctypes.POINTER + (ctypes.c_ubyte)), + ctypes.c_uint(len(openpgp)))) # New empty GnuTLS certificate - crt = gnutls.openpgp_crt_t() - gnutls.openpgp_crt_init(ctypes.byref(crt)) + crt = gnutls.library.types.gnutls_openpgp_crt_t() + (gnutls.library.functions + .gnutls_openpgp_crt_init(ctypes.byref(crt))) # Import the OpenPGP public key into the certificate - gnutls.openpgp_crt_import(crt, ctypes.byref(datum), - gnutls.OPENPGP_FMT_RAW) + (gnutls.library.functions + .gnutls_openpgp_crt_import(crt, ctypes.byref(datum), + gnutls.library.constants + .GNUTLS_OPENPGP_FMT_RAW)) # Verify the self signature in the key crtverify = ctypes.c_uint() - gnutls.openpgp_crt_verify_self(crt, 0, - ctypes.byref(crtverify)) + (gnutls.library.functions + .gnutls_openpgp_crt_verify_self(crt, 0, + ctypes.byref(crtverify))) if crtverify.value != 0: - gnutls.openpgp_crt_deinit(crt) - raise gnutls.CertificateSecurityError(code - =crtverify.value) + gnutls.library.functions.gnutls_openpgp_crt_deinit(crt) + raise (gnutls.errors.CertificateSecurityError + (u"Verify failed")) # New buffer for the fingerprint buf = ctypes.create_string_buffer(20) buf_len = ctypes.c_size_t() # Get the fingerprint from the certificate into the buffer - gnutls.openpgp_crt_get_fingerprint(crt, ctypes.byref(buf), - ctypes.byref(buf_len)) + (gnutls.library.functions + .gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf), + ctypes.byref(buf_len))) # Deinit the certificate - gnutls.openpgp_crt_deinit(crt) + gnutls.library.functions.gnutls_openpgp_crt_deinit(crt) # Convert the buffer to a Python bytestring fpr = ctypes.string_at(buf, buf_len.value) # Convert the bytestring to hexadecimal notation - hex_fpr = binascii.hexlify(fpr).upper() + hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr) return hex_fpr -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.start() - return proc - - -class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object): - """ adds a pipe to the MixIn """ - +class ForkingMixInWithPipes(socketserver.ForkingMixIn, object): + """Like socketserver.ForkingMixIn, but also pass a pipe pair.""" 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): + # Child writes to child_pipe + self.child_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0)) + # Parent writes to parent_pipe + self.parent_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0)) + super(ForkingMixInWithPipes, + self).process_request(request, client_address) + # Close unused ends for parent + self.parent_pipe[0].close() # close read end + self.child_pipe[1].close() # close write end + self.add_pipe_fds(self.child_pipe[0], self.parent_pipe[1]) + def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd): """Dummy function; override as necessary""" - raise NotImplementedError() - - -class IPv6_TCPServer(MultiprocessingMixInWithPipe, + child_pipe_fd.close() + parent_pipe_fd.close() + + +class IPv6_TCPServer(ForkingMixInWithPipes, 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, - socketfd=None): - """If socketfd is set, use that file descriptor instead of - creating a new one with socket.socket(). - """ + interface=None, use_ipv6=True): self.interface = interface if use_ipv6: self.address_family = socket.AF_INET6 - if socketfd is not None: - # Save the file descriptor - 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): - # Restore original function so subsequent calls are - # not affected. - socket.socket = self.socket_socket - del self.socket_socket - # This time only, return a new socket object from the - # saved file descriptor. - return socket.fromfd(self.socketfd, *args, **kwargs) - # Replace socket.socket() function with wrapper - socket.socket = socket_wrapper - # The socketserver.TCPServer.__init__ will call - # socket.socket(), which might be our replacement, - # 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. - logger.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: - 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 + logger.error(u"SO_BINDTODEVICE does not exist;" + u" cannot bind to interface %s", + self.interface) + else: + try: + self.socket.setsockopt(socket.SOL_SOCKET, + SO_BINDTODEVICE, + str(self.interface + + u'\0')) + except socket.error, error: + if error[0] == errno.EPERM: + logger.error(u"No permission to" + u" bind to interface %s", + self.interface) + elif error[0] == errno.ENOPROTOOPT: + logger.error(u"SO_BINDTODEVICE not available;" + u" cannot bind to interface %s", + 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 = u"::" # in6addr_any else: - any_address = "0.0.0.0" # INADDR_ANY + any_address = socket.INADDR_ANY self.server_address = (any_address, self.server_address[1]) elif not self.server_address[1]: - self.server_address = (self.server_address[0], 0) + self.server_address = (self.server_address[0], + 0) # if self.interface: # self.server_address = (self.server_address[0], # 0, # port @@ -2627,287 +1249,202 @@ 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. + + Assumes a gobject.MainLoop event loop. """ - def __init__(self, server_address, RequestHandlerClass, - interface=None, - use_ipv6=True, - clients=None, - gnutls_priority=None, - use_dbus=True, - socketfd=None): + interface=None, use_ipv6=True, clients=None, + gnutls_priority=None, use_dbus=True): self.enabled = False self.clients = clients if self.clients is None: - self.clients = {} + self.clients = set() self.use_dbus = use_dbus 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) 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): + def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd): # Call "handle_ipc" for both data and EOF events - GLib.io_add_watch( - parent_pipe.fileno(), - GLib.IO_IN | GLib.IO_HUP, - functools.partial(self.handle_ipc, - parent_pipe=parent_pipe, - proc=proc)) - - def handle_ipc(self, source, condition, - parent_pipe=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] - - for c in self.clients.values(): - if key_id == "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855": - continue - if key_id and c.key_id == key_id: - client = c - break - if fpr and c.fingerprint == fpr: - client = c - break - else: - logger.info("Client not found for key ID: %s, address" - ": %s", key_id or fpr, address) - if self.use_dbus: - # Emit D-Bus signal - mandos_dbus_service.ClientNotFound(key_id or fpr, - address[0]) - parent_pipe.send(False) - return False - - GLib.io_add_watch( - 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.send(True) - # remove the old hook in favor of the new above hook on - # same fileno - return False - if command == 'funcall': - funcname = request[1] - args = request[2] - kwargs = request[3] - - parent_pipe.send(('data', getattr(client_object, - funcname)(*args, - **kwargs))) - - if command == 'getattr': - attrname = request[1] - if isinstance(client_object.__getattribute__(attrname), - collections.Callable): - parent_pipe.send(('function', )) - else: - parent_pipe.send(( - 'data', client_object.__getattribute__(attrname))) - - if command == 'setattr': - attrname = request[1] - value = request[2] - setattr(client_object, attrname, value) - + gobject.io_add_watch(child_pipe_fd.fileno(), + gobject.IO_IN | gobject.IO_HUP, + functools.partial(self.handle_ipc, + reply = parent_pipe_fd, + sender= child_pipe_fd)) + def handle_ipc(self, source, condition, reply=None, sender=None): + condition_names = { + gobject.IO_IN: u"IN", # There is data to read. + gobject.IO_OUT: u"OUT", # Data can be written (without + # blocking). + gobject.IO_PRI: u"PRI", # There is urgent data to read. + gobject.IO_ERR: u"ERR", # Error condition. + gobject.IO_HUP: u"HUP" # Hung up (the connection has been + # broken, usually for pipes and + # sockets). + } + conditions_string = ' | '.join(name + for cond, name in + condition_names.iteritems() + if cond & condition) + logger.debug(u"Handling IPC: FD = %d, condition = %s", source, + conditions_string) + + # Read a line from the file object + cmdline = sender.readline() + if not cmdline: # Empty line means end of file + # close the IPC pipes + sender.close() + reply.close() + + # Stop calling this function + return False + + logger.debug(u"IPC command: %r", cmdline) + + # Parse and act on command + cmd, args = cmdline.rstrip(u"\r\n").split(None, 1) + + if cmd == u"NOTFOUND": + fpr, address = args.split(None, 1) + logger.warning(u"Client not found for fingerprint: %s, ad" + u"dress: %s", fpr, address) + if self.use_dbus: + # Emit D-Bus signal + mandos_dbus_service.ClientNotFound(fpr, address) + elif cmd == u"DISABLED": + for client in self.clients: + if client.name == args: + logger.warning(u"Client %s is disabled", args) + if self.use_dbus: + # Emit D-Bus signal + client.Rejected() + break + else: + logger.error(u"Unknown client %s is disabled", args) + elif cmd == u"SENDING": + for client in self.clients: + if client.name == args: + logger.info(u"Sending secret to %s", client.name) + client.checked_ok() + if self.use_dbus: + # Emit D-Bus signal + client.GotSecret() + break + else: + logger.error(u"Sending secret to unknown client %s", + args) + elif cmd == u"GETATTR": + attr_name, fpr = args.split(None, 1) + for client in self.clients: + if client.fingerprint == fpr: + attr_value = getattr(client, attr_name, None) + logger.debug("IPC reply: %r", attr_value) + pickle.dump(attr_value, reply) + break + else: + logger.error(u"Client %s on address %s requesting " + u"attribute %s not found", fpr, address, + attr_name) + pickle.dump(None, reply) + else: + logger.error(u"Unknown IPC command: %r", cmdline) + + # Keep calling this function return True -def rfc3339_duration_to_delta(duration): - """Parse an RFC 3339 "duration" and return a datetime.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 - # a "group" containing digits - "value", # datetime.timedelta or None - "followers")) # Tokens valid after this token - # RFC 3339 "duration" tokens, syntax, and semantics; taken from - # the "duration" ABNF definition in RFC 3339, Appendix A. - token_end = Token(re.compile(r"$"), None, frozenset()) - token_second = Token(re.compile(r"(\d+)S"), - datetime.timedelta(seconds=1), - frozenset((token_end, ))) - token_minute = Token(re.compile(r"(\d+)M"), - datetime.timedelta(minutes=1), - frozenset((token_second, token_end))) - token_hour = Token(re.compile(r"(\d+)H"), - datetime.timedelta(hours=1), - frozenset((token_minute, token_end))) - token_time = Token(re.compile(r"T"), - None, - frozenset((token_hour, token_minute, - token_second))) - token_day = Token(re.compile(r"(\d+)D"), - datetime.timedelta(days=1), - frozenset((token_time, token_end))) - token_month = Token(re.compile(r"(\d+)M"), - datetime.timedelta(weeks=4), - frozenset((token_day, token_end))) - token_year = Token(re.compile(r"(\d+)Y"), - datetime.timedelta(weeks=52), - frozenset((token_month, token_end))) - token_week = Token(re.compile(r"(\d+)W"), - datetime.timedelta(weeks=1), - frozenset((token_end, ))) - token_duration = Token(re.compile(r"P"), None, - frozenset((token_year, token_month, - token_day, token_time, - token_week))) - # Define starting values: - # Value so far - value = datetime.timedelta() - found_token = None - # Following valid tokens - followers = frozenset((token_duration, )) - # String left to parse - s = duration - # Loop until end token is found - while found_token is not token_end: - # Search for any currently valid tokens - for token in followers: - match = token.regexp.match(s) - if match is not None: - # Token found - if token.value is not None: - # Value found, parse digits - factor = int(match.group(1), 10) - # Add to value so far - value += factor * token.value - # Strip token from string - s = token.regexp.sub("", s, 1) - # Go to found token - found_token = token - # Set valid next tokens - followers = found_token.followers - break - else: - # No currently valid tokens were found - raise ValueError("Invalid RFC 3339 duration: {!r}" - .format(duration)) - # End token found - return value - - def string_to_delta(interval): """Parse a string and return a datetime.timedelta - - >>> string_to_delta('7d') + + >>> string_to_delta(u'7d') datetime.timedelta(7) - >>> string_to_delta('60s') + >>> string_to_delta(u'60s') datetime.timedelta(0, 60) - >>> string_to_delta('60m') + >>> string_to_delta(u'60m') datetime.timedelta(0, 3600) - >>> string_to_delta('24h') + >>> string_to_delta(u'24h') datetime.timedelta(1) - >>> string_to_delta('1w') + >>> string_to_delta(u'1w') datetime.timedelta(7) - >>> string_to_delta('5m 30s') + >>> string_to_delta(u'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: - suffix = s[-1] + suffix = unicode(s[-1]) value = int(s[:-1]) - if suffix == "d": + if suffix == u"d": delta = datetime.timedelta(value) - elif suffix == "s": + elif suffix == u"s": delta = datetime.timedelta(0, value) - elif suffix == "m": + elif suffix == u"m": delta = datetime.timedelta(0, 0, 0, 0, value) - elif suffix == "h": + elif suffix == u"h": delta = datetime.timedelta(0, 0, 0, 0, 0, value) - elif suffix == "w": + elif suffix == u"w": delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value) else: - raise ValueError("Unknown suffix {!r}".format(suffix)) - except IndexError as e: - raise ValueError(*(e.args)) + raise ValueError(u"Unknown suffix %r" % suffix) + except (ValueError, IndexError), e: + raise ValueError(e.message) timevalue += delta return timevalue -def daemon(nochdir=False, noclose=False): +def if_nametoindex(interface): + """Call the C function if_nametoindex(), or equivalent + + Note: This function cannot accept a unicode string.""" + global if_nametoindex + try: + if_nametoindex = (ctypes.cdll.LoadLibrary + (ctypes.util.find_library(u"c")) + .if_nametoindex) + except (OSError, AttributeError): + logger.warning(u"Doing if_nametoindex the hard way") + def if_nametoindex(interface): + "Get an interface index the hard way, i.e. using fcntl()" + SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h + with contextlib.closing(socket.socket()) as s: + ifreq = fcntl.ioctl(s, SIOCGIFINDEX, + struct.pack(str(u"16s16x"), + interface)) + interface_index = struct.unpack(str(u"I"), + ifreq[16:20])[0] + return interface_index + return if_nametoindex(interface) + + +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() os.setsid() if not nochdir: - os.chdir("/") + os.chdir(u"/") if os.fork(): sys.exit() if not noclose: # Close all standard open file descriptors - null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR) + null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR) if not stat.S_ISCHR(os.fstat(null).st_mode): raise OSError(errno.ENODEV, - "{} not a character device" - .format(os.devnull)) + u"%s not a character device" + % os.path.devnull) os.dup2(null, sys.stdin.fileno()) os.dup2(null, sys.stdout.fileno()) os.dup2(null, sys.stderr.fileno()) @@ -2916,701 +1453,348 @@ 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), - help="show version number and exit") - parser.add_argument("-i", "--interface", metavar="IF", - help="Bind to interface IF") - parser.add_argument("-a", "--address", - help="Address to listen for requests on") - parser.add_argument("-p", "--port", type=int, - help="Port number to receive requests on") - parser.add_argument("--check", action="store_true", - help="Run self-test") - parser.add_argument("--debug", action="store_true", - help="Debug mode; run in foreground and log" - " to terminal", default=None) - parser.add_argument("--debuglevel", metavar="LEVEL", - help="Debug level for stdout output") - parser.add_argument("--priority", help="GnuTLS" - " priority string (see GnuTLS documentation)") - parser.add_argument("--servicename", - metavar="NAME", help="Zeroconf service name") - parser.add_argument("--configdir", - default="/etc/mandos", metavar="DIR", - help="Directory to search for configuration" - " files") - parser.add_argument("--no-dbus", action="store_false", - dest="use_dbus", help="Do not provide D-Bus" - " system bus interface", default=None) - parser.add_argument("--no-ipv6", action="store_false", - dest="use_ipv6", help="Do not use IPv6", - default=None) - parser.add_argument("--no-restore", action="store_false", - dest="restore", help="Do not restore stored" - " state", default=None) - parser.add_argument("--socket", type=int, - help="Specify a file descriptor to a network" - " socket to use instead of creating one") - parser.add_argument("--statedir", metavar="DIR", - help="Directory to save/restore state in") - parser.add_argument("--foreground", action="store_true", - help="Run in foreground", default=None) - parser.add_argument("--no-zeroconf", action="store_false", - dest="zeroconf", help="Do not use Zeroconf", - default=None) - - options = parser.parse_args() - + + parser = optparse.OptionParser(version = "%%prog %s" % version) + parser.add_option("-i", u"--interface", type=u"string", + metavar="IF", help=u"Bind to interface IF") + parser.add_option("-a", u"--address", type=u"string", + help=u"Address to listen for requests on") + parser.add_option("-p", u"--port", type=u"int", + help=u"Port number to receive requests on") + parser.add_option("--check", action=u"store_true", + help=u"Run self-test") + parser.add_option("--debug", action=u"store_true", + help=u"Debug mode; run in foreground and log to" + u" terminal") + parser.add_option("--priority", type=u"string", help=u"GnuTLS" + u" priority string (see GnuTLS documentation)") + parser.add_option("--servicename", type=u"string", + metavar=u"NAME", help=u"Zeroconf service name") + parser.add_option("--configdir", type=u"string", + default=u"/etc/mandos", metavar=u"DIR", + help=u"Directory to search for configuration" + u" files") + parser.add_option("--no-dbus", action=u"store_false", + dest=u"use_dbus", help=u"Do not provide D-Bus" + u" system bus interface") + parser.add_option("--no-ipv6", action=u"store_false", + dest=u"use_ipv6", help=u"Do not use IPv6") + options = parser.parse_args()[0] + if options.check: import doctest - fail_count, test_count = doctest.testmod() - sys.exit(os.EX_OK if fail_count == 0 else 1) - + doctest.testmod() + sys.exit() + # 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 = { u"interface": u"", + u"address": u"", + u"port": u"", + u"debug": u"False", + u"priority": + u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP", + u"servicename": u"Mandos", + u"use_dbus": u"True", + u"use_ipv6": u"True", + } + # Parse config file for server-global settings server_config = configparser.SafeConfigParser(server_defaults) del server_defaults - server_config.read(os.path.join(options.configdir, "mandos.conf")) + server_config.read(os.path.join(options.configdir, + u"mandos.conf")) # 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"): - server_settings[option] = server_config.getboolean("DEFAULT", + for option in (u"debug", u"use_dbus", u"use_ipv6"): + server_settings[option] = server_config.getboolean(u"DEFAULT", option) if server_settings["port"]: - server_settings["port"] = server_config.getint("DEFAULT", - "port") - if server_settings["socket"]: - server_settings["socket"] = server_config.getint("DEFAULT", - "socket") - # Later, stdin will, and stdout and stderr might, be dup'ed - # over with an opened os.devnull. But we don't want this to - # happen with a supplied network socket. - if 0 <= server_settings["socket"] <= 2: - server_settings["socket"] = os.dup(server_settings - ["socket"]) + server_settings["port"] = server_config.getint(u"DEFAULT", + u"port") del server_config - + # Override the settings from the config file with command line # options, if set. - for option in ("interface", "address", "port", "debug", - "priority", "servicename", "configdir", "use_dbus", - "use_ipv6", "debuglevel", "restore", "statedir", - "socket", "foreground", "zeroconf"): + for option in (u"interface", u"address", u"port", u"debug", + u"priority", u"servicename", u"configdir", + u"use_dbus", u"use_ipv6"): value = getattr(options, option) if value is not None: server_settings[option] = value del options # Force all strings to be unicode for option in server_settings.keys(): - if isinstance(server_settings[option], bytes): - server_settings[option] = (server_settings[option] - .decode("utf-8")) - # Force all boolean options to be boolean - for option in ("debug", "use_dbus", "use_ipv6", "restore", - "foreground", "zeroconf"): - server_settings[option] = bool(server_settings[option]) - # Debug implies foreground - if server_settings["debug"]: - server_settings["foreground"] = True + if type(server_settings[option]) is str: + server_settings[option] = unicode(server_settings[option]) # 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"] - use_dbus = server_settings["use_dbus"] - use_ipv6 = server_settings["use_ipv6"] - stored_state_path = os.path.join(server_settings["statedir"], - stored_state_file) - foreground = server_settings["foreground"] - zeroconf = server_settings["zeroconf"] - - if debug: - initlogger(debug, logging.DEBUG) - else: - if not debuglevel: - initlogger(debug) - 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( - server_settings["servicename"]))) - + debug = server_settings[u"debug"] + use_dbus = server_settings[u"use_dbus"] + use_ipv6 = server_settings[u"use_ipv6"] + + if not debug: + syslogger.setLevel(logging.WARNING) + console.setLevel(logging.WARNING) + + if server_settings[u"servicename"] != u"Mandos": + syslogger.setFormatter(logging.Formatter + (u'Mandos (%s) [%%(process)d]:' + u' %%(levelname)s: %%(message)s' + % server_settings[u"servicename"])) + # Parse config file with clients - client_config = configparser.SafeConfigParser(Client - .client_defaults) - client_config.read(os.path.join(server_settings["configdir"], - "clients.conf")) - + client_defaults = { u"timeout": u"1h", + u"interval": u"5m", + u"checker": u"fping -q -- %%(host)s", + u"host": u"", + } + client_config = configparser.SafeConfigParser(client_defaults) + client_config.read(os.path.join(server_settings[u"configdir"], + u"clients.conf")) + global mandos_dbus_service mandos_dbus_service = None - - socketfd = None - if server_settings["socket"] != "": - socketfd = server_settings["socket"] - tcp_server = MandosServer( - (server_settings["address"], server_settings["port"]), - ClientHandler, - interface=(server_settings["interface"] or None), - use_ipv6=use_ipv6, - gnutls_priority=server_settings["priority"], - use_dbus=use_dbus, - socketfd=socketfd) - if not foreground: - pidfilename = "/run/mandos.pid" - if not os.path.isdir("/run/."): - pidfilename = "/var/run/mandos.pid" - pidfile = None - try: - pidfile = codecs.open(pidfilename, "w", encoding="utf-8") - except IOError as e: - logger.error("Could not open file %r", pidfilename, - exc_info=e) - - for name, group in (("_mandos", "_mandos"), - ("mandos", "mandos"), - ("nobody", "nogroup")): - try: - uid = pwd.getpwnam(name).pw_uid - gid = pwd.getpwnam(group).pw_gid - break + + tcp_server = MandosServer((server_settings[u"address"], + server_settings[u"port"]), + ClientHandler, + interface=server_settings[u"interface"], + use_ipv6=use_ipv6, + gnutls_priority= + server_settings[u"priority"], + use_dbus=use_dbus) + pidfilename = u"/var/run/mandos.pid" + try: + pidfile = open(pidfilename, u"w") + except IOError: + logger.error(u"Could not open file %r", pidfilename) + + try: + uid = pwd.getpwnam(u"_mandos").pw_uid + gid = pwd.getpwnam(u"_mandos").pw_gid + except KeyError: + try: + uid = pwd.getpwnam(u"mandos").pw_uid + gid = pwd.getpwnam(u"mandos").pw_gid except KeyError: - continue - else: - uid = 65534 - gid = 65534 + try: + uid = pwd.getpwnam(u"nobody").pw_uid + gid = pwd.getpwnam(u"nobody").pw_gid + except KeyError: + uid = 65534 + gid = 65534 try: os.setgid(gid) os.setuid(uid) - if debug: - logger.debug("Did setuid/setgid to {}:{}".format(uid, - gid)) - except OSError as error: - logger.warning("Failed to setuid/setgid to {}:{}: {}" - .format(uid, gid, os.strerror(error.errno))) - if error.errno != errno.EPERM: - raise - + except OSError, error: + if error[0] != errno.EPERM: + raise error + + # Enable all possible GnuTLS debugging 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 + gnutls.library.functions.gnutls_global_set_log_level(11) + + @gnutls.library.types.gnutls_log_func def debug_gnutls(level, string): - 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() - - # multiprocessing will use threads, so before we use GLib we need - # to inform GLib that threads will be used. - GLib.threads_init() - + logger.debug(u"GnuTLS: %s", string[:-1]) + + (gnutls.library.functions + .gnutls_global_set_log_function(debug_gnutls)) + global main_loop # From the Avahi example code - DBusGMainLoop(set_as_default=True) - main_loop = GLib.MainLoop() + DBusGMainLoop(set_as_default=True ) + main_loop = gobject.MainLoop() bus = dbus.SystemBus() # End of Avahi example code if use_dbus: try: - bus_name = dbus.service.BusName("se.recompile.Mandos", - bus, - do_not_queue=True) - old_bus_name = dbus.service.BusName( - "se.bsnet.fukt.Mandos", bus, - do_not_queue=True) - except dbus.exceptions.DBusException as e: - logger.error("Disabling D-Bus:", exc_info=e) + bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", + bus, do_not_queue=True) + except dbus.exceptions.NameExistsException, e: + logger.error(unicode(e) + u", disabling D-Bus") use_dbus = False - server_settings["use_dbus"] = False + server_settings[u"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) - if server_settings["interface"]: - service.interface = if_nametoindex( - server_settings["interface"].encode("utf-8")) - - global multiprocessing_manager - multiprocessing_manager = multiprocessing.Manager() - + protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET + service = AvahiService(name = server_settings[u"servicename"], + servicetype = u"_mandos._tcp", + protocol = protocol, bus = bus) + if server_settings["interface"]: + service.interface = (if_nametoindex + (str(server_settings[u"interface"]))) + client_class = Client if use_dbus: - 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 - # 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: - 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 - # .keys() - 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()} - 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 - for k in ("name", "host"): - if isinstance(value[k], bytes): - value[k] = value[k].decode("utf-8") - if not value.has_key("key_id"): - value["key_id"] = "" - elif not value.has_key("fingerprint"): - value["fingerprint"] = "" - # old_client_settings - # .keys() - old_client_settings = { - (key.decode("utf-8") - if isinstance(key, bytes) - else key): value - for key, value in - bytes_old_client_settings.items()} - del bytes_old_client_settings - # .host - for value in old_client_settings.values(): - 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: - logger.warning("Could not load persistent state:" - " {}".format(os.strerror(e.errno))) - else: - logger.critical("Could not load persistent state:", - exc_info=e) - raise - except EOFError as 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. - # New config value takes precedence if it differs from old - # config value, otherwise use saved state. - for name, value in client_settings[client_name].items(): - try: - # For each value in new config, check if it - # differs from the old config value (Except for - # the "secret" attribute) - if (name != "secret" - and (value != - old_client_settings[client_name][name])): - 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 - # assumed to have successfully run all checkers during - # downtime. - if client["enabled"]: - if datetime.datetime.utcnow() >= client["expires"]: - if not client["last_checked_ok"]: - logger.warning( - "disabling client {} - Client never " - "performed a successful checker".format( - client_name)) - client["enabled"] = False - elif client["last_checker_status"] != 0: - 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"]) - 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 - 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)): - del clients_data[client_name] - 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) - + client_class = functools.partial(ClientDBus, bus = bus) + tcp_server.clients.update(set( + client_class(name = section, + config= dict(client_config.items(section))) + for section in client_config.sections())) if not tcp_server.clients: - logger.warning("No clients defined") - - if not foreground: - if pidfile is not None: + logger.warning(u"No clients defined") + + if debug: + # Redirect stdin so all checkers get /dev/null + null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR) + os.dup2(null, sys.stdin.fileno()) + if null > 2: + os.close(null) + else: + # No console logging + logger.removeHandler(console) + # Close all input and output, do double fork, etc. + daemon() + + try: + with pidfile: pid = os.getpid() - try: - with pidfile: - print(pid, file=pidfile) - except IOError: - logger.error("Could not write to file %r with PID %d", - pidfilename, pid) + pidfile.write(str(pid) + "\n") 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) - + except IOError: + logger.error(u"Could not write to file %r with PID %d", + pidfilename, pid) + except NameError: + # "pidfile" was never created + pass + del pidfilename + + if not debug: + signal.signal(signal.SIGINT, signal.SIG_IGN) + signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit()) + signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit()) + if use_dbus: - - @alternate_dbus_interfaces( - {"se.recompile.Mandos": "se.bsnet.fukt.Mandos"}) - class MandosDBusService(DBusObjectWithObjectManager): + class MandosDBusService(dbus.service.Object): """A D-Bus proxy object""" - def __init__(self): - dbus.service.Object.__init__(self, bus, "/") - - _interface = "se.recompile.Mandos" - - @dbus.service.signal(_interface, signature="o") + dbus.service.Object.__init__(self, bus, u"/") + _interface = u"se.bsnet.fukt.Mandos" + + @dbus.service.signal(_interface, signature=u"o") def ClientAdded(self, objpath): "D-Bus signal" pass - - @dbus.service.signal(_interface, signature="ss") - def ClientNotFound(self, key_id, address): + + @dbus.service.signal(_interface, signature=u"ss") + def ClientNotFound(self, fingerprint, address): "D-Bus signal" pass - - @dbus_annotations({"org.freedesktop.DBus.Deprecated": - "true"}) - @dbus.service.signal(_interface, signature="os") + + @dbus.service.signal(_interface, signature=u"os") def ClientRemoved(self, objpath, name): "D-Bus signal" pass - - @dbus_annotations({"org.freedesktop.DBus.Deprecated": - "true"}) - @dbus.service.method(_interface, out_signature="ao") + + @dbus.service.method(_interface, out_signature=u"ao") def GetAllClients(self): "D-Bus method" - return dbus.Array(c.dbus_object_path for c in - tcp_server.clients.values()) - - @dbus_annotations({"org.freedesktop.DBus.Deprecated": - "true"}) + return dbus.Array(c.dbus_object_path + for c in tcp_server.clients) + @dbus.service.method(_interface, - out_signature="a{oa{sv}}") + out_signature=u"a{oa{sv}}") def GetAllClientsWithProperties(self): "D-Bus method" return dbus.Dictionary( - {c.dbus_object_path: c.GetAll( - "se.recompile.Mandos.Client") - for c in tcp_server.clients.values()}, - signature="oa{sv}") - - @dbus.service.method(_interface, in_signature="o") + ((c.dbus_object_path, c.GetAll(u"")) + for c in tcp_server.clients), + signature=u"oa{sv}") + + @dbus.service.method(_interface, in_signature=u"o") def RemoveClient(self, object_path): "D-Bus method" - for c in tcp_server.clients.values(): + for c in tcp_server.clients: if c.dbus_object_path == object_path: - del tcp_server.clients[c.name] + tcp_server.clients.remove(c) c.remove_from_connection() - # Don't signal the disabling + # Don't signal anything except ClientRemoved c.disable(quiet=True) - # Emit D-Bus signal for removal - self.client_removed_signal(c) + # Emit D-Bus signal + self.ClientRemoved(object_path, c.name) return raise KeyError(object_path) - + del _interface - - @dbus.service.method(dbus.OBJECT_MANAGER_IFACE, - 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()}) - - def client_added_signal(self, client): - """Send the new standard signal and the old signal""" - if use_dbus: - # New standard signal - self.InterfacesAdded( - client.dbus_object_path, - dbus.Dictionary( - {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: - # New standard signal - self.InterfacesRemoved( - client.dbus_object_path, - client._get_all_interface_names()) - # 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() - 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. - clients = {} - with PGPEngine() as pgp: - for client in tcp_server.clients.values(): - key = client_settings[client.name]["secret"] - 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"} - 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', - suffix=".pickle", - prefix='clients-', - dir=os.path.dirname(stored_state_path), - delete=False) as stored_state: - pickle.dump((clients, client_settings), stored_state, - protocol=2) - tempname = stored_state.name - os.rename(tempname, stored_state_path) - except (IOError, OSError) as e: - if not debug: - try: - os.remove(tempname) - except NameError: - pass - if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST): - logger.warning("Could not save persistent state: {}" - .format(os.strerror(e.errno))) - else: - logger.warning("Could not save persistent state:", - exc_info=e) - raise - - # Delete all clients, and settings from config + service.cleanup() + while tcp_server.clients: - name, client = tcp_server.clients.popitem() + client = tcp_server.clients.pop() if use_dbus: client.remove_from_connection() - # Don't signal the disabling + client.disable_hook = None + # Don't signal anything except ClientRemoved client.disable(quiet=True) - # Emit D-Bus signal for removal if use_dbus: - mandos_dbus_service.client_removed_signal(client) - client_settings.clear() - + # Emit D-Bus signal + mandos_dbus_service.ClientRemoved(client.dbus_object_path, + client.name) + atexit.register(cleanup) - - for client in tcp_server.clients.values(): + + for client in tcp_server.clients: if use_dbus: - # Emit D-Bus signal for adding - mandos_dbus_service.client_added_signal(client) - # Need to initiate checking of clients - if client.enabled: - client.init_checker() - + # Emit D-Bus signal + mandos_dbus_service.ClientAdded(client.dbus_object_path) + client.enable() + tcp_server.enable() tcp_server.server_activate() - + # Find out what port we got - if zeroconf: - service.port = tcp_server.socket.getsockname()[1] + service.port = tcp_server.socket.getsockname()[1] if use_ipv6: - logger.info("Now listening on address %r, port %d," - " flowinfo %d, scope_id %d", - *tcp_server.socket.getsockname()) + logger.info(u"Now listening on address %r, port %d," + " flowinfo %d, scope_id %d" + % tcp_server.socket.getsockname()) else: # IPv4 - logger.info("Now listening on address %r, port %d", - *tcp_server.socket.getsockname()) - - # service.interface = tcp_server.socket.getsockname()[3] - + logger.info(u"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: - logger.critical("D-Bus Exception", exc_info=error) - cleanup() - sys.exit(1) - # End of Avahi example code - - 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") + # From the Avahi example code + try: + service.activate() + except dbus.exceptions.DBusException, error: + logger.critical(u"DBusException: %s", error) + cleanup() + sys.exit(1) + # End of Avahi example code + + gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN, + lambda *args, **kwargs: + (tcp_server.handle_request + (*args[2:], **kwargs) or True)) + + logger.debug(u"Starting main loop") main_loop.run() - except AvahiError as error: - logger.critical("Avahi Error", exc_info=error) + except AvahiError, error: + logger.critical(u"AvahiError: %s", error) cleanup() sys.exit(1) except KeyboardInterrupt: if debug: - print("", file=sys.stderr) - logger.debug("Server received KeyboardInterrupt") - logger.debug("Server exiting") + print >> sys.stderr + logger.debug(u"Server received KeyboardInterrupt") + logger.debug(u"Server exiting") # Must run before the D-Bus bus name gets deregistered cleanup() - 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 2009-12-25 23:13:47 +0000 @@ -3,7 +3,7 @@ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ /etc/mandos/clients.conf"> - + %common; ]> @@ -20,30 +20,20 @@ Björn Påhlsson
- belorn@recompile.se + belorn@fukt.bsnet.se
Teddy Hogeborn
- teddy@recompile.se + teddy@fukt.bsnet.se
2008 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -73,13 +63,8 @@ >mandos 8, read by it at startup. The file needs to list all clients that should be able to use - the service. The settings in this file can be overridden by - runtime changes to the server, which it saves across restarts. - (See the section called PERSISTENT STATE in - mandos8.) However, any changes to this file (including adding and removing - clients) will, at startup, override changes done during runtime. + the service. All clients listed will be regarded as enabled, + even if a client was disabled in a previous run of the server. The format starts with a [section @@ -115,53 +100,53 @@ - - - - This option is optional. - - - How long to wait for external approval before resorting to - use the value. The - default is PT0S, i.e. not to wait. - - - The format of TIME is the same - as for timeout below. - - - - - - - - - This option is optional. - - - How long an external approval lasts. The default is 1 - second. - - - The format of TIME is the same - as for timeout below. - - - - - - - - - Whether to approve a client by default after - the . The default - is True. + + + + This option is optional. + + + The timeout is how long the server will wait (for either a + successful checker run or a client receiving its secret) + until a client is disabled and not allowed to get the data + this server holds. By default Mandos will use 1 hour. + + + The TIME is specified as a + space-separated number of values, each of which is a + number and a one-character suffix. The suffix must be one + of d, s, m, + h, and w for days, seconds, + minutes, hours, and weeks, respectively. The values are + added together to give the total time value, so all of + 330s, + 110s 110s 110s, and + 5m 30s will give a value + of five minutes and thirty seconds. + + + + + + + + + This option is optional. + + + How often to run the checker to confirm that a client is + still up. Note: a new checker will + not be started if an old one is still 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. The default interval is 5 minutes. + + + The format of TIME is the same + as for timeout above. @@ -174,23 +159,17 @@ This option is optional. - This option overrides the default shell command that the - server will use to check if the client is still up. Any - output of the command will be ignored, only the exit code - is checked: If the exit code of the command is zero, the - client is considered up. The command will be run using - /bin/sh + This option allows you to override the default shell + command that the server will use to check if the client is + still up. Any output of the command will be ignored, only + the exit code is checked: If the exit code of the command + is zero, the client is considered up. The command will be + run using /bin/sh , so PATH will be searched. The default value for the checker command is fping %%(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 - a option to check for the - client’s SSH key fingerprint – this is more secure against - spoofing. + >-- %%(host)s. In addition to normal start time expansion, this option @@ -201,29 +180,6 @@ - - - - This option is optional. - - - Extended timeout is an added timeout that is given once - after a password has been sent successfully to a client. - The timeout is by default longer than the normal timeout, - and is used for handling the extra long downtime while a - machine is booting up. Time to take into consideration - when changing this value is file system checks and quota - checks. The default value is 15 minutes. - - - The format of TIME is the same - as for timeout below. - - - - - @@ -233,88 +189,8 @@ 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, - but spaces or upper/lower case are not significant. - - - - - - - - - This option is optional, but highly - recommended unless the - option is modified to a - non-standard value without %%(host)s in it. - - - Host name for this client. This is not used by the server - directly, but can be, and is by default, used by the - checker. See the option. - - - - - - - - - This option is optional. - - - How often to run the checker to confirm that a client is - still up. Note: a new checker will - not be started if an old one is still running. The server - will wait for a checker to complete until the below - timeout occurs, at which - time the client will be disabled, and any running checker - killed. The default interval is 2 minutes. - - - The format of TIME is the same - as for timeout below. - - - - - - - - - This option is only used if is not - specified, in which case this option is - required. - - - Similar to the , except the secret - data is in an external file. The contents of the file - should not be base64-encoded, but - will be sent to clients verbatim. - - - File names of the form ~user/foo/bar - and $ENVVAR/foo/bar - are supported. + through TLS. The string needs to be in hexidecimal form, + but spaces or upper/lower case are not significant. @@ -331,9 +207,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 @@ -350,40 +226,42 @@ - + - This option is optional. - - - The timeout is how long the server will wait, after a - successful checker run, until a client is disabled and not - allowed to get the data this server holds. By default - Mandos will use 5 minutes. See also the - option. - - - The TIME is specified as an RFC - 3339 duration; for example - P1Y2M3DT4H5M6S meaning - one year, two months, three days, four hours, five - minutes, and six seconds. Some values can be omitted, see - RFC 3339 Appendix A for details. + This option is only used if is not + specified, in which case this option is + required. + + + Similar to the , except the secret + data is in an external file. The contents of the file + should not be base64-encoded, but + will be sent to clients verbatim. + + + File names of the form ~user/foo/bar + and $ENVVAR/foo/bar + are supported. - + - Whether this client should be enabled by default. The - default is true. + This option is optional, but highly + recommended unless the + option is modified to a + non-standard value without %%(host)s in it. + + + Host name for this client. This is not used by the server + directly, but can be, and is by default, used by the + checker. See the option. @@ -426,30 +304,10 @@ %%(foo)s will be replaced by the value of the attribute foo of the internal - Client object in the - Mandos server. The currently allowed values for - foo are: - approval_delay, - approval_duration, - created, - enabled, - expires, - key_id, - fingerprint, - host, - interval, - last_approval_request, - last_checked_ok, - last_enabled, - name, - timeout, and, if using - D-Bus, dbus_object_path. - See the source code for details. Currently, none of these attributes - except host are guaranteed - to be valid in future versions. Therefore, please - let the authors know of any attributes that are useful so they - may be preserved to any new versions of this software. + Client object. See the + source code for details, and let the authors know of any + attributes that are useful so they may be preserved to any new + versions of this software. Note that this means that, in order to include an actual @@ -483,7 +341,6 @@ %(foo)s is obscure. - @@ -491,13 +348,12 @@ [DEFAULT] -timeout = PT5M -interval = PT2M +timeout = 1h +interval = 5m checker = fping -q -- %%(host)s # Client "foo" [foo] -key_id = 788cd77115cd0bb7b2d5e0ae8496f6b48149d5e712c652076b1fd2d957ef7c1f fingerprint = 7788 2722 5BA7 DE53 9C5A 7CFA 59CF F7CD BD9A 5920 secret = hQIOA6QdEjBs2L/HEAf/TCyrDe5Xnm9esa+Pb/vWF9CUqfn4srzVgSu234 @@ -516,16 +372,13 @@ 4T2zw4dxS5NswXWU0sVEXxjs6PYxuIiCTL7vdpx8QjBkrPWDrAbcMyBr2O QlnHIvPzEArRQLo= host = foo.example.org -interval = PT1M +interval = 1m # Client "bar" [bar] -key_id = F90C7A81D72D1EA69A51031A91FF8885F36C8B46D155C8C58709A4C99AE9E361 fingerprint = 3e393aeaefb84c7e89e2f547b3a107558fca3a27 secfile = /etc/mandos/bar-secret -timeout = PT15M -approved_by_default = False -approval_delay = PT30S +timeout = 15m @@ -533,31 +386,13 @@ SEE ALSO - intro - 8mandos, mandos-keygen 8, mandos.conf 5, mandos - 8, - fping 8 - - - - RFC 3339: Date and Time on the Internet: - Timestamps - - - - The time intervals are in the "duration" format, as - specified in ABNF in Appendix A of RFC 3339. - - - - === modified file 'mandos-ctl' --- mandos-ctl 2019-07-06 22:13:13 +0000 +++ mandos-ctl 2009-12-26 01:19:47 +0000 @@ -1,2531 +1,204 @@ #!/usr/bin/python -# -*- after-save-hook: (lambda () (let ((command (if (fboundp 'file-local-name) (file-local-name (buffer-file-name)) (or (file-remote-p (buffer-file-name) 'localname) (buffer-file-name))))) (if (= (progn (if (get-buffer "*Test*") (kill-buffer "*Test*")) (process-file-shell-command (format "%s --test" (shell-quote-argument command)) nil "*Test*")) 0) (let ((w (get-buffer-window "*Test*"))) (if w (delete-window w))) (progn (with-current-buffer "*Test*" (compilation-mode)) (display-buffer "*Test*" '(display-buffer-in-side-window)))))); coding: utf-8 -*- -# -# Mandos Monitor - Control and monitor the Mandos server -# -# 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 -# 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 . -# - -from __future__ import (division, absolute_import, print_function, - unicode_literals) - -try: - from future_builtins import * -except ImportError: - pass - +# -*- mode: python; coding: utf-8 -*- + +from __future__ import division import sys -import argparse +import dbus +from optparse import OptionParser import locale import datetime import re -import os -import collections -import json -import unittest -import logging -import io -import tempfile -import contextlib - -try: - import pydbus - import gi - dbus_python = None -except ImportError: - import dbus as dbus_python - pydbus = None - class gi(object): - """Dummy gi module, for the tests""" - class repository(object): - class GLib(object): - class Error(Exception): - pass - -# Show warnings by default -if not sys.warnoptions: - import warnings - warnings.simplefilter("default") - -log = logging.getLogger(sys.argv[0]) -logging.basicConfig(level="INFO", # Show info level messages - format="%(message)s") # Show basic log messages - -logging.captureWarnings(True) # Show warnings via the logging system - -if sys.version_info.major == 2: - str = unicode - import StringIO - io.StringIO = StringIO.StringIO - -locale.setlocale(locale.LC_ALL, "") - -version = "1.8.4" - - -def main(): - parser = argparse.ArgumentParser() - add_command_line_options(parser) - - options = parser.parse_args() - check_option_syntax(parser, options) - - clientnames = options.client - - if options.debug: - log.setLevel(logging.DEBUG) - - if pydbus is not None: - bus = pydbus_adapter.CachingBus(pydbus) - else: - bus = dbus_python_adapter.CachingBus(dbus_python) - - try: - all_clients = bus.get_clients_and_properties() - except dbus.ConnectFailed as e: - log.critical("Could not connect to Mandos server: %s", e) - sys.exit(1) - except dbus.Error as e: - log.critical( - "Failed to access Mandos server through D-Bus:\n%s", e) - sys.exit(1) - - # Compile dict of (clientpath: properties) to process - if not clientnames: - clients = all_clients - else: - clients = {} - for name in clientnames: - for objpath, properties in all_clients.items(): - if properties["Name"] == name: - clients[objpath] = properties - break + +locale.setlocale(locale.LC_ALL, u'') + +tablewords = { + 'name': u'Name', + 'enabled': u'Enabled', + 'timeout': u'Timeout', + 'last_checked_ok': u'Last Successful Check', + 'created': u'Created', + 'interval': u'Interval', + 'host': u'Host', + 'fingerprint': u'Fingerprint', + 'checker_running': u'Check Is Running', + 'last_enabled': u'Last Enabled', + 'checker': u'Checker', + } +defaultkeywords = ('name', 'enabled', 'timeout', 'last_checked_ok') +domain = 'se.bsnet.fukt' +busname = domain + '.Mandos' +server_path = '/' +server_interface = domain + '.Mandos' +client_interface = domain + '.Mandos.Client' +version = "1.0.14" +bus = dbus.SystemBus() +mandos_dbus_objc = bus.get_object(busname, server_path) +mandos_serv = dbus.Interface(mandos_dbus_objc, + dbus_interface = server_interface) +mandos_clients = mandos_serv.GetAllClientsWithProperties() + +def timedelta_to_milliseconds(td): + "Convert a datetime.timedelta object to milliseconds" + return ((td.days * 24 * 60 * 60 * 1000) + + (td.seconds * 1000) + + (td.microseconds // 1000)) + +def milliseconds_to_string(ms): + td = datetime.timedelta(0, 0, 0, ms) + return (u"%(days)s%(hours)02d:%(minutes)02d:%(seconds)02d" + % { "days": "%dT" % td.days if td.days else "", + "hours": td.seconds // 3600, + "minutes": (td.seconds % 3600) // 60, + "seconds": td.seconds % 60, + }) + + +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(u'1w') + datetime.timedelta(7) + >>> string_to_delta('5m 30s') + datetime.timedelta(0, 330) + """ + timevalue = datetime.timedelta(0) + regexp = re.compile("\d+[dsmhw]") + + for s in regexp.findall(interval): + try: + suffix = unicode(s[-1]) + value = int(s[:-1]) + if suffix == u"d": + delta = datetime.timedelta(value) + elif suffix == u"s": + delta = datetime.timedelta(0, value) + elif suffix == u"m": + delta = datetime.timedelta(0, 0, 0, 0, value) + elif suffix == u"h": + delta = datetime.timedelta(0, 0, 0, 0, 0, value) + elif suffix == u"w": + delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value) 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) - - -def rfc3339_duration_to_delta(duration): - """Parse an RFC 3339 "duration" and return a datetime.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("P60M") - datetime.timedelta(1680) - >>> 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) - >>> # Can not be empty: - >>> rfc3339_duration_to_delta("") - Traceback (most recent call last): - ... - ValueError: Invalid RFC 3339 duration: "" - >>> # Must start with "P": - >>> rfc3339_duration_to_delta("1D") - Traceback (most recent call last): - ... - ValueError: Invalid RFC 3339 duration: "1D" - >>> # Must use correct order - >>> rfc3339_duration_to_delta("PT1S2M") - Traceback (most recent call last): - ... - ValueError: Invalid RFC 3339 duration: "PT1S2M" - >>> # Time needs time marker - >>> rfc3339_duration_to_delta("P1H2S") - Traceback (most recent call last): - ... - ValueError: Invalid RFC 3339 duration: "P1H2S" - >>> # Weeks can not be combined with anything else - >>> rfc3339_duration_to_delta("P1D2W") - Traceback (most recent call last): - ... - ValueError: Invalid RFC 3339 duration: "P1D2W" - >>> rfc3339_duration_to_delta("P2W2H") - Traceback (most recent call last): - ... - ValueError: Invalid RFC 3339 duration: "P2W2H" - """ - - # Parsing an RFC 3339 duration with regular expressions is not - # 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 - # a "group" containing digits - "value", # datetime.timedelta or None - "followers")) # Tokens valid after this token - # RFC 3339 "duration" tokens, syntax, and semantics; taken from - # the "duration" ABNF definition in RFC 3339, Appendix A. - token_end = Token(re.compile(r"$"), None, frozenset()) - token_second = Token(re.compile(r"(\d+)S"), - datetime.timedelta(seconds=1), - frozenset((token_end, ))) - token_minute = Token(re.compile(r"(\d+)M"), - datetime.timedelta(minutes=1), - frozenset((token_second, token_end))) - token_hour = Token(re.compile(r"(\d+)H"), - datetime.timedelta(hours=1), - frozenset((token_minute, token_end))) - token_time = Token(re.compile(r"T"), - None, - frozenset((token_hour, token_minute, - token_second))) - token_day = Token(re.compile(r"(\d+)D"), - datetime.timedelta(days=1), - frozenset((token_time, token_end))) - token_month = Token(re.compile(r"(\d+)M"), - datetime.timedelta(weeks=4), - frozenset((token_day, token_end))) - token_year = Token(re.compile(r"(\d+)Y"), - datetime.timedelta(weeks=52), - frozenset((token_month, token_end))) - token_week = Token(re.compile(r"(\d+)W"), - datetime.timedelta(weeks=1), - frozenset((token_end, ))) - token_duration = Token(re.compile(r"P"), None, - frozenset((token_year, token_month, - token_day, token_time, - token_week))) - # Define starting values: - # Value so far - value = datetime.timedelta() - found_token = None - # Following valid tokens - followers = frozenset((token_duration, )) - # String left to parse - s = duration - # Loop until end token is found - while found_token is not token_end: - # Search for any currently valid tokens - for token in followers: - match = token.regexp.match(s) - if match is not None: - # Token found - if token.value is not None: - # Value found, parse digits - factor = int(match.group(1), 10) - # Add to value so far - value += factor * token.value - # Strip token from string - s = token.regexp.sub("", s, 1) - # Go to found token - found_token = token - # Set valid next tokens - followers = found_token.followers - break - else: - # No currently valid tokens were found - raise ValueError("Invalid RFC 3339 duration: \"{}\"" - .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(7) - >>> parse_pre_1_6_1_interval('60s') - datetime.timedelta(0, 60) - >>> parse_pre_1_6_1_interval('60m') - datetime.timedelta(0, 3600) - >>> parse_pre_1_6_1_interval('24h') - datetime.timedelta(1) - >>> parse_pre_1_6_1_interval('1w') - datetime.timedelta(7) - >>> parse_pre_1_6_1_interval('5m 30s') - datetime.timedelta(0, 330) - >>> parse_pre_1_6_1_interval('') - datetime.timedelta(0) - >>> # Ignore unknown characters, allow any order and repetitions - >>> parse_pre_1_6_1_interval('2dxy7zz11y3m5m') - datetime.timedelta(2, 480, 18000) - - """ - - 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)) - elif suffix == "s": - value += datetime.timedelta(0, int(num)) - elif suffix == "m": - value += datetime.timedelta(0, 0, 0, 0, int(num)) - elif suffix == "h": - value += datetime.timedelta(0, 0, 0, 0, 0, int(num)) - elif suffix == "w": - value += datetime.timedelta(0, 0, 0, 0, 0, 0, int(num)) - elif suffix == "": - value += datetime.timedelta(0, 0, 0, int(num)) - 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): - 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): - parser.error("--all requires an action.") - if (has_commands(options, (command.IsEnabled,)) - and len(options.client) > 1): - parser.error("--is-enabled requires exactly one client") - if (len(options.commands) > 1 - and has_commands(options, (command.Remove,)) - and not has_commands(options, (command.Deny,))): - parser.error("--remove can only be combined with --deny") - - -class dbus(object): - - class SystemBus(object): - - object_manager_iface = "org.freedesktop.DBus.ObjectManager" - def get_managed_objects(self, busname, objectpath): - return self.call_method("GetManagedObjects", busname, - objectpath, - self.object_manager_iface) - - properties_iface = "org.freedesktop.DBus.Properties" - def set_property(self, busname, objectpath, interface, key, - value): - self.call_method("Set", busname, objectpath, - self.properties_iface, interface, key, - value) - - - class MandosBus(SystemBus): - busname_domain = "se.recompile" - busname = busname_domain + ".Mandos" - server_path = "/" - server_interface = busname_domain + ".Mandos" - client_interface = busname_domain + ".Mandos.Client" - del busname_domain - - def get_clients_and_properties(self): - managed_objects = self.get_managed_objects( - self.busname, self.server_path) - return {objpath: properties[self.client_interface] - for objpath, properties in managed_objects.items() - if self.client_interface in properties} - - def set_client_property(self, objectpath, key, value): - return self.set_property(self.busname, objectpath, - self.client_interface, key, - value) - - def call_client_method(self, objectpath, method, *args): - return self.call_method(method, self.busname, objectpath, - self.client_interface, *args) - - def call_server_method(self, method, *args): - return self.call_method(method, self.busname, - self.server_path, - self.server_interface, *args) - - class Error(Exception): - pass - - class ConnectFailed(Error): - pass - - -class dbus_python_adapter(object): - - class SystemBus(dbus.MandosBus): - """Use dbus-python""" - - def __init__(self, module=dbus_python): - self.dbus_python = module - self.bus = self.dbus_python.SystemBus() - - @contextlib.contextmanager - def convert_exception(self, exception_class=dbus.Error): - try: - yield - except self.dbus_python.exceptions.DBusException as e: - # This does what "raise from" would do - exc = exception_class(*e.args) - exc.__cause__ = e - raise exc - - def call_method(self, methodname, busname, objectpath, - interface, *args): - proxy_object = self.get_object(busname, objectpath) - log.debug("D-Bus: %s:%s:%s.%s(%s)", busname, objectpath, - interface, methodname, - ", ".join(repr(a) for a in args)) - method = getattr(proxy_object, methodname) - with self.convert_exception(): - with dbus_python_adapter.SilenceLogger( - "dbus.proxies"): - value = method(*args, dbus_interface=interface) - return self.type_filter(value) - - def get_object(self, busname, objectpath): - log.debug("D-Bus: Connect to: (busname=%r, path=%r)", - busname, objectpath) - with self.convert_exception(dbus.ConnectFailed): - return self.bus.get_object(busname, objectpath) - - def type_filter(self, value): - """Convert the most bothersome types to Python types""" - if isinstance(value, self.dbus_python.Boolean): - return bool(value) - if isinstance(value, self.dbus_python.ObjectPath): - return str(value) - # Also recurse into dictionaries - if isinstance(value, self.dbus_python.Dictionary): - return {self.type_filter(key): - self.type_filter(subval) - for key, subval in value.items()} - return value - - def set_client_property(self, objectpath, key, value): - if key == "Secret": - if not isinstance(value, bytes): - value = value.encode("utf-8") - value = self.dbus_python.ByteArray(value) - return self.set_property(self.busname, objectpath, - self.client_interface, key, - value) - - class SilenceLogger(object): - "Simple context manager to silence a particular logger" - def __init__(self, loggername): - self.logger = logging.getLogger(loggername) - - def __enter__(self): - self.logger.addFilter(self.nullfilter) - - class NullFilter(logging.Filter): - def filter(self, record): - return False - - nullfilter = NullFilter() - - def __exit__(self, exc_type, exc_val, exc_tb): - self.logger.removeFilter(self.nullfilter) - - - class CachingBus(SystemBus): - """A caching layer for dbus_python_adapter.SystemBus""" - def __init__(self, *args, **kwargs): - self.object_cache = {} - super(dbus_python_adapter.CachingBus, - self).__init__(*args, **kwargs) - def get_object(self, busname, objectpath): - try: - return self.object_cache[(busname, objectpath)] - except KeyError: - new_object = super( - dbus_python_adapter.CachingBus, - self).get_object(busname, objectpath) - self.object_cache[(busname, objectpath)] = new_object - return new_object - - -class pydbus_adapter(object): - class SystemBus(dbus.MandosBus): - def __init__(self, module=pydbus): - self.pydbus = module - self.bus = self.pydbus.SystemBus() - - @contextlib.contextmanager - def convert_exception(self, exception_class=dbus.Error): - try: - yield - except gi.repository.GLib.Error as e: - # This does what "raise from" would do - exc = exception_class(*e.args) - exc.__cause__ = e - raise exc - - def call_method(self, methodname, busname, objectpath, - interface, *args): - proxy_object = self.get(busname, objectpath) - log.debug("D-Bus: %s:%s:%s.%s(%s)", busname, objectpath, - interface, methodname, - ", ".join(repr(a) for a in args)) - method = getattr(proxy_object[interface], methodname) - with self.convert_exception(): - return method(*args) - - def get(self, busname, objectpath): - log.debug("D-Bus: Connect to: (busname=%r, path=%r)", - busname, objectpath) - with self.convert_exception(dbus.ConnectFailed): - if sys.version_info.major <= 2: - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", "", DeprecationWarning, - r"^xml\.etree\.ElementTree$") - return self.bus.get(busname, objectpath) - else: - return self.bus.get(busname, objectpath) - - def set_property(self, busname, objectpath, interface, key, - value): - proxy_object = self.get(busname, objectpath) - log.debug("D-Bus: %s:%s:%s.Set(%r, %r, %r)", busname, - objectpath, self.properties_iface, interface, - key, value) - setattr(proxy_object[interface], key, value) - - class CachingBus(SystemBus): - """A caching layer for pydbus_adapter.SystemBus""" - def __init__(self, *args, **kwargs): - self.object_cache = {} - super(pydbus_adapter.CachingBus, - self).__init__(*args, **kwargs) - def get(self, busname, objectpath): - try: - return self.object_cache[(busname, objectpath)] - except KeyError: - new_object = (super(pydbus_adapter.CachingBus, self) - .get(busname, objectpath)) - self.object_cache[(busname, objectpath)] = new_object - return new_object - - -def commands_from_options(options): - - commands = list(options.commands) - - def find_cmd(cmd, commands): - i = 0 - for i, c in enumerate(commands): - if isinstance(c, cmd): - return i - return i+1 - - # If command.Remove is present, move any instances of command.Deny - # to occur ahead of command.Remove. - index_of_remove = find_cmd(command.Remove, commands) - before_remove = commands[:index_of_remove] - after_remove = commands[index_of_remove:] - cleaned_after = [] - for cmd in after_remove: - if isinstance(cmd, command.Deny): - before_remove.append(cmd) - else: - cleaned_after.append(cmd) - if cleaned_after != after_remove: - commands = before_remove + cleaned_after - - # If no command option has been given, show table of clients, - # optionally verbosely - if not commands: - commands.append(command.PrintTable(verbose=options.verbose)) - - return commands - - -class command(object): - """A namespace for command classes""" - - class Base(object): - """Abstract base class for commands""" - def run(self, clients, bus=None): - """Normal commands should implement run_on_one_client(), -but commands which want to operate on all clients at the same time can -override this run() method instead. -""" - self.bus = bus - for client, properties in clients.items(): - self.run_on_one_client(client, properties) - - - class IsEnabled(Base): - def run(self, clients, bus=None): - properties = next(iter(clients.values())) - if properties["Enabled"]: - sys.exit(0) - sys.exit(1) - - - class Approve(Base): - def run_on_one_client(self, client, properties): - self.bus.call_client_method(client, "Approve", True) - - - class Deny(Base): - def run_on_one_client(self, client, properties): - self.bus.call_client_method(client, "Approve", False) - - - class Remove(Base): - def run(self, clients, bus): - for clientpath in frozenset(clients.keys()): - bus.call_server_method("RemoveClient", clientpath) - - - class Output(Base): - """Abstract class for commands outputting client details""" - all_keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK", - "Created", "Interval", "Host", "KeyID", - "Fingerprint", "CheckerRunning", - "LastEnabled", "ApprovalPending", - "ApprovedByDefault", "LastApprovalRequest", - "ApprovalDelay", "ApprovalDuration", - "Checker", "ExtendedTimeout", "Expires", - "LastCheckerStatus") - - - class DumpJSON(Output): - def run(self, clients, bus=None): - data = {properties["Name"]: - {key: properties[key] - for key in self.all_keywords} - for properties in clients.values()} - print(json.dumps(data, indent=4, separators=(',', ': '))) - - - class PrintTable(Output): - def __init__(self, verbose=False): - self.verbose = verbose - - def run(self, clients, bus=None): - default_keywords = ("Name", "Enabled", "Timeout", - "LastCheckedOK") - keywords = default_keywords - if self.verbose: - keywords = self.all_keywords - print(self.TableOfClients(clients.values(), keywords)) - - class TableOfClients(object): - tableheaders = { - "Name": "Name", - "Enabled": "Enabled", - "Timeout": "Timeout", - "LastCheckedOK": "Last Successful Check", - "LastApprovalRequest": "Last Approval Request", - "Created": "Created", - "Interval": "Interval", - "Host": "Host", - "Fingerprint": "Fingerprint", - "KeyID": "Key ID", - "CheckerRunning": "Check Is Running", - "LastEnabled": "Last Enabled", - "ApprovalPending": "Approval Is Pending", - "ApprovedByDefault": "Approved By Default", - "ApprovalDelay": "Approval Delay", - "ApprovalDuration": "Approval Duration", - "Checker": "Checker", - "ExtendedTimeout": "Extended Timeout", - "Expires": "Expires", - "LastCheckerStatus": "Last Checker Status", - } - - def __init__(self, clients, keywords): - self.clients = clients - self.keywords = keywords - - def __str__(self): - return "\n".join(self.rows()) - - if sys.version_info.major == 2: - __unicode__ = __str__ - def __str__(self): - return str(self).encode( - locale.getpreferredencoding()) - - def rows(self): - format_string = self.row_formatting_string() - rows = [self.header_line(format_string)] - rows.extend(self.client_line(client, format_string) - for client in self.clients) - return rows - - def row_formatting_string(self): - "Format string used to format table rows" - return " ".join("{{{key}:{width}}}".format( - width=max(len(self.tableheaders[key]), - *(len(self.string_from_client(client, - key)) - for client in self.clients)), - key=key) - for key in self.keywords) - - def string_from_client(self, client, key): - return self.valuetostring(client[key], key) - - @classmethod - def valuetostring(cls, value, keyword): - if isinstance(value, bool): - return "Yes" if value else "No" - if keyword in ("Timeout", "Interval", "ApprovalDelay", - "ApprovalDuration", "ExtendedTimeout"): - return cls.milliseconds_to_string(value) - return str(value) - - def header_line(self, format_string): - return format_string.format(**self.tableheaders) - - def client_line(self, client, format_string): - return format_string.format( - **{key: self.string_from_client(client, key) - for key in self.keywords}) - - @staticmethod - def milliseconds_to_string(ms): - td = datetime.timedelta(0, 0, 0, ms) - return ("{days}{hours:02}:{minutes:02}:{seconds:02}" - .format(days="{}T".format(td.days) - if td.days else "", - hours=td.seconds // 3600, - minutes=(td.seconds % 3600) // 60, - seconds=td.seconds % 60)) - - - class PropertySetter(Base): - "Abstract class for Actions for setting one client property" - - def run_on_one_client(self, client, properties=None): - """Set the Client's D-Bus property""" - self.bus.set_client_property(client, self.propname, - self.value_to_set) - - @property - def propname(self): - raise NotImplementedError() - - - class Enable(PropertySetter): - propname = "Enabled" - value_to_set = True - - - class Disable(PropertySetter): - propname = "Enabled" - value_to_set = False - - - class BumpTimeout(PropertySetter): - propname = "LastCheckedOK" - value_to_set = "" - - - class StartChecker(PropertySetter): - propname = "CheckerRunning" - value_to_set = True - - - class StopChecker(PropertySetter): - propname = "CheckerRunning" - value_to_set = False - - - class ApproveByDefault(PropertySetter): - propname = "ApprovedByDefault" - value_to_set = True - - - class DenyByDefault(PropertySetter): - propname = "ApprovedByDefault" - value_to_set = False - - - class PropertySetterValue(PropertySetter): - """Abstract class for PropertySetter recieving a value as -constructor argument instead of a class attribute.""" - def __init__(self, value): - self.value_to_set = value - - @classmethod - def argparse(cls, argtype): - def cmdtype(arg): - return cls(argtype(arg)) - return cmdtype - - class SetChecker(PropertySetterValue): - propname = "Checker" - - - class SetHost(PropertySetterValue): - propname = "Host" - - - class SetSecret(PropertySetterValue): - propname = "Secret" - - @property - def value_to_set(self): - return self._vts - - @value_to_set.setter - def value_to_set(self, value): - """When setting, read data from supplied file object""" - self._vts = value.read() - value.close() - - - class PropertySetterValueMilliseconds(PropertySetterValue): - """Abstract class for PropertySetterValue taking a value -argument as a datetime.timedelta() but should store it as -milliseconds.""" - - @property - def value_to_set(self): - return self._vts - - @value_to_set.setter - def value_to_set(self, value): - "When setting, convert value from a datetime.timedelta" - self._vts = int(round(value.total_seconds() * 1000)) - - - class SetTimeout(PropertySetterValueMilliseconds): - propname = "Timeout" - - - class SetExtendedTimeout(PropertySetterValueMilliseconds): - propname = "ExtendedTimeout" - - - class SetInterval(PropertySetterValueMilliseconds): - propname = "Interval" - - - class SetApprovalDelay(PropertySetterValueMilliseconds): - propname = "ApprovalDelay" - - - class SetApprovalDuration(PropertySetterValueMilliseconds): - propname = "ApprovalDuration" - - - -class TestCaseWithAssertLogs(unittest.TestCase): - """unittest.TestCase.assertLogs only exists in Python 3.4""" - - if not hasattr(unittest.TestCase, "assertLogs"): - @contextlib.contextmanager - def assertLogs(self, logger, level=logging.INFO): - capturing_handler = self.CapturingLevelHandler(level) - old_level = logger.level - old_propagate = logger.propagate - logger.addHandler(capturing_handler) - logger.setLevel(level) - logger.propagate = False - try: - yield capturing_handler.watcher - finally: - logger.propagate = old_propagate - logger.removeHandler(capturing_handler) - logger.setLevel(old_level) - self.assertGreater(len(capturing_handler.watcher.records), - 0) - - class CapturingLevelHandler(logging.Handler): - def __init__(self, level, *args, **kwargs): - logging.Handler.__init__(self, *args, **kwargs) - self.watcher = self.LoggingWatcher([], []) - def emit(self, record): - self.watcher.records.append(record) - self.watcher.output.append(self.format(record)) - - LoggingWatcher = collections.namedtuple("LoggingWatcher", - ("records", - "output")) - - -class Unique(object): - """Class for objects which exist only to be unique objects, since -unittest.mock.sentinel only exists in Python 3.3""" - - -class Test_string_to_delta(TestCaseWithAssertLogs): - # Just test basic RFC 3339 functionality here, the doc string for - # rfc3339_duration_to_delta() already has more comprehensive - # tests, which are run by doctest. - - def test_rfc3339_zero_seconds(self): - self.assertEqual(datetime.timedelta(), - string_to_delta("PT0S")) - - def test_rfc3339_zero_days(self): - self.assertEqual(datetime.timedelta(), string_to_delta("P0D")) - - def test_rfc3339_one_second(self): - self.assertEqual(datetime.timedelta(0, 1), - string_to_delta("PT1S")) - - def test_rfc3339_two_hours(self): - self.assertEqual(datetime.timedelta(0, 7200), - string_to_delta("PT2H")) - - def test_falls_back_to_pre_1_6_1_with_warning(self): - with self.assertLogs(log, logging.WARNING): - value = string_to_delta("2h") - self.assertEqual(datetime.timedelta(0, 7200), value) - - -class Test_check_option_syntax(unittest.TestCase): - def setUp(self): - self.parser = argparse.ArgumentParser() - add_command_line_options(self.parser) - - def test_actions_requires_client_or_all(self): - for action, value in self.actions.items(): - args = self.actionargs(action, value) - with self.assertParseError(): - self.parse_args(args) - - # This mostly corresponds to the definition from has_commands() in - # check_option_syntax() - actions = { - "--enable": None, - "--disable": None, - "--bump-timeout": None, - "--start-checker": None, - "--stop-checker": None, - "--is-enabled": None, - "--remove": None, - "--checker": "x", - "--timeout": "PT0S", - "--extended-timeout": "PT0S", - "--interval": "PT0S", - "--approve-by-default": None, - "--deny-by-default": None, - "--approval-delay": "PT0S", - "--approval-duration": "PT0S", - "--host": "hostname", - "--secret": "/dev/null", - "--approve": None, - "--deny": None, - } - - @staticmethod - def actionargs(action, value, *args): - if value is not None: - return [action, value] + list(args) - else: - return [action] + list(args) - - @contextlib.contextmanager - def assertParseError(self): - with self.assertRaises(SystemExit) as e: - with self.redirect_stderr_to_devnull(): - yield - # Exit code from argparse is guaranteed to be "2". Reference: - # https://docs.python.org/3/library - # /argparse.html#exiting-methods - self.assertEqual(2, e.exception.code) - - def parse_args(self, args): - options = self.parser.parse_args(args) - check_option_syntax(self.parser, options) - - @staticmethod - @contextlib.contextmanager - def redirect_stderr_to_devnull(): - old_stderr = sys.stderr - with contextlib.closing(open(os.devnull, "w")) as null: - sys.stderr = null - try: - yield - finally: - sys.stderr = old_stderr - - def check_option_syntax(self, options): - check_option_syntax(self.parser, options) - - def test_actions_all_conflicts_with_verbose(self): - for action, value in self.actions.items(): - args = self.actionargs(action, value, "--all", - "--verbose") - with self.assertParseError(): - self.parse_args(args) - - def test_actions_with_client_conflicts_with_verbose(self): - for action, value in self.actions.items(): - args = self.actionargs(action, value, "--verbose", - "client") - with self.assertParseError(): - self.parse_args(args) - - def test_dump_json_conflicts_with_verbose(self): - args = ["--dump-json", "--verbose"] - with self.assertParseError(): - self.parse_args(args) - - def test_dump_json_conflicts_with_action(self): - for action, value in self.actions.items(): - args = self.actionargs(action, value, "--dump-json") - with self.assertParseError(): - self.parse_args(args) - - def test_all_can_not_be_alone(self): - args = ["--all"] - with self.assertParseError(): - self.parse_args(args) - - def test_all_is_ok_with_any_action(self): - for action, value in self.actions.items(): - args = self.actionargs(action, value, "--all") - self.parse_args(args) - - def test_any_action_is_ok_with_one_client(self): - for action, value in self.actions.items(): - args = self.actionargs(action, value, "client") - self.parse_args(args) - - def test_one_client_with_all_actions_except_is_enabled(self): - for action, value in self.actions.items(): - if action == "--is-enabled": - continue - args = self.actionargs(action, value, "client") - self.parse_args(args) - - def test_two_clients_with_all_actions_except_is_enabled(self): - for action, value in self.actions.items(): - if action == "--is-enabled": - continue - args = self.actionargs(action, value, "client1", - "client2") - self.parse_args(args) - - def test_two_clients_are_ok_with_actions_except_is_enabled(self): - for action, value in self.actions.items(): - if action == "--is-enabled": - continue - args = self.actionargs(action, value, "client1", - "client2") - self.parse_args(args) - - def test_is_enabled_fails_without_client(self): - args = ["--is-enabled"] - with self.assertParseError(): - self.parse_args(args) - - def test_is_enabled_fails_with_two_clients(self): - args = ["--is-enabled", "client1", "client2"] - with self.assertParseError(): - self.parse_args(args) - - def test_remove_can_only_be_combined_with_action_deny(self): - for action, value in self.actions.items(): - if action in {"--remove", "--deny"}: - continue - args = self.actionargs(action, value, "--all", - "--remove") - with self.assertParseError(): - self.parse_args(args) - - -class Test_dbus_exceptions(unittest.TestCase): - - def test_dbus_ConnectFailed_is_Error(self): - with self.assertRaises(dbus.Error): - raise dbus.ConnectFailed() - - -class Test_dbus_MandosBus(unittest.TestCase): - - class MockMandosBus(dbus.MandosBus): - def __init__(self): - self._name = "se.recompile.Mandos" - self._server_path = "/" - self._server_interface = "se.recompile.Mandos" - self._client_interface = "se.recompile.Mandos.Client" - self.calls = [] - self.call_method_return = Unique() - - def call_method(self, methodname, busname, objectpath, - interface, *args): - self.calls.append((methodname, busname, objectpath, - interface, args)) - return self.call_method_return - - def setUp(self): - self.bus = self.MockMandosBus() - - def test_set_client_property(self): - self.bus.set_client_property("objectpath", "key", "value") - expected_call = ("Set", self.bus._name, "objectpath", - "org.freedesktop.DBus.Properties", - (self.bus._client_interface, "key", "value")) - self.assertIn(expected_call, self.bus.calls) - - def test_call_client_method(self): - ret = self.bus.call_client_method("objectpath", "methodname") - self.assertIs(self.bus.call_method_return, ret) - expected_call = ("methodname", self.bus._name, "objectpath", - self.bus._client_interface, ()) - self.assertIn(expected_call, self.bus.calls) - - def test_call_client_method_with_args(self): - args = (Unique(), Unique()) - ret = self.bus.call_client_method("objectpath", "methodname", - *args) - self.assertIs(self.bus.call_method_return, ret) - expected_call = ("methodname", self.bus._name, "objectpath", - self.bus._client_interface, - (args[0], args[1])) - self.assertIn(expected_call, self.bus.calls) - - def test_get_clients_and_properties(self): - managed_objects = { - "objectpath": { - self.bus._client_interface: { - "key": "value", - "bool": True, - }, - "irrelevant_interface": { - "key": "othervalue", - "bool": False, - }, - }, - "other_objectpath": { - "other_irrelevant_interface": { - "key": "value 3", - "bool": None, - }, - }, - } - expected_clients_and_properties = { - "objectpath": { - "key": "value", - "bool": True, - } - } - self.bus.call_method_return = managed_objects - ret = self.bus.get_clients_and_properties() - self.assertDictEqual(expected_clients_and_properties, ret) - expected_call = ("GetManagedObjects", self.bus._name, - self.bus._server_path, - "org.freedesktop.DBus.ObjectManager", ()) - self.assertIn(expected_call, self.bus.calls) - - def test_call_server_method(self): - ret = self.bus.call_server_method("methodname") - self.assertIs(self.bus.call_method_return, ret) - expected_call = ("methodname", self.bus._name, - self.bus._server_path, - self.bus._server_interface, ()) - self.assertIn(expected_call, self.bus.calls) - - def test_call_server_method_with_args(self): - args = (Unique(), Unique()) - ret = self.bus.call_server_method("methodname", *args) - self.assertIs(self.bus.call_method_return, ret) - expected_call = ("methodname", self.bus._name, - self.bus._server_path, - self.bus._server_interface, - (args[0], args[1])) - self.assertIn(expected_call, self.bus.calls) - - -class Test_dbus_python_adapter_SystemBus(TestCaseWithAssertLogs): - - def MockDBusPython_func(self, func): - class mock_dbus_python(object): - """mock dbus-python module""" - class exceptions(object): - """Pseudo-namespace""" - class DBusException(Exception): - pass - class SystemBus(object): - @staticmethod - def get_object(busname, objectpath): - DBusObject = collections.namedtuple( - "DBusObject", ("methodname", "Set")) - def method(*args, **kwargs): - self.assertEqual({"dbus_interface": - "interface"}, - kwargs) - return func(*args) - def set_property(interface, key, value, - dbus_interface=None): - self.assertEqual( - "org.freedesktop.DBus.Properties", - dbus_interface) - self.assertEqual("Secret", key) - return func(interface, key, value, - dbus_interface=dbus_interface) - return DBusObject(methodname=method, - Set=set_property) - class Boolean(object): - def __init__(self, value): - self.value = bool(value) - def __bool__(self): - return self.value - if sys.version_info.major == 2: - __nonzero__ = __bool__ - class ObjectPath(str): - pass - class Dictionary(dict): - pass - class ByteArray(bytes): - pass - return mock_dbus_python - - def call_method(self, bus, methodname, busname, objectpath, - interface, *args): - with self.assertLogs(log, logging.DEBUG): - return bus.call_method(methodname, busname, objectpath, - interface, *args) - - def test_call_method_returns(self): - expected_method_return = Unique() - method_args = (Unique(), Unique()) - def func(*args): - self.assertEqual(len(method_args), len(args)) - for marg, arg in zip(method_args, args): - self.assertIs(marg, arg) - return expected_method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface", - *method_args) - self.assertIs(ret, expected_method_return) - - def test_call_method_filters_bool_true(self): - def func(): - return method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - method_return = mock_dbus_python.Boolean(True) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - self.assertTrue(ret) - self.assertNotIsInstance(ret, mock_dbus_python.Boolean) - - def test_call_method_filters_bool_false(self): - def func(): - return method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - method_return = mock_dbus_python.Boolean(False) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - self.assertFalse(ret) - self.assertNotIsInstance(ret, mock_dbus_python.Boolean) - - def test_call_method_filters_objectpath(self): - def func(): - return method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - method_return = mock_dbus_python.ObjectPath("objectpath") - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - self.assertEqual("objectpath", ret) - self.assertIsNot("objectpath", ret) - self.assertNotIsInstance(ret, mock_dbus_python.ObjectPath) - - def test_call_method_filters_booleans_in_dict(self): - def func(): - return method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - method_return = mock_dbus_python.Dictionary( - {mock_dbus_python.Boolean(True): - mock_dbus_python.Boolean(False), - mock_dbus_python.Boolean(False): - mock_dbus_python.Boolean(True)}) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - expected_method_return = {True: False, - False: True} - self.assertEqual(expected_method_return, ret) - self.assertNotIsInstance(ret, mock_dbus_python.Dictionary) - - def test_call_method_filters_objectpaths_in_dict(self): - def func(): - return method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - method_return = mock_dbus_python.Dictionary( - {mock_dbus_python.ObjectPath("objectpath_key_1"): - mock_dbus_python.ObjectPath("objectpath_value_1"), - mock_dbus_python.ObjectPath("objectpath_key_2"): - mock_dbus_python.ObjectPath("objectpath_value_2")}) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - expected_method_return = {str(key): str(value) - for key, value in - method_return.items()} - self.assertEqual(expected_method_return, ret) - self.assertIsInstance(ret, dict) - self.assertNotIsInstance(ret, mock_dbus_python.Dictionary) - - def test_call_method_filters_dict_in_dict(self): - def func(): - return method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - method_return = mock_dbus_python.Dictionary( - {"key1": mock_dbus_python.Dictionary({"key11": "value11", - "key12": "value12"}), - "key2": mock_dbus_python.Dictionary({"key21": "value21", - "key22": "value22"})}) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - expected_method_return = { - "key1": {"key11": "value11", - "key12": "value12"}, - "key2": {"key21": "value21", - "key22": "value22"}, - } - self.assertEqual(expected_method_return, ret) - self.assertIsInstance(ret, dict) - self.assertNotIsInstance(ret, mock_dbus_python.Dictionary) - for key, value in ret.items(): - self.assertIsInstance(value, dict) - self.assertEqual(expected_method_return[key], value) - self.assertNotIsInstance(value, - mock_dbus_python.Dictionary) - - def test_call_method_filters_dict_three_deep(self): - def func(): - return method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - method_return = mock_dbus_python.Dictionary( - {"key1": - mock_dbus_python.Dictionary( - {"key2": - mock_dbus_python.Dictionary( - {"key3": - mock_dbus_python.Boolean(True), - }), - }), - }) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - expected_method_return = {"key1": {"key2": {"key3": True}}} - self.assertEqual(expected_method_return, ret) - self.assertIsInstance(ret, dict) - self.assertNotIsInstance(ret, mock_dbus_python.Dictionary) - self.assertIsInstance(ret["key1"], dict) - self.assertNotIsInstance(ret["key1"], - mock_dbus_python.Dictionary) - self.assertIsInstance(ret["key1"]["key2"], dict) - self.assertNotIsInstance(ret["key1"]["key2"], - mock_dbus_python.Dictionary) - self.assertTrue(ret["key1"]["key2"]["key3"]) - self.assertNotIsInstance(ret["key1"]["key2"]["key3"], - mock_dbus_python.Boolean) - - def test_call_method_handles_exception(self): - dbus_logger = logging.getLogger("dbus.proxies") - - def func(): - dbus_logger.error("Test") - raise mock_dbus_python.exceptions.DBusException() - - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - - class CountingHandler(logging.Handler): - count = 0 - def emit(self, record): - self.count += 1 - - counting_handler = CountingHandler() - - dbus_logger.addHandler(counting_handler) - - try: - with self.assertRaises(dbus.Error) as e: - self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - finally: - dbus_logger.removeFilter(counting_handler) - - self.assertNotIsInstance(e, dbus.ConnectFailed) - - # Make sure the dbus logger was suppressed - self.assertEqual(0, counting_handler.count) - - def test_Set_Secret_sends_bytearray(self): - ret = [None] - def func(*args, **kwargs): - ret[0] = (args, kwargs) - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - bus.set_client_property("objectpath", "Secret", "value") - expected_call = (("se.recompile.Mandos.Client", "Secret", - mock_dbus_python.ByteArray(b"value")), - {"dbus_interface": - "org.freedesktop.DBus.Properties"}) - self.assertEqual(expected_call, ret[0]) - if sys.version_info.major == 2: - self.assertIsInstance(ret[0][0][-1], - mock_dbus_python.ByteArray) - - def test_get_object_converts_to_correct_exception(self): - bus = dbus_python_adapter.SystemBus( - self.fake_dbus_python_raises_exception_on_connect) - with self.assertRaises(dbus.ConnectFailed): - self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - - class fake_dbus_python_raises_exception_on_connect(object): - """fake dbus-python module""" - class exceptions(object): - """Pseudo-namespace""" - class DBusException(Exception): - pass - - @classmethod - def SystemBus(cls): - def get_object(busname, objectpath): - raise cls.exceptions.DBusException() - Bus = collections.namedtuple("Bus", ["get_object"]) - return Bus(get_object=get_object) - - -class Test_dbus_python_adapter_CachingBus(unittest.TestCase): - class mock_dbus_python(object): - """mock dbus-python modules""" - class SystemBus(object): - @staticmethod - def get_object(busname, objectpath): - return Unique() - - def setUp(self): - self.bus = dbus_python_adapter.CachingBus( - self.mock_dbus_python) - - def test_returns_distinct_objectpaths(self): - obj1 = self.bus.get_object("busname", "objectpath1") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get_object("busname", "objectpath2") - self.assertIsInstance(obj2, Unique) - self.assertIsNot(obj1, obj2) - - def test_returns_distinct_busnames(self): - obj1 = self.bus.get_object("busname1", "objectpath") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get_object("busname2", "objectpath") - self.assertIsInstance(obj2, Unique) - self.assertIsNot(obj1, obj2) - - def test_returns_distinct_both(self): - obj1 = self.bus.get_object("busname1", "objectpath") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get_object("busname2", "objectpath") - self.assertIsInstance(obj2, Unique) - self.assertIsNot(obj1, obj2) - - def test_returns_same(self): - obj1 = self.bus.get_object("busname", "objectpath") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get_object("busname", "objectpath") - self.assertIsInstance(obj2, Unique) - self.assertIs(obj1, obj2) - - def test_returns_same_old(self): - obj1 = self.bus.get_object("busname1", "objectpath1") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get_object("busname2", "objectpath2") - self.assertIsInstance(obj2, Unique) - obj1b = self.bus.get_object("busname1", "objectpath1") - self.assertIsInstance(obj1b, Unique) - self.assertIsNot(obj1, obj2) - self.assertIsNot(obj2, obj1b) - self.assertIs(obj1, obj1b) - - -class Test_pydbus_adapter_SystemBus(TestCaseWithAssertLogs): - - def Stub_pydbus_func(self, func): - class stub_pydbus(object): - """stub pydbus module""" - class SystemBus(object): - @staticmethod - def get(busname, objectpath): - DBusObject = collections.namedtuple( - "DBusObject", ("methodname",)) - return {"interface": - DBusObject(methodname=func)} - return stub_pydbus - - def call_method(self, bus, methodname, busname, objectpath, - interface, *args): - with self.assertLogs(log, logging.DEBUG): - return bus.call_method(methodname, busname, objectpath, - interface, *args) - - def test_call_method_returns(self): - expected_method_return = Unique() - method_args = (Unique(), Unique()) - def func(*args): - self.assertEqual(len(method_args), len(args)) - for marg, arg in zip(method_args, args): - self.assertIs(marg, arg) - return expected_method_return - stub_pydbus = self.Stub_pydbus_func(func) - bus = pydbus_adapter.SystemBus(stub_pydbus) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface", - *method_args) - self.assertIs(ret, expected_method_return) - - def test_call_method_handles_exception(self): - dbus_logger = logging.getLogger("dbus.proxies") - - def func(): - raise gi.repository.GLib.Error() - - stub_pydbus = self.Stub_pydbus_func(func) - bus = pydbus_adapter.SystemBus(stub_pydbus) - - with self.assertRaises(dbus.Error) as e: - self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - - self.assertNotIsInstance(e, dbus.ConnectFailed) - - def test_get_converts_to_correct_exception(self): - bus = pydbus_adapter.SystemBus( - self.fake_pydbus_raises_exception_on_connect) - with self.assertRaises(dbus.ConnectFailed): - self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - - class fake_pydbus_raises_exception_on_connect(object): - """fake dbus-python module""" - @classmethod - def SystemBus(cls): - def get(busname, objectpath): - raise gi.repository.GLib.Error() - Bus = collections.namedtuple("Bus", ["get"]) - return Bus(get=get) - - def test_set_property_uses_setattr(self): - class Object(object): - pass - obj = Object() - class pydbus_spy(object): - class SystemBus(object): - @staticmethod - def get(busname, objectpath): - return {"interface": obj} - bus = pydbus_adapter.SystemBus(pydbus_spy) - value = Unique() - bus.set_property("busname", "objectpath", "interface", "key", - value) - self.assertIs(value, obj.key) - - def test_get_suppresses_xml_deprecation_warning(self): - if sys.version_info.major >= 3: - return - class stub_pydbus_get(object): - class SystemBus(object): - @staticmethod - def get(busname, objectpath): - warnings.warn_explicit( - "deprecated", DeprecationWarning, - "xml.etree.ElementTree", 0) - bus = pydbus_adapter.SystemBus(stub_pydbus_get) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - bus.get("busname", "objectpath") - self.assertEqual(0, len(w)) - - -class Test_pydbus_adapter_CachingBus(unittest.TestCase): - class stub_pydbus(object): - """stub pydbus module""" - class SystemBus(object): - @staticmethod - def get(busname, objectpath): - return Unique() - - def setUp(self): - self.bus = pydbus_adapter.CachingBus(self.stub_pydbus) - - def test_returns_distinct_objectpaths(self): - obj1 = self.bus.get("busname", "objectpath1") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get("busname", "objectpath2") - self.assertIsInstance(obj2, Unique) - self.assertIsNot(obj1, obj2) - - def test_returns_distinct_busnames(self): - obj1 = self.bus.get("busname1", "objectpath") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get("busname2", "objectpath") - self.assertIsInstance(obj2, Unique) - self.assertIsNot(obj1, obj2) - - def test_returns_distinct_both(self): - obj1 = self.bus.get("busname1", "objectpath") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get("busname2", "objectpath") - self.assertIsInstance(obj2, Unique) - self.assertIsNot(obj1, obj2) - - def test_returns_same(self): - obj1 = self.bus.get("busname", "objectpath") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get("busname", "objectpath") - self.assertIsInstance(obj2, Unique) - self.assertIs(obj1, obj2) - - def test_returns_same_old(self): - obj1 = self.bus.get("busname1", "objectpath1") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get("busname2", "objectpath2") - self.assertIsInstance(obj2, Unique) - obj1b = self.bus.get("busname1", "objectpath1") - self.assertIsInstance(obj1b, Unique) - self.assertIsNot(obj1, obj2) - self.assertIsNot(obj2, obj1b) - self.assertIs(obj1, obj1b) - - -class Test_commands_from_options(unittest.TestCase): - - def setUp(self): - self.parser = argparse.ArgumentParser() - add_command_line_options(self.parser) - - def test_is_enabled(self): - self.assert_command_from_args(["--is-enabled", "client"], - command.IsEnabled) - - def assert_command_from_args(self, args, command_cls, 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("--verbose".split(), - command.PrintTable, - clients=[], - verbose=True) - - 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) - for clientpath in self.bus.clients: - self.assertIn(("Approve", busname, clientpath, - client_interface, (True,)), self.bus.calls) - - def test_Deny(self): - busname = "se.recompile.Mandos" - client_interface = "se.recompile.Mandos.Client" - command.Deny().run(self.bus.clients, self.bus) - for clientpath in self.bus.clients: - self.assertIn(("Approve", busname, clientpath, - client_interface, (False,)), - self.bus.calls) - - def test_Remove(self): - command.Remove().run(self.bus.clients, self.bus) - for clientpath in self.bus.clients: - self.assertIn(("RemoveClient", dbus_busname, - dbus_server_path, dbus_server_interface, - (clientpath,)), self.bus.calls) - - expected_json = { - "foo": { - "Name": "foo", - "KeyID": ("92ed150794387c03ce684574b1139a65" - "94a34f895daaaf09fd8ea90a27cddb12"), - "Host": "foo.example.org", - "Enabled": True, - "Timeout": 300000, - "LastCheckedOK": "2019-02-03T00:00:00", - "Created": "2019-01-02T00:00:00", - "Interval": 120000, - "Fingerprint": ("778827225BA7DE539C5A" - "7CFA59CFF7CDBD9A5920"), - "CheckerRunning": False, - "LastEnabled": "2019-01-03T00:00:00", - "ApprovalPending": False, - "ApprovedByDefault": True, - "LastApprovalRequest": "", - "ApprovalDelay": 0, - "ApprovalDuration": 1000, - "Checker": "fping -q -- %(host)s", - "ExtendedTimeout": 900000, - "Expires": "2019-02-04T00:00:00", - "LastCheckerStatus": 0, - }, - "barbar": { - "Name": "barbar", - "KeyID": ("0558568eedd67d622f5c83b35a115f79" - "6ab612cff5ad227247e46c2b020f441c"), - "Host": "192.0.2.3", - "Enabled": True, - "Timeout": 300000, - "LastCheckedOK": "2019-02-04T00:00:00", - "Created": "2019-01-03T00:00:00", - "Interval": 120000, - "Fingerprint": ("3E393AEAEFB84C7E89E2" - "F547B3A107558FCA3A27"), - "CheckerRunning": True, - "LastEnabled": "2019-01-04T00:00:00", - "ApprovalPending": False, - "ApprovedByDefault": False, - "LastApprovalRequest": "2019-01-03T00:00:00", - "ApprovalDelay": 30000, - "ApprovalDuration": 93785000, - "Checker": ":", - "ExtendedTimeout": 900000, - "Expires": "2019-02-05T00:00:00", - "LastCheckerStatus": -2, - }, - } - - def test_DumpJSON_normal(self): - with self.capture_stdout_to_buffer() as buffer: - command.DumpJSON().run(self.bus.clients) - json_data = json.loads(buffer.getvalue()) - self.assertDictEqual(self.expected_json, json_data) - - @staticmethod - @contextlib.contextmanager - def capture_stdout_to_buffer(): - capture_buffer = io.StringIO() - old_stdout = sys.stdout - sys.stdout = capture_buffer - try: - yield capture_buffer - finally: - sys.stdout = old_stdout - - def test_DumpJSON_one_client(self): - with self.capture_stdout_to_buffer() as buffer: - command.DumpJSON().run(self.bus.one_client) - json_data = json.loads(buffer.getvalue()) - expected_json = {"foo": self.expected_json["foo"]} - self.assertDictEqual(expected_json, json_data) - - def test_PrintTable_normal(self): - with self.capture_stdout_to_buffer() as buffer: - command.PrintTable().run(self.bus.clients) - expected_output = "\n".join(( - "Name Enabled Timeout Last Successful Check", - "foo Yes 00:05:00 2019-02-03T00:00:00 ", - "barbar Yes 00:05:00 2019-02-04T00:00:00 ", - )) + "\n" - self.assertEqual(expected_output, buffer.getvalue()) - - def test_PrintTable_verbose(self): - with self.capture_stdout_to_buffer() as buffer: - command.PrintTable(verbose=True).run(self.bus.clients) - columns = ( - ( - "Name ", - "foo ", - "barbar ", - ),( - "Enabled ", - "Yes ", - "Yes ", - ),( - "Timeout ", - "00:05:00 ", - "00:05:00 ", - ),( - "Last Successful Check ", - "2019-02-03T00:00:00 ", - "2019-02-04T00:00:00 ", - ),( - "Created ", - "2019-01-02T00:00:00 ", - "2019-01-03T00:00:00 ", - ),( - "Interval ", - "00:02:00 ", - "00:02:00 ", - ),( - "Host ", - "foo.example.org ", - "192.0.2.3 ", - ),( - ("Key ID " - " "), - ("92ed150794387c03ce684574b1139a6594a34f895daaaf09fd8" - "ea90a27cddb12 "), - ("0558568eedd67d622f5c83b35a115f796ab612cff5ad227247e" - "46c2b020f441c "), - ),( - "Fingerprint ", - "778827225BA7DE539C5A7CFA59CFF7CDBD9A5920 ", - "3E393AEAEFB84C7E89E2F547B3A107558FCA3A27 ", - ),( - "Check Is Running ", - "No ", - "Yes ", - ),( - "Last Enabled ", - "2019-01-03T00:00:00 ", - "2019-01-04T00:00:00 ", - ),( - "Approval Is Pending ", - "No ", - "No ", - ),( - "Approved By Default ", - "Yes ", - "No ", - ),( - "Last Approval Request ", - " ", - "2019-01-03T00:00:00 ", - ),( - "Approval Delay ", - "00:00:00 ", - "00:00:30 ", - ),( - "Approval Duration ", - "00:00:01 ", - "1T02:03:05 ", - ),( - "Checker ", - "fping -q -- %(host)s ", - ": ", - ),( - "Extended Timeout ", - "00:15:00 ", - "00:15:00 ", - ),( - "Expires ", - "2019-02-04T00:00:00 ", - "2019-02-05T00:00:00 ", - ),( - "Last Checker Status", - "0 ", - "-2 ", - ) - ) - num_lines = max(len(rows) for rows in columns) - expected_output = ("\n".join("".join(rows[line] - for rows in columns) - for line in range(num_lines)) - + "\n") - self.assertEqual(expected_output, buffer.getvalue()) - - def test_PrintTable_one_client(self): - with self.capture_stdout_to_buffer() as buffer: - command.PrintTable().run(self.bus.one_client) - expected_output = "\n".join(( - "Name Enabled Timeout Last Successful Check", - "foo Yes 00:05:00 2019-02-03T00:00:00 ", - )) + "\n" - self.assertEqual(expected_output, buffer.getvalue()) - - -class TestPropertySetterCmd(TestCommand): - """Abstract class for tests of command.PropertySetter classes""" - - def runTest(self): - if not hasattr(self, "command"): - return # Abstract TestCase class - - if hasattr(self, "values_to_set"): - cmd_args = [(value,) for value in self.values_to_set] - values_to_get = getattr(self, "values_to_get", - self.values_to_set) - else: - cmd_args = [() for x in range(len(self.values_to_get))] - values_to_get = self.values_to_get - for value_to_get, cmd_arg in zip(values_to_get, cmd_args): - for clientpath in self.bus.clients: - self.bus.clients[clientpath][self.propname] = ( - Unique()) - self.command(*cmd_arg).run(self.bus.clients, self.bus) - for clientpath in self.bus.clients: - value = (self.bus.clients[clientpath] - [self.propname]) - self.assertNotIsInstance(value, Unique) - self.assertEqual(value_to_get, value) - - -class TestEnableCmd(TestPropertySetterCmd): - command = command.Enable - propname = "Enabled" - values_to_get = [True] - - -class TestDisableCmd(TestPropertySetterCmd): - command = command.Disable - propname = "Enabled" - values_to_get = [False] - - -class TestBumpTimeoutCmd(TestPropertySetterCmd): - command = command.BumpTimeout - propname = "LastCheckedOK" - values_to_get = [""] - - -class TestStartCheckerCmd(TestPropertySetterCmd): - command = command.StartChecker - propname = "CheckerRunning" - values_to_get = [True] - - -class TestStopCheckerCmd(TestPropertySetterCmd): - command = command.StopChecker - propname = "CheckerRunning" - values_to_get = [False] - - -class TestApproveByDefaultCmd(TestPropertySetterCmd): - command = command.ApproveByDefault - propname = "ApprovedByDefault" - values_to_get = [True] - - -class TestDenyByDefaultCmd(TestPropertySetterCmd): - command = command.DenyByDefault - propname = "ApprovedByDefault" - values_to_get = [False] - - -class TestSetCheckerCmd(TestPropertySetterCmd): - command = command.SetChecker - propname = "Checker" - values_to_set = ["", ":", "fping -q -- %s"] - - -class TestSetHostCmd(TestPropertySetterCmd): - command = command.SetHost - propname = "Host" - values_to_set = ["192.0.2.3", "client.example.org"] - - -class TestSetSecretCmd(TestPropertySetterCmd): - command = command.SetSecret - propname = "Secret" - values_to_set = [io.BytesIO(b""), - io.BytesIO(b"secret\0xyzzy\nbar")] - values_to_get = [f.getvalue() for f in values_to_set] - - -class TestSetTimeoutCmd(TestPropertySetterCmd): - command = command.SetTimeout - propname = "Timeout" - values_to_set = [datetime.timedelta(), - datetime.timedelta(minutes=5), - datetime.timedelta(seconds=1), - datetime.timedelta(weeks=1), - datetime.timedelta(weeks=52)] - values_to_get = [dt.total_seconds()*1000 for dt in values_to_set] - - -class TestSetExtendedTimeoutCmd(TestPropertySetterCmd): - command = command.SetExtendedTimeout - propname = "ExtendedTimeout" - values_to_set = [datetime.timedelta(), - datetime.timedelta(minutes=5), - datetime.timedelta(seconds=1), - datetime.timedelta(weeks=1), - datetime.timedelta(weeks=52)] - values_to_get = [dt.total_seconds()*1000 for dt in values_to_set] - - -class TestSetIntervalCmd(TestPropertySetterCmd): - command = command.SetInterval - propname = "Interval" - values_to_set = [datetime.timedelta(), - datetime.timedelta(minutes=5), - datetime.timedelta(seconds=1), - datetime.timedelta(weeks=1), - datetime.timedelta(weeks=52)] - values_to_get = [dt.total_seconds()*1000 for dt in values_to_set] - - -class TestSetApprovalDelayCmd(TestPropertySetterCmd): - command = command.SetApprovalDelay - propname = "ApprovalDelay" - values_to_set = [datetime.timedelta(), - datetime.timedelta(minutes=5), - datetime.timedelta(seconds=1), - datetime.timedelta(weeks=1), - datetime.timedelta(weeks=52)] - values_to_get = [dt.total_seconds()*1000 for dt in values_to_set] - - -class TestSetApprovalDurationCmd(TestPropertySetterCmd): - command = command.SetApprovalDuration - propname = "ApprovalDuration" - values_to_set = [datetime.timedelta(), - datetime.timedelta(minutes=5), - datetime.timedelta(seconds=1), - datetime.timedelta(weeks=1), - datetime.timedelta(weeks=52)] - values_to_get = [dt.total_seconds()*1000 for dt in values_to_set] - - - -def should_only_run_tests(): - parser = argparse.ArgumentParser(add_help=False) - parser.add_argument("--check", action='store_true') - args, unknown_args = parser.parse_known_args() - run_tests = args.check - if run_tests: - # Remove --check argument from sys.argv - sys.argv[1:] = unknown_args - return run_tests - -# Add all tests from doctest strings -def load_tests(loader, tests, none): - import doctest - tests.addTests(doctest.DocTestSuite()) - return tests - -if __name__ == "__main__": - try: - if should_only_run_tests(): - # Call using ./tdd-python-script --check [--verbose] - unittest.main() - else: - main() - finally: - logging.shutdown() + raise ValueError + except (ValueError, IndexError): + raise ValueError + timevalue += delta + return timevalue + +def print_clients(clients): + def valuetostring(value, keyword): + if type(value) is dbus.Boolean: + return u"Yes" if value else u"No" + if keyword in (u"timeout", u"interval"): + return milliseconds_to_string(value) + return unicode(value) + + # Create format string to print table rows + format_string = u' '.join(u'%%-%ds' % + max(len(tablewords[key]), + max(len(valuetostring(client[key], key)) + for client in + clients)) + for key in keywords) + # Print header line + print format_string % tuple(tablewords[key] for key in keywords) + for client in clients: + print format_string % tuple(valuetostring(client[key], key) + for key in keywords) + +parser = OptionParser(version = "%%prog %s" % version) +parser.add_option("-a", "--all", action="store_true", + help="Print all fields") +parser.add_option("-e", "--enable", action="store_true", + help="Enable client") +parser.add_option("-d", "--disable", action="store_true", + help="disable client") +parser.add_option("-b", "--bump-timeout", action="store_true", + help="Bump timeout for client") +parser.add_option("--start-checker", action="store_true", + help="Start checker for client") +parser.add_option("--stop-checker", action="store_true", + help="Stop checker for client") +parser.add_option("-V", "--is-enabled", action="store_true", + help="Check if client is enabled") +parser.add_option("-r", "--remove", action="store_true", + help="Remove client") +parser.add_option("-c", "--checker", type="string", + help="Set checker command for client") +parser.add_option("-t", "--timeout", type="string", + help="Set timeout for client") +parser.add_option("-i", "--interval", type="string", + help="Set checker interval for client") +parser.add_option("-H", "--host", type="string", + help="Set host for client") +parser.add_option("-s", "--secret", type="string", + help="Set password blob (file) for client") +options, client_names = parser.parse_args() + +# Compile list of clients to process +clients=[] +for name in client_names: + for path, client in mandos_clients.iteritems(): + if client['name'] == name: + client_objc = bus.get_object(busname, path) + clients.append(client_objc) + break + else: + print >> sys.stderr, "Client not found on server: %r" % name + sys.exit(1) + +if not clients and mandos_clients.values(): + keywords = defaultkeywords + if options.all: + keywords = ('name', 'enabled', 'timeout', 'last_checked_ok', + 'created', 'interval', 'host', 'fingerprint', + 'checker_running', 'last_enabled', 'checker') + print_clients(mandos_clients.values()) + +# Process each client in the list by all selected options +for client in clients: + if options.remove: + mandos_serv.RemoveClient(client.__dbus_object_path__) + if options.enable: + client.Enable(dbus_interface=client_interface) + if options.disable: + client.Disable(dbus_interface=client_interface) + if options.bump_timeout: + client.CheckedOK(dbus_interface=client_interface) + if options.start_checker: + client.StartChecker(dbus_interface=client_interface) + if options.stop_checker: + client.StopChecker(dbus_interface=client_interface) + if options.is_enabled: + sys.exit(0 if client.Get(client_interface, + u"enabled", + dbus_interface=dbus.PROPERTIES_IFACE) + else 1) + if options.checker: + client.Set(client_interface, u"checker", options.checker, + dbus_interface=dbus.PROPERTIES_IFACE) + if options.host: + client.Set(client_interface, u"host", options.host, + dbus_interface=dbus.PROPERTIES_IFACE) + if options.interval: + client.Set(client_interface, u"interval", + timedelta_to_milliseconds + (string_to_delta(options.interval)), + dbus_interface=dbus.PROPERTIES_IFACE) + if options.timeout: + client.Set(client_interface, u"timeout", + timedelta_to_milliseconds(string_to_delta + (options.timeout)), + dbus_interface=dbus.PROPERTIES_IFACE) + if options.secret: + client.Set(client_interface, u"secret", + dbus.ByteArray(open(options.secret, u'rb').read()), + dbus_interface=dbus.PROPERTIES_IFACE) === removed file 'mandos-ctl.xml' --- mandos-ctl.xml 2019-03-08 23:55:34 +0000 +++ mandos-ctl.xml 1970-01-01 00:00:00 +0000 @@ -1,644 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 - Teddy Hogeborn - Björn Påhlsson - - -
- - - &COMMANDNAME; - 8 - - - - &COMMANDNAME; - - Control or query the operation of the Mandos server - - - - - - &COMMANDNAME; - - - - - - - - - - - CLIENT - - - - - &COMMANDNAME; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CLIENT - - - - - &COMMANDNAME; - - - - - - - - - - - - - - - CLIENT - - - - - &COMMANDNAME; - - - - - - CLIENT - - - &COMMANDNAME; - - - - - - - &COMMANDNAME; - - - - - - - &COMMANDNAME; - - - - - - DESCRIPTION - - &COMMANDNAME; is a program to control or - query the operation of the Mandos server - mandos8. - - - This program can be used to change client settings, approve or - deny client requests, and to remove clients from the server. - - - - - 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 - - - - - - - - Show a help message and exit - - - - - - - - - - Enable client(s). An enabled client will be eligble to - receive its secret. - - - - - - - - - - Disable client(s). A disabled client will not be eligble - to receive its secret, and no checkers will be started for - it. - - - - - - - - - Bump the timeout of the specified client(s), just as if a - checker had completed successfully for it/them. - - - - - - - - - Start a new checker now for the specified client(s). - - - - - - - - - Stop any running checker for the specified client(s). - - - - - - - - - - Remove the specified client(s) from the server. - - - - - - - - - - Set the checker option of the specified - client(s); see mandos-clients.conf5. - - - - - - - - - - Set the timeout option of the specified - client(s); see mandos-clients.conf5. - - - - - - - - - Set the extended_timeout option of the - specified client(s); see mandos-clients.conf5. - - - - - - - - - - Set the interval option of the - specified client(s); see mandos-clients.conf5. - - - - - - - - - - Set the approved_by_default option of - the specified client(s) to True or - False, respectively; see - mandos-clients.conf5. - - - - - - - - - Set the approval_delay option of the - specified client(s); see mandos-clients.conf5. - - - - - - - - - Set the approval_duration option of the - specified client(s); see mandos-clients.conf5. - - - - - - - - - - Set the host option of the specified - client(s); see mandos-clients.conf5. - - - - - - - - - - Set the secfile option of the specified - client(s); see mandos-clients.conf5. - - - - - - - - - - Approve client(s) if currently waiting for approval. - - - - - - - - - - Deny client(s) if currently waiting for approval. - - - - - - - - - - Make the client-modifying options modify all clients. - - - - - - - - - - Show all client settings, not just a subset. - - - - - - - - - - Dump client settings as JSON to standard output. - - - - - - - - - - Check if a single client is enabled or not, and exit with - a successful exit status only if the client is enabled. - - - - - - - - - Show debug output; currently, this means show D-Bus calls. - - - - - - - - - Run self-tests. This includes any unit tests, etc. - - - - - - - - - OVERVIEW - - - 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. - - - - - EXIT STATUS - - If the option is used, the exit - status will be 0 only if the specified client is enabled. - - - - - BUGS - - - - - EXAMPLE - - - To list all clients: - - - &COMMANDNAME; - - - - - - To list all settings for the clients - named foo1.example.org and foo2.example.org: - - - - -&COMMANDNAME; --verbose foo1.example.org foo2.example.org - - - - - - - To enable all clients: - - - &COMMANDNAME; --enable --all - - - - - - To change timeout and interval value for the clients - named foo1.example.org and foo2.example.org: - - - - -&COMMANDNAME; --timeout="PT5M" --interval="PT1M" foo1.example.org foo2.example.org - - - - - - - To approve all clients currently waiting for it: - - - &COMMANDNAME; --approve --all - - - - - - SECURITY - - This program must be permitted to access the Mandos server via - the D-Bus interface. This normally requires the root user, but - could be configured otherwise by reconfiguring the D-Bus server. - - - - - SEE ALSO - - intro - 8mandos, - mandos - 8, - mandos-clients.conf - 5, - mandos-monitor - 8 - - - -
- - - - - === modified file 'mandos-keygen' --- mandos-keygen 2019-07-17 23:37:13 +0000 +++ mandos-keygen 2009-11-19 18:31:28 +0000 @@ -1,42 +1,38 @@ #!/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,2009 Teddy Hogeborn +# Copyright © 2008,2009 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 . +# Contact the authors at . # -VERSION="1.8.4" +VERSION="1.0.14" KEYDIR="/etc/keys/mandos" -KEYTYPE=RSA -KEYLENGTH=4096 -SUBKEYTYPE=RSA -SUBKEYLENGTH=4096 +KEYTYPE=DSA +KEYLENGTH=2048 +SUBKEYTYPE=ELG-E +SUBKEYLENGTH=2048 KEYNAME="`hostname --fqdn 2>/dev/null || hostname`" KEYEMAIL="" -KEYCOMMENT="" +KEYCOMMENT="Mandos client key" KEYEXPIRE=0 -TLS_KEYTYPE=ed25519 FORCE=no -SSH=yes KEYCOMMENT_ORIG="$KEYCOMMENT" mode=keygen @@ -45,12 +41,12 @@ 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:f \ + --longoptions version,help,password,passfile:,dir:,type:,length:,subtype:,sublength:,name:,email:,comment:,expire:,force \ --name "$0" -- "$@"` help(){ -basename="`basename "$0"`" +basename="`basename $0`" cat <&2 + echo "Unknown arguments: '$@'" >&2 exit 1 fi 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 +159,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 +175,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,15 +189,12 @@ trap " set +e; \ test -n \"$SECFILE\" && shred --remove \"$SECFILE\"; \ -test -n \"$TLS_PRIVKEYTMP\" && shred --remove \"$TLS_PRIVKEYTMP\"; \ -shred --remove \"$RINGDIR\"/sec* 2>/dev/null; +shred --remove \"$RINGDIR\"/sec*; test -n \"$BATCHFILE\" && rm --force \"$BATCHFILE\"; \ rm --recursive --force \"$RINGDIR\"; -tty --quiet && stty echo; \ +stty echo; \ " EXIT -set -e - umask 077 if [ "$mode" = keygen ]; then @@ -218,10 +202,10 @@ cat >"$BATCHFILE" <<-EOF Key-Type: $KEYTYPE Key-Length: $KEYLENGTH - Key-Usage: sign,auth + #Key-Usage: encrypt,sign,auth Subkey-Type: $SUBKEYTYPE Subkey-Length: $SUBKEYLENGTH - Subkey-Usage: encrypt + #Subkey-Usage: encrypt,sign,auth Name-Real: $KEYNAME $KEYCOMMENTLINE $KEYEMAILLINE @@ -230,7 +214,6 @@ #Handle: #%pubring pubring.gpg #%secring secring.gpg - %no-protection %commit EOF @@ -243,45 +226,7 @@ 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 - gpg --quiet --batch --no-tty --no-options --enable-dsa2 \ - --homedir "$RINGDIR" \ - --import-ownertrust < /dev/null # Generate a new key in the key rings gpg --quiet --batch --no-tty --no-options --enable-dsa2 \ --homedir "$RINGDIR" --trust-model always \ @@ -296,7 +241,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 @@ -323,30 +268,6 @@ fi if [ "$mode" = password ]; then - - # Make SSH be 0 or 1 - case "$SSH" in - [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]) SSH=1;; - [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|*) SSH=0;; - esac - - if [ $SSH -eq 1 ]; then - for ssh_keytype in ecdsa-sha2-nistp256 ed25519 rsa; do - set +e - ssh_fingerprint="`ssh-keyscan -t $ssh_keytype localhost 2>/dev/null`" - err=$? - set -e - if [ $err -ne 0 ]; then - ssh_fingerprint="" - continue - fi - if [ -n "$ssh_fingerprint" ]; then - ssh_fingerprint="${ssh_fingerprint#localhost }" - break - fi - done - fi - # Import key into temporary key rings gpg --quiet --batch --no-tty --no-options --enable-dsa2 \ --homedir "$RINGDIR" --trust-model always --armor \ @@ -357,70 +278,43 @@ # Get fingerprint of key FINGERPRINT="`gpg --quiet --batch --no-tty --no-options \ - --enable-dsa2 --homedir "$RINGDIR" --trust-model always \ + --enable-dsa2 --homedir \"$RINGDIR\" --trust-model always \ --fingerprint --with-colons \ | sed --quiet \ --expression='/^fpr:/{s/^fpr:.*:\\([0-9A-Z]*\\):\$/\\1/p;q}'`" 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" + if [ -n "$PASSFILE" ]; then + cat "$PASSFILE" + else + stty -echo + echo -n "Enter passphrase: " >&2 + first="$(head --lines=1 | tr --delete '\n')" + echo -n -e "\nRepeat passphrase: " >&2 + second="$(head --lines=1 | tr --delete '\n')" + echo >&2 + stty echo + if [ "$first" != "$second" ]; then + echo -e "Passphrase mismatch" >&2 + touch "$RINGDIR"/mismatch else - tty --quiet && stty -echo - echo -n "Enter passphrase: " >/dev/tty - read -r first - tty --quiet && echo >&2 - echo -n "Repeat passphrase: " >/dev/tty - read -r second - if tty --quiet; then - echo >&2 - stty echo - fi - if [ "$first" != "$second" ]; then - echo "Passphrase mismatch" >&2 - touch "$RINGDIR"/mismatch - else - echo -n "$first" - fi - fi | gpg --quiet --batch --no-tty --no-options --enable-dsa2 \ - --homedir "$RINGDIR" --trust-model always --armor \ - --encrypt --sign --recipient "$FINGERPRINT" --comment \ - "$FILECOMMENT" > "$SECFILE" - if [ -e "$RINGDIR"/mismatch ]; then - rm --force "$RINGDIR"/mismatch - if tty --quiet; then - > "$SECFILE" - else - exit 1 - fi + echo -n "$first" fi - done + fi | gpg --quiet --batch --no-tty --no-options --enable-dsa2 \ + --homedir "$RINGDIR" --trust-model always --armor --encrypt \ + --sign --recipient "$FINGERPRINT" --comment "$FILECOMMENT" \ + > "$SECFILE" + if [ -e "$RINGDIR"/mismatch ]; then + rm --force "$RINGDIR"/mismatch + exit 1 + fi cat <<-EOF [$KEYNAME] host = $KEYNAME - EOF - if [ -n "$KEY_ID" ]; then - echo "key_id = $KEY_ID" - fi - cat <<-EOF fingerprint = $FINGERPRINT secret = EOF @@ -433,10 +327,6 @@ /^[^-]/s/^/ /p } }' < "$SECFILE" - if [ -n "$ssh_fingerprint" ]; then - echo 'checker = ssh-keyscan -t '"$ssh_keytype"' %%(host)s 2>/dev/null | grep --fixed-strings --line-regexp --quiet --regexp=%%(host)s" %(ssh_fingerprint)s"' - echo "ssh_fingerprint = ${ssh_fingerprint}" - fi fi trap - EXIT @@ -444,8 +334,8 @@ 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 +shred --remove "$RINGDIR"/sec* rm --recursive --force "$RINGDIR" === modified file 'mandos-keygen.xml' --- mandos-keygen.xml 2019-07-18 00:02:43 +0000 +++ mandos-keygen.xml 2009-01-04 21:54:55 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -19,30 +19,20 @@ Björn Påhlsson
- belorn@recompile.se + belorn@fukt.bsnet.se
Teddy Hogeborn
- teddy@recompile.se + teddy@fukt.bsnet.se
2008 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -127,17 +117,7 @@ TIME - - - - - - - - - + &COMMANDNAME; @@ -163,10 +143,6 @@ - - - - &COMMANDNAME; @@ -188,12 +164,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 +212,8 @@ DIRECTORY - Target directory for key files. Default is /etc/keys/mandos. + Target directory for key files. Default is + /etc/mandos. @@ -249,7 +225,7 @@ TYPE - OpenPGP key type. Default is RSA. + Key type. Default is DSA. @@ -261,7 +237,7 @@ BITS - OpenPGP key length in bits. Default is 4096. + Key length in bits. Default is 2048. @@ -273,7 +249,8 @@ KEYTYPE - OpenPGP subkey type. Default is RSA + Subkey type. Default is ELG-E (Elgamal + encryption-only). @@ -285,7 +262,7 @@ BITS - OpenPGP subkey length in bits. Default is 4096. + Subkey length in bits. Default is 2048. @@ -309,7 +286,8 @@ TEXT - Comment field for key. Default is empty. + Comment field for key. The default value is + Mandos client key. @@ -329,18 +307,6 @@ - - - - - TLS key type. Default is ed25519 - - - - - @@ -355,17 +321,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,24 +341,7 @@ The same as , but read from - FILE, not the terminal, and - white space is not stripped from the password in any way. - - - - - - - - - When or - is given, this option will - prevent &COMMANDNAME; from calling - ssh-keyscan to get an SSH fingerprint - for this host and, if successful, output suitable config - options to use this fingerprint as a - option in the output. This is - otherwise the default behavior. + FILE, not the terminal. @@ -405,9 +352,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 +392,7 @@ - /etc/keys/mandos/seckey.txt + /etc/mandos/seckey.txt OpenPGP secret key file which will be created or @@ -454,7 +401,7 @@ - /etc/keys/mandos/pubkey.txt + /etc/mandos/pubkey.txt OpenPGP public key file which will be created or @@ -463,23 +410,7 @@ - /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 + /tmp Temporary files will be written here if @@ -490,17 +421,11 @@ - - 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. - - - + + + + + EXAMPLE @@ -526,9 +451,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 +461,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. @@ -567,8 +492,6 @@ SEE ALSO - intro - 8mandos, gpg 1, mandos-clients.conf @@ -576,9 +499,7 @@ mandos 8, mandos-client - 8mandos, - ssh-keyscan - 1 + 8mandos === modified file 'mandos-monitor' --- mandos-monitor 2019-04-09 20:09:51 +0000 +++ mandos-monitor 2010-09-05 20:19:02 +0000 @@ -1,38 +1,11 @@ #!/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 -# 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 . -# -from __future__ import (division, absolute_import, print_function, - unicode_literals) -try: - from future_builtins import * -except ImportError: - pass +from __future__ import division, absolute_import, with_statement import sys import os +import signal import datetime @@ -40,89 +13,80 @@ import urwid from dbus.mainloop.glib import DBusGMainLoop -from gi.repository import GLib +import gobject import dbus +import UserList + import locale -import logging - -if sys.version_info.major == 2: - str = unicode - -locale.setlocale(locale.LC_ALL, '') - -logging.getLogger('dbus.proxies').setLevel(logging.CRITICAL) +locale.setlocale(locale.LC_ALL, u'') # Some useful constants -domain = 'se.recompile' +domain = 'se.bsnet.fukt' server_interface = domain + '.Mandos' client_interface = domain + '.Mandos.Client' -version = "1.8.4" - -try: - dbus.OBJECT_MANAGER_IFACE -except AttributeError: - dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager" - +version = "1.0.14" + +# Always run in monochrome mode +urwid.curses_display.curses.has_colors = lambda : False + +# Urwid doesn't support blinking, but we want it. Since we have no +# use for underline on its own, we make underline also always blink. +urwid.curses_display.curses.A_UNDERLINE |= ( + urwid.curses_display.curses.A_BLINK) def isoformat_to_datetime(iso): "Parse an ISO 8601 date string to a datetime.datetime()" if not iso: return None - d, t = iso.split("T", 1) - year, month, day = d.split("-", 2) - hour, minute, second = t.split(":", 2) + d, t = iso.split(u"T", 1) + year, month, day = d.split(u"-", 2) + hour, minute, second = t.split(u":", 2) second, fraction = divmod(float(second), 1) return datetime.datetime(int(year), int(month), int(day), int(hour), int(minute), - int(second), # Whole seconds - int(fraction*1000000)) # Microseconds - + 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.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)) - - 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. + def __init__(self, proxy_object=None, *args, **kwargs): + self.proxy = proxy_object # Mandos Client proxy object + + self.properties = dict() + self.proxy.connect_to_signal(u"PropertyChanged", + self.property_changed, + client_interface, + byte_arrays=True) + + self.properties.update( + self.proxy.GetAll(client_interface, + dbus_interface = dbus.PROPERTIES_IFACE)) + super(MandosClientPropertyCache, self).__init__( + proxy_object=proxy_object, *args, **kwargs) + + def property_changed(self, property=None, value=None): + """This is called whenever we get a PropertyChanged signal + It updates the changed property in the "properties" dict. """ # Update properties dict with new value - if interface == client_interface: - self.properties.update(properties) - - def delete(self): - self.property_changed_match.remove() + self.properties[property] = value 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, logger=None, **kwargs): + delete_hook=None, logger=None, *args, **kwargs): # Called on update self.update_hook = update_hook # Called on delete @@ -131,169 +95,151 @@ self.server_proxy_object = server_proxy_object # Logger self.logger = logger - + self._update_timer_callback_tag = None - + self.last_checker_failed = False + # The widget shown normally - self._text_widget = urwid.Text("") + self._text_widget = urwid.Text(u"") # The widget shown when we have focus - self._focus_text_widget = urwid.Text("") - super(MandosClientWidget, self).__init__(**kwargs) + self._focus_text_widget = urwid.Text(u"") + super(MandosClientWidget, self).__init__( + update_hook=update_hook, delete_hook=delete_hook, + *args, **kwargs) self.update() self.opened = False - - self.match_objects = ( - self.proxy.connect_to_signal("CheckerCompleted", - self.checker_completed, - client_interface, - byte_arrays=True), - self.proxy.connect_to_signal("CheckerStarted", - self.checker_started, - client_interface, - byte_arrays=True), - self.proxy.connect_to_signal("GotSecret", - self.got_secret, - client_interface, - byte_arrays=True), - self.proxy.connect_to_signal("NeedApproval", - self.need_approval, - client_interface, - byte_arrays=True), - self.proxy.connect_to_signal("Rejected", - self.rejected, - client_interface, - byte_arrays=True)) - 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 + self.proxy.connect_to_signal(u"CheckerCompleted", + self.checker_completed, + client_interface, + byte_arrays=True) + self.proxy.connect_to_signal(u"CheckerStarted", + self.checker_started, + client_interface, + byte_arrays=True) + self.proxy.connect_to_signal(u"GotSecret", + self.got_secret, + client_interface, + byte_arrays=True) + self.proxy.connect_to_signal(u"Rejected", + self.rejected, + client_interface, + byte_arrays=True) + last_checked_ok = isoformat_to_datetime(self.properties + ["last_checked_ok"]) + if last_checked_ok is None: + self.last_checker_failed = True + else: + self.last_checker_failed = ((datetime.datetime.utcnow() + - last_checked_ok) + > datetime.timedelta + (milliseconds= + self.properties["interval"])) + if self.last_checker_failed: + self._update_timer_callback_tag = (gobject.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: - self.logger('Checker for client {} (command "{}")' - ' succeeded'.format(self.properties["Name"], - command), level=0) + if self.last_checker_failed: + self.last_checker_failed = False + gobject.source_remove(self._update_timer_callback_tag) + self._update_timer_callback_tag = None + self.logger(u'Checker for client %s (command "%s")' + u' was successful' + % (self.properties[u"name"], command)) self.update() return # Checker failed + if not self.last_checker_failed: + self.last_checker_failed = True + self._update_timer_callback_tag = (gobject.timeout_add + (1000, + self.update_timer)) if os.WIFEXITED(condition): - self.logger('Checker for client {} (command "{}") failed' - ' with exit code {}' - .format(self.properties["Name"], command, - os.WEXITSTATUS(condition))) + self.logger(u'Checker for client %s (command "%s")' + u' failed with exit code %s' + % (self.properties[u"name"], command, + os.WEXITSTATUS(condition))) elif os.WIFSIGNALED(condition): - self.logger('Checker for client {} (command "{}") was' - ' killed by signal {}' - .format(self.properties["Name"], command, - os.WTERMSIG(condition))) + self.logger(u'Checker for client %s (command "%s")' + u' was killed by signal %s' + % (self.properties[u"name"], command, + os.WTERMSIG(condition))) + elif os.WCOREDUMP(condition): + self.logger(u'Checker for client %s (command "%s")' + u' dumped core' + % (self.properties[u"name"], command)) + else: + self.logger(u'Checker for client %s completed mysteriously') self.update() - + def checker_started(self, command): - """Server signals that a checker started.""" - self.logger('Client {} started checker "{}"' - .format(self.properties["Name"], - command), level=0) - + self.logger(u'Client %s started checker "%s"' + % (self.properties[u"name"], unicode(command))) + def got_secret(self): - self.logger('Client {} received its secret' - .format(self.properties["Name"])) - - def need_approval(self, timeout, default): - if not default: - message = 'Client {} needs approval within {} seconds' - else: - message = 'Client {} will get its secret in {} seconds' - self.logger(message.format(self.properties["Name"], - timeout/1000)) - - def rejected(self, reason): - self.logger('Client {} was rejected; reason: {}' - .format(self.properties["Name"], reason)) - + self.logger(u'Client %s received its secret' + % self.properties[u"name"]) + + def rejected(self): + self.logger(u'Client %s was rejected' + % self.properties[u"name"]) + def selectable(self): """Make this a "selectable" widget. This overrides the method from urwid.FlowWidget.""" return True - - def rows(self, maxcolrow, focus=False): + + def rows(self, (maxcol,), 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) - + return self.current_widget(focus).rows((maxcol,), 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 = { u"normal": u"standout", + u"bold": u"bold-standout", + u"underline-blink": + u"underline-blink-standout", + u"bold-underline-blink": + u"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"]) - if not self.properties["Enabled"]: - message = "DISABLED" - self.using_timer(False) - elif self.properties["ApprovalPending"]: - timeout = datetime.timedelta( - milliseconds=self.properties["ApprovalDelay"]) - last_approval_request = isoformat_to_datetime( - self.properties["LastApprovalRequest"]) - if last_approval_request is not None: - timer = max(timeout - (datetime.datetime.utcnow() - - last_approval_request), - datetime.timedelta()) - else: - timer = datetime.timedelta() - if self.properties["ApprovedByDefault"]: - message = "Approval in {}. (d)eny?" - else: - message = "Denial in {}. (a)pprove?" - message = message.format(str(timer).rsplit(".", 1)[0]) - self.using_timer(True) - elif self.properties["LastCheckerStatus"] != 0: - # When checker has failed, show timer until client expires - expires = self.properties["Expires"] - if expires == "": - timer = datetime.timedelta(0) - else: - expires = (datetime.datetime.strptime - (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: {}' - .format(str(timer).rsplit(".", 1)[0])) - self.using_timer(True) - else: - message = "enabled" - self.using_timer(False) - self._text = "{}{}".format(base, message) - + self._text = (u'%(name)s: %(enabled)s%(timer)s' + % { u"name": self.properties[u"name"], + u"enabled": + (u"enabled" + if self.properties[u"enabled"] + else u"DISABLED"), + u"timer": (unicode(datetime.timedelta + (milliseconds = + self.properties + [u"timeout"]) + - (datetime.datetime + .utcnow() + - isoformat_to_datetime + (max((self.properties + ["last_checked_ok"] + or + self.properties + ["created"]), + self.properties[u"last_enabled"])))) + if (self.last_checker_failed + and self.properties + [u"enabled"]) + else u"")}) if not urwid.supports_unicode(): self._text = self._text.encode("ascii", "replace") - textlist = [("normal", self._text)] + textlist = [(u"normal", self._text)] self._text_widget.set_text(textlist) self._focus_text_widget.set_text([(with_standout[text[0]], text[1]) @@ -306,84 +252,60 @@ # 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 - """ + "called by gobject" self.update() return True # Keep calling this - - def delete(self, **kwargs): + + def delete(self): if self._update_timer_callback_tag is not None: - GLib.source_remove(self._update_timer_callback_tag) + gobject.source_remove(self._update_timer_callback_tag) self._update_timer_callback_tag = None - for match in self.match_objects: - match.remove() - self.match_objects = () if self.delete_hook is not None: self.delete_hook(self) - return super(MandosClientWidget, self).delete(**kwargs) - - def render(self, maxcolrow, focus=False): + + def render(self, (maxcol,), focus=False): """Render differently if we have focus. This overrides the method from urwid.FlowWidget""" - return self.current_widget(focus).render(maxcolrow, + return self.current_widget(focus).render((maxcol,), focus=focus) - - def keypress(self, maxcolrow, key): + + def keypress(self, (maxcol,), 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) - elif key == "-": - self.proxy.Set(client_interface, "Enabled", False, - ignore_reply=True, - dbus_interface=dbus.PROPERTIES_IFACE) - elif key == "a": - self.proxy.Approve(dbus.Boolean(True, variant_level=1), - dbus_interface=client_interface, - ignore_reply=True) - elif key == "d": - self.proxy.Approve(dbus.Boolean(False, variant_level=1), - dbus_interface=client_interface, - ignore_reply=True) - elif key == "R" or key == "_" or key == "ctrl k": + if key == u"e" or key == u"+": + self.proxy.Enable() + elif key == u"d" or key == u"-": + self.proxy.Disable() + elif key == u"r" or key == u"_" or key == u"ctrl k": self.server_proxy_object.RemoveClient(self.proxy - .object_path, - ignore_reply=True) - elif key == "s": - self.proxy.Set(client_interface, "CheckerRunning", - 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) - elif key == "C": - self.proxy.CheckedOK(dbus_interface=client_interface, - ignore_reply=True) + .object_path) + elif key == u"s": + self.proxy.StartChecker() + elif key == u"S": + self.proxy.StopChecker() + elif key == u"C": + self.proxy.CheckedOK() # xxx -# elif key == "p" or key == "=": +# elif key == u"p" or key == "=": # self.proxy.pause() -# elif key == "u" or key == ":": +# elif key == u"u" or key == ":": # self.proxy.unpause() -# elif key == "RET": +# elif key == u"RET": # self.open() else: return key - - def properties_changed(self, interface, properties, invalidated): - """Call self.update() if any properties changed. + + def property_changed(self, property=None, value=None, + *args, **kwargs): + """Call self.update() if old value is not new value. This overrides the method from MandosClientPropertyCache""" - 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) - for key in old_values): + property_name = unicode(property) + old_value = self.properties.get(property_name) + super(MandosClientWidget, self).property_changed( + property=property, value=value, *args, **kwargs) + if self.properties.get(property_name) != old_value: self.update() @@ -392,10 +314,9 @@ "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)) - if ret in ("up", "down"): + def keypress(self, (maxcol, maxrow), key): + ret = super(ConstrainedListBox, self).keypress((maxcol, maxrow), key) + if ret in (u"up", u"down"): return return ret @@ -404,343 +325,312 @@ """This is the entire user interface - the whole screen with boxes, lists of client widgets, etc. """ - def __init__(self, max_log_length=1000, log_level=1): + def __init__(self, max_log_length=1000): DBusGMainLoop(set_as_default=True) - + self.screen = urwid.curses_display.Screen() - + self.screen.register_palette(( - ("normal", - "default", "default", None), - ("bold", - "bold", "default", "bold"), - ("underline-blink", - "underline,blink", "default", "underline,blink"), - ("standout", - "standout", "default", "standout"), - ("bold-underline-blink", - "bold,underline,blink", "default", - "bold,underline,blink"), - ("bold-standout", - "bold,standout", "default", "bold,standout"), - ("underline-blink-standout", - "underline,blink,standout", "default", - "underline,blink,standout"), - ("bold-underline-blink-standout", - "bold,underline,blink,standout", "default", - "bold,underline,blink,standout"), + (u"normal", + u"default", u"default", None), + (u"bold", + u"default", u"default", u"bold"), + (u"underline-blink", + u"default", u"default", u"underline"), + (u"standout", + u"default", u"default", u"standout"), + (u"bold-underline-blink", + u"default", u"default", (u"bold", u"underline")), + (u"bold-standout", + u"default", u"default", (u"bold", u"standout")), + (u"underline-blink-standout", + u"default", u"default", (u"underline", u"standout")), + (u"bold-underline-blink-standout", + u"default", u"default", (u"bold", u"underline", + u"standout")), )) - + if urwid.supports_unicode(): - self.divider = "─" # \u2500 + self.divider = u"─" # \u2500 + #self.divider = u"━" # \u2501 else: - self.divider = "_" # \u005f - + #self.divider = u"-" # \u002d + self.divider = u"_" # \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.log_wrap = u"any" + self.rebuild() - self.log_message_raw(("bold", - "Mandos Monitor version " + version)) - self.log_message_raw(("bold", - "q: Quit ?: Help")) - + self.log_message_raw((u"bold", + u"Mandos Monitor version " + version)) + self.log_message_raw((u"bold", + u"q: Quit ?: Help")) + self.busname = domain + '.Mandos' - self.main_loop = GLib.MainLoop() - + self.main_loop = gobject.MainLoop() + self.bus = dbus.SystemBus() + mandos_dbus_objc = self.bus.get_object( + self.busname, u"/", follow_name_owner_changes=True) + self.mandos_serv = dbus.Interface(mandos_dbus_objc, + dbus_interface + = server_interface) + try: + mandos_clients = (self.mandos_serv + .GetAllClientsWithProperties()) + except dbus.exceptions.DBusException: + mandos_clients = dbus.Dictionary() + + (self.mandos_serv + .connect_to_signal(u"ClientRemoved", + self.find_and_remove_client, + dbus_interface=server_interface, + byte_arrays=True)) + (self.mandos_serv + .connect_to_signal(u"ClientAdded", + self.add_new_client, + dbus_interface=server_interface, + byte_arrays=True)) + (self.mandos_serv + .connect_to_signal(u"ClientNotFound", + self.client_not_found, + dbus_interface=server_interface, + byte_arrays=True)) + for path, client in mandos_clients.iteritems(): + 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, + logger + =self.log_message), + path=path) + def client_not_found(self, fingerprint, address): - self.log_message("Client with address {} and fingerprint {}" - " could not be found" - .format(address, fingerprint)) - + self.log_message((u"Client with address %s and fingerprint %s" + u" could not be found" % (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.Frame(ConstrainedListBox(self. - clients), - # header=urwid.Divider(), + #self.uilist.append(urwid.ListBox(self.clients)) + self.uilist.append(urwid.Frame(ConstrainedListBox(self.clients), + #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) + pass self.topwidget = urwid.Pile(self.uilist) - - def log_message(self, message, level=1): - """Log message formatted with timestamp""" - if level < self.log_level: - return + + def log_message(self, message): timestamp = datetime.datetime.now().isoformat() - self.log_message_raw("{}: {}".format(timestamp, message), - level=level) - - def log_message_raw(self, markup, level=1): + self.log_message_raw(timestamp + u": " + message) + + def log_message_raw(self, markup): """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-1] - self.logbox.set_focus(len(self.logbox.body.contents)-1, - coming_from="above") + 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=u"above") self.refresh() - + def toggle_log_display(self): """Toggle visibility of the log buffer.""" self.log_visible = not self.log_visible self.rebuild() - self.log_message("Log visibility changed to: {}" - .format(self.log_visible), level=0) - + self.log_message(u"Log visibility changed to: " + + unicode(self.log_visible)) + def change_log_display(self): """Change type of log display. Currently, this toggles wrapping of text lines.""" - if self.log_wrap == "clip": - self.log_wrap = "any" + if self.log_wrap == u"clip": + self.log_wrap = u"any" else: - self.log_wrap = "clip" + self.log_wrap = u"clip" for textwidget in self.log: textwidget.set_wrap_mode(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 + self.log_message(u"Wrap mode: " + self.log_wrap) + + def find_and_remove_client(self, path, name): + """Find an client from its object path and remove it. + + This is connected to the ClientRemoved signal from the Mandos server object.""" - if client_interface not in interfaces: - # Not a Mandos client object; ignore - return try: client = self.clients_dict[path] except KeyError: # not found? - 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. - """ - if client_interface not in ifs_and_props: - # Not a Mandos client object; ignore - return + return + self.remove_client(client, path) + + def add_new_client(self, path): 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, - logger=self.log_message, - 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), path=path) - + def add_client(self, client, path=None): self.clients.append(client) if path is None: path = client.proxy.object_path self.clients_dict[path] = client - self.clients.sort(key=lambda c: c.properties["Name"]) + self.clients.sort(None, lambda c: c.properties[u"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] + if not self.clients_dict: + # Work around bug in Urwid 0.9.8.3 - if a SimpleListWalker + # is completely emptied, we need to recreate it. + self.clients = urwid.SimpleListWalker([]) + self.rebuild() 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.""" - 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) - try: - mandos_clients = (self.mandos_serv - .GetAllClientsWithProperties()) - if not mandos_clients: - self.log_message_raw(("bold", - "Note: Server has no clients.")) - except dbus.exceptions.DBusException: - 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, - byte_arrays=True)) - (self.mandos_serv - .connect_to_signal("InterfacesAdded", - self.add_new_client, - dbus_interface=dbus.OBJECT_MANAGER_IFACE, - byte_arrays=True)) - (self.mandos_serv - .connect_to_signal("ClientNotFound", - self.client_not_found, - dbus_interface=server_interface, - byte_arrays=True)) - 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, - logger=self.log_message), - path=path) - self.refresh() - self._input_callback_tag = (GLib.io_add_watch + self._input_callback_tag = (gobject.io_add_watch (sys.stdin.fileno(), - GLib.IO_IN, + gobject.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) + gobject.source_remove(self._input_callback_tag) self.screen.stop() - + def stop(self): self.main_loop.quit() - + 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 = { u"ctrl n": u"down", # Emacs + u"ctrl p": u"up", # Emacs + u"ctrl v": u"page down", # Emacs + u"meta v": u"page up", # Emacs + u" ": u"page down", # less + u"f": u"page down", # less + u"b": u"page up", # less + u"j": u"down", # vi + u"k": u"up", # vi + } for key in keys: try: key = translations[key] except KeyError: # :-) pass - - if key == "q" or key == "Q": + + if key == u"q" or key == u"Q": self.stop() break - elif key == "window resize": + elif key == u"window resize": self.size = self.screen.get_cols_rows() self.refresh() - elif key == "ctrl l": - self.screen.clear() + elif key == u"\f": # Ctrl-L self.refresh() - elif key == "l" or key == "D": + elif key == u"l" or key == u"D": self.toggle_log_display() self.refresh() - elif key == "w" or key == "i": + elif key == u"w" or key == u"i": self.change_log_display() self.refresh() - elif key == "?" or key == "f1" or key == "esc": + elif key == u"?" or key == u"f1" or key == u"esc": if not self.log_visible: self.log_visible = True self.rebuild() - 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.log_message_raw((u"bold", + u" ". + join((u"q: Quit", + u"?: Help", + u"l: Log window toggle", + u"TAB: Switch window", + u"w: Wrap (log)")))) + self.log_message_raw((u"bold", + u" " + .join((u"Clients:", + u"e: Enable", + u"d: Disable", + u"r: Remove", + u"s: Start new checker", + u"S: Stop checker", + u"C: Checker OK")))) self.refresh() - elif key == "tab": + elif key == u"tab": if self.topwidget.get_focus() is self.logbox: self.topwidget.set_focus(0) else: self.topwidget.set_focus(self.logbox) self.refresh() - elif key == "v": - if self.log_level == 0: - self.log_level = 1 - self.log_message("Verbose mode: Off") - else: - 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 (key == u"end" or key == u"meta >" or key == u"G" + # or key == u">"): + # pass # xxx end-of-buffer + #elif (key == u"home" or key == u"meta <" or key == u"g" + # or key == u"<"): + # pass # xxx beginning-of-buffer + #elif key == u"ctrl e" or key == u"$": + # pass # xxx move-end-of-line + #elif key == u"ctrl a" or key == u"^": + # pass # xxx move-beginning-of-line + #elif key == u"ctrl b" or key == u"meta (" or key == u"h": + # pass # xxx left + #elif key == u"ctrl f" or key == u"meta )" or key == u"l": + # pass # xxx right + #elif key == u"a": + # pass # scroll up log + #elif key == u"z": + # pass # scroll down log elif self.topwidget.selectable(): self.topwidget.keypress(self.size, key) self.refresh() return True - ui = UserInterface() try: ui.run() -except KeyboardInterrupt: - ui.screen.stop() -except Exception as e: - ui.log_message(str(e)) +except Exception, e: + ui.log_message(unicode(e)) ui.screen.stop() raise === removed file 'mandos-monitor.xml' --- mandos-monitor.xml 2019-02-10 04:20:26 +0000 +++ mandos-monitor.xml 1970-01-01 00:00:00 +0000 @@ -1,249 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 - Teddy Hogeborn - Björn Påhlsson - - -
- - - &COMMANDNAME; - 8 - - - - &COMMANDNAME; - - Text-based GUI to control the Mandos server. - - - - - - &COMMANDNAME; - - - - - DESCRIPTION - - &COMMANDNAME; is an interactive program to - monitor and control the operations of the Mandos server (see - mandos8). - - - - - 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. - - - - - OVERVIEW - - - This program is used to monitor and control the Mandos server. - In particular, it can be used to approve Mandos clients which - have been configured to require approval. It also shows all - significant events reported by the Mandos server. - - - - - KEYS - - This program is used to monitor and control the Mandos server. - In particular, it can be used to approve Mandos clients which - have been configured to require approval. It also shows all - significant events reported by the Mandos server. - - - Global Keys - - Keys - Function - - - - q, Q - Quit - - - Ctrl-L - Redraw screen - - - ?, F1 - Show help - - - l, D - Toggle log window - - - TAB - Switch window - - - w, i - Toggle log window line wrap - - - v - Toggle verbose logging - - - Up, Ctrl-P, k - Move up a line - - - Down, Ctrl-N, j - Move down a line - - - PageUp, Meta-V, b - Move up a page - - - PageDown, Ctrl-V, SPACE, f - Move down a page - -
- - Client List Keys - - Keys - Function - - - - + - Enable client - - - - - Disable client - - - a - Approve client - - - d - Deny client - - - R, _, Ctrl-K - Remove client - - - s - Start checker for client - - - S - Stop checker for client - - - C - Force a successful check for this client. - -
-
- - - BUGS - - This program can currently only be used to monitor and control a - Mandos server with the default D-Bus bus name of - se.recompile.Mandos. - - - - - - EXAMPLE - - - This program takes no options: - - - &COMMANDNAME; - - - - - - SECURITY - - This program must be permitted to access the Mandos server via - the D-Bus interface. This normally requires the root user, but - could be configured otherwise by reconfiguring the D-Bus server. - - - - - SEE ALSO - - intro - 8mandos, - mandos - 8, - mandos-ctl - 8 - - - -
- - - - - === modified file 'mandos-options.xml' --- mandos-options.xml 2019-02-09 23:23:26 +0000 +++ mandos-options.xml 2009-02-13 05:38:21 +0000 @@ -48,17 +48,13 @@ 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 gnutls_priority_init + The default is SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP. See + gnutls_priority_init 3 for the syntax. Warning: changing this may make the TLS handshake fail, making server-client - communication impossible. Changing this option may also make the - network traffic decryptable by an attacker. + communication impossible. @@ -90,35 +86,4 @@ advanced users should consider changing this option. - - This option controls whether the server will restore its state - from the last time it ran. Default is to restore last state. - - - - Directory to save (and restore) state in. Default is - /var/lib/mandos. - - - - If this option is used, the server will not create a new network - socket, but will instead use the supplied file descriptor. By - default, the server will create a new network socket. - - - - This option will make the server run in the foreground and not - write a PID file. The default is to not run - in the foreground, except in mode, which - implies this option. - - - - This option controls whether the server will announce its - existence using Zeroconf. Default is to use Zeroconf. If - Zeroconf is not used, a number or a - is required. - - === removed file 'mandos-to-cryptroot-unlock' --- mandos-to-cryptroot-unlock 2018-08-19 14:58:40 +0000 +++ mandos-to-cryptroot-unlock 1970-01-01 00:00:00 +0000 @@ -1,82 +0,0 @@ -#!/bin/sh -# -# Script to get password from plugin-runner to cryptroot-unlock -# -# Copyright © 2018 Teddy Hogeborn -# Copyright © 2018 Björn Påhlsson -# -# This file is part of Mandos. -# -# Mandos is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Mandos is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Mandos. If not, see . -# -# Contact the authors at . - -# This script is made to run in the initramfs, and must not be run in -# the normal system environment. - -# Temporary file for the password -passfile=$(mktemp -p /run -t mandos.XXXXXX) -trap "rm -f -- $passfile 2>/dev/null" EXIT - -# Disable the plugins which conflict with "askpass" as distributed by -# cryptsetup. -cat <<-EOF >>/conf/conf.d/mandos/plugin-runner.conf - - --disable=askpass-fifo - --disable=password-prompt - --disable=plymouth -EOF - -# In case a password is retrieved by other means than by plugin-runner -# (such as typing it on the console into the prompt given by the -# "askpass" program), this dummy plugin will be made to exit -# successfully, thereby forcing plugin-runner to stop all its plugins -# and also exit itself. -cat <<-EOF > /lib/mandos/plugins.d/dummy - #!/bin/sh - - while [ -e /run/mandos-keep-running ]; do - sleep 1 - done - - # exit successfully to force plugin-runner to finish - exit 0 -EOF -chmod u=rwx,go=rx /lib/mandos/plugins.d/dummy - -# This file is the flag which keeps the dummy plugin running -touch /run/mandos-keep-running - -# Keep running plugin-runner and trying any password, until either a -# password is accepted by cryptroot-unlock, or plugin-runner fails, or -# the file /run/mandos-keep-running has been removed. -while type cryptroot-unlock >/dev/null 2>&1; do - /lib/mandos/plugin-runner > "$passfile" & - echo $! > /run/mandos-plugin-runner.pid - wait %% || break - - # Try this password ten times (or ten seconds) - for loop in 1 2 3 4 5 6 7 8 9 10; do - if [ -e /run/mandos-keep-running ]; then - cryptroot-unlock < "$passfile" >/dev/null 2>&1 && break 2 - sleep 1 - else - break 2 - fi - done -done - -exec >/dev/null 2>&1 - -rm -f /run/mandos-plugin-runner.pid /run/mandos-keep-running === modified file 'mandos.conf' --- mandos.conf 2015-07-20 03:03:33 +0000 +++ mandos.conf 2009-02-13 05:38:21 +0000 @@ -4,26 +4,32 @@ # These are the default values for the server, uncomment and change # them if needed. + # If "interface" is set, the server will only listen to a specific # network interface. ;interface = + # If "address" is set, the server will only listen to a specific # address. This must currently be an IPv6 address; an IPv4 address # can be specified using the "::FFFF:192.0.2.3" syntax. Also, if this # is a link-local address, an interface should be set above. ;address = + # If "port" is set, the server to bind to that port. By default, the # server will listen to an arbitrary port. ;port = + # If "debug" is true, the server will run in the foreground and print # a lot of debugging information. ;debug = False + # GnuTLS priority for the TLS handshake. See gnutls_priority_init(3). -;priority = SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA:+SIGN-DSA-SHA256 +;priority = SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP + # Zeroconf service name. You need to change this if you for some # reason want to run more than one server on the same *host*. @@ -36,18 +42,3 @@ # Whether to use IPv6. (Changing this is NOT recommended.) ;use_ipv6 = True - -# Whether to restore saved state on startup -;restore = True - -# The directory where state is saved -;statedir = /var/lib/mandos - -# Whether to run in the foreground -;foreground = False - -# File descriptor number to use for network socket -;socket = - -# Whether to use ZeroConf; if false, requires port or socket -;zeroconf = True === modified file 'mandos.conf.xml' --- mandos.conf.xml 2019-06-20 18:54:10 +0000 +++ mandos.conf.xml 2009-02-25 01:14:29 +0000 @@ -3,7 +3,7 @@ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ /etc/mandos/mandos.conf"> - + %common; ]> @@ -20,30 +20,20 @@ Björn Påhlsson
- belorn@recompile.se + belorn@fukt.bsnet.se
Teddy Hogeborn
- teddy@recompile.se + teddy@fukt.bsnet.se
2008 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -163,33 +153,6 @@ - - - - - - - - - - - - - - - - - - - - -
@@ -207,7 +170,6 @@ built-in module ConfigParser requires it.
- @@ -227,16 +189,14 @@ [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 +debug = true +priority = SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP servicename = Daena use_dbus = False use_ipv6 = True -restore = True -statedir = /var/lib/mandos @@ -244,8 +204,6 @@ SEE ALSO - intro - 8mandos, gnutls_priority_init3, mandos === modified file 'mandos.lsm' --- mandos.lsm 2019-04-09 20:09:51 +0000 +++ mandos.lsm 2009-10-26 21:16:16 +0000 @@ -1,23 +1,22 @@ Begin4 Title: Mandos -Version: 1.8.4 -Entered-date: 2019-04-09 +Version: 1.0.14 +Entered-date: 2009-10-25 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. +root file systems and at the same time be capable of remote and/or +unattended reboots. Keywords: boot, encryption, luks, cryptsetup, network, openpgp, - tls, dm-crypt -Author: teddy@recompile.se (Teddy Hogeborn), - belorn@recompile.se (Björn Påhlsson) -Maintained-by: teddy@recompile.se (Teddy Hogeborn), - belorn@recompile.se (Björn Påhlsson) -Primary-site: https://www.recompile.se/mandos - 193K mandos_1.8.4.orig.tar.gz -Alternate-site: ftp://ftp.recompile.se/pub/mandos - 193K mandos_1.8.4.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 - distributions, but not other Unixes. -Copying-policy: GNU General Public License version 3.0 or later +tls, dm-crypt +Author: teddy@fukt.bsnet.se (Teddy Hogeborn), + belorn@fukt.bsnet.se (Björn Påhlsson) +Maintained-by: teddy@fukt.bsnet.se (Teddy Hogeborn), + belorn@fukt.bsnet.se (Björn Påhlsson) +Primary-site: http://www.fukt.bsnet.se/mandos + 103K mandos_1.0.14.orig.tar.gz +Alternate-site: ftp://ftp.fukt.bsnet.se/pub/mandos + 103K mandos_1.0.14.orig.tar.gz +Platforms: Requires GCC, GNU libC, Avahi, GnuPG, Python 2.5, and +various other libraries. While made for Debian GNU/Linux, it is +probably portable to other distributions, but not other Unixes. +Copying-policy: GNU General Public License version 3.0 or later End === removed file 'mandos.service' --- mandos.service 2017-08-20 14:14:14 +0000 +++ mandos.service 1970-01-01 00:00:00 +0000 @@ -1,35 +0,0 @@ -[Unit] -Description=Server of encrypted passwords to Mandos clients -Documentation=man:intro(8mandos) man:mandos(8) -## If the server is configured to listen to a specific IP or network -## interface, it may be necessary to change "network.target" to -## "network-online.target". -After=network.target -## 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 - -[Service] -## If the server's D-Bus interface is disabled, the "BusName" setting -## should be removed or commented out. -BusName=se.recompile.Mandos -ExecStart=/usr/sbin/mandos --foreground -Restart=always -KillMode=mixed -## Using socket activation won't work, because systemd always does -## bind() on the socket, and also won't announce the ZeroConf service. -#ExecStart=/usr/sbin/mandos --foreground --socket=0 -#StandardInput=socket -# Restrict what the Mandos daemon can do. Note that this also affects -# "checker" programs! -PrivateTmp=yes -PrivateDevices=yes -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 2019-02-10 04:20:26 +0000 +++ mandos.xml 2009-12-25 23:13:47 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -19,30 +19,20 @@ Björn Påhlsson
- belorn@recompile.se + belorn@fukt.bsnet.se
Teddy Hogeborn
- teddy@recompile.se + teddy@fukt.bsnet.se
2008 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -96,24 +86,9 @@ - - - - - - - - - - - - &COMMANDNAME; @@ -137,16 +112,14 @@ &COMMANDNAME; is a server daemon which 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 - TLS to communicate securely with and to authenticate the - clients. The Mandos server uses IPv6 to allow Mandos clients to - use IPv6 link-local addresses, since the clients will probably - not have any other addresses configured (see ). Any authenticated client is then given - the stored pre-encrypted password for that specific client. + client host computers. The Mandos server uses Zeroconf to + announce itself on the local network, and uses TLS to + communicate securely with and to authenticate the clients. The + Mandos server uses IPv6 to allow Mandos clients to use IPv6 + link-local addresses, since the clients will probably not have + any other addresses configured (see ). + Any authenticated client is then given the stored pre-encrypted + password for that specific client.
@@ -221,24 +194,6 @@ - - - - Set the debugging log level. - LEVEL is a string, one of - CRITICAL, - ERROR, - WARNING, - INFO, or - DEBUG, in order of - increasing verbosity. The default level is - WARNING. - - - - - @@ -295,48 +250,6 @@ - - - - - - - See also . - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -362,11 +275,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 +305,7 @@ - Public key (part of TLS handshake) + OpenPGP public key (part of TLS handshake) -> @@ -416,62 +329,32 @@ for some time, the client is assumed to be compromised and is no longer eligible to receive the encrypted password. (Manual intervention is required to re-enable a client.) The timeout, - extended timeout, checker program, and interval between checks - can be configured both globally and per client; see - mandos-clients.conf - 5. - - - - - APPROVAL - - The server can be configured to require manual approval for a - client before it is sent its secret. The delay to wait for such - approval and the default action (approve or deny) can be - configured both globally and per client; see + checker program, and interval between checks can be configured + both globally and per client; see mandos-clients.conf - 5. By default all clients - will be approved immediately without delay. - - - This can be used to deny a client its secret if not manually - approved within a specified time. It can also be used to make - the server delay before giving a client its secret, allowing - optional manual denying of this specific client. - - + 5. A client successfully + receiving its password will also be treated as a successful + checker run. + LOGGING The server will send log message with various severity levels to - /dev/log. With the + /dev/log. With the option, it will log even more messages, and also show them on the console. - - PERSISTENT STATE - - Client settings, initially read from - clients.conf, are persistent across - restarts, and run-time changes will override settings in - clients.conf. However, if a setting is - changed (or a client added, or removed) in - clients.conf, this will take precedence. - - - D-BUS INTERFACE The server will by default provide a D-Bus system bus interface. This interface will only be accessible by the root user or a - Mandos-specific user, if such a user exists. For documentation - of the D-Bus API, see the file DBUS-API. + Mandos-specific user, if such a user exists. + @@ -532,31 +415,16 @@ - /run/mandos.pid - - - The file containing the process id of the - &COMMANDNAME; process started last. - Note: If the /run directory does not - exist, /var/run/mandos.pid will be - used instead. - - - - - /var/lib/mandos - - - Directory where persistent state will be saved. Change - this with the option. See - also the option. - - - - - /dev/log + /var/run/mandos.pid + + + The file containing the process id of + &COMMANDNAME;. + + + + + /dev/log The Unix domain socket to where local syslog messages are @@ -585,9 +453,28 @@ backtrace. This could be considered a feature. + Currently, if a client is disabled due to having timed out, the + server does not record this fact onto permanent storage. This + has some security implications, see . + + + There is currently no way of querying the server of the current + status of clients, other than analyzing its syslog output. + + There is no fine-grained control over logging and debug output. - + + Debug mode is conflated with running in the foreground. + + + The console log messages do not show a time stamp. + + + This server does not check the expire time of clients’ OpenPGP + keys. + @@ -603,9 +490,9 @@ Run the server in debug mode, read configuration files from - the ~/mandos directory, - and use the Zeroconf service name Test to not - collide with any other official Mandos server on this host: + the ~/mandos directory, and use the + Zeroconf service name Test to not collide with + any other official Mandos server on this host: @@ -643,12 +530,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 @@ -660,6 +547,21 @@ compromised if they are gone for too long. + If a client is compromised, its downtime should be duly noted + by the server which would therefore disable the client. But + if the server was ever restarted, it would re-read its client + list from its configuration file and again regard all clients + therein as enabled, and hence eligible to receive their + passwords. Therefore, be careful when restarting servers if + it is suspected that a client has, in fact, been compromised + by parties who may now be running a fake Mandos client with + the keys from the non-encrypted initial RAM + image of the client host. What should be done in that case + (if restarting the server program really is necessary) is to + stop the server program, edit the configuration file to omit + any suspect clients, and restart the server program. + + For more details on client-side security, see mandos-client 8mandos. @@ -670,16 +572,15 @@ SEE ALSO - intro - 8mandos, - mandos-clients.conf - 5, - mandos.conf - 5, - mandos-client - 8mandos, - sh - 1 + + mandos-clients.conf + 5, + mandos.conf + 5, + mandos-client + 8mandos, + sh1 + @@ -706,13 +607,14 @@ - 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. @@ -750,12 +652,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. @@ -771,28 +673,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. + RFC 5081: Using OpenPGP Keys for Transport Layer + Security + + + + This is implemented by GnuTLS and used by this server so + that OpenPGP keys can be used. === removed directory 'network-hooks.d' === removed file 'network-hooks.d/bridge' --- network-hooks.d/bridge 2018-02-08 10:23:55 +0000 +++ network-hooks.d/bridge 1970-01-01 00:00:00 +0000 @@ -1,93 +0,0 @@ -#!/bin/sh -# -# This is an example of a Mandos client network hook. This hook -# brings up a bridge interface as specified in a separate -# configuration file. To be used, this file and any needed -# 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 -# -# Copying and distribution of this file, with or without modification, -# are permitted in any medium without royalty provided the copyright -# notice and this notice are preserved. This file is offered as-is, -# without any warranty. - -set -e - -CONFIG="$MANDOSNETHOOKDIR/bridge.conf" - -addrtoif(){ - grep -liFe "$1" /sys/class/net/*/address \ - | sed -e 's,.*/\([^/]*\)/[^/]*,\1,' -e "/^${BRIDGE}\$/d" -} - -# Read config file, which must set "BRIDGE", "PORT_ADDRESSES", and -# optionally "IPADDRS" and "ROUTES". -if [ -e "$CONFIG" ]; then - . "$CONFIG" -fi - -if [ -z "$BRIDGE" ] || [ -z "$PORT_ADDRESSES" ]; then - exit -fi - -if [ -n "$DEVICE" ]; then - case "$DEVICE" in - *,"$BRIDGE"|*,"$BRIDGE",*|"$BRIDGE",*|"$BRIDGE") :;; - *) exit;; - esac -fi - -brctl="/sbin/brctl" -for b in "$brctl" /usr/sbin/brctl; do - if [ -e "$b" ]; then - brctl="$b" - break - fi -done - -do_start(){ - "$brctl" addbr "$BRIDGE" - for address in $PORT_ADDRESSES; do - interface=`addrtoif "$address"` - "$brctl" addif "$BRIDGE" "$interface" - ip link set dev "$interface" up - done - ip link set dev "$BRIDGE" up - sleep "${DELAY%%.*}" - if [ -n "$IPADDRS" ]; then - for ipaddr in $IPADDRS; do - ip addr add "$ipaddr" dev "$BRIDGE" - done - fi - if [ -n "$ROUTES" ]; then - for route in $ROUTES; do - ip route add "$route" dev "$BRIDGE" - done - fi -} - -do_stop(){ - ip link set dev "$BRIDGE" down - for address in $PORT_ADDRESSES; do - interface=`addrtoif "$address"` - ip link set dev "$interface" down - "$brctl" delif "$BRIDGE" "$interface" - done - "$brctl" delbr "$BRIDGE" -} - -case "${MODE:-$1}" in - start|stop) - do_"${MODE:-$1}" - ;; - files) - echo /bin/ip - echo "$brctl" - ;; - modules) - echo bridge - ;; -esac === removed file 'network-hooks.d/bridge.conf' --- network-hooks.d/bridge.conf 2011-12-31 13:25:58 +0000 +++ network-hooks.d/bridge.conf 1970-01-01 00:00:00 +0000 @@ -1,11 +0,0 @@ -## Required - -#BRIDGE=br0 - -#PORT_ADDRESSES="00:11:22:33:44:55 11:22:33:44:55:66" - -## Optional - -#IPADDRS="192.0.2.3/24 2001:DB8::aede:48ff:fe71:f6f2/32" - -#ROUTES="192.0.2.0/24 2001:DB8::/32" === removed file 'network-hooks.d/openvpn' --- network-hooks.d/openvpn 2018-02-08 10:23:55 +0000 +++ network-hooks.d/openvpn 1970-01-01 00:00:00 +0000 @@ -1,66 +0,0 @@ -#!/bin/sh -# -# This is an example of a Mandos client network hook. This hook -# brings up an OpenVPN interface as specified in a separate -# configuration file. To be used, this file and any needed -# 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 -# -# Copying and distribution of this file, with or without modification, -# are permitted in any medium without royalty provided the copyright -# notice and this notice are preserved. This file is offered as-is, -# without any warranty. - -set -e - -CONFIG="openvpn.conf" - -# Extract the "dev" setting from the config file -VPNDEVICE=`sed -n -e 's/[[:space:]]#.*//' \ - -e 's/^[[:space:]]*dev[[:space:]]\+//p' \ - "$MANDOSNETHOOKDIR/$CONFIG"` - -PIDFILE=/run/openvpn-mandos.pid - -# Exit if no device set in config -if [ -z "$VPNDEVICE" ]; then - exit -fi - -# Exit if DEVICE is set and it doesn't match the VPN interface -if [ -n "$DEVICE" ]; then - case "$DEVICE" in - *,"$VPNDEVICE"*|"$VPNDEVICE"*) :;; - *) exit;; - esac -fi - -openvpn=/usr/sbin/openvpn - -do_start(){ - "$openvpn" --cd "$MANDOSNETHOOKDIR" --daemon 'openvpn(Mandos)' \ - --writepid "$PIDFILE" --config "$CONFIG" - sleep "$DELAY" -} - -do_stop(){ - PID="`cat \"$PIDFILE\"`" - if [ "$PID" -gt 0 ]; then - kill "$PID" - fi -} - -case "${MODE:-$1}" in - start|stop) - do_"${MODE:-$1}" - ;; - files) - echo "$openvpn" - ;; - modules) - echo tun - ;; -esac === removed file 'network-hooks.d/openvpn.conf' --- network-hooks.d/openvpn.conf 2011-12-02 16:52:50 +0000 +++ network-hooks.d/openvpn.conf 1970-01-01 00:00:00 +0000 @@ -1,19 +0,0 @@ -# Sample OpenVPN configuration file -# Uncomment and change - see openvpn(8) - -# Network device. -#dev tun - -# Our remote peer -#remote 192.0.2.3 -#float 192.0.2.3 -#port 1194 - -# VPN endpoints -#ifconfig 10.1.0.1 10.1.0.2 - -# A pre-shared static key -#secret openvpn.key - -# Cipher -#cipher AES-128-CBC === removed file 'network-hooks.d/wireless' --- network-hooks.d/wireless 2018-02-08 10:23:55 +0000 +++ network-hooks.d/wireless 1970-01-01 00:00:00 +0000 @@ -1,165 +0,0 @@ -#!/bin/sh -# -# This is an example of a Mandos client network hook. This hook -# brings up a wireless interface as specified in a separate -# configuration file. To be used, this file and any needed -# 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 -# -# Copying and distribution of this file, with or without modification, -# are permitted in any medium without royalty provided the copyright -# notice and this notice are preserved. This file is offered as-is, -# without any warranty. - -set -e - -RUNDIR="/run" -CTRL="$RUNDIR/wpa_supplicant-global" -CTRLDIR="$RUNDIR/wpa_supplicant" -PIDFILE="$RUNDIR/wpa_supplicant-mandos.pid" - -CONFIG="$MANDOSNETHOOKDIR/wireless.conf" - -addrtoif(){ - grep -liFe "$1" /sys/class/net/*/address \ - | sed -e 's,.*/\([^/]*\)/[^/]*,\1,' -} - -# Read config file -if [ -e "$CONFIG" ]; then - . "$CONFIG" -else - exit -fi - -ifkeys=`sed -n -e 's/^ADDRESS_\([^=]*\)=.*/\1/p' "$CONFIG" | sort -u` - -# Exit if DEVICE is set and is not any of the wireless interfaces -if [ -n "$DEVICE" ]; then - while :; do - for KEY in $ifkeys; do - ADDRESS=`eval 'echo "$ADDRESS_'"$KEY"\"` - INTERFACE=`addrtoif "$ADDRESS"` - - case "$DEVICE" in - *,"$INTERFACE"|*,"$INTERFACE",*|"$INTERFACE",*|"$INTERFACE") - break 2;; - esac - done - exit - done -fi - -wpa_supplicant=/sbin/wpa_supplicant -wpa_cli=/sbin/wpa_cli -ip=/bin/ip - -# Used by the wpa_interface_* functions in the wireless.conf file -wpa_cli_set(){ - case "$1" in - ssid|psk) arg="\"$2\"" ;; - *) arg="$2" ;; - esac - "$wpa_cli" -p "$CTRLDIR" -i "$INTERFACE" set_network "$NETWORK" \ - "$1" "$arg" 2>&1 | sed -e '/^OK$/d' -} - -if [ $VERBOSITY -gt 0 ]; then - WPAS_OPTIONS="-d $WPAS_OPTIONS" -fi -if [ -n "$PIDFILE" ]; then - WPAS_OPTIONS="-P$PIDFILE $WPAS_OPTIONS" -fi - -do_start(){ - mkdir -m u=rwx,go= -p "$CTRLDIR" - "$wpa_supplicant" -B -g "$CTRL" -p "$CTRLDIR" $WPAS_OPTIONS - for KEY in $ifkeys; do - ADDRESS=`eval 'echo "$ADDRESS_'"$KEY"\"` - INTERFACE=`addrtoif "$ADDRESS"` - DRIVER=`eval 'echo "$WPA_DRIVER_'"$KEY"\"` - IFDELAY=`eval 'echo "$DELAY_'"$KEY"\"` - "$wpa_cli" -g "$CTRL" interface_add "$INTERFACE" "" \ - "${DRIVER:-wext}" "$CTRLDIR" > /dev/null \ - | sed -e '/^OK$/d' - NETWORK=`"$wpa_cli" -p "$CTRLDIR" -i "$INTERFACE" add_network` - eval wpa_interface_"$KEY" - "$wpa_cli" -p "$CTRLDIR" -i "$INTERFACE" enable_network \ - "$NETWORK" | sed -e '/^OK$/d' - sleep "${IFDELAY:-$DELAY}" & - sleep=$! - while :; do - kill -0 $sleep 2>/dev/null || break - STATE=`"$wpa_cli" -p "$CTRLDIR" -i "$INTERFACE" status \ - | sed -n -e 's/^wpa_state=//p'` - if [ "$STATE" = COMPLETED ]; then - while :; do - kill -0 $sleep 2>/dev/null || break 2 - UP=`cat /sys/class/net/"$INTERFACE"/operstate` - if [ "$UP" = up ]; then - kill $sleep 2>/dev/null - break 2 - fi - sleep 1 - done - fi - sleep 1 - done & - wait $sleep || : - IPADDRS=`eval 'echo "$IPADDRS_'"$KEY"\"` - if [ -n "$IPADDRS" ]; then - if [ "$IPADDRS" = dhcp ]; then - ipconfig -c dhcp -d "$INTERFACE" || : - #dhclient "$INTERFACE" - else - for ipaddr in $IPADDRS; do - "$ip" addr add "$ipaddr" dev "$INTERFACE" - done - fi - fi - ROUTES=`eval 'echo "$ROUTES_'"$KEY"\"` - if [ -n "$ROUTES" ]; then - for route in $ROUTES; do - "$ip" route add "$route" dev "$INTERFACE" - done - fi - done -} - -do_stop(){ - "$wpa_cli" -g "$CTRL" terminate 2>&1 | sed -e '/^OK$/d' - for KEY in $ifkeys; do - ADDRESS=`eval 'echo "$ADDRESS_'"$KEY"\"` - INTERFACE=`addrtoif "$ADDRESS"` - "$ip" addr show scope global permanent dev "$INTERFACE" \ - | while read type addr rest; do - case "$type" in - inet|inet6) - "$ip" addr del "$addr" dev "$INTERFACE" - ;; - esac - done - "$ip" link set dev "$INTERFACE" down - done -} - -case "${MODE:-$1}" in - start|stop) - do_"${MODE:-$1}" - ;; - files) - echo "$wpa_supplicant" - echo "$wpa_cli" - echo "$ip" - ;; - modules) - if [ "$IPADDRS" = dhcp ]; then - echo af_packet - fi - sed -n -e 's/#.*$//' -e 's/[ ]*$//' \ - -e 's/^MODULE_[^=]\+=//p' "$CONFIG" - ;; -esac === removed file 'network-hooks.d/wireless.conf' --- network-hooks.d/wireless.conf 2011-12-31 13:25:58 +0000 +++ network-hooks.d/wireless.conf 1970-01-01 00:00:00 +0000 @@ -1,23 +0,0 @@ -# Extra options for wpa_supplicant, if any -#WPAS_OPTIONS="" - -# wlan0 -ADDRESS_0=00:11:22:33:44:55 -MODULE_0=ath9k -#WPA_DRIVER_0=wext -wpa_interface_0(){ - # Use this format to set simple things: - wpa_cli_set ssid home - wpa_cli_set psk "secret passphrase" - # Use this format to do more complex things with wpa_cli: - #"$wpa_cli" -p "$CTRLDIR" -i "$INTERFACE" bssid "$NETWORK" 00:11:22:33:44:55 - #"$wpa_cli" -g "$CTRL" ping -} -#DELAY_0=10 -IPADDRS_0=dhcp -#IPADDRS_0="192.0.2.3/24 2001:DB8::aede:48ff:fe71:f6f2/32" -#ROUTES_0="192.0.2.0/24 2001:DB8::/32" - -#ADDRESS_1=11:22:33:44:55:66 -#MODULE_1=... -#... === 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. === removed directory 'plugin-helpers' === removed file 'plugin-helpers/mandos-client-iprouteadddel.c' --- plugin-helpers/mandos-client-iprouteadddel.c 2018-02-18 01:29:21 +0000 +++ plugin-helpers/mandos-client-iprouteadddel.c 1970-01-01 00:00:00 +0000 @@ -1,282 +0,0 @@ -/* -*- coding: utf-8 -*- */ -/* - * iprouteadddel - Add or delete direct route to a local IP address - * - * Copyright © 2015-2018 Teddy Hogeborn - * Copyright © 2015-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 . - */ - -#define _GNU_SOURCE /* program_invocation_short_name */ -#include /* bool, false, true */ -#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() */ -#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(), - nl_addr_put() */ -#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 /* rtnl_link_get_kernel(), - rtnl_link_get_ifindex(), - rtnl_link_put() */ - -bool debug = false; -const char *argp_program_version = "mandos-client-iprouteadddel " VERSION; -const char *argp_program_bug_address = ""; - -/* Function to use when printing errors */ -void perror_plus(const char *print_text){ - int e = errno; - fprintf(stderr, "Mandos plugin helper %s: ", - program_invocation_short_name); - errno = e; - perror(print_text); -} - -__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 helper %s: ", - program_invocation_short_name); - return vfprintf(stream, format, ap); -} - -int main(int argc, char *argv[]){ - int ret; - int exitcode = EXIT_SUCCESS; - struct arguments { - bool add; /* true: add, false: delete */ - char *address; /* IP address as string */ - struct nl_addr *nl_addr; /* Netlink IP address */ - char *interface; /* interface name */ - } arguments = { .add = true, .address = NULL, .interface = NULL }; - struct argp_option options[] = { - { .name = "debug", .key = 128, - .doc = "Debug mode" }, - { .name = NULL } - }; - struct rtnl_route *route = NULL; - struct rtnl_nexthop *nexthop = NULL; - struct nl_sock *sk = NULL; - - error_t parse_opt(int key, char *arg, struct argp_state *state){ - int lret; - errno = 0; - switch(key){ - case 128: /* --debug */ - debug = true; - break; - case ARGP_KEY_ARG: - switch(state->arg_num){ - case 0: - if(strcasecmp(arg, "add") == 0){ - ((struct arguments *)(state->input))->add = true; - } else if(strcasecmp(arg, "delete") == 0){ - ((struct arguments *)(state->input))->add = false; - } else { - fprintf_plus(stderr, "Unrecognized command: %s\n", arg); - argp_usage(state); - } - break; - case 1: - ((struct arguments *)(state->input))->address = arg; - lret = nl_addr_parse(arg, AF_UNSPEC, &(((struct arguments *) - (state->input)) - ->nl_addr)); - if(lret != 0){ - fprintf_plus(stderr, "Failed to parse address %s: %s\n", - arg, nl_geterror(lret)); - argp_usage(state); - } - break; - case 2: - ((struct arguments *)(state->input))->interface = arg; - break; - default: - argp_usage(state); - } - break; - case ARGP_KEY_END: - if(state->arg_num < 3){ - argp_usage(state); - } - break; - default: - return ARGP_ERR_UNKNOWN; - } - return errno; - } - - struct argp argp = { .options = options, .parser = parse_opt, - .args_doc = "[ add | delete ] ADDRESS INTERFACE", - .doc = "Mandos client helper -- Add or delete" - " local route to IP address on interface" }; - - ret = argp_parse(&argp, argc, argv, ARGP_IN_ORDER, 0, &arguments); - switch(ret){ - case 0: - break; - case EINVAL: - exit(EX_USAGE); - case ENOMEM: - default: - errno = ret; - perror_plus("argp_parse"); - exitcode = EX_OSERR; - goto end; - } - /* Get netlink socket */ - sk = nl_socket_alloc(); - if(sk == NULL){ - fprintf_plus(stderr, "Failed to allocate netlink socket: %s\n", - nl_geterror(ret)); - exitcode = EX_OSERR; - goto end; - } - /* Connect socket to netlink */ - ret = nl_connect(sk, NETLINK_ROUTE); - if(ret < 0){ - fprintf_plus(stderr, "Failed to connect socket to netlink: %s\n", - nl_geterror(ret)); - exitcode = EX_OSERR; - goto end; - } - /* Get link object of specified interface */ - struct rtnl_link *link = NULL; - ret = rtnl_link_get_kernel(sk, 0, arguments.interface, &link); - if(ret < 0){ - fprintf_plus(stderr, "Failed to use interface %s: %s\n", - arguments.interface, nl_geterror(ret)); - exitcode = EX_OSERR; - goto end; - } - /* Get netlink route object */ - route = rtnl_route_alloc(); - if(route == NULL){ - fprintf_plus(stderr, "Failed to get netlink route:\n"); - exitcode = EX_OSERR; - goto end; - } - /* Get address family of specified address */ - sa_family_t af = (sa_family_t)nl_addr_get_family(arguments.nl_addr); - if(debug){ - fprintf_plus(stderr, "Address family of %s is %s (%" PRIdMAX - ")\n", arguments.address, - af == AF_INET6 ? "AF_INET6" : - ( af == AF_INET ? "AF_INET" : "UNKNOWN"), - (intmax_t)af); - } - /* Set route parameters: */ - rtnl_route_set_family(route, (uint8_t)af); /* Address family */ - rtnl_route_set_protocol(route, RTPROT_BOOT); /* protocol - see - ip-route(8) */ - rtnl_route_set_scope(route, RT_SCOPE_LINK); /* link scope */ - rtnl_route_set_type(route, RTN_UNICAST); /* normal unicast - address route */ - rtnl_route_set_dst(route, arguments.nl_addr); /* Destination - address */ - rtnl_route_set_table(route, RT_TABLE_MAIN); /* "main" routing - table */ - /* Create nexthop */ - nexthop = rtnl_route_nh_alloc(); - if(nexthop == NULL){ - fprintf_plus(stderr, "Failed to get netlink route nexthop\n"); - exitcode = EX_OSERR; - goto end; - } - /* Get index number of specified interface */ - int ifindex = rtnl_link_get_ifindex(link); - if(debug){ - fprintf_plus(stderr, "ifindex of %s is %d\n", arguments.interface, - ifindex); - } - /* Set interface index number on nexthop object */ - rtnl_route_nh_set_ifindex(nexthop, ifindex); - /* Set route to use nexthop object */ - rtnl_route_add_nexthop(route, nexthop); - /* Add or delete route? */ - if(arguments.add){ - ret = rtnl_route_add(sk, route, NLM_F_EXCL); - } else { - ret = rtnl_route_delete(sk, route, 0); - } - if(ret < 0){ - fprintf_plus(stderr, "Failed to %s route: %s\n", - arguments.add ? "add" : "delete", - nl_geterror(ret)); - exitcode = EX_OSERR; - goto end; - } - end: - /* Deallocate route */ - if(route){ - rtnl_route_put(route); - } else if(nexthop) { - /* Deallocate route nexthop */ - rtnl_route_nh_free(nexthop); - } - /* Deallocate parsed address */ - if(arguments.nl_addr){ - nl_addr_put(arguments.nl_addr); - } - /* Deallocate link struct */ - if(link){ - rtnl_link_put(link); - } - /* Deallocate netlink socket struct */ - if(sk){ - nl_socket_free(sk); - } - return exitcode; -} === modified file 'plugin-runner.c' --- plugin-runner.c 2019-07-07 20:50:21 +0000 +++ plugin-runner.c 2010-03-27 18:39:02 +0000 @@ -2,58 +2,59 @@ /* * Mandos plugin runner - Run Mandos plugins * - * Copyright © 2008-2018 Teddy Hogeborn - * Copyright © 2008-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 + * Copyright © 2008,2009 Teddy Hogeborn + * Copyright © 2008,2009 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 . + * Contact the authors at . */ #define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), getline(), - O_CLOEXEC, pipe2() */ + asprintf(), O_CLOEXEC */ #include /* size_t, NULL */ #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 /* perror, fileno(), fprintf(), + stderr, STDOUT_FILENO */ +#include /* DIR, fdopendir(), stat(), struct + stat, waitpid(), WIFEXITED(), + WEXITSTATUS(), wait(), pid_t, + uid_t, gid_t, getuid(), getgid(), + dirfd() */ #include /* fd_set, select(), FD_ZERO(), FD_SET(), FD_ISSET(), FD_CLR */ #include /* wait(), waitpid(), WIFEXITED(), - WEXITSTATUS(), WTERMSIG() */ -#include /* struct stat, fstat(), S_ISREG() */ + WEXITSTATUS(), WTERMSIG(), + WCOREDUMP() */ +#include /* struct stat, stat(), 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 /* DIR, struct dirent, fdopendir(), + readdir(), closedir(), dirfd() */ +#include /* struct stat, stat(), S_ISREG(), + fcntl(), setuid(), setgid(), + F_GETFD, F_SETFD, FD_CLOEXEC, + access(), pipe(), fork(), close() + dup2(), STDOUT_FILENO, _exit(), + execv(), write(), read(), + close() */ #include /* fcntl(), F_GETFD, F_SETFD, - FD_CLOEXEC, openat(), scandirat(), - pipe2() */ -#include /* strsep, strlen(), strsignal(), - strcmp(), strncmp() */ + FD_CLOEXEC */ +#include /* strsep, strlen(), asprintf(), + strsignal(), strcmp(), strncmp() */ #include /* errno */ #include /* struct argp_option, struct argp_state, struct argp, @@ -69,18 +70,14 @@ #include /* intmax_t, PRIdMAX, strtoimax() */ #include /* EX_OSERR, EX_USAGE, EX_IOERR, EX_CONFIG, EX_UNAVAILABLE, EX_OK */ -#include /* errno */ -#include /* error() */ -#include /* fnmatch() */ #define BUFFER_SIZE 256 #define PDIR "/lib/mandos/plugins.d" -#define PHDIR "/lib/mandos/plugin-helpers" #define AFILE "/conf/conf.d/mandos/plugin-runner.conf" const char *argp_program_version = "plugin-runner " VERSION; -const char *argp_program_bug_address = ""; +const char *argp_program_bug_address = ""; typedef struct plugin{ char *name; /* can be NULL or any plugin name */ @@ -106,7 +103,6 @@ /* Gets an existing plugin based on name, or if none is found, creates a new one */ -__attribute__((warn_unused_result)) static plugin *getplugin(char *name){ /* Check for existing plugin with that name */ for(plugin *p = plugin_list; p != NULL; p = p->next){ @@ -173,20 +169,17 @@ } /* Helper function for add_argument and add_environment */ -__attribute__((nonnull, warn_unused_result)) static bool add_to_char_array(const char *new, char ***array, int *len){ /* Resize the pointed-to array to hold one more pointer */ - char **new_array = NULL; do { - new_array = realloc(*array, sizeof(char *) - * (size_t) ((*len) + 2)); - } while(new_array == NULL and errno == EINTR); + *array = realloc(*array, sizeof(char *) + * (size_t) ((*len) + 2)); + } while(*array == NULL and errno == EINTR); /* Malloc check */ - if(new_array == NULL){ + if(*array == NULL){ return false; } - *array = new_array; /* Make a copy of the new string */ char *copy; do { @@ -204,7 +197,6 @@ } /* Add to a plugin's argument vector */ -__attribute__((nonnull(2), warn_unused_result)) static bool add_argument(plugin *p, const char *arg){ if(p == NULL){ return false; @@ -213,7 +205,6 @@ } /* Add to a plugin's environment */ -__attribute__((nonnull(2), warn_unused_result)) static bool add_environment(plugin *p, const char *def, bool replace){ if(p == NULL){ return false; @@ -221,19 +212,19 @@ /* namelen = length of name of environment variable */ size_t namelen = (size_t)(strchrnul(def, '=') - def); /* Search for this environment variable */ - for(char **envdef = p->environ; *envdef != NULL; envdef++){ - if(strncmp(*envdef, def, namelen + 1) == 0){ + for(char **e = p->environ; *e != NULL; e++){ + if(strncmp(*e, def, namelen + 1) == 0){ /* It already exists */ if(replace){ - char *new_envdef; + char *new; do { - new_envdef = realloc(*envdef, strlen(def) + 1); - } while(new_envdef == NULL and errno == EINTR); - if(new_envdef == NULL){ + new = realloc(*e, strlen(def) + 1); + } while(new == NULL and errno == EINTR); + if(new == NULL){ return false; } - *envdef = new_envdef; - strcpy(*envdef, def); + *e = new; + strcpy(*e, def); } return true; } @@ -241,13 +232,11 @@ return add_to_char_array(def, &(p->environ), &(p->envc)); } -#ifndef O_CLOEXEC /* * Based on the example in the GNU LibC manual chapter 13.13 "File * Descriptor Flags". | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] | */ -__attribute__((warn_unused_result)) static int set_cloexec_flag(int fd){ int ret = (int)TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD, 0)); /* If reading the flags failed, return error indication now. */ @@ -258,7 +247,6 @@ return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD, ret | FD_CLOEXEC)); } -#endif /* not O_CLOEXEC */ /* Mark processes as completed when they exit, and save their exit @@ -278,7 +266,7 @@ /* No child processes */ break; } - error(0, errno, "waitpid"); + perror("waitpid"); } /* A child exited, find it in process_list */ @@ -296,7 +284,6 @@ } /* Prints out a password to stdout */ -__attribute__((nonnull, warn_unused_result)) static bool print_out_password(const char *buffer, size_t length){ ssize_t ret; for(size_t written = 0; written < length; written += (size_t)ret){ @@ -310,13 +297,11 @@ } /* Removes and free a plugin from the plugin list */ -__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); @@ -349,10 +334,11 @@ int main(int argc, char *argv[]){ char *plugindir = NULL; - char *pluginhelperdir = NULL; char *argfile = NULL; FILE *conffp; - struct dirent **direntries = NULL; + size_t d_name_len; + DIR *dir = NULL; + struct dirent *dirst; struct stat st; fd_set rfds_all; int ret, maxfd = 0; @@ -366,19 +352,18 @@ .sa_flags = SA_NOCLDSTOP }; char **custom_argv = NULL; int custom_argc = 0; - int dir_fd = -1; /* Establish a signal handler */ sigemptyset(&sigchld_action.sa_mask); ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD); if(ret == -1){ - error(0, errno, "sigaddset"); + perror("sigaddset"); exitstatus = EX_OSERR; goto fallback; } ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action); if(ret == -1){ - error(0, errno, "sigaction"); + perror("sigaction"); exitstatus = EX_OSERR; goto fallback; } @@ -417,10 +402,6 @@ .doc = "Group ID the plugins will run as", .group = 3 }, { .name = "debug", .key = 132, .doc = "Debug mode", .group = 4 }, - { .name = "plugin-helper-dir", .key = 133, - .arg = "DIRECTORY", - .doc = "Specify a different plugin helper directory", - .group = 2 }, /* * These reproduce what we would get without ARGP_NO_HELP */ @@ -433,12 +414,11 @@ { .name = NULL } }; - __attribute__((nonnull(3))) error_t parse_opt(int key, char *arg, struct argp_state *state){ errno = 0; switch(key){ char *tmp; - intmax_t tmp_id; + intmax_t tmpmax; case 'g': /* --global-options */ { char *plugin_option; @@ -447,13 +427,10 @@ break; } } - errno = 0; } break; case 'G': /* --global-env */ - if(add_environment(getplugin(NULL), arg, true)){ - errno = 0; - } + add_environment(getplugin(NULL), arg, true); break; case 'o': /* --options-for */ { @@ -476,7 +453,6 @@ break; } } - errno = 0; } break; case 'E': /* --env-for */ @@ -494,9 +470,7 @@ errno = EINVAL; break; } - if(add_environment(getplugin(arg), envdef, true)){ - errno = 0; - } + add_environment(getplugin(arg), envdef, true); } break; case 'd': /* --disable */ @@ -504,7 +478,6 @@ plugin *p = getplugin(arg); if(p != NULL){ p->disabled = true; - errno = 0; } } break; @@ -513,64 +486,49 @@ plugin *p = getplugin(arg); if(p != NULL){ p->disabled = false; - errno = 0; } } break; case 128: /* --plugin-dir */ free(plugindir); plugindir = strdup(arg); - if(plugindir != NULL){ - errno = 0; - } break; case 129: /* --config-file */ /* This is already done by parse_opt_config_file() */ break; case 130: /* --userid */ - tmp_id = strtoimax(arg, &tmp, 10); + tmpmax = strtoimax(arg, &tmp, 10); if(errno != 0 or tmp == arg or *tmp != '\0' - or tmp_id != (uid_t)tmp_id){ + or tmpmax != (uid_t)tmpmax){ argp_error(state, "Bad user ID number: \"%s\", using %" PRIdMAX, arg, (intmax_t)uid); break; } - uid = (uid_t)tmp_id; - errno = 0; + uid = (uid_t)tmpmax; break; case 131: /* --groupid */ - tmp_id = strtoimax(arg, &tmp, 10); + tmpmax = strtoimax(arg, &tmp, 10); if(errno != 0 or tmp == arg or *tmp != '\0' - or tmp_id != (gid_t)tmp_id){ + or tmpmax != (gid_t)tmpmax){ argp_error(state, "Bad group ID number: \"%s\", using %" PRIdMAX, arg, (intmax_t)gid); break; } - gid = (gid_t)tmp_id; - errno = 0; + gid = (gid_t)tmpmax; break; case 132: /* --debug */ debug = true; break; - case 133: /* --plugin-helper-dir */ - free(pluginhelperdir); - pluginhelperdir = strdup(arg); - if(pluginhelperdir != NULL){ - errno = 0; - } - break; /* * These reproduce what we would get without ARGP_NO_HELP */ case '?': /* --help */ state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */ argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP); - __builtin_unreachable(); case -3: /* --usage */ state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */ argp_state_help(state, state->out_stream, ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK); - __builtin_unreachable(); case 'V': /* --version */ fprintf(state->out_stream, "%s\n", argp_program_version); exit(EXIT_SUCCESS); @@ -586,11 +544,6 @@ if(arg[0] == '\0'){ break; } -#if __GNUC__ >= 7 - __attribute__((fallthrough)); -#else - /* FALLTHROUGH */ -#endif default: return ARGP_ERR_UNKNOWN; } @@ -615,14 +568,10 @@ case 129: /* --config-file */ free(argfile); argfile = strdup(arg); - if(argfile != NULL){ - errno = 0; - } break; case 130: /* --userid */ case 131: /* --groupid */ case 132: /* --debug */ - case 133: /* --plugin-helper-dir */ case '?': /* --help */ case -3: /* --usage */ case 'V': /* --version */ @@ -650,7 +599,7 @@ case ENOMEM: default: errno = ret; - error(0, errno, "argp_parse"); + perror("argp_parse"); exitstatus = EX_OSERR; goto fallback; case EINVAL: @@ -677,7 +626,7 @@ custom_argc = 1; custom_argv = malloc(sizeof(char*) * 2); if(custom_argv == NULL){ - error(0, errno, "malloc"); + perror("malloc"); exitstatus = EX_OSERR; goto fallback; } @@ -700,25 +649,20 @@ } new_arg = strdup(p); if(new_arg == NULL){ - error(0, errno, "strdup"); + perror("strdup"); exitstatus = EX_OSERR; free(org_line); goto fallback; } custom_argc += 1; - { - char **new_argv = realloc(custom_argv, sizeof(char *) - * ((size_t)custom_argc + 1)); - if(new_argv == NULL){ - error(0, errno, "realloc"); - exitstatus = EX_OSERR; - free(new_arg); - free(org_line); - goto fallback; - } else { - custom_argv = new_argv; - } + custom_argv = realloc(custom_argv, sizeof(char *) + * ((unsigned int) custom_argc + 1)); + if(custom_argv == NULL){ + perror("realloc"); + exitstatus = EX_OSERR; + free(org_line); + goto fallback; } custom_argv[custom_argc-1] = new_arg; custom_argv[custom_argc] = NULL; @@ -728,7 +672,7 @@ ret = fclose(conffp); } while(ret == EOF and errno == EINTR); if(ret == EOF){ - error(0, errno, "fclose"); + perror("fclose"); exitstatus = EX_IOERR; goto fallback; } @@ -737,7 +681,7 @@ /* Check for harmful errors and go to fallback. Other errors might not affect opening plugins */ if(errno == EMFILE or errno == ENFILE or errno == ENOMEM){ - error(0, errno, "fopen"); + perror("fopen"); exitstatus = EX_OSERR; goto fallback; } @@ -754,7 +698,7 @@ case ENOMEM: default: errno = ret; - error(0, errno, "argp_parse"); + perror("argp_parse"); exitstatus = EX_OSERR; goto fallback; case EINVAL: @@ -774,7 +718,7 @@ case ENOMEM: default: errno = ret; - error(0, errno, "argp_parse"); + perror("argp_parse"); exitstatus = EX_OSERR; goto fallback; case EINVAL: @@ -782,26 +726,8 @@ goto fallback; } - { - char *pluginhelperenv; - bool bret = true; - ret = asprintf(&pluginhelperenv, "MANDOSPLUGINHELPERDIR=%s", - pluginhelperdir != NULL ? pluginhelperdir : PHDIR); - if(ret != -1){ - bret = add_environment(getplugin(NULL), pluginhelperenv, true); - } - if(ret == -1 or not bret){ - error(0, errno, "Failed to set MANDOSPLUGINHELPERDIR" - " environment variable to \"%s\" for all plugins\n", - pluginhelperdir != NULL ? pluginhelperdir : PHDIR); - } - if(ret != -1){ - free(pluginhelperenv); - } - } - 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++){ @@ -814,51 +740,38 @@ } } - if(getuid() == 0){ - /* Work around Debian bug #633582: - */ - int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY); - if(plugindir_fd == -1){ - if(errno != ENOENT){ - error(0, errno, "open(\"" PDIR "\")"); - } - } else { - ret = (int)TEMP_FAILURE_RETRY(fstat(plugindir_fd, &st)); - if(ret == -1){ - error(0, errno, "fstat"); - } else { - if(S_ISDIR(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){ - ret = fchown(plugindir_fd, uid, gid); - if(ret == -1){ - error(0, errno, "fchown"); - } - } - } - close(plugindir_fd); - } - } - - /* Lower permissions */ - ret = setgid(gid); + /* Strip permissions down to nobody */ + setgid(gid); if(ret == -1){ - error(0, errno, "setgid"); + perror("setgid"); } ret = setuid(uid); if(ret == -1){ - error(0, errno, "setuid"); + perror("setuid"); } /* Open plugin directory with close_on_exec flag */ { - dir_fd = open(plugindir != NULL ? plugindir : PDIR, O_RDONLY | -#ifdef O_CLOEXEC - O_CLOEXEC -#else /* not O_CLOEXEC */ - 0 -#endif /* not O_CLOEXEC */ - ); + int dir_fd = -1; + if(plugindir == NULL){ + dir_fd = open(PDIR, O_RDONLY | +#ifdef O_CLOEXEC + O_CLOEXEC +#else /* not O_CLOEXEC */ + 0 +#endif /* not O_CLOEXEC */ + ); + } else { + dir_fd = open(plugindir, O_RDONLY | +#ifdef O_CLOEXEC + O_CLOEXEC +#else /* not O_CLOEXEC */ + 0 +#endif /* not O_CLOEXEC */ + ); + } if(dir_fd == -1){ - error(0, errno, "Could not open plugin dir"); + perror("Could not open plugin dir"); exitstatus = EX_UNAVAILABLE; goto fallback; } @@ -867,95 +780,130 @@ /* Set the FD_CLOEXEC flag on the directory */ ret = set_cloexec_flag(dir_fd); if(ret < 0){ - error(0, errno, "set_cloexec_flag"); + perror("set_cloexec_flag"); + TEMP_FAILURE_RETRY(close(dir_fd)); exitstatus = EX_OSERR; goto fallback; } #endif /* O_CLOEXEC */ - } - - int good_name(const struct dirent * const dirent){ - const char * const patterns[] = { ".*", "#*#", "*~", "*.dpkg-new", - "*.dpkg-old", "*.dpkg-bak", - "*.dpkg-divert", NULL }; -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - for(const char **pat = (const char **)patterns; - *pat != NULL; pat++){ -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - if(fnmatch(*pat, dirent->d_name, FNM_FILE_NAME | FNM_PERIOD) - != FNM_NOMATCH){ - if(debug){ - fprintf(stderr, "Ignoring plugin dir entry \"%s\"" - " matching pattern %s\n", dirent->d_name, *pat); - } - return 0; - } + + dir = fdopendir(dir_fd); + if(dir == NULL){ + perror("Could not open plugin dir"); + TEMP_FAILURE_RETRY(close(dir_fd)); + exitstatus = EX_OSERR; + goto fallback; } - return 1; - } - - int numplugins = scandirat(dir_fd, ".", &direntries, good_name, - alphasort); - if(numplugins == -1){ - error(0, errno, "Could not scan plugin dir"); - direntries = NULL; - exitstatus = EX_OSERR; - goto fallback; } FD_ZERO(&rfds_all); /* Read and execute any executable in the plugin directory*/ - for(int i = 0; i < numplugins; i++){ - - int plugin_fd = openat(dir_fd, direntries[i]->d_name, O_RDONLY); - if(plugin_fd == -1){ - error(0, errno, "Could not open plugin"); - free(direntries[i]); + while(true){ + do { + dirst = readdir(dir); + } while(dirst == NULL and errno == EINTR); + + /* All directory entries have been processed */ + if(dirst == NULL){ + if(errno == EBADF){ + perror("readdir"); + exitstatus = EX_IOERR; + goto fallback; + } + break; + } + + d_name_len = strlen(dirst->d_name); + + /* Ignore dotfiles, backup files and other junk */ + { + bool bad_name = false; + + const char const *bad_prefixes[] = { ".", "#", NULL }; + + const char const *bad_suffixes[] = { "~", "#", ".dpkg-new", + ".dpkg-old", + ".dpkg-bak", + ".dpkg-divert", NULL }; + for(const char **pre = bad_prefixes; *pre != NULL; pre++){ + size_t pre_len = strlen(*pre); + if((d_name_len >= pre_len) + and strncmp((dirst->d_name), *pre, pre_len) == 0){ + if(debug){ + fprintf(stderr, "Ignoring plugin dir entry \"%s\"" + " with bad prefix %s\n", dirst->d_name, *pre); + } + bad_name = true; + break; + } + } + if(bad_name){ + continue; + } + for(const char **suf = bad_suffixes; *suf != NULL; suf++){ + size_t suf_len = strlen(*suf); + if((d_name_len >= suf_len) + and (strcmp((dirst->d_name) + d_name_len-suf_len, *suf) + == 0)){ + if(debug){ + fprintf(stderr, "Ignoring plugin dir entry \"%s\"" + " with bad suffix %s\n", dirst->d_name, *suf); + } + bad_name = true; + break; + } + } + + if(bad_name){ + continue; + } + } + + char *filename; + if(plugindir == NULL){ + ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, PDIR "/%s", + dirst->d_name)); + } else { + ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, "%s/%s", + plugindir, + dirst->d_name)); + } + if(ret < 0){ + perror("asprintf"); continue; } - ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st)); + + ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st)); if(ret == -1){ - error(0, errno, "stat"); - close(plugin_fd); - free(direntries[i]); + perror("stat"); + free(filename); continue; } /* Ignore non-executable files */ if(not S_ISREG(st.st_mode) - or (TEMP_FAILURE_RETRY(faccessat(dir_fd, direntries[i]->d_name, - X_OK, 0)) != 0)){ + or (TEMP_FAILURE_RETRY(access(filename, X_OK)) != 0)){ if(debug){ - fprintf(stderr, "Ignoring plugin dir entry \"%s/%s\"" - " with bad type or mode\n", - plugindir != NULL ? plugindir : PDIR, - direntries[i]->d_name); + fprintf(stderr, "Ignoring plugin dir entry \"%s\"" + " with bad type or mode\n", filename); } - close(plugin_fd); - free(direntries[i]); + free(filename); continue; } - plugin *p = getplugin(direntries[i]->d_name); + plugin *p = getplugin(dirst->d_name); if(p == NULL){ - error(0, errno, "getplugin"); - close(plugin_fd); - free(direntries[i]); + perror("getplugin"); + free(filename); continue; } if(p->disabled){ if(debug){ fprintf(stderr, "Ignoring disabled plugin \"%s\"\n", - direntries[i]->d_name); + dirst->d_name); } - close(plugin_fd); - free(direntries[i]); + free(filename); continue; } { @@ -964,77 +912,55 @@ if(g != NULL){ for(char **a = g->argv + 1; *a != NULL; a++){ if(not add_argument(p, *a)){ - error(0, errno, "add_argument"); + perror("add_argument"); } } /* Add global environment variables */ for(char **e = g->environ; *e != NULL; e++){ if(not add_environment(p, *e, false)){ - error(0, errno, "add_environment"); + perror("add_environment"); } } } } - /* If this plugin has any environment variables, we need to - duplicate the environment from this process, too. */ + /* If this plugin has any environment variables, we will call + using execve and need to duplicate the environment from this + process, too. */ if(p->environ[0] != NULL){ for(char **e = environ; *e != NULL; e++){ if(not add_environment(p, *e, false)){ - error(0, errno, "add_environment"); + perror("add_environment"); } } } int pipefd[2]; -#ifndef O_CLOEXEC ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd)); -#else /* O_CLOEXEC */ - ret = (int)TEMP_FAILURE_RETRY(pipe2(pipefd, O_CLOEXEC)); -#endif /* O_CLOEXEC */ if(ret == -1){ - error(0, errno, "pipe"); - exitstatus = EX_OSERR; - free(direntries[i]); - goto fallback; - } - if(pipefd[0] >= FD_SETSIZE){ - fprintf(stderr, "pipe()[0] (%d) >= FD_SETSIZE (%d)", pipefd[0], - FD_SETSIZE); - close(pipefd[0]); - close(pipefd[1]); - exitstatus = EX_OSERR; - free(direntries[i]); - goto fallback; - } -#ifndef O_CLOEXEC + perror("pipe"); + exitstatus = EX_OSERR; + goto fallback; + } /* Ask OS to automatic close the pipe on exec */ ret = set_cloexec_flag(pipefd[0]); if(ret < 0){ - error(0, errno, "set_cloexec_flag"); - close(pipefd[0]); - close(pipefd[1]); + perror("set_cloexec_flag"); exitstatus = EX_OSERR; - free(direntries[i]); goto fallback; } ret = set_cloexec_flag(pipefd[1]); if(ret < 0){ - error(0, errno, "set_cloexec_flag"); - close(pipefd[0]); - close(pipefd[1]); + perror("set_cloexec_flag"); exitstatus = EX_OSERR; - free(direntries[i]); goto fallback; } -#endif /* not O_CLOEXEC */ /* Block SIGCHLD until process is safely in process list */ ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL)); if(ret < 0){ - error(0, errno, "sigprocmask"); + perror("sigprocmask"); exitstatus = EX_OSERR; - free(direntries[i]); goto fallback; } /* Starting a new process to be watched */ @@ -1043,91 +969,88 @@ pid = fork(); } while(pid == -1 and errno == EINTR); if(pid == -1){ - error(0, errno, "fork"); - TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK, - &sigchld_action.sa_mask, NULL)); - close(pipefd[0]); - close(pipefd[1]); + perror("fork"); exitstatus = EX_OSERR; - free(direntries[i]); goto fallback; } if(pid == 0){ /* this is the child process */ ret = sigaction(SIGCHLD, &old_sigchld_action, NULL); if(ret < 0){ - error(0, errno, "sigaction"); + perror("sigaction"); _exit(EX_OSERR); } ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL); if(ret < 0){ - error(0, errno, "sigprocmask"); + perror("sigprocmask"); _exit(EX_OSERR); } ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */ if(ret == -1){ - error(0, errno, "dup2"); + perror("dup2"); _exit(EX_OSERR); } - if(fexecve(plugin_fd, p->argv, - (p->environ[0] != NULL) ? p->environ : environ) < 0){ - error(0, errno, "fexecve for %s/%s", - plugindir != NULL ? plugindir : PDIR, - direntries[i]->d_name); - _exit(EX_OSERR); + if(dirfd(dir) < 0){ + /* If dir has no file descriptor, we could not set FD_CLOEXEC + above and must now close it manually here. */ + closedir(dir); + } + if(p->environ[0] == NULL){ + if(execv(filename, p->argv) < 0){ + perror("execv"); + _exit(EX_OSERR); + } + } else { + if(execve(filename, p->argv, p->environ) < 0){ + perror("execve"); + _exit(EX_OSERR); + } } /* no return */ } /* Parent process */ - close(pipefd[1]); /* Close unused write end of pipe */ - close(plugin_fd); - plugin *new_plugin = getplugin(direntries[i]->d_name); + TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of + pipe */ + free(filename); + plugin *new_plugin = getplugin(dirst->d_name); if(new_plugin == NULL){ - error(0, errno, "getplugin"); + perror("getplugin"); ret = (int)(TEMP_FAILURE_RETRY (sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL))); if(ret < 0){ - error(0, errno, "sigprocmask"); + perror("sigprocmask"); } exitstatus = EX_OSERR; - free(direntries[i]); goto fallback; } - free(direntries[i]); 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, &sigchld_action.sa_mask, NULL)); if(ret < 0){ - error(0, errno, "sigprocmask"); + perror("sigprocmask"); exitstatus = EX_OSERR; goto fallback; } - FD_SET(new_plugin->fd, &rfds_all); + FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from + -Wconversion */ if(maxfd < new_plugin->fd){ maxfd = new_plugin->fd; } } - free(direntries); - direntries = NULL; - close(dir_fd); - dir_fd = -1; + TEMP_FAILURE_RETRY(closedir(dir)); + dir = NULL; free_plugin(getplugin(NULL)); for(plugin *p = plugin_list; p != NULL; p = p->next){ @@ -1146,7 +1069,7 @@ fd_set rfds = rfds_all; int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL); if(select_ret == -1 and errno != EINTR){ - error(0, errno, "select"); + perror("select"); exitstatus = EX_OSERR; goto fallback; } @@ -1172,11 +1095,15 @@ (intmax_t) (proc->pid), WTERMSIG(proc->status), strsignal(WTERMSIG(proc->status))); + } else if(WCOREDUMP(proc->status)){ + fprintf(stderr, "Plugin %s [%" PRIdMAX "] dumped" + " core\n", proc->name, (intmax_t) (proc->pid)); } } /* Remove the plugin */ - FD_CLR(proc->fd, &rfds_all); + FD_CLR(proc->fd, &rfds_all); /* Spurious warning from + -Wconversion */ /* Block signal while modifying process_list */ ret = (int)TEMP_FAILURE_RETRY(sigprocmask @@ -1184,7 +1111,7 @@ &sigchld_action.sa_mask, NULL)); if(ret < 0){ - error(0, errno, "sigprocmask"); + perror("sigprocmask"); exitstatus = EX_OSERR; goto fallback; } @@ -1198,7 +1125,7 @@ (sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL))); if(ret < 0){ - error(0, errno, "sigprocmask"); + perror("sigprocmask"); exitstatus = EX_OSERR; goto fallback; } @@ -1215,28 +1142,29 @@ bool bret = print_out_password(proc->buffer, proc->buffer_length); if(not bret){ - error(0, errno, "print_out_password"); + perror("print_out_password"); exitstatus = EX_IOERR; } goto fallback; } /* This process has not completed. Does it have any output? */ - if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ + if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ /* Spurious + warning from + -Wconversion */ /* This process had nothing to say at this time */ proc = proc->next; continue; } /* Before reading, make the process' data buffer large enough */ if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){ - char *new_buffer = realloc(proc->buffer, proc->buffer_size - + (size_t) BUFFER_SIZE); - if(new_buffer == NULL){ - error(0, errno, "malloc"); + proc->buffer = realloc(proc->buffer, proc->buffer_size + + (size_t) BUFFER_SIZE); + if(proc->buffer == NULL){ + perror("malloc"); exitstatus = EX_OSERR; goto fallback; } - proc->buffer = new_buffer; proc->buffer_size += BUFFER_SIZE; } /* Read from the process */ @@ -1276,7 +1204,7 @@ } bret = print_out_password(passwordbuffer, len); if(not bret){ - error(0, errno, "print_out_password"); + perror("print_out_password"); exitstatus = EX_IOERR; } } @@ -1284,7 +1212,7 @@ /* Restore old signal handler */ ret = sigaction(SIGCHLD, &old_sigchld_action, NULL); if(ret == -1){ - error(0, errno, "sigaction"); + perror("sigaction"); exitstatus = EX_OSERR; } @@ -1295,10 +1223,8 @@ free(custom_argv); } - free(direntries); - - if(dir_fd != -1){ - close(dir_fd); + if(dir != NULL){ + closedir(dir); } /* Kill the processes */ @@ -1308,7 +1234,7 @@ ret = kill(p->pid, SIGTERM); if(ret == -1 and errno != ESRCH){ /* Set-uid proccesses might not get closed */ - error(0, errno, "kill"); + perror("kill"); } } } @@ -1318,13 +1244,12 @@ ret = wait(NULL); } while(ret >= 0); if(errno != ECHILD){ - error(0, errno, "wait"); + perror("wait"); } free_plugin_list(); free(plugindir); - free(pluginhelperdir); free(argfile); return exitstatus; === modified file 'plugin-runner.xml' --- plugin-runner.xml 2019-02-10 04:20:26 +0000 +++ plugin-runner.xml 2009-01-18 06:41:57 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -19,30 +19,20 @@ Björn Påhlsson
- belorn@recompile.se + belorn@fukt.bsnet.se
Teddy Hogeborn
- teddy@recompile.se + teddy@fukt.bsnet.se
2008 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -123,9 +113,6 @@ - - @@ -273,7 +260,7 @@ Disable the plugin named PLUGIN. The plugin will not be started. - + @@ -332,21 +319,6 @@ - - - - Specify a different plugin helper directory. The default - is /lib/mandos/plugin-helpers, which - will exist in the initial RAM disk - environment. (This will simply be passed to all plugins - via the MANDOSPLUGINHELPERDIR environment - variable. See ) - - - - - @@ -453,11 +425,7 @@ The plugin will run in the initial RAM disk environment, so care must be taken not to depend on any files or running - services not available there. Any helper executables required - by the plugin (which are not in the PATH) can - be placed in the plugin helper directory, the name of which - will be made available to the plugin via the - MANDOSPLUGINHELPERDIR environment variable. + services not available there. The plugin must exit cleanly and free all allocated resources @@ -506,9 +474,7 @@ only passes on its environment to all the plugins. The environment passed to plugins can be modified using the and - options. Also, the option - will affect the environment variable - MANDOSPLUGINHELPERDIR for the plugins. + options. @@ -547,26 +513,6 @@ - - /lib/mandos/plugins.d - - - The default plugin directory; can be changed by the - option. - - - - - /lib/mandos/plugin-helpers - - - The default plugin helper directory; can be changed by - the option. - - - @@ -577,7 +523,6 @@ The option is ignored when specified from within a configuration file. - @@ -626,16 +571,15 @@ - Read a different configuration file, run plugins from a - different directory, specify an alternate plugin helper - directory and add two options to the + Run plugins from a different directory, read a different + configuration file, 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/mandos/plugins.d --options-for=mandos-client:--pubkey=pubkey.txt,--seckey=seckey.txt @@ -673,8 +617,6 @@ SEE ALSO - intro - 8mandos, cryptsetup 8, crypttab === modified file 'plugins.d/askpass-fifo.c' --- plugins.d/askpass-fifo.c 2019-02-11 07:06:55 +0000 +++ plugins.d/askpass-fifo.c 2009-10-18 16:09:05 +0000 @@ -2,29 +2,28 @@ /* * Askpass-FIFO - Read a password from a FIFO and output it * - * 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 - * 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,2009 Teddy Hogeborn + * Copyright © 2008,2009 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 . + * Contact the authors at . */ #define _GNU_SOURCE /* TEMP_FAILURE_RETRY() */ -#include /* uid_t, gid_t, ssize_t */ +#include /* ssize_t */ #include /* mkfifo(), S_IRUSR, S_IWUSR */ #include /* and */ #include /* errno, EACCES, ENOTDIR, ELOOP, @@ -32,9 +31,7 @@ ENOENT, EEXIST, EFAULT, EMFILE, ENFILE, ENOMEM, EBADF, EINVAL, EIO, EISDIR, EFBIG */ -#include /* error() */ -#include /* fprintf(), vfprintf(), - vasprintf() */ +#include /* perror() */ #include /* EXIT_FAILURE, NULL, size_t, free(), realloc(), EXIT_SUCCESS */ #include /* open(), O_RDONLY */ @@ -42,71 +39,31 @@ STDOUT_FILENO */ #include /* EX_OSERR, EX_OSFILE, EX_UNAVAILABLE, EX_IOERR */ -#include /* strerror() */ -#include /* va_list, va_start(), ... */ - -uid_t uid = 65534; -gid_t gid = 65534; - -/* Function to use when printing errors */ -__attribute__((format (gnu_printf, 3, 4))) -void error_plus(int status, int errnum, const char *formatstring, - ...){ - va_list ap; - char *text; - int ret; - - va_start(ap, formatstring); - ret = vasprintf(&text, formatstring, ap); - if(ret == -1){ - fprintf(stderr, "Mandos plugin %s: ", - program_invocation_short_name); - vfprintf(stderr, formatstring, ap); - 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); -} + int main(__attribute__((unused))int argc, __attribute__((unused))char **argv){ int ret = 0; ssize_t sret; - uid = getuid(); - gid = getgid(); - /* Create FIFO */ const char passfifo[] = "/lib/cryptsetup/passfifo"; ret = mkfifo(passfifo, S_IRUSR | S_IWUSR); if(ret == -1){ int e = errno; + perror("mkfifo"); switch(e){ case EACCES: case ENOTDIR: case ELOOP: - error_plus(EX_OSFILE, errno, "mkfifo"); - __builtin_unreachable(); + return EX_OSFILE; case ENAMETOOLONG: case ENOSPC: case EROFS: default: - error_plus(EX_OSERR, errno, "mkfifo"); - __builtin_unreachable(); + return EX_OSERR; case ENOENT: - /* no "/lib/cryptsetup"? */ - error_plus(EX_UNAVAILABLE, errno, "mkfifo"); - __builtin_unreachable(); + return EX_UNAVAILABLE; /* no "/lib/cryptsetup"? */ case EEXIST: break; /* not an error */ } @@ -116,7 +73,7 @@ int fifo_fd = open(passfifo, O_RDONLY); if(fifo_fd == -1){ int e = errno; - error_plus(0, errno, "open"); + perror("open"); switch(e){ case EACCES: case ENOENT: @@ -134,16 +91,6 @@ } } - /* Lower group privileges */ - if(setgid(gid) == -1){ - error_plus(0, errno, "setgid"); - } - - /* Lower user privileges */ - if(setuid(uid) == -1){ - error_plus(0, errno, "setuid"); - } - /* Read from FIFO */ char *buf = NULL; size_t buf_len = 0; @@ -154,7 +101,7 @@ if(buf_len + blocksize > buf_allocated){ char *tmp = realloc(buf, buf_allocated + blocksize); if(tmp == NULL){ - error_plus(0, errno, "realloc"); + perror("realloc"); free(buf); return EX_OSERR; } @@ -166,7 +113,7 @@ int e = errno; free(buf); errno = e; - error_plus(0, errno, "read"); + perror("read"); switch(e){ case EBADF: case EFAULT: @@ -194,7 +141,7 @@ int e = errno; free(buf); errno = e; - error_plus(0, errno, "write"); + perror("write"); switch(e){ case EBADF: case EFAULT: @@ -214,7 +161,7 @@ ret = close(STDOUT_FILENO); if(ret == -1){ int e = errno; - error_plus(0, errno, "close"); + perror("close"); switch(e){ case EBADF: return EX_OSFILE; === modified file 'plugins.d/askpass-fifo.xml' --- plugins.d/askpass-fifo.xml 2019-02-10 04:20:26 +0000 +++ plugins.d/askpass-fifo.xml 2009-01-04 21:54:55 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -19,33 +19,24 @@ Björn Påhlsson
- belorn@recompile.se + belorn@fukt.bsnet.se
Teddy Hogeborn
- teddy@recompile.se + teddy@fukt.bsnet.se
2008 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 Teddy Hogeborn Björn Påhlsson + @@ -120,11 +111,6 @@
- - BUGS - - - EXAMPLE @@ -162,8 +148,6 @@ SEE ALSO - intro - 8mandos, fifo 7, plugin-runner === modified file 'plugins.d/mandos-client.c' --- plugins.d/mandos-client.c 2019-02-11 05:14:10 +0000 +++ plugins.d/mandos-client.c 2009-11-01 00:10:28 +0000 @@ -9,68 +9,61 @@ * "browse_callback", and parts of "main". * * Everything else is - * 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 - * 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,2009 Teddy Hogeborn + * Copyright © 2008,2009 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 . + * Contact the authors at . */ /* Needed by GPGME, specifically gpgme_data_seek() */ #ifndef _LARGEFILE_SOURCE #define _LARGEFILE_SOURCE -#endif /* not _LARGEFILE_SOURCE */ +#endif #ifndef _FILE_OFFSET_BITS #define _FILE_OFFSET_BITS 64 -#endif /* not _FILE_OFFSET_BITS */ +#endif #define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), asprintf() */ #include /* fprintf(), stderr, fwrite(), - stdout, ferror() */ -#include /* uint16_t, uint32_t, intptr_t */ + stdout, ferror(), remove() */ +#include /* uint16_t, uint32_t */ #include /* NULL, size_t, ssize_t */ #include /* free(), EXIT_SUCCESS, srand(), strtof(), abort() */ #include /* bool, false, true */ -#include /* strcmp(), strlen(), strerror(), - asprintf(), strncpy(), strsignal() - */ +#include /* memset(), strcmp(), strlen(), + strerror(), asprintf(), strcpy() */ #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 /* open() */ #include /* socket(), struct sockaddr_in6, - inet_pton(), connect(), - getnameinfo() */ -#include /* open(), unlinkat(), AT_REMOVEDIR */ + inet_pton(), connect() */ +#include /* open() */ #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, ENOMEM, EISDIR, - ENOTEMPTY, - program_invocation_short_name */ -#include /* nanosleep(), time(), sleep() */ +#include /* assert() */ +#include /* perror(), errno */ +#include /* nanosleep(), time() */ #include /* ioctl, ifreq, SIOCGIFFLAGS, IFF_UP, SIOCSIFFLAGS, if_indextoname(), if_nametoindex(), IF_NAMESIZE */ @@ -79,9 +72,8 @@ */ #include /* close(), SEEK_SET, off_t, write(), getuid(), getgid(), seteuid(), - setgid(), pause(), _exit(), - unlinkat() */ -#include /* inet_pton(), htons() */ + setgid(), pause() */ +#include /* inet_pton(), htons */ #include /* not, or, and */ #include /* struct argp_option, error_t, struct argp_state, struct argp, @@ -92,15 +84,6 @@ raise() */ #include /* EX_OSERR, EX_USAGE, EX_UNAVAILABLE, EX_NOHOST, EX_IOERR, EX_PROTOCOL */ -#include /* waitpid(), WIFEXITED(), - WEXITSTATUS(), WTERMSIG() */ -#include /* setgroups() */ -#include /* argz_add_sep(), argz_next(), - argz_delete(), argz_append(), - argz_stringify(), argz_add(), - argz_count() */ -#include /* getnameinfo(), NI_NUMERICHOST, - EAI_SYSTEM, gai_strerror() */ #ifdef __linux__ #include /* klogctl() */ @@ -123,15 +106,9 @@ gnutls_* init_gnutls_session(), GNUTLS_* */ -#if GNUTLS_VERSION_NUMBER < 0x030600 #include - /* gnutls_certificate_set_openpgp_key_file(), - GNUTLS_OPENPGP_FMT_BASE64 */ -#elif GNUTLS_VERSION_NUMBER >= 0x030606 -#include /* gnutls_pkcs_encrypt_flags_t, - GNUTLS_PKCS_PLAIN, - GNUTLS_PKCS_NULL_PASSWORD */ -#endif + /* gnutls_certificate_set_openpgp_key_file(), + GNUTLS_OPENPGP_FMT_BASE64 */ /* GPGME */ #include /* All GPGME types, constants and @@ -145,383 +122,111 @@ #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; static const char mandos_protocol_version[] = "1"; const char *argp_program_version = "mandos-client " VERSION; -const char *argp_program_bug_address = ""; -static const char sys_class_net[] = "/sys/class/net"; -char *connect_to = NULL; -const char *hookdir = HOOKDIR; -int hookdir_fd = -1; -uid_t uid = 65534; -gid_t gid = 65534; - -/* Doubly linked list that need to be circularly linked when used */ -typedef struct server{ - const char *ip; - in_port_t port; - AvahiIfIndex if_index; - int af; - struct timespec last_seen; - struct server *next; - struct server *prev; -} server; +const char *argp_program_bug_address = ""; /* Used for passing in values through the Avahi callback functions */ typedef struct { + AvahiSimplePoll *simple_poll; AvahiServer *server; gnutls_certificate_credentials_t cred; unsigned int dh_bits; gnutls_dh_params_t dh_params; const char *priority; gpgme_ctx_t ctx; - server *current_server; - char *interfaces; - size_t interfaces_size; } mandos_context; -/* global so signal handler can reach it*/ -AvahiSimplePoll *simple_poll; +/* global context so signal handler can reach it*/ +mandos_context mc = { .simple_poll = NULL, .server = NULL, + .dh_bits = 1024, .priority = "SECURE256" + ":!CTYPE-X.509:+CTYPE-OPENPGP" }; sig_atomic_t quit_now = 0; int signal_received = 0; -/* Function to use when printing errors */ -void perror_plus(const char *print_text){ - int e = errno; - fprintf(stderr, "Mandos plugin %s: ", - program_invocation_short_name); - errno = e; - perror(print_text); -} - -__attribute__((format (gnu_printf, 2, 3), nonnull)) -int fprintf_plus(FILE *stream, const char *format, ...){ - va_list ap; - va_start (ap, format); - - TEMP_FAILURE_RETRY(fprintf(stream, "Mandos plugin %s: ", - program_invocation_short_name)); - return (int)TEMP_FAILURE_RETRY(vfprintf(stream, format, ap)); -} - /* * Make additional room in "buffer" for at least BUFFER_SIZE more * bytes. "buffer_capacity" is how much is currently allocated, * "buffer_length" is how much is already used. */ -__attribute__((nonnull, warn_unused_result)) size_t incbuffer(char **buffer, size_t buffer_length, - size_t buffer_capacity){ + size_t buffer_capacity){ if(buffer_length + BUFFER_SIZE > buffer_capacity){ - char *new_buf = realloc(*buffer, buffer_capacity + BUFFER_SIZE); - if(new_buf == NULL){ - int old_errno = errno; - free(*buffer); - errno = old_errno; - *buffer = NULL; + *buffer = realloc(*buffer, buffer_capacity + BUFFER_SIZE); + if(buffer == NULL){ return 0; } - *buffer = new_buf; buffer_capacity += BUFFER_SIZE; } return buffer_capacity; } -/* Add server to set of servers to retry periodically */ -__attribute__((nonnull, warn_unused_result)) -bool add_server(const char *ip, in_port_t port, AvahiIfIndex if_index, - int af, server **current_server){ - int ret; - server *new_server = malloc(sizeof(server)); - if(new_server == NULL){ - perror_plus("malloc"); - return false; - } - *new_server = (server){ .ip = strdup(ip), - .port = port, - .if_index = if_index, - .af = af }; - if(new_server->ip == NULL){ - perror_plus("strdup"); - free(new_server); - return false; - } - ret = clock_gettime(CLOCK_MONOTONIC, &(new_server->last_seen)); - if(ret == -1){ - perror_plus("clock_gettime"); -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - free((char *)(new_server->ip)); -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - free(new_server); - return false; - } - /* Special case of first server */ - if(*current_server == NULL){ - new_server->next = new_server; - new_server->prev = new_server; - *current_server = new_server; - } else { - /* Place the new server last in the list */ - new_server->next = *current_server; - new_server->prev = (*current_server)->prev; - new_server->prev->next = new_server; - (*current_server)->prev = new_server; - } - 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. */ -__attribute__((nonnull, warn_unused_result)) -static bool init_gpgme(const char * const seckey, - const char * const pubkey, - const char * const tempdir, - mandos_context *mc){ +static bool init_gpgme(const char *seckey, + const char *pubkey, const char *tempdir){ gpgme_error_t rc; gpgme_engine_info_t engine_info; + /* * Helper function to insert pub and seckey to the engine keyring. */ - bool import_key(const char * const filename){ + bool import_key(const char *filename){ int ret; int fd; gpgme_data_t pgp_data; fd = (int)TEMP_FAILURE_RETRY(open(filename, O_RDONLY)); if(fd == -1){ - perror_plus("open"); + perror("open"); return false; } - /* Workaround for systems without a real-time clock; see also - Debian bug #894495: */ - do { - { - time_t currtime = time(NULL); - if(currtime != (time_t)-1){ - struct tm tm; - if(gmtime_r(&currtime, &tm) == NULL) { - perror_plus("gmtime_r"); - break; - } - if(tm.tm_year != 70 or tm.tm_mon != 0){ - break; - } - if(debug){ - fprintf_plus(stderr, "System clock is January 1970"); - } - } else { - if(debug){ - fprintf_plus(stderr, "System clock is invalid"); - } - } - } - struct stat keystat; - ret = fstat(fd, &keystat); - if(ret != 0){ - perror_plus("fstat"); - break; - } - ret = raise_privileges(); - if(ret != 0){ - errno = ret; - perror_plus("Failed to raise privileges"); - break; - } - if(debug){ - fprintf_plus(stderr, - "Setting system clock to key file mtime"); - } - time_t keytime = keystat.st_mtim.tv_sec; - if(stime(&keytime) != 0){ - perror_plus("stime"); - } - ret = lower_privileges(); - if(ret != 0){ - errno = ret; - perror_plus("Failed to lower privileges"); - } - } while(false); - rc = gpgme_data_new_from_fd(&pgp_data, fd); if(rc != GPG_ERR_NO_ERROR){ - fprintf_plus(stderr, "bad gpgme_data_new_from_fd: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf(stderr, "bad gpgme_data_new_from_fd: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); return false; } - rc = gpgme_op_import(mc->ctx, pgp_data); + rc = gpgme_op_import(mc.ctx, pgp_data); if(rc != GPG_ERR_NO_ERROR){ - fprintf_plus(stderr, "bad gpgme_op_import: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf(stderr, "bad gpgme_op_import: %s: %s\n", + 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); + ret = (int)TEMP_FAILURE_RETRY(close(fd)); if(ret == -1){ - perror_plus("close"); + perror("close"); } gpgme_data_release(pgp_data); return true; } if(debug){ - fprintf_plus(stderr, "Initializing GPGME\n"); + fprintf(stderr, "Initializing GPGME\n"); } /* Init GPGME */ gpgme_check_version(NULL); rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP); if(rc != GPG_ERR_NO_ERROR){ - fprintf_plus(stderr, "bad gpgme_engine_check_version: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf(stderr, "bad gpgme_engine_check_version: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); return false; } - /* Set GPGME home directory for the OpenPGP engine only */ + /* Set GPGME home directory for the OpenPGP engine only */ rc = gpgme_get_engine_info(&engine_info); if(rc != GPG_ERR_NO_ERROR){ - fprintf_plus(stderr, "bad gpgme_get_engine_info: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf(stderr, "bad gpgme_get_engine_info: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); return false; } while(engine_info != NULL){ @@ -533,16 +238,15 @@ engine_info = engine_info->next; } if(engine_info == NULL){ - fprintf_plus(stderr, "Could not set GPGME home dir to %s\n", - tempdir); + fprintf(stderr, "Could not set GPGME home dir to %s\n", tempdir); return false; } /* Create new GPGME "context" */ - rc = gpgme_new(&(mc->ctx)); + 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(stderr, "bad gpgme_new: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); return false; } @@ -557,11 +261,9 @@ * Decrypt OpenPGP data. * Returns -1 on error */ -__attribute__((nonnull, warn_unused_result)) static ssize_t pgp_packet_decrypt(const char *cryptotext, size_t crypto_size, - char **plaintext, - mandos_context *mc){ + char **plaintext){ gpgme_data_t dh_crypto, dh_plain; gpgme_error_t rc; ssize_t ret; @@ -569,57 +271,57 @@ ssize_t plaintext_length = 0; if(debug){ - fprintf_plus(stderr, "Trying to decrypt OpenPGP data\n"); + fprintf(stderr, "Trying to decrypt OpenPGP data\n"); } /* Create new GPGME data buffer from memory cryptotext */ rc = gpgme_data_new_from_mem(&dh_crypto, cryptotext, crypto_size, 0); if(rc != GPG_ERR_NO_ERROR){ - fprintf_plus(stderr, "bad gpgme_data_new_from_mem: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf(stderr, "bad gpgme_data_new_from_mem: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); return -1; } /* 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", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf(stderr, "bad gpgme_data_new: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); gpgme_data_release(dh_crypto); return -1; } /* Decrypt data from the cryptotext data buffer to the plaintext data buffer */ - rc = gpgme_op_decrypt(mc->ctx, dh_crypto, dh_plain); + rc = gpgme_op_decrypt(mc.ctx, dh_crypto, dh_plain); if(rc != GPG_ERR_NO_ERROR){ - fprintf_plus(stderr, "bad gpgme_op_decrypt: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf(stderr, "bad gpgme_op_decrypt: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); plaintext_length = -1; if(debug){ gpgme_decrypt_result_t result; - result = gpgme_op_decrypt_result(mc->ctx); + result = gpgme_op_decrypt_result(mc.ctx); if(result == NULL){ - fprintf_plus(stderr, "gpgme_op_decrypt_result failed\n"); + fprintf(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(stderr, "Unsupported algorithm: %s\n", + result->unsupported_algorithm); + fprintf(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); + fprintf(stderr, "File name: %s\n", result->file_name); } - - for(gpgme_recipient_t r = result->recipients; r != NULL; - r = r->next){ - fprintf_plus(stderr, "Public key algorithm: %s\n", - gpgme_pubkey_algo_name(r->pubkey_algo)); - fprintf_plus(stderr, "Key ID: %s\n", r->keyid); - fprintf_plus(stderr, "Secret key available: %s\n", - r->status == GPG_ERR_NO_SECKEY ? "No" : "Yes"); + gpgme_recipient_t recipient; + recipient = result->recipients; + while(recipient != NULL){ + fprintf(stderr, "Public key algorithm: %s\n", + gpgme_pubkey_algo_name(recipient->pubkey_algo)); + fprintf(stderr, "Key ID: %s\n", recipient->keyid); + fprintf(stderr, "Secret key available: %s\n", + recipient->status == GPG_ERR_NO_SECKEY + ? "No" : "Yes"); + recipient = recipient->next; } } } @@ -627,12 +329,12 @@ } if(debug){ - fprintf_plus(stderr, "Decryption of OpenPGP data succeeded\n"); + fprintf(stderr, "Decryption of OpenPGP data succeeded\n"); } /* Seek back to the beginning of the GPGME plaintext data buffer */ if(gpgme_data_seek(dh_plain, (off_t)0, SEEK_SET) == -1){ - perror_plus("gpgme_data_seek"); + perror("gpgme_data_seek"); plaintext_length = -1; goto decrypt_end; } @@ -640,12 +342,12 @@ *plaintext = NULL; while(true){ plaintext_capacity = incbuffer(plaintext, - (size_t)plaintext_length, - plaintext_capacity); + (size_t)plaintext_length, + plaintext_capacity); if(plaintext_capacity == 0){ - perror_plus("incbuffer"); - plaintext_length = -1; - goto decrypt_end; + perror("incbuffer"); + plaintext_length = -1; + goto decrypt_end; } ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length, @@ -656,7 +358,7 @@ break; } if(ret < 0){ - perror_plus("gpgme_data_read"); + perror("gpgme_data_read"); plaintext_length = -1; goto decrypt_end; } @@ -664,7 +366,7 @@ } if(debug){ - fprintf_plus(stderr, "Decrypted password is: "); + fprintf(stderr, "Decrypted password is: "); for(ssize_t i = 0; i < plaintext_length; i++){ fprintf(stderr, "%02hhX ", (*plaintext)[i]); } @@ -681,35 +383,33 @@ return plaintext_length; } -__attribute__((warn_unused_result, const)) -static const char *safe_string(const char *str){ - if(str == NULL) - return "(unknown)"; - return str; -} - -__attribute__((warn_unused_result)) -static const char *safer_gnutls_strerror(int value){ - const char *ret = gnutls_strerror(value); - return safe_string(ret); +static const char * safer_gnutls_strerror(int value){ + const char *ret = gnutls_strerror(value); /* Spurious warning from + -Wunreachable-code */ + if(ret == NULL) + ret = "(unknown)"; + return ret; } /* GnuTLS log function callback */ -__attribute__((nonnull)) static void debuggnutls(__attribute__((unused)) int level, const char* string){ - fprintf_plus(stderr, "GnuTLS: %s", string); + fprintf(stderr, "GnuTLS: %s", string); } -__attribute__((nonnull(1, 2, 4), warn_unused_result)) static int init_gnutls_global(const char *pubkeyfilename, - const char *seckeyfilename, - const char *dhparamsfilename, - mandos_context *mc){ + const char *seckeyfilename){ int ret; if(debug){ - fprintf_plus(stderr, "Initializing GnuTLS\n"); + fprintf(stderr, "Initializing GnuTLS\n"); + } + + ret = gnutls_global_init(); + if(ret != GNUTLS_E_SUCCESS){ + fprintf(stderr, "GnuTLS global_init: %s\n", + safer_gnutls_strerror(ret)); + return -1; } if(debug){ @@ -721,285 +421,88 @@ } /* OpenPGP credentials */ - ret = gnutls_certificate_allocate_credentials(&mc->cred); + gnutls_certificate_allocate_credentials(&mc.cred); if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, "GnuTLS memory error: %s\n", - safer_gnutls_strerror(ret)); + fprintf(stderr, "GnuTLS memory error: %s\n", /* Spurious warning + from + -Wunreachable-code + */ + safer_gnutls_strerror(ret)); + gnutls_global_deinit(); return -1; } if(debug){ - fprintf_plus(stderr, "Attempting to use public key %s and" - " private key %s as GnuTLS credentials\n", - pubkeyfilename, - seckeyfilename); + fprintf(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, + (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'," - " '%s')\n", ret, pubkeyfilename, seckeyfilename); - fprintf_plus(stderr, "The GnuTLS error is: %s\n", - safer_gnutls_strerror(ret)); + fprintf(stderr, + "Error[%d] while reading the OpenPGP key pair ('%s'," + " '%s')\n", ret, pubkeyfilename, seckeyfilename); + fprintf(stderr, "The GnuTLS error is: %s\n", + safer_gnutls_strerror(ret)); goto globalfail; } /* GnuTLS server initialization */ - ret = gnutls_dh_params_init(&mc->dh_params); - if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, "Error in GnuTLS DH parameter" - " initialization: %s\n", - safer_gnutls_strerror(ret)); - goto globalfail; - } - /* If a Diffie-Hellman parameters file was given, try to use it */ - if(dhparamsfilename != NULL){ - gnutls_datum_t params = { .data = NULL, .size = 0 }; - do { - int dhpfile = open(dhparamsfilename, O_RDONLY); - if(dhpfile == -1){ - perror_plus("open"); - dhparamsfilename = NULL; - break; - } - size_t params_capacity = 0; - while(true){ - params_capacity = incbuffer((char **)¶ms.data, - (size_t)params.size, - (size_t)params_capacity); - if(params_capacity == 0){ - perror_plus("incbuffer"); - free(params.data); - params.data = NULL; - dhparamsfilename = NULL; - break; - } - ssize_t bytes_read = read(dhpfile, - params.data + params.size, - BUFFER_SIZE); - /* EOF */ - if(bytes_read == 0){ - break; - } - /* check bytes_read for failure */ - if(bytes_read < 0){ - perror_plus("read"); - free(params.data); - params.data = NULL; - dhparamsfilename = NULL; - break; - } - params.size += (unsigned int)bytes_read; - } - ret = close(dhpfile); - if(ret == -1){ - perror_plus("close"); - } - if(params.data == NULL){ - dhparamsfilename = NULL; - } - if(dhparamsfilename == NULL){ - break; - } - ret = gnutls_dh_params_import_pkcs3(mc->dh_params, ¶ms, - GNUTLS_X509_FMT_PEM); - if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, "Failed to parse DH parameters in file" - " \"%s\": %s\n", dhparamsfilename, - 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 }; - do { - int secfile = open(seckeyfilename, O_RDONLY); - if(secfile == -1){ - perror_plus("open"); - break; - } - size_t buffer_capacity = 0; - while(true){ - buffer_capacity = incbuffer((char **)&buffer.data, - (size_t)buffer.size, - (size_t)buffer_capacity); - if(buffer_capacity == 0){ - perror_plus("incbuffer"); - free(buffer.data); - buffer.data = NULL; - break; - } - ssize_t bytes_read = read(secfile, - buffer.data + buffer.size, - BUFFER_SIZE); - /* EOF */ - if(bytes_read == 0){ - break; - } - /* check bytes_read for failure */ - if(bytes_read < 0){ - perror_plus("read"); - free(buffer.data); - buffer.data = NULL; - break; - } - buffer.size += (unsigned int)bytes_read; - } - close(secfile); - } while(false); - /* If successful, use buffer to parse private key */ - gnutls_sec_param_t sec_param = GNUTLS_SEC_PARAM_ULTRA; - if(buffer.data != NULL){ - { - gnutls_openpgp_privkey_t privkey = NULL; - ret = gnutls_openpgp_privkey_init(&privkey); - if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, "Error initializing OpenPGP key" - " structure: %s", - safer_gnutls_strerror(ret)); - free(buffer.data); - buffer.data = NULL; - } else { - ret = gnutls_openpgp_privkey_import - (privkey, &buffer, GNUTLS_OPENPGP_FMT_BASE64, "", 0); - if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, "Error importing OpenPGP key : %s", - safer_gnutls_strerror(ret)); - privkey = NULL; - } - free(buffer.data); - buffer.data = NULL; - if(privkey != NULL){ - /* Use private key to suggest an appropriate - sec_param */ - sec_param = gnutls_openpgp_privkey_sec_param(privkey); - gnutls_openpgp_privkey_deinit(privkey); - if(debug){ - fprintf_plus(stderr, "This OpenPGP key implies using" - " a GnuTLS security parameter \"%s\".\n", - safe_string(gnutls_sec_param_get_name - (sec_param))); - } - } - } - } - if(sec_param == GNUTLS_SEC_PARAM_UNKNOWN){ - /* Err on the side of caution */ - sec_param = GNUTLS_SEC_PARAM_ULTRA; - if(debug){ - fprintf_plus(stderr, "Falling back to security parameter" - " \"%s\"\n", - safe_string(gnutls_sec_param_get_name - (sec_param))); - } - } - } - unsigned int uret = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, sec_param); - if(uret != 0){ - mc->dh_bits = uret; - if(debug){ - fprintf_plus(stderr, "A \"%s\" GnuTLS security parameter" - " implies %u DH bits; using that.\n", - safe_string(gnutls_sec_param_get_name - (sec_param)), - mc->dh_bits); - } - } else { - fprintf_plus(stderr, "Failed to get implied number of DH" - " bits for security parameter \"%s\"): %s\n", - safe_string(gnutls_sec_param_get_name - (sec_param)), - 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); - } - } + ret = gnutls_dh_params_init(&mc.dh_params); + if(ret != GNUTLS_E_SUCCESS){ + fprintf(stderr, "Error in GnuTLS DH parameter initialization:" + " %s\n", safer_gnutls_strerror(ret)); + goto globalfail; + } + ret = gnutls_dh_params_generate2(mc.dh_params, mc.dh_bits); + if(ret != GNUTLS_E_SUCCESS){ + fprintf(stderr, "Error in GnuTLS prime generation: %s\n", + safer_gnutls_strerror(ret)); + goto globalfail; + } + + gnutls_certificate_set_dh_params(mc.cred, mc.dh_params); return 0; globalfail: - gnutls_certificate_free_credentials(mc->cred); - gnutls_dh_params_deinit(mc->dh_params); + gnutls_certificate_free_credentials(mc.cred); + gnutls_global_deinit(); + gnutls_dh_params_deinit(mc.dh_params); return -1; } -__attribute__((nonnull, warn_unused_result)) -static int init_gnutls_session(gnutls_session_t *session, - mandos_context *mc){ +static int init_gnutls_session(gnutls_session_t *session){ 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; } } while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN); if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, - "Error in GnuTLS session initialization: %s\n", - safer_gnutls_strerror(ret)); + fprintf(stderr, "Error in GnuTLS session initialization: %s\n", + safer_gnutls_strerror(ret)); } { const char *err; do { - ret = gnutls_priority_set_direct(*session, mc->priority, &err); + ret = gnutls_priority_set_direct(*session, mc.priority, &err); if(quit_now){ gnutls_deinit(*session); return -1; } } while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN); if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, "Syntax error at: %s\n", err); - fprintf_plus(stderr, "GnuTLS error: %s\n", - safer_gnutls_strerror(ret)); + fprintf(stderr, "Syntax error at: %s\n", err); + fprintf(stderr, "GnuTLS error: %s\n", + safer_gnutls_strerror(ret)); gnutls_deinit(*session); return -1; } @@ -1007,15 +510,15 @@ do { ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE, - mc->cred); + mc.cred); if(quit_now){ gnutls_deinit(*session); return -1; } } while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN); if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, "Error setting GnuTLS credentials: %s\n", - safer_gnutls_strerror(ret)); + fprintf(stderr, "Error setting GnuTLS credentials: %s\n", + safer_gnutls_strerror(ret)); gnutls_deinit(*session); return -1; } @@ -1023,6 +526,8 @@ /* ignore client certificate if any. */ gnutls_certificate_server_set_request(*session, GNUTLS_CERT_IGNORE); + gnutls_dh_set_prime_bits(*session, mc.dh_bits); + return 0; } @@ -1030,180 +535,16 @@ static void empty_log(__attribute__((unused)) AvahiLogLevel level, __attribute__((unused)) const char *txt){} -/* Helper function to add_local_route() and delete_local_route() */ -__attribute__((nonnull, warn_unused_result)) -static bool add_delete_local_route(const bool add, - const char *address, - AvahiIfIndex if_index){ - int ret; - char helper[] = "mandos-client-iprouteadddel"; - char add_arg[] = "add"; - char delete_arg[] = "delete"; - char debug_flag[] = "--debug"; - char *pluginhelperdir = getenv("MANDOSPLUGINHELPERDIR"); - if(pluginhelperdir == NULL){ - if(debug){ - fprintf_plus(stderr, "MANDOSPLUGINHELPERDIR environment" - " variable not set; cannot run helper\n"); - } - return false; - } - - char interface[IF_NAMESIZE]; - if(if_indextoname((unsigned int)if_index, interface) == NULL){ - perror_plus("if_indextoname"); - return false; - } - - int devnull = (int)TEMP_FAILURE_RETRY(open("/dev/null", O_RDONLY)); - if(devnull == -1){ - perror_plus("open(\"/dev/null\", O_RDONLY)"); - return false; - } - pid_t pid = fork(); - if(pid == 0){ - /* Child */ - /* Raise privileges */ - errno = raise_privileges_permanently(); - if(errno != 0){ - perror_plus("Failed to raise privileges"); - /* _exit(EX_NOPERM); */ - } else { - /* Set group */ - errno = 0; - ret = setgid(0); - if(ret == -1){ - perror_plus("setgid"); - _exit(EX_NOPERM); - } - /* Reset supplementary groups */ - errno = 0; - ret = setgroups(0, NULL); - if(ret == -1){ - perror_plus("setgroups"); - _exit(EX_NOPERM); - } - } - ret = dup2(devnull, STDIN_FILENO); - if(ret == -1){ - perror_plus("dup2(devnull, STDIN_FILENO)"); - _exit(EX_OSERR); - } - ret = close(devnull); - if(ret == -1){ - perror_plus("close"); - _exit(EX_OSERR); - } - ret = dup2(STDERR_FILENO, STDOUT_FILENO); - if(ret == -1){ - perror_plus("dup2(STDERR_FILENO, STDOUT_FILENO)"); - _exit(EX_OSERR); - } - int helperdir_fd = (int)TEMP_FAILURE_RETRY(open(pluginhelperdir, - O_RDONLY - | O_DIRECTORY - | O_PATH - | O_CLOEXEC)); - if(helperdir_fd == -1){ - perror_plus("open"); - _exit(EX_UNAVAILABLE); - } - int helper_fd = (int)TEMP_FAILURE_RETRY(openat(helperdir_fd, - helper, O_RDONLY)); - if(helper_fd == -1){ - perror_plus("openat"); - close(helperdir_fd); - _exit(EX_UNAVAILABLE); - } - close(helperdir_fd); -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - if(fexecve(helper_fd, (char *const []) - { helper, add ? add_arg : delete_arg, (char *)address, - interface, debug ? debug_flag : NULL, NULL }, - environ) == -1){ -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - perror_plus("fexecve"); - _exit(EXIT_FAILURE); - } - } - if(pid == -1){ - perror_plus("fork"); - return false; - } - int status; - pid_t pret = -1; - errno = 0; - do { - pret = waitpid(pid, &status, 0); - if(pret == -1 and errno == EINTR and quit_now){ - int errno_raising = 0; - if((errno = raise_privileges()) != 0){ - errno_raising = errno; - perror_plus("Failed to raise privileges in order to" - " kill helper program"); - } - if(kill(pid, SIGTERM) == -1){ - perror_plus("kill"); - } - if((errno_raising == 0) and (errno = lower_privileges()) != 0){ - perror_plus("Failed to lower privileges after killing" - " helper program"); - } - return false; - } - } while(pret == -1 and errno == EINTR); - if(pret == -1){ - perror_plus("waitpid"); - return false; - } - if(WIFEXITED(status)){ - if(WEXITSTATUS(status) != 0){ - fprintf_plus(stderr, "Error: iprouteadddel exited" - " with status %d\n", WEXITSTATUS(status)); - return false; - } - return true; - } - if(WIFSIGNALED(status)){ - fprintf_plus(stderr, "Error: iprouteadddel died by" - " signal %d\n", WTERMSIG(status)); - return false; - } - fprintf_plus(stderr, "Error: iprouteadddel crashed\n"); - return false; -} - -__attribute__((nonnull, warn_unused_result)) -static bool add_local_route(const char *address, - AvahiIfIndex if_index){ - if(debug){ - fprintf_plus(stderr, "Adding route to %s\n", address); - } - return add_delete_local_route(true, address, if_index); -} - -__attribute__((nonnull, warn_unused_result)) -static bool delete_local_route(const char *address, - AvahiIfIndex if_index){ - if(debug){ - fprintf_plus(stderr, "Removing route to %s\n", address); - } - return add_delete_local_route(false, address, if_index); -} - /* Called when a Mandos server is found */ -__attribute__((nonnull, warn_unused_result)) -static int start_mandos_communication(const char *ip, in_port_t port, +static int start_mandos_communication(const char *ip, uint16_t port, AvahiIfIndex if_index, - int af, mandos_context *mc){ + int af){ int ret, tcp_sd = -1; ssize_t sret; - struct sockaddr_storage to; + union { + struct sockaddr_in in; + struct sockaddr_in6 in6; + } to; char *buffer = NULL; char *decrypted_buffer = NULL; size_t buffer_length = 0; @@ -1212,7 +553,6 @@ int retval = -1; gnutls_session_t session; int pf; /* Protocol family */ - bool route_added = false; errno = 0; @@ -1229,58 +569,25 @@ pf = PF_INET; break; default: - fprintf_plus(stderr, "Bad address family: %d\n", af); + fprintf(stderr, "Bad address family: %d\n", af); errno = EINVAL; return -1; } - /* If the interface is specified and we have a list of interfaces */ - if(if_index != AVAHI_IF_UNSPEC and mc->interfaces != NULL){ - /* Check if the interface is one of the interfaces we are using */ - bool match = false; - { - char *interface = NULL; - while((interface = argz_next(mc->interfaces, - mc->interfaces_size, - interface))){ - if(if_nametoindex(interface) == (unsigned int)if_index){ - match = true; - break; - } - } - } - if(not match){ - /* This interface does not match any in the list, so we don't - connect to the server */ - if(debug){ - char interface[IF_NAMESIZE]; - if(if_indextoname((unsigned int)if_index, interface) == NULL){ - perror_plus("if_indextoname"); - } else { - fprintf_plus(stderr, "Skipping server on non-used interface" - " \"%s\"\n", - if_indextoname((unsigned int)if_index, - interface)); - } - } - return -1; - } - } - - ret = init_gnutls_session(&session, mc); + ret = init_gnutls_session(&session); if(ret != 0){ return -1; } if(debug){ - fprintf_plus(stderr, "Setting up a TCP connection to %s, port %" - PRIuMAX "\n", ip, (uintmax_t)port); + fprintf(stderr, "Setting up a TCP connection to %s, port %" PRIu16 + "\n", ip, port); } - tcp_sd = socket(pf, SOCK_STREAM | SOCK_CLOEXEC, 0); + tcp_sd = socket(pf, SOCK_STREAM, 0); if(tcp_sd < 0){ int e = errno; - perror_plus("socket"); + perror("socket"); errno = e; goto mandos_end; } @@ -1290,42 +597,47 @@ goto mandos_end; } + memset(&to, 0, sizeof(to)); if(af == AF_INET6){ - struct sockaddr_in6 *to6 = (struct sockaddr_in6 *)&to; - *to6 = (struct sockaddr_in6){ .sin6_family = (sa_family_t)af }; - ret = inet_pton(af, ip, &to6->sin6_addr); + to.in6.sin6_family = (sa_family_t)af; + ret = inet_pton(af, ip, &to.in6.sin6_addr); } else { /* IPv4 */ - struct sockaddr_in *to4 = (struct sockaddr_in *)&to; - *to4 = (struct sockaddr_in){ .sin_family = (sa_family_t)af }; - ret = inet_pton(af, ip, &to4->sin_addr); + to.in.sin_family = (sa_family_t)af; + ret = inet_pton(af, ip, &to.in.sin_addr); } if(ret < 0 ){ int e = errno; - perror_plus("inet_pton"); + perror("inet_pton"); errno = e; goto mandos_end; } if(ret == 0){ int e = errno; - fprintf_plus(stderr, "Bad address: %s\n", ip); + fprintf(stderr, "Bad address: %s\n", ip); errno = e; goto mandos_end; } if(af == AF_INET6){ - ((struct sockaddr_in6 *)&to)->sin6_port = htons(port); - if(IN6_IS_ADDR_LINKLOCAL - (&((struct sockaddr_in6 *)&to)->sin6_addr)){ + to.in6.sin6_port = htons(port); /* Spurious warnings from + -Wconversion and + -Wunreachable-code */ + + if(IN6_IS_ADDR_LINKLOCAL /* Spurious warnings from */ + (&to.in6.sin6_addr)){ /* -Wstrict-aliasing=2 or lower and + -Wunreachable-code*/ if(if_index == AVAHI_IF_UNSPEC){ - fprintf_plus(stderr, "An IPv6 link-local address is" - " incomplete without a network interface\n"); + fprintf(stderr, "An IPv6 link-local address is incomplete" + " without a network interface\n"); errno = EINVAL; goto mandos_end; } /* Set the network interface number as scope */ - ((struct sockaddr_in6 *)&to)->sin6_scope_id = (uint32_t)if_index; + to.in6.sin6_scope_id = (uint32_t)if_index; } } else { - ((struct sockaddr_in *)&to)->sin_port = htons(port); + to.in.sin_port = htons(port); /* Spurious warnings from + -Wconversion and + -Wunreachable-code */ } if(quit_now){ @@ -1337,97 +649,54 @@ if(af == AF_INET6 and if_index != AVAHI_IF_UNSPEC){ char interface[IF_NAMESIZE]; if(if_indextoname((unsigned int)if_index, interface) == NULL){ - perror_plus("if_indextoname"); + perror("if_indextoname"); } else { - fprintf_plus(stderr, "Connection to: %s%%%s, port %" PRIuMAX - "\n", ip, interface, (uintmax_t)port); + fprintf(stderr, "Connection to: %s%%%s, port %" PRIu16 "\n", + ip, interface, port); } } else { - fprintf_plus(stderr, "Connection to: %s, port %" PRIuMAX "\n", - ip, (uintmax_t)port); + fprintf(stderr, "Connection to: %s, port %" PRIu16 "\n", ip, + port); } char addrstr[(INET_ADDRSTRLEN > INET6_ADDRSTRLEN) ? INET_ADDRSTRLEN : INET6_ADDRSTRLEN] = ""; - if(af == AF_INET6){ - ret = getnameinfo((struct sockaddr *)&to, - sizeof(struct sockaddr_in6), - addrstr, sizeof(addrstr), NULL, 0, - NI_NUMERICHOST); - } else { - ret = getnameinfo((struct sockaddr *)&to, - sizeof(struct sockaddr_in), - addrstr, sizeof(addrstr), NULL, 0, - NI_NUMERICHOST); - } - if(ret == EAI_SYSTEM){ - perror_plus("getnameinfo"); - } else if(ret != 0) { - fprintf_plus(stderr, "getnameinfo: %s", gai_strerror(ret)); - } else if(strcmp(addrstr, ip) != 0){ - fprintf_plus(stderr, "Canonical address form: %s\n", addrstr); - } - } - - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - - while(true){ - if(af == AF_INET6){ - ret = connect(tcp_sd, (struct sockaddr *)&to, - sizeof(struct sockaddr_in6)); - } else { - ret = connect(tcp_sd, (struct sockaddr *)&to, /* IPv4 */ - sizeof(struct sockaddr_in)); - } - if(ret < 0){ - if(((errno == ENETUNREACH) or (errno == EHOSTUNREACH)) - and if_index != AVAHI_IF_UNSPEC - and connect_to == NULL - and not route_added and - ((af == AF_INET6 and not - IN6_IS_ADDR_LINKLOCAL(&(((struct sockaddr_in6 *) - &to)->sin6_addr))) - or (af == AF_INET and - /* Not a a IPv4LL address */ - (ntohl(((struct sockaddr_in *)&to)->sin_addr.s_addr) - & 0xFFFF0000L) != 0xA9FE0000L))){ - /* Work around Avahi bug - Avahi does not announce link-local - addresses if it has a global address, so local hosts with - *only* a link-local address (e.g. Mandos clients) cannot - connect to a Mandos server announced by Avahi on a server - host with a global address. Work around this by retrying - with an explicit route added with the server's address. - - Avahi bug reference: - https://lists.freedesktop.org/archives/avahi/2010-February/001833.html - https://bugs.debian.org/587961 - */ - if(debug){ - fprintf_plus(stderr, "Mandos server unreachable, trying" - " direct route\n"); - } - int e = errno; - route_added = add_local_route(ip, if_index); - if(route_added){ - continue; - } - errno = e; - } - if(errno != ECONNREFUSED or debug){ - int e = errno; - perror_plus("connect"); - errno = e; - } - goto mandos_end; - } - - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - break; + const char *pcret; + if(af == AF_INET6){ + pcret = inet_ntop(af, &(to.in6.sin6_addr), addrstr, + sizeof(addrstr)); + } else { + pcret = inet_ntop(af, &(to.in.sin_addr), addrstr, + sizeof(addrstr)); + } + if(pcret == NULL){ + perror("inet_ntop"); + } else { + if(strcmp(addrstr, ip) != 0){ + fprintf(stderr, "Canonical address form: %s\n", addrstr); + } + } + } + + if(quit_now){ + errno = EINTR; + goto mandos_end; + } + + if(af == AF_INET6){ + ret = connect(tcp_sd, &to.in6, sizeof(to)); + } else { + ret = connect(tcp_sd, &to.in, sizeof(to)); /* IPv4 */ + } + if(ret < 0){ + int e = errno; + perror("connect"); + errno = e; + goto mandos_end; + } + + if(quit_now){ + errno = EINTR; + goto mandos_end; } const char *out = mandos_protocol_version; @@ -1435,10 +704,10 @@ while(true){ size_t out_size = strlen(out); ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written, - out_size - written)); + out_size - written)); if(ret == -1){ int e = errno; - perror_plus("write"); + perror("write"); errno = e; goto mandos_end; } @@ -1461,7 +730,7 @@ } if(debug){ - fprintf_plus(stderr, "Establishing TLS session with %s\n", ip); + fprintf(stderr, "Establishing TLS session with %s\n", ip); } if(quit_now){ @@ -1469,11 +738,7 @@ goto mandos_end; } - /* This casting via intptr_t is to eliminate warning about casting - an int to a pointer type. This is exactly how the GnuTLS Guile - function "set-session-transport-fd!" does it. */ - gnutls_transport_set_ptr(session, - (gnutls_transport_ptr_t)(intptr_t)tcp_sd); + gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) tcp_sd); if(quit_now){ errno = EINTR; @@ -1490,7 +755,7 @@ if(ret != GNUTLS_E_SUCCESS){ if(debug){ - fprintf_plus(stderr, "*** GnuTLS Handshake failed ***\n"); + fprintf(stderr, "*** GnuTLS Handshake failed ***\n"); gnutls_perror(ret); } errno = EPROTO; @@ -1500,8 +765,8 @@ /* Read OpenPGP packet that contains the wanted password */ if(debug){ - fprintf_plus(stderr, "Retrieving OpenPGP encrypted password from" - " %s\n", ip); + fprintf(stderr, "Retrieving OpenPGP encrypted password from %s\n", + ip); } while(true){ @@ -1512,10 +777,10 @@ } buffer_capacity = incbuffer(&buffer, buffer_length, - buffer_capacity); + buffer_capacity); if(buffer_capacity == 0){ int e = errno; - perror_plus("incbuffer"); + perror("incbuffer"); errno = e; goto mandos_end; } @@ -1545,16 +810,15 @@ } } while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED); if(ret < 0){ - fprintf_plus(stderr, "*** GnuTLS Re-handshake failed " - "***\n"); + fprintf(stderr, "*** GnuTLS Re-handshake failed ***\n"); gnutls_perror(ret); errno = EPROTO; goto mandos_end; } break; default: - fprintf_plus(stderr, "Unknown error while reading data from" - " encrypted session with Mandos server\n"); + fprintf(stderr, "Unknown error while reading data from" + " encrypted session with Mandos server\n"); gnutls_bye(session, GNUTLS_SHUT_RDWR); errno = EIO; goto mandos_end; @@ -1565,7 +829,7 @@ } if(debug){ - fprintf_plus(stderr, "Closing TLS session\n"); + fprintf(stderr, "Closing TLS session\n"); } if(quit_now){ @@ -1583,11 +847,11 @@ if(buffer_length > 0){ ssize_t decrypted_buffer_size; - decrypted_buffer_size = pgp_packet_decrypt(buffer, buffer_length, - &decrypted_buffer, mc); + decrypted_buffer_size = pgp_packet_decrypt(buffer, + buffer_length, + &decrypted_buffer); if(decrypted_buffer_size >= 0){ - clearerr(stdout); written = 0; while(written < (size_t) decrypted_buffer_size){ if(quit_now){ @@ -1601,24 +865,14 @@ if(ret == 0 and ferror(stdout)){ int e = errno; if(debug){ - fprintf_plus(stderr, "Error writing encrypted data: %s\n", - strerror(errno)); + fprintf(stderr, "Error writing encrypted data: %s\n", + strerror(errno)); } errno = e; goto mandos_end; } 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; } } @@ -1627,30 +881,24 @@ mandos_end: { - if(route_added){ - if(not delete_local_route(ip, if_index)){ - fprintf_plus(stderr, "Failed to delete local route to %s on" - " interface %d", ip, if_index); - } - } int e = errno; free(decrypted_buffer); free(buffer); if(tcp_sd >= 0){ - ret = close(tcp_sd); + ret = (int)TEMP_FAILURE_RETRY(close(tcp_sd)); } if(ret == -1){ if(e == 0){ e = errno; } - perror_plus("close"); + perror("close"); } gnutls_deinit(session); - errno = e; if(quit_now){ - errno = EINTR; + e = EINTR; retval = -1; } + errno = e; } return retval; } @@ -1668,27 +916,22 @@ AVAHI_GCC_UNUSED AvahiStringList *txt, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, - void *mc){ - if(r == NULL){ - return; - } + AVAHI_GCC_UNUSED void* userdata){ + assert(r); /* Called whenever a service has been resolved successfully or timed out */ if(quit_now){ - avahi_s_service_resolver_free(r); return; } switch(event){ default: case AVAHI_RESOLVER_FAILURE: - fprintf_plus(stderr, "(Avahi Resolver) Failed to resolve service " - "'%s' of type '%s' in domain '%s': %s\n", name, type, - domain, - avahi_strerror(avahi_server_errno - (((mandos_context*)mc)->server))); + fprintf(stderr, "(Avahi Resolver) Failed to resolve service '%s'" + " of type '%s' in domain '%s': %s\n", name, type, domain, + avahi_strerror(avahi_server_errno(mc.server))); break; case AVAHI_RESOLVER_FOUND: @@ -1696,23 +939,14 @@ char ip[AVAHI_ADDRESS_STR_MAX]; avahi_address_snprint(ip, sizeof(ip), address); if(debug){ - fprintf_plus(stderr, "Mandos server \"%s\" found on %s (%s, %" - PRIdMAX ") on port %" PRIu16 "\n", name, - host_name, ip, (intmax_t)interface, port); + fprintf(stderr, "Mandos server \"%s\" found on %s (%s, %" + PRIdMAX ") on port %" PRIu16 "\n", name, host_name, + ip, (intmax_t)interface, port); } - int ret = start_mandos_communication(ip, (in_port_t)port, - interface, - avahi_proto_to_af(proto), - mc); + int ret = start_mandos_communication(ip, port, interface, + avahi_proto_to_af(proto)); if(ret == 0){ - avahi_simple_poll_quit(simple_poll); - } else { - if(not add_server(ip, (in_port_t)port, interface, - avahi_proto_to_af(proto), - &((mandos_context*)mc)->current_server)){ - fprintf_plus(stderr, "Failed to add server \"%s\" to server" - " list\n", name); - } + avahi_simple_poll_quit(mc.simple_poll); } } } @@ -1728,10 +962,8 @@ const char *domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, - void *mc){ - if(b == NULL){ - return; - } + AVAHI_GCC_UNUSED void* userdata){ + assert(b); /* Called whenever a new services becomes available on the LAN or is removed from the LAN */ @@ -1744,10 +976,9 @@ default: case AVAHI_BROWSER_FAILURE: - fprintf_plus(stderr, "(Avahi browser) %s\n", - avahi_strerror(avahi_server_errno - (((mandos_context*)mc)->server))); - avahi_simple_poll_quit(simple_poll); + fprintf(stderr, "(Avahi browser) %s\n", + avahi_strerror(avahi_server_errno(mc.server))); + avahi_simple_poll_quit(mc.simple_poll); return; case AVAHI_BROWSER_NEW: @@ -1756,14 +987,11 @@ the callback function is called the Avahi server will free the resolver for us. */ - if(avahi_s_service_resolver_new(((mandos_context*)mc)->server, - interface, protocol, name, type, - domain, protocol, 0, - resolve_callback, mc) == NULL) - fprintf_plus(stderr, "Avahi: Failed to resolve service '%s':" - " %s\n", name, - avahi_strerror(avahi_server_errno - (((mandos_context*)mc)->server))); + if(avahi_s_service_resolver_new(mc.server, interface, protocol, + name, type, domain, protocol, 0, + resolve_callback, NULL) == NULL) + fprintf(stderr, "Avahi: Failed to resolve service '%s': %s\n", + name, avahi_strerror(avahi_server_errno(mc.server))); break; case AVAHI_BROWSER_REMOVE: @@ -1772,14 +1000,13 @@ case AVAHI_BROWSER_ALL_FOR_NOW: case AVAHI_BROWSER_CACHE_EXHAUSTED: if(debug){ - fprintf_plus(stderr, "No Mandos server found, still" - " searching...\n"); + fprintf(stderr, "No Mandos server found, still searching...\n"); } break; } } -/* Signal handler that stops main loop after SIGTERM */ +/* stop main loop after sigterm has been called */ static void handle_sigterm(int sig){ if(quit_now){ return; @@ -1787,718 +1014,35 @@ quit_now = 1; signal_received = sig; int old_errno = errno; - /* set main loop to exit */ - if(simple_poll != NULL){ - avahi_simple_poll_quit(simple_poll); - } - errno = old_errno; -} - -__attribute__((nonnull, warn_unused_result)) -bool get_flags(const char *ifname, struct ifreq *ifr){ - int ret; - int old_errno; - - int s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); - if(s < 0){ - old_errno = errno; - perror_plus("socket"); - errno = old_errno; - return false; - } - strncpy(ifr->ifr_name, ifname, IF_NAMESIZE); - ifr->ifr_name[IF_NAMESIZE-1] = '\0'; /* NUL terminate */ - ret = ioctl(s, SIOCGIFFLAGS, ifr); - if(ret == -1){ - if(debug){ - old_errno = errno; - 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; -} - -__attribute__((nonnull, warn_unused_result)) -bool good_flags(const char *ifname, const struct ifreq *ifr){ - - /* Reject the loopback device */ - if(ifr->ifr_flags & IFF_LOOPBACK){ - if(debug){ - fprintf_plus(stderr, "Rejecting loopback interface \"%s\"\n", - ifname); - } - return false; - } - /* Accept point-to-point devices only if connect_to is specified */ - if(connect_to != NULL and (ifr->ifr_flags & IFF_POINTOPOINT)){ - if(debug){ - fprintf_plus(stderr, "Accepting point-to-point interface" - " \"%s\"\n", ifname); - } - return true; - } - /* Otherwise, reject non-broadcast-capable devices */ - if(not (ifr->ifr_flags & IFF_BROADCAST)){ - if(debug){ - fprintf_plus(stderr, "Rejecting non-broadcast interface" - " \"%s\"\n", ifname); - } - return false; - } - /* Reject non-ARP interfaces (including dummy interfaces) */ - if(ifr->ifr_flags & IFF_NOARP){ - if(debug){ - fprintf_plus(stderr, "Rejecting non-ARP interface \"%s\"\n", - ifname); - } - return false; - } - - /* Accept this device */ - if(debug){ - fprintf_plus(stderr, "Interface \"%s\" is good\n", ifname); - } - return true; -} - -/* - * This function determines if a directory entry in /sys/class/net - * corresponds to an acceptable network device. - * (This function is passed to scandir(3) as a filter function.) - */ -__attribute__((nonnull, warn_unused_result)) -int good_interface(const struct dirent *if_entry){ - if(if_entry->d_name[0] == '.'){ - return 0; - } - - struct ifreq ifr; - if(not get_flags(if_entry->d_name, &ifr)){ - if(debug){ - fprintf_plus(stderr, "Failed to get flags for interface " - "\"%s\"\n", if_entry->d_name); - } - return 0; - } - - if(not good_flags(if_entry->d_name, &ifr)){ - return 0; - } - return 1; -} - -/* - * This function determines if a network interface is up. - */ -__attribute__((nonnull, warn_unused_result)) -bool interface_is_up(const char *interface){ - struct ifreq ifr; - if(not get_flags(interface, &ifr)){ - if(debug){ - fprintf_plus(stderr, "Failed to get flags for interface " - "\"%s\"\n", interface); - } - return false; - } - - return (bool)(ifr.ifr_flags & IFF_UP); -} - -/* - * This function determines if a network interface is running - */ -__attribute__((nonnull, warn_unused_result)) -bool interface_is_running(const char *interface){ - struct ifreq ifr; - if(not get_flags(interface, &ifr)){ - if(debug){ - fprintf_plus(stderr, "Failed to get flags for interface " - "\"%s\"\n", interface); - } - return false; - } - - return (bool)(ifr.ifr_flags & IFF_RUNNING); -} - -__attribute__((nonnull, pure, warn_unused_result)) -int notdotentries(const struct dirent *direntry){ - /* Skip "." and ".." */ - if(direntry->d_name[0] == '.' - and (direntry->d_name[1] == '\0' - or (direntry->d_name[1] == '.' - and direntry->d_name[2] == '\0'))){ - return 0; - } - return 1; -} - -/* Is this directory entry a runnable program? */ -__attribute__((nonnull, warn_unused_result)) -int runnable_hook(const struct dirent *direntry){ - int ret; - size_t sret; - struct stat st; - - if((direntry->d_name)[0] == '\0'){ - /* Empty name? */ - return 0; - } - - sret = strspn(direntry->d_name, "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789" - "_.-"); - if((direntry->d_name)[sret] != '\0'){ - /* Contains non-allowed characters */ - if(debug){ - fprintf_plus(stderr, "Ignoring hook \"%s\" with bad name\n", - direntry->d_name); - } - return 0; - } - - ret = fstatat(hookdir_fd, direntry->d_name, &st, 0); - if(ret == -1){ - if(debug){ - perror_plus("Could not stat hook"); - } - return 0; - } - if(not (S_ISREG(st.st_mode))){ - /* Not a regular file */ - if(debug){ - fprintf_plus(stderr, "Ignoring hook \"%s\" - not a file\n", - direntry->d_name); - } - return 0; - } - if(not (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))){ - /* Not executable */ - if(debug){ - fprintf_plus(stderr, "Ignoring hook \"%s\" - not executable\n", - direntry->d_name); - } - return 0; - } - if(debug){ - fprintf_plus(stderr, "Hook \"%s\" is acceptable\n", - direntry->d_name); - } - return 1; -} - -__attribute__((nonnull, warn_unused_result)) -int avahi_loop_with_timeout(AvahiSimplePoll *s, int retry_interval, - mandos_context *mc){ - int ret; - struct timespec now; - struct timespec waited_time; - intmax_t block_time; - - while(true){ - if(mc->current_server == NULL){ - if(debug){ - fprintf_plus(stderr, "Wait until first server is found." - " No timeout!\n"); - } - ret = avahi_simple_poll_iterate(s, -1); - } else { - if(debug){ - fprintf_plus(stderr, "Check current_server if we should run" - " it, or wait\n"); - } - /* the current time */ - ret = clock_gettime(CLOCK_MONOTONIC, &now); - if(ret == -1){ - perror_plus("clock_gettime"); - return -1; - } - /* Calculating in ms how long time between now and server - who we visted longest time ago. Now - last seen. */ - waited_time.tv_sec = (now.tv_sec - - mc->current_server->last_seen.tv_sec); - waited_time.tv_nsec = (now.tv_nsec - - mc->current_server->last_seen.tv_nsec); - /* total time is 10s/10,000ms. - Converting to s from ms by dividing by 1,000, - and ns to ms by dividing by 1,000,000. */ - block_time = ((retry_interval - - ((intmax_t)waited_time.tv_sec * 1000)) - - ((intmax_t)waited_time.tv_nsec / 1000000)); - - if(debug){ - fprintf_plus(stderr, "Blocking for %" PRIdMAX " ms\n", - block_time); - } - - if(block_time <= 0){ - ret = start_mandos_communication(mc->current_server->ip, - mc->current_server->port, - mc->current_server->if_index, - mc->current_server->af, mc); - if(ret == 0){ - avahi_simple_poll_quit(s); - return 0; - } - ret = clock_gettime(CLOCK_MONOTONIC, - &mc->current_server->last_seen); - if(ret == -1){ - perror_plus("clock_gettime"); - return -1; - } - mc->current_server = mc->current_server->next; - block_time = 0; /* Call avahi to find new Mandos - servers, but don't block */ - } - - ret = avahi_simple_poll_iterate(s, (int)block_time); - } - if(ret != 0){ - if(ret > 0 or errno != EINTR){ - return (ret != 1) ? ret : 0; - } - } - } -} - -__attribute__((nonnull)) -void run_network_hooks(const char *mode, const char *interface, - const float delay){ - struct dirent **direntries = NULL; - if(hookdir_fd == -1){ - hookdir_fd = open(hookdir, O_RDONLY | O_DIRECTORY | O_PATH - | O_CLOEXEC); - if(hookdir_fd == -1){ - if(errno == ENOENT){ - if(debug){ - fprintf_plus(stderr, "Network hook directory \"%s\" not" - " found\n", hookdir); - } - } else { - perror_plus("open"); - } - 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; - for(int i = 0; i < numhooks; i++){ - direntry = direntries[i]; - if(debug){ - fprintf_plus(stderr, "Running network hook \"%s\"\n", - direntry->d_name); - } - pid_t hook_pid = fork(); - if(hook_pid == 0){ - /* Child */ - /* Raise privileges */ - errno = raise_privileges_permanently(); - if(errno != 0){ - perror_plus("Failed to raise privileges"); - _exit(EX_NOPERM); - } - /* Set group */ - errno = 0; - ret = setgid(0); - if(ret == -1){ - perror_plus("setgid"); - _exit(EX_NOPERM); - } - /* Reset supplementary groups */ - errno = 0; - ret = setgroups(0, NULL); - if(ret == -1){ - perror_plus("setgroups"); - _exit(EX_NOPERM); - } - ret = setenv("MANDOSNETHOOKDIR", hookdir, 1); - if(ret == -1){ - perror_plus("setenv"); - _exit(EX_OSERR); - } - ret = setenv("DEVICE", interface, 1); - if(ret == -1){ - perror_plus("setenv"); - _exit(EX_OSERR); - } - ret = setenv("VERBOSITY", debug ? "1" : "0", 1); - if(ret == -1){ - perror_plus("setenv"); - _exit(EX_OSERR); - } - ret = setenv("MODE", mode, 1); - if(ret == -1){ - perror_plus("setenv"); - _exit(EX_OSERR); - } - char *delaystring; - ret = asprintf(&delaystring, "%f", (double)delay); - if(ret == -1){ - perror_plus("asprintf"); - _exit(EX_OSERR); - } - ret = setenv("DELAY", delaystring, 1); - if(ret == -1){ - free(delaystring); - perror_plus("setenv"); - _exit(EX_OSERR); - } - free(delaystring); - if(connect_to != NULL){ - ret = setenv("CONNECT", connect_to, 1); - if(ret == -1){ - perror_plus("setenv"); - _exit(EX_OSERR); - } - } - int hook_fd = (int)TEMP_FAILURE_RETRY(openat(hookdir_fd, - direntry->d_name, - O_RDONLY)); - if(hook_fd == -1){ - perror_plus("openat"); - _exit(EXIT_FAILURE); - } - if(close(hookdir_fd) == -1){ - perror_plus("close"); - _exit(EXIT_FAILURE); - } - ret = dup2(devnull, STDIN_FILENO); - if(ret == -1){ - perror_plus("dup2(devnull, STDIN_FILENO)"); - _exit(EX_OSERR); - } - ret = close(devnull); - if(ret == -1){ - perror_plus("close"); - _exit(EX_OSERR); - } - ret = dup2(STDERR_FILENO, STDOUT_FILENO); - if(ret == -1){ - perror_plus("dup2(STDERR_FILENO, STDOUT_FILENO)"); - _exit(EX_OSERR); - } - if(fexecve(hook_fd, (char *const []){ direntry->d_name, NULL }, - environ) == -1){ - perror_plus("fexecve"); - _exit(EXIT_FAILURE); - } - } else { - if(hook_pid == -1){ - perror_plus("fork"); - free(direntry); - continue; - } - int status; - if(TEMP_FAILURE_RETRY(waitpid(hook_pid, &status, 0)) == -1){ - perror_plus("waitpid"); - free(direntry); - continue; - } - if(WIFEXITED(status)){ - if(WEXITSTATUS(status) != 0){ - fprintf_plus(stderr, "Warning: network hook \"%s\" exited" - " with status %d\n", direntry->d_name, - WEXITSTATUS(status)); - free(direntry); - continue; - } - } else if(WIFSIGNALED(status)){ - fprintf_plus(stderr, "Warning: network hook \"%s\" died by" - " signal %d\n", direntry->d_name, - WTERMSIG(status)); - free(direntry); - continue; - } else { - fprintf_plus(stderr, "Warning: network hook \"%s\"" - " crashed\n", direntry->d_name); - free(direntry); - continue; - } - } - if(debug){ - fprintf_plus(stderr, "Network hook \"%s\" ran successfully\n", - direntry->d_name); - } - free(direntry); - } - free(direntries); - if(close(hookdir_fd) == -1){ - perror_plus("close"); - } else { - hookdir_fd = -1; - } - close(devnull); -} - -__attribute__((nonnull, warn_unused_result)) -int bring_up_interface(const char *const interface, - const float delay){ - int old_errno = errno; - int ret; - struct ifreq network; - unsigned int if_index = if_nametoindex(interface); - if(if_index == 0){ - fprintf_plus(stderr, "No such interface: \"%s\"\n", interface); - errno = old_errno; - return ENXIO; - } - - if(quit_now){ - errno = old_errno; - return EINTR; - } - - if(not interface_is_up(interface)){ - int ret_errno = 0; - int ioctl_errno = 0; - if(not get_flags(interface, &network)){ - ret_errno = errno; - fprintf_plus(stderr, "Failed to get flags for interface " - "\"%s\"\n", interface); - errno = old_errno; - return ret_errno; - } - network.ifr_flags |= IFF_UP; /* set flag */ - - int sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); - if(sd == -1){ - ret_errno = errno; - perror_plus("socket"); - errno = old_errno; - return ret_errno; - } - - if(quit_now){ - ret = close(sd); - if(ret == -1){ - perror_plus("close"); - } - errno = old_errno; - return EINTR; - } - - if(debug){ - fprintf_plus(stderr, "Bringing up interface \"%s\"\n", - interface); - } - - /* Raise privileges */ - ret_errno = raise_privileges(); - if(ret_errno != 0){ - errno = ret_errno; - perror_plus("Failed to raise privileges"); - } - -#ifdef __linux__ - int ret_linux; - bool restore_loglevel = false; - if(ret_errno == 0){ - /* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO - messages about the network interface to mess up the prompt */ - ret_linux = klogctl(8, NULL, 5); - if(ret_linux == -1){ - perror_plus("klogctl"); - } else { - restore_loglevel = true; - } - } -#endif /* __linux__ */ - int ret_setflags = ioctl(sd, SIOCSIFFLAGS, &network); - ioctl_errno = errno; -#ifdef __linux__ - if(restore_loglevel){ - ret_linux = klogctl(7, NULL, 0); - if(ret_linux == -1){ - perror_plus("klogctl"); - } - } -#endif /* __linux__ */ - - /* If raise_privileges() succeeded above */ - if(ret_errno == 0){ - /* Lower privileges */ - ret_errno = lower_privileges(); - if(ret_errno != 0){ - errno = ret_errno; - perror_plus("Failed to lower privileges"); - } - } - - /* Close the socket */ - ret = close(sd); - if(ret == -1){ - perror_plus("close"); - } - - if(ret_setflags == -1){ - errno = ioctl_errno; - perror_plus("ioctl SIOCSIFFLAGS +IFF_UP"); - errno = old_errno; - return ioctl_errno; - } - } else if(debug){ - fprintf_plus(stderr, "Interface \"%s\" is already up; good\n", - interface); - } - - /* Sleep checking until interface is running. - Check every 0.25s, up to total time of delay */ - for(int i = 0; i < delay * 4; i++){ - if(interface_is_running(interface)){ - break; - } - struct timespec sleeptime = { .tv_nsec = 250000000 }; - ret = nanosleep(&sleeptime, NULL); - if(ret == -1 and errno != EINTR){ - perror_plus("nanosleep"); - } - } - - errno = old_errno; - return 0; -} - -__attribute__((nonnull, warn_unused_result)) -int take_down_interface(const char *const interface){ - int old_errno = errno; - struct ifreq network; - unsigned int if_index = if_nametoindex(interface); - if(if_index == 0){ - fprintf_plus(stderr, "No such interface: \"%s\"\n", interface); - errno = old_errno; - return ENXIO; - } - if(interface_is_up(interface)){ - int ret_errno = 0; - int ioctl_errno = 0; - if(not get_flags(interface, &network) and debug){ - ret_errno = errno; - fprintf_plus(stderr, "Failed to get flags for interface " - "\"%s\"\n", interface); - errno = old_errno; - return ret_errno; - } - network.ifr_flags &= ~(short)IFF_UP; /* clear flag */ - - int sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); - if(sd == -1){ - ret_errno = errno; - perror_plus("socket"); - errno = old_errno; - return ret_errno; - } - - if(debug){ - fprintf_plus(stderr, "Taking down interface \"%s\"\n", - interface); - } - - /* Raise privileges */ - ret_errno = raise_privileges(); - if(ret_errno != 0){ - errno = ret_errno; - perror_plus("Failed to raise privileges"); - } - - int ret_setflags = ioctl(sd, SIOCSIFFLAGS, &network); - ioctl_errno = errno; - - /* If raise_privileges() succeeded above */ - if(ret_errno == 0){ - /* Lower privileges */ - ret_errno = lower_privileges(); - if(ret_errno != 0){ - errno = ret_errno; - perror_plus("Failed to lower privileges"); - } - } - - /* Close the socket */ - int ret = close(sd); - if(ret == -1){ - perror_plus("close"); - } - - if(ret_setflags == -1){ - errno = ioctl_errno; - perror_plus("ioctl SIOCSIFFLAGS -IFF_UP"); - errno = old_errno; - return ioctl_errno; - } - } else if(debug){ - fprintf_plus(stderr, "Interface \"%s\" is already down; odd\n", - interface); - } - - errno = old_errno; - return 0; + if(mc.simple_poll != NULL){ + avahi_simple_poll_quit(mc.simple_poll); + } + errno = old_errno; } 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; - error_t ret_errno; + int error; int ret; intmax_t tmpmax; char *tmp; int exitcode = EXIT_SUCCESS; - char *interfaces_to_take_down = NULL; - size_t interfaces_to_take_down_size = 0; - char run_tempdir[] = "/run/tmp/mandosXXXXXX"; - char old_tempdir[] = "/tmp/mandosXXXXXX"; - char *tempdir = NULL; + const char *interface = "eth0"; + struct ifreq network; + int sd = -1; + bool take_down_interface = false; + uid_t uid; + gid_t gid; + char *connect_to = NULL; + char tempdir[] = "/tmp/mandosXXXXXX"; + bool tempdir_created = false; 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; bool gnutls_initialized = false; bool gpgme_initialized = false; float delay = 2.5f; - double retry_interval = 10; /* 10s between trying a server and - retrying the same server again */ struct sigaction old_sigterm_action = { .sa_handler = SIG_DFL }; struct sigaction sigterm_action = { .sa_handler = handle_sigterm }; @@ -2510,14 +1054,14 @@ errno = 0; ret = setgid(gid); if(ret == -1){ - perror_plus("setgid"); + perror("setgid"); } /* Lower user privileges (temporarily) */ errno = 0; ret = seteuid(uid); if(ret == -1){ - perror_plus("seteuid"); + perror("seteuid"); } if(quit_now){ @@ -2544,33 +1088,12 @@ { .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" " Diffie-Hellman key exchange", .group = 2 }, - { .name = "dh-params", .key = 134, - .arg = "FILE", - .doc = "PEM-encoded PKCS#3 file with pre-generated parameters" - " for the Diffie-Hellman key exchange", - .group = 2 }, { .name = "priority", .key = 130, .arg = "STRING", .doc = "GnuTLS priority string for the TLS handshake", @@ -2579,14 +1102,6 @@ .arg = "SECONDS", .doc = "Maximum delay to wait for interface startup", .group = 2 }, - { .name = "retry", .key = 132, - .arg = "SECONDS", - .doc = "Retry interval used when denied by the Mandos server", - .group = 2 }, - { .name = "network-hook-dir", .key = 133, - .arg = "DIR", - .doc = "Directory where network hooks are located", - .group = 2 }, /* * These reproduce what we would get without ARGP_NO_HELP */ @@ -2610,11 +1125,7 @@ connect_to = arg; break; case 'i': /* --interface */ - ret_errno = argz_add_sep(&mc.interfaces, &mc.interfaces_size, - arg, (int)','); - if(ret_errno != 0){ - argp_error(state, "%s", strerror(ret_errno)); - } + interface = arg; break; case 's': /* --seckey */ seckey = arg; @@ -2622,16 +1133,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); @@ -2641,9 +1142,6 @@ } mc.dh_bits = (typeof(mc.dh_bits))tmpmax; break; - case 134: /* --dh-params */ - dh_params_file = arg; - break; case 130: /* --priority */ mc.priority = arg; break; @@ -2653,17 +1151,6 @@ if(errno != 0 or tmp == arg or *tmp != '\0'){ argp_error(state, "Bad delay"); } - case 132: /* --retry */ - errno = 0; - retry_interval = strtod(arg, &tmp); - if(errno != 0 or tmp == arg or *tmp != '\0' - or (retry_interval * 1000) > INT_MAX - or retry_interval < 0){ - argp_error(state, "Bad retry interval"); - } - break; - case 133: /* --network-hook-dir */ - hookdir = arg; break; /* * These reproduce what we would get without ARGP_NO_HELP @@ -2672,13 +1159,11 @@ 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); + fprintf(state->out_stream, "%s\n", argp_program_version); exit(argp_err_exit_status); break; default: @@ -2691,15 +1176,15 @@ .args_doc = "", .doc = "Mandos client -- Get and decrypt" " passwords from a Mandos server" }; - ret_errno = argp_parse(&argp, argc, argv, - ARGP_IN_ORDER | ARGP_NO_HELP, 0, NULL); - switch(ret_errno){ + ret = argp_parse(&argp, argc, argv, + ARGP_IN_ORDER | ARGP_NO_HELP, 0, NULL); + switch(ret){ case 0: break; case ENOMEM: default: - errno = ret_errno; - perror_plus("argp_parse"); + errno = ret; + perror("argp_parse"); exitcode = EX_OSERR; goto end; case EINVAL: @@ -2708,123 +1193,6 @@ } } - { - /* Work around Debian bug #633582: - */ - - /* Re-raise privileges */ - ret = raise_privileges(); - if(ret != 0){ - errno = ret; - perror_plus("Failed to raise privileges"); - } else { - struct stat st; - - if(strcmp(seckey, PATHDIR "/" SECKEY) == 0){ - int seckey_fd = open(seckey, O_RDONLY); - if(seckey_fd == -1){ - perror_plus("open"); - } else { - ret = (int)TEMP_FAILURE_RETRY(fstat(seckey_fd, &st)); - if(ret == -1){ - perror_plus("fstat"); - } else { - if(S_ISREG(st.st_mode) - and st.st_uid == 0 and st.st_gid == 0){ - ret = fchown(seckey_fd, uid, gid); - if(ret == -1){ - perror_plus("fchown"); - } - } - } - close(seckey_fd); - } - } - - if(strcmp(pubkey, PATHDIR "/" PUBKEY) == 0){ - int pubkey_fd = open(pubkey, O_RDONLY); - if(pubkey_fd == -1){ - perror_plus("open"); - } else { - ret = (int)TEMP_FAILURE_RETRY(fstat(pubkey_fd, &st)); - if(ret == -1){ - perror_plus("fstat"); - } else { - if(S_ISREG(st.st_mode) - and st.st_uid == 0 and st.st_gid == 0){ - ret = fchown(pubkey_fd, uid, gid); - if(ret == -1){ - perror_plus("fchown"); - } - } - } - close(pubkey_fd); - } - } - - if(dh_params_file != NULL - and strcmp(dh_params_file, PATHDIR "/dhparams.pem" ) == 0){ - int dhparams_fd = open(dh_params_file, O_RDONLY); - if(dhparams_fd == -1){ - perror_plus("open"); - } else { - ret = (int)TEMP_FAILURE_RETRY(fstat(dhparams_fd, &st)); - if(ret == -1){ - perror_plus("fstat"); - } else { - if(S_ISREG(st.st_mode) - and st.st_uid == 0 and st.st_gid == 0){ - ret = fchown(dhparams_fd, uid, gid); - if(ret == -1){ - perror_plus("fchown"); - } - } - } - close(dhparams_fd); - } - } - - /* Lower privileges */ - ret = lower_privileges(); - if(ret != 0){ - errno = ret; - perror_plus("Failed to lower privileges"); - } - } - } - - /* Remove invalid interface names (except "none") */ - { - char *interface = NULL; - while((interface = argz_next(mc.interfaces, mc.interfaces_size, - interface))){ - if(strcmp(interface, "none") != 0 - and if_nametoindex(interface) == 0){ - if(interface[0] != '\0'){ - fprintf_plus(stderr, "Not using nonexisting interface" - " \"%s\"\n", interface); - } - argz_delete(&mc.interfaces, &mc.interfaces_size, interface); - interface = NULL; - } - } - } - - /* Run network hooks */ - { - if(mc.interfaces != NULL){ - interfaces_hooks = malloc(mc.interfaces_size); - if(interfaces_hooks == NULL){ - perror_plus("malloc"); - goto end; - } - memcpy(interfaces_hooks, mc.interfaces, mc.interfaces_size); - argz_stringify(interfaces_hooks, mc.interfaces_size, (int)','); - } - run_network_hooks("start", interfaces_hooks != NULL ? - interfaces_hooks : "", delay); - } - if(not debug){ avahi_set_log_function(empty_log); } @@ -2833,10 +1201,9 @@ from the signal handler */ /* Initialize the pseudo-RNG for Avahi */ srand((unsigned int) time(NULL)); - simple_poll = avahi_simple_poll_new(); - if(simple_poll == NULL){ - fprintf_plus(stderr, - "Avahi: Failed to create simple poll object.\n"); + mc.simple_poll = avahi_simple_poll_new(); + if(mc.simple_poll == NULL){ + fprintf(stderr, "Avahi: Failed to create simple poll object.\n"); exitcode = EX_UNAVAILABLE; goto end; } @@ -2844,19 +1211,19 @@ sigemptyset(&sigterm_action.sa_mask); ret = sigaddset(&sigterm_action.sa_mask, SIGINT); if(ret == -1){ - perror_plus("sigaddset"); + perror("sigaddset"); exitcode = EX_OSERR; goto end; } ret = sigaddset(&sigterm_action.sa_mask, SIGHUP); if(ret == -1){ - perror_plus("sigaddset"); + perror("sigaddset"); exitcode = EX_OSERR; goto end; } ret = sigaddset(&sigterm_action.sa_mask, SIGTERM); if(ret == -1){ - perror_plus("sigaddset"); + perror("sigaddset"); exitcode = EX_OSERR; goto end; } @@ -2866,142 +1233,195 @@ */ ret = sigaction(SIGINT, NULL, &old_sigterm_action); if(ret == -1){ - perror_plus("sigaction"); + perror("sigaction"); return EX_OSERR; } if(old_sigterm_action.sa_handler != SIG_IGN){ ret = sigaction(SIGINT, &sigterm_action, NULL); if(ret == -1){ - perror_plus("sigaction"); + perror("sigaction"); exitcode = EX_OSERR; goto end; } } ret = sigaction(SIGHUP, NULL, &old_sigterm_action); if(ret == -1){ - perror_plus("sigaction"); + perror("sigaction"); return EX_OSERR; } if(old_sigterm_action.sa_handler != SIG_IGN){ ret = sigaction(SIGHUP, &sigterm_action, NULL); if(ret == -1){ - perror_plus("sigaction"); + perror("sigaction"); exitcode = EX_OSERR; goto end; } } ret = sigaction(SIGTERM, NULL, &old_sigterm_action); if(ret == -1){ - perror_plus("sigaction"); + perror("sigaction"); return EX_OSERR; } if(old_sigterm_action.sa_handler != SIG_IGN){ ret = sigaction(SIGTERM, &sigterm_action, NULL); if(ret == -1){ - perror_plus("sigaction"); + perror("sigaction"); exitcode = EX_OSERR; goto end; } } - /* If no interfaces were specified, make a list */ - if(mc.interfaces == NULL){ - struct dirent **direntries = NULL; - /* Look for any good interfaces */ - ret = scandir(sys_class_net, &direntries, good_interface, - alphasort); - if(ret >= 1){ - /* Add all found interfaces to interfaces list */ - for(int i = 0; i < ret; ++i){ - ret_errno = argz_add(&mc.interfaces, &mc.interfaces_size, - direntries[i]->d_name); - if(ret_errno != 0){ - errno = ret_errno; - perror_plus("argz_add"); - free(direntries[i]); - continue; - } - if(debug){ - fprintf_plus(stderr, "Will use interface \"%s\"\n", - direntries[i]->d_name); - } - free(direntries[i]); - } - free(direntries); + /* If the interface is down, bring it up */ + if(interface[0] != '\0'){ + if_index = (AvahiIfIndex) if_nametoindex(interface); + if(if_index == 0){ + fprintf(stderr, "No such interface: \"%s\"\n", interface); + exitcode = EX_UNAVAILABLE; + goto end; + } + + if(quit_now){ + goto end; + } + + /* Re-raise priviliges */ + errno = 0; + ret = seteuid(0); + if(ret == -1){ + perror("seteuid"); + } + +#ifdef __linux__ + /* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO + messages about the network interface to mess up the prompt */ + ret = klogctl(8, NULL, 5); + bool restore_loglevel = true; + if(ret == -1){ + restore_loglevel = false; + perror("klogctl"); + } +#endif /* __linux__ */ + + sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); + if(sd < 0){ + perror("socket"); + exitcode = EX_OSERR; +#ifdef __linux__ + if(restore_loglevel){ + ret = klogctl(7, NULL, 0); + if(ret == -1){ + perror("klogctl"); + } + } +#endif /* __linux__ */ + /* Lower privileges */ + errno = 0; + ret = seteuid(uid); + if(ret == -1){ + perror("seteuid"); + } + goto end; + } + strcpy(network.ifr_name, interface); + ret = ioctl(sd, SIOCGIFFLAGS, &network); + if(ret == -1){ + perror("ioctl SIOCGIFFLAGS"); +#ifdef __linux__ + if(restore_loglevel){ + ret = klogctl(7, NULL, 0); + if(ret == -1){ + perror("klogctl"); + } + } +#endif /* __linux__ */ + exitcode = EX_OSERR; + /* Lower privileges */ + errno = 0; + ret = seteuid(uid); + if(ret == -1){ + perror("seteuid"); + } + goto end; + } + if((network.ifr_flags & IFF_UP) == 0){ + network.ifr_flags |= IFF_UP; + take_down_interface = true; + ret = ioctl(sd, SIOCSIFFLAGS, &network); + if(ret == -1){ + take_down_interface = false; + perror("ioctl SIOCSIFFLAGS"); + exitcode = EX_OSERR; +#ifdef __linux__ + if(restore_loglevel){ + ret = klogctl(7, NULL, 0); + if(ret == -1){ + perror("klogctl"); + } + } +#endif /* __linux__ */ + /* Lower privileges */ + errno = 0; + ret = seteuid(uid); + if(ret == -1){ + perror("seteuid"); + } + goto end; + } + } + /* sleep checking until interface is running */ + for(int i=0; i < delay * 4; i++){ + ret = ioctl(sd, SIOCGIFFLAGS, &network); + if(ret == -1){ + perror("ioctl SIOCGIFFLAGS"); + } else if(network.ifr_flags & IFF_RUNNING){ + break; + } + struct timespec sleeptime = { .tv_nsec = 250000000 }; + ret = nanosleep(&sleeptime, NULL); + if(ret == -1 and errno != EINTR){ + perror("nanosleep"); + } + } + if(not take_down_interface){ + /* We won't need the socket anymore */ + ret = (int)TEMP_FAILURE_RETRY(close(sd)); + if(ret == -1){ + perror("close"); + } + } +#ifdef __linux__ + if(restore_loglevel){ + /* Restores kernel loglevel to default */ + ret = klogctl(7, NULL, 0); + if(ret == -1){ + perror("klogctl"); + } + } +#endif /* __linux__ */ + /* Lower privileges */ + errno = 0; + if(take_down_interface){ + /* Lower privileges */ + ret = seteuid(uid); + if(ret == -1){ + perror("seteuid"); + } } else { - if(ret == 0){ - free(direntries); - } - fprintf_plus(stderr, "Could not find a network interface\n"); - exitcode = EXIT_FAILURE; - goto end; - } - } - - /* Bring up interfaces which are down, and remove any "none"s */ - { - char *interface = NULL; - while((interface = argz_next(mc.interfaces, mc.interfaces_size, - interface))){ - /* If interface name is "none", stop bringing up interfaces. - Also remove all instances of "none" from the list */ - if(strcmp(interface, "none") == 0){ - argz_delete(&mc.interfaces, &mc.interfaces_size, - interface); - interface = NULL; - while((interface = argz_next(mc.interfaces, - mc.interfaces_size, interface))){ - if(strcmp(interface, "none") == 0){ - argz_delete(&mc.interfaces, &mc.interfaces_size, - interface); - interface = NULL; - } - } - break; - } - bool interface_was_up = interface_is_up(interface); - errno = bring_up_interface(interface, delay); - if(not interface_was_up){ - if(errno != 0){ - fprintf_plus(stderr, "Failed to bring up interface \"%s\":" - " %s\n", interface, strerror(errno)); - } else { - errno = argz_add(&interfaces_to_take_down, - &interfaces_to_take_down_size, - interface); - if(errno != 0){ - perror_plus("argz_add"); - } - } - } - } - if(debug and (interfaces_to_take_down == NULL)){ - fprintf_plus(stderr, "No interfaces were brought up\n"); - } - } - - /* If we only got one interface, explicitly use only that one */ - if(argz_count(mc.interfaces, mc.interfaces_size) == 1){ - if(debug){ - fprintf_plus(stderr, "Using only interface \"%s\"\n", - mc.interfaces); - } - if_index = (AvahiIfIndex)if_nametoindex(mc.interfaces); + /* Lower privileges permanently */ + ret = setuid(uid); + if(ret == -1){ + perror("setuid"); + } + } } if(quit_now){ 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 + ret = init_gnutls_global(pubkey, seckey); if(ret == -1){ - fprintf_plus(stderr, "init_gnutls_global failed\n"); + fprintf(stderr, "init_gnutls_global failed\n"); exitcode = EX_UNAVAILABLE; goto end; } else { @@ -3012,17 +1432,10 @@ goto end; } - /* Try /run/tmp before /tmp */ - tempdir = mkdtemp(run_tempdir); - if(tempdir == NULL and errno == ENOENT){ - if(debug){ - fprintf_plus(stderr, "Tempdir %s did not work, trying %s\n", - run_tempdir, old_tempdir); - } - tempdir = mkdtemp(old_tempdir); - } - if(tempdir == NULL){ - perror_plus("mkdtemp"); + tempdir_created = true; + if(mkdtemp(tempdir) == NULL){ + tempdir_created = false; + perror("mkdtemp"); goto end; } @@ -3030,8 +1443,8 @@ goto end; } - if(not init_gpgme(pubkey, seckey, tempdir, &mc)){ - fprintf_plus(stderr, "init_gpgme failed\n"); + if(not init_gpgme(pubkey, seckey, tempdir)){ + fprintf(stderr, "init_gpgme failed\n"); exitcode = EX_UNAVAILABLE; goto end; } else { @@ -3046,9 +1459,8 @@ /* Connect directly, do not use Zeroconf */ /* (Mainly meant for debugging) */ char *address = strrchr(connect_to, ':'); - if(address == NULL){ - fprintf_plus(stderr, "No colon in address\n"); + fprintf(stderr, "No colon in address\n"); exitcode = EX_USAGE; goto end; } @@ -3057,58 +1469,59 @@ goto end; } - in_port_t port; + uint16_t port; errno = 0; tmpmax = strtoimax(address+1, &tmp, 10); if(errno != 0 or tmp == address+1 or *tmp != '\0' - or tmpmax != (in_port_t)tmpmax){ - fprintf_plus(stderr, "Bad port number\n"); + or tmpmax != (uint16_t)tmpmax){ + fprintf(stderr, "Bad port number\n"); exitcode = EX_USAGE; goto end; } - + if(quit_now){ goto end; } - port = (in_port_t)tmpmax; + port = (uint16_t)tmpmax; *address = '\0'; + address = connect_to; /* Colon in address indicates IPv6 */ int af; - if(strchr(connect_to, ':') != NULL){ + if(strchr(address, ':') != NULL){ af = AF_INET6; - /* Accept [] around IPv6 address - see RFC 5952 */ - if(connect_to[0] == '[' and address[-1] == ']') - { - connect_to++; - address[-1] = '\0'; - } } else { af = AF_INET; } - address = connect_to; if(quit_now){ goto end; } - while(not quit_now){ - ret = start_mandos_communication(address, port, if_index, af, - &mc); - if(quit_now or ret == 0){ - break; - } - if(debug){ - fprintf_plus(stderr, "Retrying in %d seconds\n", - (int)retry_interval); - } - sleep((unsigned int)retry_interval); - } - - if(not quit_now){ + ret = start_mandos_communication(address, port, if_index, af); + if(ret < 0){ + switch(errno){ + case ENETUNREACH: + case EHOSTDOWN: + case EHOSTUNREACH: + exitcode = EX_NOHOST; + break; + case EINVAL: + exitcode = EX_USAGE; + break; + case EIO: + exitcode = EX_IOERR; + break; + case EPROTO: + exitcode = EX_PROTOCOL; + break; + default: + exitcode = EX_OSERR; + break; + } + } else { exitcode = EXIT_SUCCESS; } - goto end; } @@ -3126,8 +1539,9 @@ config.publish_domain = 0; /* Allocate a new server */ - mc.server = avahi_server_new(avahi_simple_poll_get(simple_poll), - &config, NULL, NULL, &ret); + mc.server = avahi_server_new(avahi_simple_poll_get + (mc.simple_poll), &config, NULL, + NULL, &error); /* Free the Avahi configuration data */ avahi_server_config_free(&config); @@ -3135,8 +1549,8 @@ /* Check if creating the Avahi server object succeeded */ if(mc.server == NULL){ - fprintf_plus(stderr, "Failed to create Avahi server: %s\n", - avahi_strerror(ret)); + fprintf(stderr, "Failed to create Avahi server: %s\n", + avahi_strerror(error)); exitcode = EX_UNAVAILABLE; goto end; } @@ -3148,11 +1562,10 @@ /* Create the Avahi service browser */ sb = avahi_s_service_browser_new(mc.server, if_index, AVAHI_PROTO_UNSPEC, "_mandos._tcp", - NULL, 0, browse_callback, - (void *)&mc); + NULL, 0, browse_callback, NULL); if(sb == NULL){ - fprintf_plus(stderr, "Failed to create service browser: %s\n", - avahi_strerror(avahi_server_errno(mc.server))); + fprintf(stderr, "Failed to create service browser: %s\n", + avahi_strerror(avahi_server_errno(mc.server))); exitcode = EX_UNAVAILABLE; goto end; } @@ -3164,42 +1577,30 @@ /* Run the main loop */ if(debug){ - fprintf_plus(stderr, "Starting Avahi loop search\n"); + fprintf(stderr, "Starting Avahi loop search\n"); } - ret = avahi_loop_with_timeout(simple_poll, - (int)(retry_interval * 1000), &mc); - if(debug){ - fprintf_plus(stderr, "avahi_loop_with_timeout exited %s\n", - (ret == 0) ? "successfully" : "with error"); - } + avahi_simple_poll_loop(mc.simple_poll); 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(stderr, "%s exiting\n", argv[0]); } /* Cleanup things */ - free(mc.interfaces); - if(sb != NULL) avahi_s_service_browser_free(sb); if(mc.server != NULL) avahi_server_free(mc.server); - if(simple_poll != NULL) - avahi_simple_poll_free(simple_poll); + if(mc.simple_poll != NULL) + avahi_simple_poll_free(mc.simple_poll); if(gnutls_initialized){ gnutls_certificate_free_credentials(mc.cred); + gnutls_global_deinit(); gnutls_dh_params_deinit(mc.dh_params); } @@ -3207,123 +1608,80 @@ gpgme_release(mc.ctx); } - /* Cleans up the circular linked list of Mandos servers the client - has seen */ - if(mc.current_server != NULL){ - mc.current_server->prev->next = NULL; - while(mc.current_server != NULL){ - server *next = mc.current_server->next; -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - free((char *)(mc.current_server->ip)); -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - free(mc.current_server); - mc.current_server = next; - } - } - - /* Re-raise privileges */ - { - ret = raise_privileges(); - if(ret != 0){ - errno = ret; - perror_plus("Failed to raise privileges"); - } else { - - /* Run network hooks */ - run_network_hooks("stop", interfaces_hooks != NULL ? - interfaces_hooks : "", delay); - - /* 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))){ - ret = take_down_interface(interface); - if(ret != 0){ - errno = ret; - perror_plus("Failed to take down interface"); - } - } - if(debug and (interfaces_to_take_down == NULL)){ - fprintf_plus(stderr, "No interfaces needed to be taken" - " down\n"); - } - } - } - - ret = lower_privileges_permanently(); - if(ret != 0){ - errno = ret; - perror_plus("Failed to lower privileges permanently"); - } - } - - free(interfaces_to_take_down); - free(interfaces_hooks); - - void clean_dir_at(int base, const char * const dirname, - uintmax_t level){ - struct dirent **direntries = NULL; - int dret; - int dir_fd = (int)TEMP_FAILURE_RETRY(openat(base, dirname, - O_RDONLY - | O_NOFOLLOW - | O_DIRECTORY - | O_PATH)); - if(dir_fd == -1){ - perror_plus("open"); - return; - } - int numentries = scandirat(dir_fd, ".", &direntries, - notdotentries, alphasort); - if(numentries >= 0){ - for(int i = 0; i < numentries; i++){ - if(debug){ - fprintf_plus(stderr, "Unlinking \"%s/%s\"\n", - dirname, direntries[i]->d_name); - } - dret = unlinkat(dir_fd, direntries[i]->d_name, 0); - if(dret == -1){ - if(errno == EISDIR){ - dret = unlinkat(dir_fd, direntries[i]->d_name, - AT_REMOVEDIR); - } - if((dret == -1) and (errno == ENOTEMPTY) - and (strcmp(direntries[i]->d_name, "private-keys-v1.d") - == 0) and (level == 0)){ - /* Recurse only in this special case */ - clean_dir_at(dir_fd, direntries[i]->d_name, level+1); - dret = 0; - } - if((dret == -1) and (errno != ENOENT)){ - fprintf_plus(stderr, "unlink(\"%s/%s\"): %s\n", dirname, - direntries[i]->d_name, strerror(errno)); - } - } - free(direntries[i]); - } - - /* need to clean even if 0 because man page doesn't specify */ - free(direntries); - dret = unlinkat(base, dirname, AT_REMOVEDIR); - if(dret == -1 and errno != ENOENT){ - perror_plus("rmdir"); - } - } else { - perror_plus("scandirat"); - } - close(dir_fd); - } - - /* Removes the GPGME temp directory and all files inside */ - if(tempdir != NULL){ - clean_dir_at(-1, tempdir, 0); + /* Take down the network interface */ + if(take_down_interface){ + /* Re-raise priviliges */ + errno = 0; + ret = seteuid(0); + if(ret == -1){ + perror("seteuid"); + } + if(geteuid() == 0){ + ret = ioctl(sd, SIOCGIFFLAGS, &network); + if(ret == -1){ + perror("ioctl SIOCGIFFLAGS"); + } else if(network.ifr_flags & IFF_UP) { + network.ifr_flags &= ~(short)IFF_UP; /* clear flag */ + ret = ioctl(sd, SIOCSIFFLAGS, &network); + if(ret == -1){ + perror("ioctl SIOCSIFFLAGS"); + } + } + ret = (int)TEMP_FAILURE_RETRY(close(sd)); + if(ret == -1){ + perror("close"); + } + /* Lower privileges permanently */ + errno = 0; + ret = setuid(uid); + if(ret == -1){ + perror("setuid"); + } + } + } + + /* Removes the temp directory used by GPGME */ + if(tempdir_created){ + DIR *d; + struct dirent *direntry; + d = opendir(tempdir); + if(d == NULL){ + if(errno != ENOENT){ + perror("opendir"); + } + } else { + while(true){ + direntry = readdir(d); + if(direntry == NULL){ + break; + } + /* Skip "." and ".." */ + if(direntry->d_name[0] == '.' + and (direntry->d_name[1] == '\0' + or (direntry->d_name[1] == '.' + and direntry->d_name[2] == '\0'))){ + continue; + } + char *fullname = NULL; + ret = asprintf(&fullname, "%s/%s", tempdir, + direntry->d_name); + if(ret < 0){ + perror("asprintf"); + continue; + } + ret = remove(fullname); + if(ret == -1){ + fprintf(stderr, "remove(\"%s\"): %s\n", fullname, + strerror(errno)); + } + free(fullname); + } + closedir(d); + } + ret = rmdir(tempdir); + if(ret == -1 and errno != ENOENT){ + perror("rmdir"); + } } if(quit_now){ @@ -3333,13 +1691,13 @@ &old_sigterm_action, NULL)); if(ret == -1){ - perror_plus("sigaction"); + perror("sigaction"); } do { ret = raise(signal_received); } while(ret != 0 and errno == EINTR); if(ret != 0){ - perror_plus("raise"); + perror("raise"); abort(); } TEMP_FAILURE_RETRY(pause()); === modified file 'plugins.d/mandos-client.xml' --- plugins.d/mandos-client.xml 2019-07-14 22:50:47 +0000 +++ plugins.d/mandos-client.xml 2009-02-09 02:01:13 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -19,30 +19,20 @@ Björn Påhlsson
- belorn@recompile.se + belorn@fukt.bsnet.se
Teddy Hogeborn
- teddy@recompile.se + teddy@fukt.bsnet.se
2008 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -73,13 +63,11 @@ >PORT - + - + NAME + @@ -96,20 +84,6 @@ FILE - - - - - - - - - - @@ -119,23 +93,10 @@ - - - - - - - - - - - - @@ -166,33 +127,15 @@ communicates with mandos8 to get a password. In slightly more detail, this client program - 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 are periodically retried. If no servers are found it - will wait indefinitely for new servers to appear. -
- - The network interfaces are selected like this: If any interfaces - are specified using the option, - those interface are used. Otherwise, - &COMMANDNAME; will use all interfaces that - are not loopback interfaces, are not point-to-point interfaces, - are capable of broadcasting and do not have the NOARP flag (see - netdevice - 7). (If the - option is used, point-to-point - interfaces and non-broadcast interfaces are accepted.) If any - used interfaces are not up and running, they are first taken up - (and later taken down again on program exit). - - - Before network interfaces are selected, all network - hooks are run; see . + brings up a network interface, uses the interface’s IPv6 + link-local address to get network connectivity, uses Zeroconf to + find servers on the local network, and communicates with 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 is received. If no servers are found, or after + all servers have been tried, it waits indefinitely for new + servers to appear. This program is not meant to be run directly; it is really meant @@ -245,50 +188,42 @@ assumed to separate the address from the port number. - Normally, Zeroconf would be used to locate Mandos servers, - in which case this option would only be used when testing - and debugging. + This option is normally only useful for testing and + debugging. + >NAME + NAME - Comma separated list of network interfaces that will be - brought up and scanned for Mandos servers to connect to. - The default is the empty string, which will automatically - use all appropriate interfaces. + Network interface that will be brought up and scanned for + Mandos servers to connect to. The default is + eth0. - If the option is used, and - exactly one interface name is specified (except - none), this specifies - the interface to use to connect to the address given. + If the option is used, this + specifies the interface to use to connect to the address + given. Note that since this program will normally run in the initial RAM disk environment, the interface must be an interface which exists at that stage. Thus, the interface - can normally not be a pseudo-interface such as - br0 or tun0; such interfaces - will not exist until much later in the boot process, and - can not be used by this program, unless created by a - network hook — see . + can not be a pseudo-interface such as br0 + or tun0; such interfaces will not exist + until much later in the boot process, and can not be used + by this program. - NAME can be the string - none; this will make - &COMMANDNAME; only bring up interfaces - specified before this string. This - is not recommended, and only meant for advanced users. + NAME can be the empty string; + this will not use any specific interface, and will not + bring up an interface on startup. This is not + recommended, and only meant for advanced users. @@ -322,34 +257,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. - - - - - @@ -364,28 +271,7 @@ 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. - - - - - - - - - Specifies a PEM-encoded PKCS#3 file to read the parameters - needed by the TLS Diffie-Hellman key exchange from. If - this option is not given, or if the file for some reason - could not be used, the parameters will be generated on - startup, which will take some time and processing power. - Those using servers running under time, power or processor - constraints may want to generate such a file in advance - and use this option. + TLS Diffie-Hellman key exchange. Default is 1024. @@ -395,7 +281,7 @@ >SECONDS - After bringing a network interface up, the program waits + After bringing the network interface up, the program waits for the interface to arrive in a running state before proceeding. During this time, the kernel log level will be lowered to reduce clutter on the system @@ -405,31 +291,6 @@ - - - - - - All Mandos servers are tried repeatedly until a password - is received. This value specifies, in seconds, how long - between each successive try for the same - server. The default is 10 seconds. - - - - - - - - - Network hook directory. The default directory is - /lib/mandos/network-hooks.d. - - - @@ -496,10 +357,8 @@ plugin-runner 8mandos) is used to run both this program and others in in parallel, - one of which ( - password-prompt - 8mandos) will prompt for - passwords on the system console. + one of which will prompt for passwords on + the system console.
@@ -510,204 +369,22 @@ server could be found and the password received from it could be successfully decrypted and output on standard output. The program will exit with a non-zero exit status only if a critical - error occurs. Otherwise, it will forever connect to any - discovered Mandos servers, trying to - get a decryptable password and print it. + error occurs. Otherwise, it will forever connect to new + Mandos servers as they appear, trying + to get a decryptable password and print it.
ENVIRONMENT - - - MANDOSPLUGINHELPERDIR - - - 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. - - - - - This program does not use any other environment variables, not - even the ones provided by cryptsetup8 . - - NETWORK HOOKS - - If a network interface like a bridge or tunnel is required to - find a Mandos server, this requires the interface to be up and - running before &COMMANDNAME; starts looking - for Mandos servers. This can be accomplished by creating a - network hook program, and placing it in a special - directory. - - - Before the network is used (and again before program exit), any - runnable programs found in the network hook directory are run - with the argument start or - stop. This should bring up or - down, respectively, any network interface which - &COMMANDNAME; should use. - - - REQUIREMENTS - - A network hook must be an executable file, and its name must - consist entirely of upper and lower case letters, digits, - underscores, periods, and hyphens. - - - A network hook will receive one argument, which can be one of - the following: - - - - start - - - This should make the network hook create (if necessary) - and bring up a network interface. - - - - - stop - - - This should make the network hook take down a network - interface, and delete it if it did not exist previously. - - - - - files - - - This should make the network hook print, one - file per line, all the files needed for it to - run. (These files will be copied into the initial RAM - filesystem.) Typical use is for a network hook which is - a shell script to print its needed binaries. - - - It is not necessary to print any non-executable files - already in the network hook directory, these will be - copied implicitly if they otherwise satisfy the name - requirements. - - - - - modules - - - This should make the network hook print, on - separate lines, all the kernel modules needed - for it to run. (These modules will be copied into the - initial RAM filesystem.) For instance, a tunnel - interface needs the - tun module. - - - - - - The network hook will be provided with a number of environment - variables: - - - - MANDOSNETHOOKDIR - - - The network hook directory, specified to - &COMMANDNAME; by the - option. Note: this - should always be used by the - network hook to refer to itself or any files in the hook - directory it may require. - - - - - DEVICE - - - The network interfaces, as specified to - &COMMANDNAME; by the - option, combined to one - string and separated by commas. If this is set, and - does not contain the interface a hook will bring up, - there is no reason for a hook to continue. - - - - - MODE - - - This will be the same as the first argument; - i.e. start, - stop, - files, or - modules. - - - - - VERBOSITY - - - This will be the 1 if - the option is passed to - &COMMANDNAME;, otherwise - 0. - - - - - DELAY - - - This will be the same as the - option passed to &COMMANDNAME;. Is - only set if MODE is - start or - stop. - - - - - CONNECT - - - This will be the same as the - option passed to &COMMANDNAME;. Is - only set if is passed and - MODE is - start or - stop. - - - - - - A hook may not read from standard input, and should be - restrictive in printing to standard output or standard error - unless VERBOSITY is - 1. - - - - FILES @@ -725,38 +402,14 @@ - - /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 - - - Directory where network hooks are located. Change this - with the option. See - . - - - - - BUGS - - + + + + + EXAMPLE @@ -768,8 +421,8 @@ - Normal invocation needs no options, if the network interfaces - can be automatically determined: + Normal invocation needs no options, if the network interface + is eth0: &COMMANDNAME; @@ -777,8 +430,8 @@ - Search for Mandos servers (and connect to them) using one - specific interface: + Search for Mandos servers (and connect to them) using another + interface: @@ -787,18 +440,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 +460,7 @@ -&COMMANDNAME; --debug --pubkey keydir/pubkey.txt --seckey keydir/seckey.txt --tls-pubkey keydir/tls-pubkey.pem --tls-privkey keydir/tls-privkey.pem --connect fe80::aede:48ff:fe71:f6f2:4711 --interface eth2 +&COMMANDNAME; --debug --pubkey keydir/pubkey.txt --seckey keydir/seckey.txt --connect fe80::aede:48ff:fe71:f6f2:4711 --interface eth2 @@ -837,20 +490,19 @@ 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. It will also help if the checker program on the server is configured to request something from the client which can not be - spoofed by someone else on the network, like SSH server key - fingerprints, and unlike unencrypted ICMP - echo (ping) replies. + spoofed by someone else on the network, unlike unencrypted + ICMP echo (ping) replies. Note: This makes it completely insecure to @@ -864,8 +516,6 @@ SEE ALSO - intro - 8mandos, cryptsetup 8, crypttab @@ -902,19 +552,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 @@ -948,7 +599,7 @@ This client uses IPv6 link-local addresses, which are immediately usable since a link-local addresses is - automatically assigned to a network interface when it + automatically assigned to a network interfaces when it is brought up. @@ -958,12 +609,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 +631,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 2019-07-10 21:51:36 +0000 +++ plugins.d/password-prompt.c 2010-03-27 18:39:02 +0000 @@ -2,58 +2,51 @@ /* * Password-prompt - Read a password from the terminal and print it * - * 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 - * 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,2009 Teddy Hogeborn + * Copyright © 2008,2009 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 . + * Contact the authors at . */ -#define _GNU_SOURCE /* getline(), asprintf() */ +#define _GNU_SOURCE /* getline() */ -#include /* struct termios, tcsetattr(), +#include /* struct termios, tcsetattr(), TCSAFLUSH, tcgetattr(), ECHO */ #include /* struct termios, tcsetattr(), STDIN_FILENO, TCSAFLUSH, - tcgetattr(), ECHO, readlink() */ + tcgetattr(), ECHO */ #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 /* ssize_t */ #include /* EXIT_SUCCESS, EXIT_FAILURE, - getenv(), free() */ -#include /* scandir(), alphasort() */ + getenv() */ #include /* fprintf(), stderr, getline(), - stdin, feof(), fputc(), vfprintf(), - vasprintf() */ + stdin, feof(), perror(), fputc() + */ #include /* errno, EBADF, ENOTTY, EINVAL, EFAULT, EFBIG, EIO, ENOSPC, EINTR */ -#include /* error() */ #include /* or, not */ #include /* bool, false, true */ -#include /* strtoumax() */ -#include /* struct stat, lstat(), open() */ -#include /* strlen, rindex, memcmp, strerror() - */ +#include /* strlen, rindex */ #include /* struct argp_option, struct argp_state, struct argp, argp_parse(), error_t, @@ -61,40 +54,12 @@ 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; bool debug = false; const char *argp_program_version = "password-prompt " VERSION; -const char *argp_program_bug_address = ""; - -/* Needed for conflict resolution */ -const char plymouth_name[] = "plymouthd"; - -/* Function to use when printing errors */ -__attribute__((format (gnu_printf, 3, 4))) -void error_plus(int status, int errnum, const char *formatstring, - ...){ - va_list ap; - char *text; - int ret; - - va_start(ap, formatstring); - ret = vasprintf(&text, formatstring, ap); - if(ret == -1){ - fprintf(stderr, "Mandos plugin %s: ", - program_invocation_short_name); - vfprintf(stderr, formatstring, ap); - fprintf(stderr, ": %s\n", strerror(errnum)); - error(status, errno, "vasprintf while printing error"); - return; - } - fprintf(stderr, "Mandos plugin "); - error(status, errnum, "%s", text); - free(text); -} +const char *argp_program_bug_address = ""; static void termination_handler(int signum){ if(quit_now){ @@ -104,132 +69,8 @@ signal_received = signum; } -bool conflict_detection(void){ - - /* plymouth conflicts with password-prompt since both want to read - from the terminal. Password-prompt will exit if it detects - plymouth since plymouth performs the same functionality. - */ - __attribute__((nonnull)) - int is_plymouth(const struct dirent *proc_entry){ - int ret; - int cl_fd; - { - uintmax_t proc_id; - char *tmp; - errno = 0; - proc_id = strtoumax(proc_entry->d_name, &tmp, 10); - - if(errno != 0 or *tmp != '\0' - or proc_id != (uintmax_t)((pid_t)proc_id)){ - return 0; - } - } - - char *cmdline_filename; - ret = asprintf(&cmdline_filename, "/proc/%s/cmdline", - proc_entry->d_name); - if(ret == -1){ - error_plus(0, errno, "asprintf"); - return 0; - } - - /* Open /proc//cmdline */ - cl_fd = open(cmdline_filename, O_RDONLY); - free(cmdline_filename); - if(cl_fd == -1){ - if(errno != ENOENT){ - error_plus(0, errno, "open"); - } - return 0; - } - - char *cmdline = NULL; - { - size_t cmdline_len = 0; - size_t cmdline_allocated = 0; - char *tmp; - const size_t blocksize = 1024; - ssize_t sret; - do { - /* Allocate more space? */ - if(cmdline_len + blocksize + 1 > cmdline_allocated){ - tmp = realloc(cmdline, cmdline_allocated + blocksize + 1); - if(tmp == NULL){ - error_plus(0, errno, "realloc"); - free(cmdline); - close(cl_fd); - return 0; - } - cmdline = tmp; - cmdline_allocated += blocksize; - } - - /* Read data */ - sret = read(cl_fd, cmdline + cmdline_len, - cmdline_allocated - cmdline_len); - if(sret == -1){ - error_plus(0, errno, "read"); - free(cmdline); - close(cl_fd); - return 0; - } - cmdline_len += (size_t)sret; - } while(sret != 0); - ret = close(cl_fd); - if(ret == -1){ - error_plus(0, errno, "close"); - free(cmdline); - return 0; - } - cmdline[cmdline_len] = '\0'; /* Make sure it is terminated */ - } - /* we now have cmdline */ - - /* get basename */ - char *cmdline_base = strrchr(cmdline, '/'); - if(cmdline_base != NULL){ - cmdline_base += 1; /* skip the slash */ - } else { - cmdline_base = cmdline; - } - - if(strcmp(cmdline_base, plymouth_name) != 0){ - if(debug){ - fprintf(stderr, "\"%s\" is not \"%s\"\n", cmdline_base, - plymouth_name); - } - free(cmdline); - return 0; - } - if(debug){ - fprintf(stderr, "\"%s\" equals \"%s\"\n", cmdline_base, - plymouth_name); - } - free(cmdline); - return 1; - } - - struct dirent **direntries = NULL; - int ret; - ret = scandir("/proc", &direntries, is_plymouth, alphasort); - if(ret == -1){ - error_plus(1, errno, "scandir"); - } - { - int i = ret; - while(i--){ - free(direntries[i]); - } - } - free(direntries); - return ret > 0; -} - - int main(int argc, char **argv){ - ssize_t sret; - int ret; + ssize_t ret; size_t n; struct termios t_new, t_old; char *buffer = NULL; @@ -257,7 +98,6 @@ { .name = NULL } }; - __attribute__((nonnull(3))) error_t parse_opt (int key, char *arg, struct argp_state *state){ errno = 0; switch (key){ @@ -274,11 +114,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); @@ -301,7 +139,7 @@ case ENOMEM: default: errno = ret; - error_plus(0, errno, "argp_parse"); + perror("argp_parse"); return EX_OSERR; case EINVAL: return EX_USAGE; @@ -311,21 +149,13 @@ if(debug){ fprintf(stderr, "Starting %s\n", argv[0]); } - - if(conflict_detection()){ - if(debug){ - fprintf(stderr, "Stopping %s because of conflict\n", argv[0]); - } - return EXIT_FAILURE; - } - if(debug){ fprintf(stderr, "Storing current terminal attributes\n"); } if(tcgetattr(STDIN_FILENO, &t_old) != 0){ int e = errno; - error_plus(0, errno, "tcgetattr"); + perror("tcgetattr"); switch(e){ case EBADF: case ENOTTY: @@ -338,17 +168,17 @@ sigemptyset(&new_action.sa_mask); ret = sigaddset(&new_action.sa_mask, SIGINT); if(ret == -1){ - error_plus(0, errno, "sigaddset"); + perror("sigaddset"); return EX_OSERR; } ret = sigaddset(&new_action.sa_mask, SIGHUP); if(ret == -1){ - error_plus(0, errno, "sigaddset"); + perror("sigaddset"); return EX_OSERR; } ret = sigaddset(&new_action.sa_mask, SIGTERM); if(ret == -1){ - error_plus(0, errno, "sigaddset"); + perror("sigaddset"); return EX_OSERR; } /* Need to check if the handler is SIG_IGN before handling: @@ -357,37 +187,37 @@ */ ret = sigaction(SIGINT, NULL, &old_action); if(ret == -1){ - error_plus(0, errno, "sigaction"); + perror("sigaction"); return EX_OSERR; } if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGINT, &new_action, NULL); if(ret == -1){ - error_plus(0, errno, "sigaction"); + perror("sigaction"); return EX_OSERR; } } ret = sigaction(SIGHUP, NULL, &old_action); if(ret == -1){ - error_plus(0, errno, "sigaction"); + perror("sigaction"); return EX_OSERR; } if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGHUP, &new_action, NULL); if(ret == -1){ - error_plus(0, errno, "sigaction"); + perror("sigaction"); return EX_OSERR; } } ret = sigaction(SIGTERM, NULL, &old_action); if(ret == -1){ - error_plus(0, errno, "sigaction"); + perror("sigaction"); return EX_OSERR; } if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGTERM, &new_action, NULL); if(ret == -1){ - error_plus(0, errno, "sigaction"); + perror("sigaction"); return EX_OSERR; } } @@ -401,7 +231,7 @@ t_new.c_lflag &= ~(tcflag_t)ECHO; if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_new) != 0){ int e = errno; - error_plus(0, errno, "tcsetattr-echo"); + perror("tcsetattr-echo"); switch(e){ case EBADF: case ENOTTY: @@ -456,11 +286,11 @@ } } } - sret = getline(&buffer, &n, stdin); - if(sret > 0){ + ret = getline(&buffer, &n, stdin); + if(ret > 0){ status = EXIT_SUCCESS; /* Make n = data size instead of allocated buffer size */ - n = (size_t)sret; + n = (size_t)ret; /* Strip final newline */ if(n > 0 and buffer[n-1] == '\n'){ buffer[n-1] = '\0'; /* not strictly necessary */ @@ -468,10 +298,10 @@ } size_t written = 0; while(written < n){ - sret = write(STDOUT_FILENO, buffer + written, n - written); - if(sret < 0){ + ret = write(STDOUT_FILENO, buffer + written, n - written); + if(ret < 0){ int e = errno; - error_plus(0, errno, "write"); + perror("write"); switch(e){ case EBADF: case EFAULT: @@ -488,12 +318,12 @@ } break; } - written += (size_t)sret; + written += (size_t)ret; } - sret = close(STDOUT_FILENO); - if(sret == -1){ + ret = close(STDOUT_FILENO); + if(ret == -1){ int e = errno; - error_plus(0, errno, "close"); + perror("close"); switch(e){ case EBADF: status = EX_OSFILE; @@ -506,29 +336,24 @@ } break; } - if(sret < 0){ + if(ret < 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; - } + if(errno != EINTR and not feof(stdin)){ + perror("getline"); + switch(e){ + case EBADF: + status = EX_UNAVAILABLE; + case EIO: + case EINVAL: + default: + status = EX_IOERR; break; - } else { - clearerr(stdin); } + break; } } - /* if(sret == 0), then the only sensible thing to do is to retry - to read from stdin */ + /* if(ret == 0), then the only sensible thing to do is to retry to + read from stdin */ fputc('\n', stderr); if(debug and not quit_now){ /* If quit_now is nonzero, we were interrupted by a signal, and @@ -543,7 +368,7 @@ fprintf(stderr, "Restoring terminal attributes\n"); } if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_old) != 0){ - error_plus(0, errno, "tcsetattr+echo"); + perror("tcsetattr+echo"); } if(quit_now){ @@ -551,7 +376,7 @@ old_action.sa_handler = SIG_DFL; ret = sigaction(signal_received, &old_action, NULL); if(ret == -1){ - error_plus(0, errno, "sigaction"); + perror("sigaction"); } raise(signal_received); } === modified file 'plugins.d/password-prompt.xml' --- plugins.d/password-prompt.xml 2019-02-10 04:20:26 +0000 +++ plugins.d/password-prompt.xml 2009-10-30 16:23:43 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -19,30 +19,20 @@ Björn Påhlsson
- belorn@recompile.se + belorn@fukt.bsnet.se
Teddy Hogeborn
- teddy@recompile.se + teddy@fukt.bsnet.se
2008 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -229,7 +219,9 @@ BUGS - + + None are known at this time. + @@ -300,8 +292,6 @@ SEE ALSO - intro - 8mandos crypttab 5 mandos-client === removed file 'plugins.d/plymouth.c' --- plugins.d/plymouth.c 2018-02-08 10:23:55 +0000 +++ plugins.d/plymouth.c 1970-01-01 00:00:00 +0000 @@ -1,520 +0,0 @@ -/* -*- coding: utf-8 -*- */ -/* - * Plymouth - Read a password from Plymouth and output it - * - * Copyright © 2010-2018 Teddy Hogeborn - * Copyright © 2010-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 . - */ - -#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 /* 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 /* TEMP_FAILURE_RETRY */ -#include /* argz_count(), argz_extract() */ -#include /* va_list, va_start(), ... */ - -sig_atomic_t interrupted_by_signal = 0; - -/* Used by Ubuntu 11.04 (Natty Narwahl) */ -const char plymouth_old_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_path[] = "/bin/plymouth"; -const char plymouthd_path[] = "/sbin/plymouthd"; -const char *plymouthd_default_argv[] = {"/sbin/plymouthd", - "--mode=boot", - "--attach-to-session", - NULL }; - -static void termination_handler(__attribute__((unused))int signum){ - if(interrupted_by_signal){ - return; - } - interrupted_by_signal = 1; -} - -/* Function to use when printing errors */ -__attribute__((format (gnu_printf, 3, 4))) -void error_plus(int status, int errnum, const char *formatstring, - ...){ - va_list ap; - char *text; - int ret; - - va_start(ap, formatstring); - ret = vasprintf(&text, formatstring, ap); - if(ret == -1){ - fprintf(stderr, "Mandos plugin %s: ", - program_invocation_short_name); - vfprintf(stderr, formatstring, ap); - fprintf(stderr, ": "); - fprintf(stderr, "%s\n", strerror(errnum)); - error(status, errno, "vasprintf while printing error"); - return; - } - fprintf(stderr, "Mandos plugin "); - error(status, errnum, "%s", text); - free(text); -} - -/* Create prompt string */ -char *makeprompt(void){ - int ret = 0; - char *prompt; - const char *const cryptsource = getenv("cryptsource"); - const char *const crypttarget = getenv("crypttarget"); - const char prompt_start[] = "Unlocking the disk"; - const char prompt_end[] = "Enter passphrase"; - - if(cryptsource == NULL){ - if(crypttarget == NULL){ - ret = asprintf(&prompt, "%s\n%s", prompt_start, prompt_end); - } else { - ret = asprintf(&prompt, "%s (%s)\n%s", prompt_start, - crypttarget, prompt_end); - } - } else { - if(crypttarget == NULL){ - ret = asprintf(&prompt, "%s %s\n%s", prompt_start, cryptsource, - prompt_end); - } else { - ret = asprintf(&prompt, "%s %s (%s)\n%s", prompt_start, - cryptsource, crypttarget, prompt_end); - } - } - if(ret == -1){ - return NULL; - } - return prompt; -} - -void kill_and_wait(pid_t pid){ - TEMP_FAILURE_RETRY(kill(pid, SIGTERM)); - TEMP_FAILURE_RETRY(waitpid(pid, NULL, 0)); -} - -bool become_a_daemon(void){ - int ret = setuid(geteuid()); - if(ret == -1){ - error_plus(0, errno, "setuid"); - } - - setsid(); - ret = chdir("/"); - if(ret == -1){ - error_plus(0, errno, "chdir"); - return false; - } - ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace our stdout */ - if(ret == -1){ - error_plus(0, errno, "dup2"); - return false; - } - return true; -} - -__attribute__((nonnull (2, 3))) -bool exec_and_wait(pid_t *pid_return, const char *path, - const char * const *argv, bool interruptable, - bool daemonize){ - int status; - int ret; - pid_t pid; - pid = fork(); - if(pid == -1){ - error_plus(0, errno, "fork"); - return false; - } - if(pid == 0){ - /* Child */ - if(daemonize){ - if(not become_a_daemon()){ - _exit(EX_OSERR); - } - } - - char **new_argv = malloc(sizeof(const char *)); - if(new_argv == NULL){ - error_plus(0, errno, "malloc"); - _exit(EX_OSERR); - } - char **tmp; - int i = 0; - for (; argv[i] != NULL; i++){ - tmp = realloc(new_argv, sizeof(const char *) * ((size_t)i + 2)); - if(tmp == NULL){ - error_plus(0, errno, "realloc"); - free(new_argv); - _exit(EX_OSERR); - } - new_argv = tmp; - new_argv[i] = strdup(argv[i]); - } - new_argv[i] = NULL; - - execv(path, (char *const *)new_argv); - error_plus(0, errno, "execv"); - _exit(EXIT_FAILURE); - } - if(pid_return != NULL){ - *pid_return = pid; - } - do { - ret = waitpid(pid, &status, 0); - } while(ret == -1 and errno == EINTR - and ((not interrupted_by_signal) - or (not interruptable))); - if(interrupted_by_signal and interruptable){ - return false; - } - if(ret == -1){ - error_plus(0, errno, "waitpid"); - return false; - } - if(WIFEXITED(status) and (WEXITSTATUS(status) == 0)){ - return true; - } - return false; -} - -__attribute__((nonnull)) -int is_plymouth(const struct dirent *proc_entry){ - int ret; - { - uintmax_t proc_id; - char *tmp; - errno = 0; - proc_id = strtoumax(proc_entry->d_name, &tmp, 10); - - if(errno != 0 or *tmp != '\0' - or proc_id != (uintmax_t)((pid_t)proc_id)){ - return 0; - } - } - char exe_target[sizeof(plymouthd_path)]; - char *exe_link; - ret = asprintf(&exe_link, "/proc/%s/exe", proc_entry->d_name); - if(ret == -1){ - error_plus(0, errno, "asprintf"); - return 0; - } - - struct stat exe_stat; - ret = lstat(exe_link, &exe_stat); - if(ret == -1){ - free(exe_link); - if(errno != ENOENT){ - error_plus(0, errno, "lstat"); - } - return 0; - } - - if(not S_ISLNK(exe_stat.st_mode) - or exe_stat.st_uid != 0 - or exe_stat.st_gid != 0){ - free(exe_link); - return 0; - } - - ssize_t sret = readlink(exe_link, exe_target, sizeof(exe_target)); - free(exe_link); - if((sret != (ssize_t)sizeof(plymouthd_path)-1) or - (memcmp(plymouthd_path, exe_target, - sizeof(plymouthd_path)-1) != 0)){ - return 0; - } - return 1; -} - -pid_t get_pid(void){ - int ret; - uintmax_t proc_id = 0; - FILE *pidfile = fopen(plymouth_pid, "r"); - /* Try the new pid file location */ - if(pidfile != NULL){ - ret = fscanf(pidfile, "%" SCNuMAX, &proc_id); - if(ret != 1){ - proc_id = 0; - } - fclose(pidfile); - } - /* 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"); - if(pidfile != NULL){ - ret = fscanf(pidfile, "%" SCNuMAX, &proc_id); - if(ret != 1){ - proc_id = 0; - } - fclose(pidfile); - } - } - /* Look for a plymouth process */ - if(proc_id == 0){ - struct dirent **direntries = NULL; - ret = scandir("/proc", &direntries, is_plymouth, alphasort); - if(ret == -1){ - 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]); - } - } - /* scandir might preallocate for this variable (man page unclear). - even if ret == 0, therefore we need to free it. */ - free(direntries); - } - pid_t pid; - pid = (pid_t)proc_id; - if((uintmax_t)pid == proc_id){ - return pid; - } - - return 0; -} - -char **getargv(pid_t pid){ - int cl_fd; - char *cmdline_filename; - ssize_t sret; - int ret; - - ret = asprintf(&cmdline_filename, "/proc/%" PRIuMAX "/cmdline", - (uintmax_t)pid); - if(ret == -1){ - error_plus(0, errno, "asprintf"); - return NULL; - } - - /* Open /proc//cmdline */ - cl_fd = open(cmdline_filename, O_RDONLY); - free(cmdline_filename); - if(cl_fd == -1){ - error_plus(0, errno, "open"); - return NULL; - } - - size_t cmdline_allocated = 0; - size_t cmdline_len = 0; - char *cmdline = NULL; - char *tmp; - const size_t blocksize = 1024; - do { - /* Allocate more space? */ - if(cmdline_len + blocksize > cmdline_allocated){ - tmp = realloc(cmdline, cmdline_allocated + blocksize); - if(tmp == NULL){ - error_plus(0, errno, "realloc"); - free(cmdline); - close(cl_fd); - return NULL; - } - cmdline = tmp; - cmdline_allocated += blocksize; - } - - /* Read data */ - sret = read(cl_fd, cmdline + cmdline_len, - cmdline_allocated - cmdline_len); - if(sret == -1){ - error_plus(0, errno, "read"); - free(cmdline); - close(cl_fd); - return NULL; - } - cmdline_len += (size_t)sret; - } while(sret != 0); - ret = close(cl_fd); - if(ret == -1){ - error_plus(0, errno, "close"); - free(cmdline); - return NULL; - } - - /* we got cmdline and cmdline_len, ignore rest... */ - char **argv = malloc((argz_count(cmdline, cmdline_len) + 1) - * sizeof(char *)); /* Get number of args */ - if(argv == NULL){ - error_plus(0, errno, "argv = malloc()"); - free(cmdline); - return NULL; - } - argz_extract(cmdline, cmdline_len, argv); /* Create argv */ - return argv; -} - -int main(__attribute__((unused))int argc, - __attribute__((unused))char **argv){ - char *prompt; - char *prompt_arg; - pid_t plymouth_command_pid; - int ret; - bool bret; - - /* 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. */ - exit(EX_UNAVAILABLE); - } - - { /* Add signal handlers */ - struct sigaction old_action, - new_action = { .sa_handler = termination_handler, - .sa_flags = 0 }; - sigemptyset(&new_action.sa_mask); - for(int *sig = (int[]){ SIGINT, SIGHUP, SIGTERM, 0 }; - *sig != 0; sig++){ - ret = sigaddset(&new_action.sa_mask, *sig); - if(ret == -1){ - error_plus(EX_OSERR, errno, "sigaddset"); - } - ret = sigaction(*sig, NULL, &old_action); - if(ret == -1){ - error_plus(EX_OSERR, errno, "sigaction"); - } - if(old_action.sa_handler != SIG_IGN){ - ret = sigaction(*sig, &new_action, NULL); - if(ret == -1){ - error_plus(EX_OSERR, errno, "sigaction"); - } - } - } - } - - /* plymouth --ping */ - bret = exec_and_wait(&plymouth_command_pid, plymouth_path, - (const char *[]) - { plymouth_path, "--ping", NULL }, - true, false); - if(not bret){ - if(interrupted_by_signal){ - kill_and_wait(plymouth_command_pid); - exit(EXIT_FAILURE); - } - /* Plymouth is probably not running. Don't print an error - message, just exit. */ - exit(EX_UNAVAILABLE); - } - - 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" */ - bret = exec_and_wait(&plymouth_command_pid, - plymouth_path, (const char *[]) - { plymouth_path, "ask-for-password", - prompt_arg, NULL }, - true, false); - free(prompt_arg); - if(bret){ - exit(EXIT_SUCCESS); - } - if(not interrupted_by_signal){ - /* exec_and_wait failed for some other reason */ - exit(EXIT_FAILURE); - } - kill_and_wait(plymouth_command_pid); - - char **plymouthd_argv = NULL; - pid_t pid = get_pid(); - if(pid == 0){ - error_plus(0, 0, "plymouthd pid not found"); - } else { - plymouthd_argv = getargv(pid); - } - - bret = exec_and_wait(NULL, plymouth_path, (const char *[]) - { 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, - false, true); - if(plymouthd_argv != NULL){ - free(*plymouthd_argv); - free(plymouthd_argv); - } - if(not bret){ - exit(EXIT_FAILURE); - } - exec_and_wait(NULL, plymouth_path, (const char *[]) - { plymouth_path, "show-splash", NULL }, - false, false); - exit(EXIT_FAILURE); -} === removed file 'plugins.d/plymouth.xml' --- plugins.d/plymouth.xml 2019-02-10 04:20:26 +0000 +++ plugins.d/plymouth.xml 1970-01-01 00:00:00 +0000 @@ -1,290 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 - Teddy Hogeborn - Björn Påhlsson - - -
- - - &COMMANDNAME; - 8mandos - - - - &COMMANDNAME; - Mandos plugin to use plymouth to get a - password. - - - - - &COMMANDNAME; - - - - - DESCRIPTION - - This program prompts for a password using - plymouth8 - and outputs any given password to standard - output. If no plymouth8 - process can be found, this program will immediately exit with an - exit code indicating failure. - - - This program is not very useful on its own. This program is - really meant to run as a plugin in the Mandos client-side system, where it is used as a - fallback and alternative to retrieving passwords from a - Mandos server. - - - If this program is killed (presumably by - plugin-runner - 8mandos because some other - plugin provided the password), it cannot tell - plymouth8 - to abort requesting a password, because - plymouth - 8 does not support this. - Therefore, this program will then kill the - running plymouth - 8 process and start a - new one using the same command line - arguments as the old one was using. - - - - - OPTIONS - - This program takes no options. - - - - - EXIT STATUS - - If exit status is 0, the output from the program is the password - as it was read. Otherwise, if exit status is other than 0, the - program was interrupted or encountered an error, and any output - so far could be corrupt and/or truncated, and should therefore - be ignored. - - - - - ENVIRONMENT - - - cryptsource - crypttarget - - - 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. - - - These variables will normally be inherited from - plugin-runner - 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. - - - - - - - - FILES - - - /bin/plymouth - - - This is the command run to retrieve a password from - plymouth - 8. - - - - - /proc - - - To find the running plymouth8 - , this directory will be searched for - numeric entries which will be assumed to be directories. - In all those directories, the exe and - cmdline entries will be used to - determine the name of the running binary, effective user - and group ID, and the command line - arguments. See proc5 - . - - - - - /sbin/plymouthd - - - This is the name of the binary which will be searched for - in the process list. See plymouth8 - . - - - - - - - - BUGS - - Killing the plymouth8 - daemon and starting a new one is ugly, but necessary as long as - it does not support aborting a password request. - - - - - - EXAMPLE - - Note that normally, this program will not be invoked directly, - but instead started by the Mandos plugin-runner8mandos - . - - - - This program takes no options. - - - &COMMANDNAME; - - - - - - SECURITY - - If this program is killed by a signal, it will kill the process - ID which at the start of this program was - determined to run plymouth8 - as root (see also ). There is a very - slight risk that, in the time between those events, that process - ID was freed and then taken up by another - process; the wrong process would then be killed. Now, this - program can only be killed by the user who started it; see - plugin-runner - 8mandos. This program - should therefore be started by a completely separate - non-privileged user, and no other programs should be allowed to - run as that special user. This means that it is not recommended - to use the user "nobody" to start this program, as other - possibly less trusted programs could be running as "nobody", and - they would then be able to kill this program, triggering the - killing of the process ID which may or may not - be plymouth - 8. - - - The only other thing that could be considered worthy of note is - this: This program is meant to be run by - plugin-runner8mandos, and will, when run - standalone, outside, in a normal environment, immediately output - on its standard output any presumably secret password it just - received. Therefore, when running this program standalone - (which should never normally be done), take care not to type in - any real secret password by force of habit, since it would then - immediately be shown as output. - - - - - SEE ALSO - - intro - 8mandos, - crypttab - 5, - plugin-runner - 8mandos, - proc - 5, - plymouth - 8 - - -
- - - - - === modified file 'plugins.d/splashy.c' --- plugins.d/splashy.c 2018-02-08 10:23:55 +0000 +++ plugins.d/splashy.c 2009-10-18 16:09:05 +0000 @@ -2,25 +2,24 @@ /* * Splashy - Read a password from splashy and output it * - * Copyright © 2008-2018 Teddy Hogeborn - * Copyright © 2008-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 + * Copyright © 2008,2009 Teddy Hogeborn + * Copyright © 2008,2009 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 . + * Contact the authors at . */ #define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), asprintf() */ @@ -30,8 +29,7 @@ SIG_IGN, kill(), SIGKILL */ #include /* NULL */ #include /* getenv() */ -#include /* asprintf(), vasprintf(), vprintf(), - fprintf() */ +#include /* asprintf(), perror() */ #include /* EXIT_FAILURE, free(), EXIT_SUCCESS */ #include /* pid_t, DIR, struct dirent, @@ -44,48 +42,21 @@ sleep(), dup2() STDERR_FILENO, STDOUT_FILENO, _exit(), pause() */ -#include /* memcmp(), strerror() */ +#include /* memcmp() */ #include /* errno, EACCES, ENOTDIR, ELOOP, ENOENT, ENAMETOOLONG, EMFILE, ENFILE, ENOMEM, ENOEXEC, EINVAL, E2BIG, EFAULT, EIO, ETXTBSY, EISDIR, ELIBBAD, EPERM, EINTR, ECHILD */ -#include /* error() */ #include /* waitpid(), WIFEXITED(), WEXITSTATUS() */ #include /* EX_OSERR, EX_OSFILE, EX_UNAVAILABLE */ -#include /* va_list, va_start(), ... */ sig_atomic_t interrupted_by_signal = 0; int signal_received; -/* Function to use when printing errors */ -__attribute__((format (gnu_printf, 3, 4))) -void error_plus(int status, int errnum, const char *formatstring, - ...){ - va_list ap; - char *text; - int ret; - - va_start(ap, formatstring); - ret = vasprintf(&text, formatstring, ap); - if(ret == -1){ - fprintf(stderr, "Mandos plugin %s: ", - program_invocation_short_name); - vfprintf(stderr, formatstring, ap); - fprintf(stderr, ": "); - fprintf(stderr, "%s\n", strerror(errnum)); - error(status, errno, "vasprintf while printing error"); - return; - } - fprintf(stderr, "Mandos plugin "); - error(status, errnum, "%s", text); - free(text); -} - - static void termination_handler(int signum){ if(interrupted_by_signal){ return; @@ -138,7 +109,7 @@ proc_dir = opendir("/proc"); if(proc_dir == NULL){ int e = errno; - error_plus(0, errno, "opendir"); + perror("opendir"); switch(e){ case EACCES: case ENOTDIR: @@ -180,7 +151,7 @@ char *exe_link; ret = asprintf(&exe_link, "/proc/%s/exe", proc_ent->d_name); if(ret == -1){ - error_plus(0, errno, "asprintf"); + perror("asprintf"); exitstatus = EX_OSERR; goto failure; } @@ -194,7 +165,7 @@ continue; } int e = errno; - error_plus(0, errno, "lstat"); + perror("lstat"); free(exe_link); switch(e){ case EACCES: @@ -242,60 +213,60 @@ sigemptyset(&new_action.sa_mask); ret = sigaddset(&new_action.sa_mask, SIGINT); if(ret == -1){ - error_plus(0, errno, "sigaddset"); + perror("sigaddset"); exitstatus = EX_OSERR; goto failure; } ret = sigaddset(&new_action.sa_mask, SIGHUP); if(ret == -1){ - error_plus(0, errno, "sigaddset"); + perror("sigaddset"); exitstatus = EX_OSERR; goto failure; } ret = sigaddset(&new_action.sa_mask, SIGTERM); if(ret == -1){ - error_plus(0, errno, "sigaddset"); + perror("sigaddset"); exitstatus = EX_OSERR; goto failure; } ret = sigaction(SIGINT, NULL, &old_action); if(ret == -1){ - error_plus(0, errno, "sigaction"); + perror("sigaction"); exitstatus = EX_OSERR; goto failure; } if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGINT, &new_action, NULL); if(ret == -1){ - error_plus(0, errno, "sigaction"); + perror("sigaction"); exitstatus = EX_OSERR; goto failure; } } ret = sigaction(SIGHUP, NULL, &old_action); if(ret == -1){ - error_plus(0, errno, "sigaction"); + perror("sigaction"); exitstatus = EX_OSERR; goto failure; } if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGHUP, &new_action, NULL); if(ret == -1){ - error_plus(0, errno, "sigaction"); + perror("sigaction"); exitstatus = EX_OSERR; goto failure; } } ret = sigaction(SIGTERM, NULL, &old_action); if(ret == -1){ - error_plus(0, errno, "sigaction"); + perror("sigaction"); exitstatus = EX_OSERR; goto failure; } if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGTERM, &new_action, NULL); if(ret == -1){ - error_plus(0, errno, "sigaction"); + perror("sigaction"); exitstatus = EX_OSERR; goto failure; } @@ -312,7 +283,7 @@ goto failure; } if(splashy_command_pid == -1){ - error_plus(0, errno, "fork"); + perror("fork"); exitstatus = EX_OSERR; goto failure; } @@ -322,7 +293,7 @@ const char splashy_command[] = "/sbin/splashy_update"; execl(splashy_command, splashy_command, prompt, (char *)NULL); int e = errno; - error_plus(0, errno, "execl"); + perror("execl"); switch(e){ case EACCES: case ENOENT: @@ -342,9 +313,7 @@ case ENOTDIR: case ELOOP: case EISDIR: -#ifdef ELIBBAD - case ELIBBAD: /* Linux only */ -#endif + case ELIBBAD: case EPERM: _exit(EX_OSFILE); } @@ -372,7 +341,7 @@ goto failure; } if(ret == -1){ - error_plus(0, errno, "waitpid"); + perror("waitpid"); if(errno == ECHILD){ splashy_command_pid = 0; } @@ -410,27 +379,27 @@ the real user ID (_mandos) */ ret = setuid(geteuid()); if(ret == -1){ - error_plus(0, errno, "setuid"); + perror("setuid"); } setsid(); ret = chdir("/"); if(ret == -1){ - error_plus(0, errno, "chdir"); + perror("chdir"); } /* if(fork() != 0){ */ /* _exit(EXIT_SUCCESS); */ /* } */ ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace stdout */ if(ret == -1){ - error_plus(0, errno, "dup2"); + perror("dup2"); _exit(EX_OSERR); } execl("/sbin/splashy", "/sbin/splashy", "boot", (char *)NULL); { int e = errno; - error_plus(0, errno, "execl"); + perror("execl"); switch(e){ case EACCES: case ENOENT: @@ -456,13 +425,13 @@ ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received, &signal_action, NULL)); if(ret == -1){ - error_plus(0, errno, "sigaction"); + perror("sigaction"); } do { ret = raise(signal_received); } while(ret != 0 and errno == EINTR); if(ret != 0){ - error_plus(0, errno, "raise"); + perror("raise"); abort(); } TEMP_FAILURE_RETRY(pause()); === modified file 'plugins.d/splashy.xml' --- plugins.d/splashy.xml 2019-02-10 04:20:26 +0000 +++ plugins.d/splashy.xml 2009-01-04 21:54:55 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -19,30 +19,20 @@ Björn Påhlsson
- belorn@recompile.se + belorn@fukt.bsnet.se
Teddy Hogeborn
- teddy@recompile.se + teddy@fukt.bsnet.se
2008 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -212,7 +202,6 @@ is ugly, but necessary as long as it does not support aborting a password request.
-
@@ -274,8 +263,6 @@ SEE ALSO - intro - 8mandos, crypttab 5, plugin-runner === modified file 'plugins.d/usplash.c' --- plugins.d/usplash.c 2018-02-08 10:23:55 +0000 +++ plugins.d/usplash.c 2010-03-27 18:39:02 +0000 @@ -2,25 +2,24 @@ /* * Usplash - Read a password from usplash and output it * - * Copyright © 2008-2018 Teddy Hogeborn - * Copyright © 2008-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 + * Copyright © 2008,2009 Teddy Hogeborn + * Copyright © 2008,2009 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 . + * Contact the authors at . */ #define _GNU_SOURCE /* asprintf(), TEMP_FAILURE_RETRY() */ @@ -32,56 +31,28 @@ #include /* open(), O_WRONLY, O_RDONLY */ #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 /* strlen(), memcmp() */ +#include /* asprintf(), perror() */ #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() */ + EXIT_SUCCESS, malloc(), _exit() */ +#include /* getenv() */ #include /* opendir(), readdir(), closedir() */ #include /* intmax_t, strtoimax() */ #include /* struct stat, lstat(), S_ISLNK */ #include /* EX_OSERR, EX_UNAVAILABLE */ -#include /* argz_count(), argz_extract() */ -#include /* va_list, va_start(), ... */ sig_atomic_t interrupted_by_signal = 0; int signal_received; const char usplash_name[] = "/sbin/usplash"; -/* Function to use when printing errors */ -__attribute__((format (gnu_printf, 3, 4))) -void error_plus(int status, int errnum, const char *formatstring, - ...){ - va_list ap; - char *text; - int ret; - - va_start(ap, formatstring); - ret = vasprintf(&text, formatstring, ap); - if(ret == -1){ - fprintf(stderr, "Mandos plugin %s: ", - program_invocation_short_name); - vfprintf(stderr, formatstring, ap); - fprintf(stderr, ": "); - fprintf(stderr, "%s\n", strerror(errnum)); - error(status, errno, "vasprintf while printing error"); - return; - } - fprintf(stderr, "Mandos plugin "); - error(status, errnum, "%s", text); - free(text); -} - static void termination_handler(int signum){ if(interrupted_by_signal){ return; @@ -118,7 +89,7 @@ ret = asprintf(&cmd_line_alloc, "%s %s", cmd, arg); if(ret == -1){ int e = errno; - close(*fifo_fd_r); + TEMP_FAILURE_RETRY(close(*fifo_fd_r)); errno = e; return false; } @@ -134,7 +105,7 @@ cmd_line_len - written); if(sret == -1){ int e = errno; - close(*fifo_fd_r); + TEMP_FAILURE_RETRY(close(*fifo_fd_r)); free(cmd_line_alloc); errno = e; return false; @@ -182,7 +153,7 @@ size_t cmdline_len = 0; DIR *proc_dir = opendir("/proc"); if(proc_dir == NULL){ - error_plus(0, errno, "opendir"); + perror("opendir"); return -1; } errno = 0; @@ -210,7 +181,7 @@ char *exe_link; ret = asprintf(&exe_link, "/proc/%s/exe", proc_ent->d_name); if(ret == -1){ - error_plus(0, errno, "asprintf"); + perror("asprintf"); goto fail_find_usplash; } @@ -222,7 +193,7 @@ free(exe_link); continue; } - error_plus(0, errno, "lstat"); + perror("lstat"); free(exe_link); goto fail_find_usplash; } @@ -253,13 +224,13 @@ ret = asprintf(&cmdline_filename, "/proc/%s/cmdline", proc_ent->d_name); if(ret == -1){ - error_plus(0, errno, "asprintf"); + perror("asprintf"); goto fail_find_usplash; } cl_fd = open(cmdline_filename, O_RDONLY); free(cmdline_filename); if(cl_fd == -1){ - error_plus(0, errno, "open"); + perror("open"); goto fail_find_usplash; } } @@ -271,7 +242,7 @@ if(cmdline_len + blocksize > cmdline_allocated){ tmp = realloc(cmdline, cmdline_allocated + blocksize); if(tmp == NULL){ - error_plus(0, errno, "realloc"); + perror("realloc"); close(cl_fd); goto fail_find_usplash; } @@ -282,7 +253,7 @@ sret = read(cl_fd, cmdline + cmdline_len, cmdline_allocated - cmdline_len); if(sret == -1){ - error_plus(0, errno, "read"); + perror("read"); close(cl_fd); goto fail_find_usplash; } @@ -290,14 +261,14 @@ } while(sret != 0); ret = close(cl_fd); if(ret == -1){ - error_plus(0, errno, "close"); + perror("close"); goto fail_find_usplash; } } /* Close directory */ ret = closedir(proc_dir); if(ret == -1){ - error_plus(0, errno, "closedir"); + perror("closedir"); goto fail_find_usplash; } /* Success */ @@ -352,26 +323,26 @@ sigemptyset(&new_action.sa_mask); ret = sigaddset(&new_action.sa_mask, SIGINT); if(ret == -1){ - error_plus(0, errno, "sigaddset"); + perror("sigaddset"); status = EX_OSERR; goto failure; } ret = sigaddset(&new_action.sa_mask, SIGHUP); if(ret == -1){ - error_plus(0, errno, "sigaddset"); + perror("sigaddset"); status = EX_OSERR; goto failure; } ret = sigaddset(&new_action.sa_mask, SIGTERM); if(ret == -1){ - error_plus(0, errno, "sigaddset"); + perror("sigaddset"); status = EX_OSERR; goto failure; } ret = sigaction(SIGINT, NULL, &old_action); if(ret == -1){ if(errno != EINTR){ - error_plus(0, errno, "sigaction"); + perror("sigaction"); status = EX_OSERR; } goto failure; @@ -380,7 +351,7 @@ ret = sigaction(SIGINT, &new_action, NULL); if(ret == -1){ if(errno != EINTR){ - error_plus(0, errno, "sigaction"); + perror("sigaction"); status = EX_OSERR; } goto failure; @@ -389,7 +360,7 @@ ret = sigaction(SIGHUP, NULL, &old_action); if(ret == -1){ if(errno != EINTR){ - error_plus(0, errno, "sigaction"); + perror("sigaction"); status = EX_OSERR; } goto failure; @@ -398,7 +369,7 @@ ret = sigaction(SIGHUP, &new_action, NULL); if(ret == -1){ if(errno != EINTR){ - error_plus(0, errno, "sigaction"); + perror("sigaction"); status = EX_OSERR; } goto failure; @@ -407,7 +378,7 @@ ret = sigaction(SIGTERM, NULL, &old_action); if(ret == -1){ if(errno != EINTR){ - error_plus(0, errno, "sigaction"); + perror("sigaction"); status = EX_OSERR; } goto failure; @@ -416,7 +387,7 @@ ret = sigaction(SIGTERM, &new_action, NULL); if(ret == -1){ if(errno != EINTR){ - error_plus(0, errno, "sigaction"); + perror("sigaction"); status = EX_OSERR; } goto failure; @@ -428,7 +399,7 @@ /* Write command to FIFO */ if(not usplash_write(&fifo_fd, "TIMEOUT", "0")){ if(errno != EINTR){ - error_plus(0, errno, "usplash_write"); + perror("usplash_write"); status = EX_OSERR; } goto failure; @@ -440,7 +411,7 @@ if(not usplash_write(&fifo_fd, "INPUTQUIET", prompt)){ if(errno != EINTR){ - error_plus(0, errno, "usplash_write"); + perror("usplash_write"); status = EX_OSERR; } goto failure; @@ -458,7 +429,7 @@ outfifo_fd = open("/dev/.initramfs/usplash_outfifo", O_RDONLY); if(outfifo_fd == -1){ if(errno != EINTR){ - error_plus(0, errno, "open"); + perror("open"); status = EX_OSERR; } goto failure; @@ -477,7 +448,7 @@ char *tmp = realloc(buf, buf_allocated + blocksize); if(tmp == NULL){ if(errno != EINTR){ - error_plus(0, errno, "realloc"); + perror("realloc"); status = EX_OSERR; } goto failure; @@ -489,10 +460,10 @@ buf_allocated - buf_len); if(sret == -1){ if(errno != EINTR){ - error_plus(0, errno, "read"); + perror("read"); status = EX_OSERR; } - close(outfifo_fd); + TEMP_FAILURE_RETRY(close(outfifo_fd)); goto failure; } if(interrupted_by_signal){ @@ -504,7 +475,7 @@ ret = close(outfifo_fd); if(ret == -1){ if(errno != EINTR){ - error_plus(0, errno, "close"); + perror("close"); status = EX_OSERR; } goto failure; @@ -517,7 +488,7 @@ if(not usplash_write(&fifo_fd, "TIMEOUT", "15")){ if(errno != EINTR){ - error_plus(0, errno, "usplash_write"); + perror("usplash_write"); status = EX_OSERR; } goto failure; @@ -530,7 +501,7 @@ ret = close(fifo_fd); if(ret == -1){ if(errno != EINTR){ - error_plus(0, errno, "close"); + perror("close"); status = EX_OSERR; } goto failure; @@ -544,7 +515,7 @@ sret = write(STDOUT_FILENO, buf + written, buf_len - written); if(sret == -1){ if(errno != EINTR){ - error_plus(0, errno, "write"); + perror("write"); status = EX_OSERR; } goto failure; @@ -579,30 +550,42 @@ /* Close FIFO */ if(fifo_fd != -1){ - ret = close(fifo_fd); + ret = (int)TEMP_FAILURE_RETRY(close(fifo_fd)); if(ret == -1 and errno != EINTR){ - error_plus(0, errno, "close"); + perror("close"); } fifo_fd = -1; } /* Close output FIFO */ if(outfifo_fd != -1){ - ret = close(outfifo_fd); + ret = (int)TEMP_FAILURE_RETRY(close(outfifo_fd)); if(ret == -1){ - error_plus(0, errno, "close"); - } - } - - /* Create argv for new usplash*/ - char **cmdline_argv = malloc((argz_count(cmdline, cmdline_len) + 1) - * sizeof(char *)); /* Count args */ - if(cmdline_argv == NULL){ - error_plus(0, errno, "malloc"); - return status; - } - argz_extract(cmdline, cmdline_len, cmdline_argv); /* Create argv */ - + perror("close"); + } + } + + /* Create argc and argv for new usplash*/ + int cmdline_argc = 0; + char **cmdline_argv = malloc(sizeof(char *)); + { + size_t position = 0; + while(position < cmdline_len){ + char **tmp = realloc(cmdline_argv, + (sizeof(char *) + * (size_t)(cmdline_argc + 2))); + if(tmp == NULL){ + perror("realloc"); + free(cmdline_argv); + return status; + } + cmdline_argv = tmp; + cmdline_argv[cmdline_argc] = cmdline + position; + cmdline_argc++; + position += strlen(cmdline + position) + 1; + } + cmdline_argv[cmdline_argc] = NULL; + } /* Kill old usplash */ kill(usplash_pid, SIGTERM); sleep(2); @@ -619,27 +602,23 @@ the real user ID (_mandos) */ ret = setuid(geteuid()); if(ret == -1){ - error_plus(0, errno, "setuid"); + perror("setuid"); } setsid(); ret = chdir("/"); - if(ret == -1){ - error_plus(0, errno, "chdir"); - _exit(EX_OSERR); - } /* if(fork() != 0){ */ /* _exit(EXIT_SUCCESS); */ /* } */ ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace our stdout */ if(ret == -1){ - error_plus(0, errno, "dup2"); + perror("dup2"); _exit(EX_OSERR); } execv(usplash_name, cmdline_argv); if(not interrupted_by_signal){ - error_plus(0, errno, "execv"); + perror("execv"); } free(cmdline); free(cmdline_argv); @@ -650,15 +629,15 @@ sleep(2); if(not usplash_write(&fifo_fd, "PULSATE", NULL)){ if(errno != EINTR){ - error_plus(0, errno, "usplash_write"); + perror("usplash_write"); } } /* Close FIFO (again) */ if(fifo_fd != -1){ - ret = close(fifo_fd); + ret = (int)TEMP_FAILURE_RETRY(close(fifo_fd)); if(ret == -1 and errno != EINTR){ - error_plus(0, errno, "close"); + perror("close"); } fifo_fd = -1; } @@ -669,13 +648,13 @@ ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received, &signal_action, NULL)); if(ret == -1){ - error_plus(0, errno, "sigaction"); + perror("sigaction"); } do { ret = raise(signal_received); } while(ret != 0 and errno == EINTR); if(ret != 0){ - error_plus(0, errno, "raise"); + perror("raise"); abort(); } TEMP_FAILURE_RETRY(pause()); === modified file 'plugins.d/usplash.xml' --- plugins.d/usplash.xml 2019-02-10 04:20:26 +0000 +++ plugins.d/usplash.xml 2009-01-04 21:54:55 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -19,30 +19,20 @@ Björn Påhlsson
- belorn@recompile.se + belorn@fukt.bsnet.se
Teddy Hogeborn
- teddy@recompile.se + teddy@fukt.bsnet.se
2008 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 Teddy Hogeborn Björn Påhlsson @@ -187,7 +177,7 @@ - /proc + /proc To find the running -
@@ -288,8 +277,6 @@ SEE ALSO - intro - 8mandos, crypttab 5, fifo === removed file 'tmpfiles.d-mandos.conf' --- tmpfiles.d-mandos.conf 2016-03-19 03:51:23 +0000 +++ tmpfiles.d-mandos.conf 1970-01-01 00:00:00 +0000 @@ -1,1 +0,0 @@ -d /var/lib/mandos 700 _mandos _mandos