=== removed directory '.bzr-builddeb' === removed file '.bzr-builddeb/default.conf' --- .bzr-builddeb/default.conf 2008-09-17 00:34:09 +0000 +++ .bzr-builddeb/default.conf 1970-01-01 00:00:00 +0000 @@ -1,2 +0,0 @@ -[BUILDDEB] -split = True === modified file '.bzrignore' --- .bzrignore 2012-05-17 01:55:58 +0000 +++ .bzrignore 2008-08-27 01:18:25 +0000 @@ -1,14 +1,8 @@ *.5 *.8 *.8mandos +plugin-runner +plugins.d/password-prompt +plugins.d/password-request confdir keydir -statedir -man -plugin-runner -plugins.d/askpass-fifo -plugins.d/mandos-client -plugins.d/password-prompt -plugins.d/splashy -plugins.d/usplash -plugins.d/plymouth === removed file 'DBUS-API' --- DBUS-API 2019-02-09 23:23: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-2018 Teddy Hogeborn - Copyright © 2010-2018 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 === removed file 'INSTALL' --- INSTALL 2019-02-09 23:23:26 +0000 +++ INSTALL 1970-01-01 00:00:00 +0000 @@ -1,147 +0,0 @@ --*- org -*- - -* Prerequisites - -** Operating System - - Debian 8.0 "jessie" or Ubuntu 15.10 "Wily Werewolf" (or later). - - 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. - -** Libraries - - The following libraries and packages are needed. (It is possible - that it might work with older versions of some of these, but these - versions are confirmed to work. Newer versions are almost - certainly OK.) - -*** Documentation - These are required to build the manual pages for both the server - and client: - - + 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 - - Package names: - docbook docbook-xsl - - 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) - + 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.) - - Strongly recommended: - + fping 2.4b2-to-ipv6 http://www.fping.org/ - + ssh-keyscan from OpenSSH http://www.openssh.com/ - - Package names: - avahi-daemon python python-dbus python-gi python-urwid pkg-config - fping ssh-client - -*** Mandos Client - + GNU C Library 2.16 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/ - - Strongly recommended: - + OpenSSH http://www.openssh.com/ - - Package names: - initramfs-tools libgnutls-dev gnutls-bin libavahi-core-dev gnupg - libgpgme11-dev pkg-config ssh - -* Installing the Mandos server - - 1. Do "make doc". - - 2. On the computer to run as a Mandos server, run the following - command: - For Debian: su -c 'make install-server' - For Ubuntu: sudo make install-server - - (This creates a configuration without any clients configured; you - need an actually configured client to do that; see below.) - -* Installing the Mandos client. - - 1. Do "make all doc". - - 2. On the computer to run as a Mandos client, run the following - command: - For Debian: su -c 'make install-client' - For Ubuntu: sudo make install-client - - This will also create an OpenPGP key, which will take some time - and entropy, so be patient. - - 3. Run the following command: - For Debian: su -c 'mandos-keygen --password' - For Ubuntu: sudo mandos-keygen --password - - When prompted, enter the password/passphrase for the encrypted - root file system on this client computer. The command will - output a section of text, starting with a [section header]. Copy - 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 - 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 - - 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 - - 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. - -* 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 === modified file 'Makefile' --- Makefile 2019-02-09 23:23:26 +0000 +++ Makefile 2008-08-25 06:44:13 +0000 @@ -1,515 +1,208 @@ -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=2 -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 -# For info about _FORTIFY_SOURCE, see feature_test_macros(7) -# and . -FORTIFY:=-D_FORTIFY_SOURCE=2 -fstack-protector-all -fPIC -# -ALL_SANITIZE_OPTIONS:=-fsanitize=leak -fsanitize=undefined \ - -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 -# Check which sanitizing options can be used -SANITIZE:=$(foreach option,$(ALL_SANITIZE_OPTIONS),$(shell \ - echo 'int main(){}' | $(CC) --language=c $(option) /dev/stdin \ - -o /dev/null >/dev/null 2>&1 && echo $(option))) -LINK_FORTIFY_LD:=-z relro -z now -LINK_FORTIFY:= - -# If BROKEN_PIE is set, do not build with -pie -ifndef BROKEN_PIE -FORTIFY += -fPIE -LINK_FORTIFY += -pie -endif + -Wconversion -Wstrict-prototypes -Wold-style-definition \ + -Wpacked -Wnested-externs -Wunreachable-code -Winline \ + -Wvolatile-register-var +DEBUG=-ggdb3 +# For info about _FORTIFY_SOURCE, see +# +FORTIFY=-D_FORTIFY_SOURCE=2 # -fstack-protector-all #COVERAGE=--coverage -OPTIMIZE:=-Os -fno-strict-aliasing -LANGUAGE:=-std=gnu11 -htmldir:=man -version:=1.7.20 -SED:=sed - -USER:=$(firstword $(subst :, ,$(shell getent passwd _mandos || getent passwd nobody || echo 65534))) -GROUP:=$(firstword $(subst :, ,$(shell getent group _mandos || getent group nogroup || echo 65534))) - -## Use these settings for a traditional /usr/local install -# PREFIX:=$(DESTDIR)/usr/local -# CONFDIR:=$(DESTDIR)/etc/mandos -# KEYDIR:=$(DESTDIR)/etc/mandos/keys -# MANDIR:=$(PREFIX)/man -# INITRAMFSTOOLS:=$(DESTDIR)/etc/initramfs-tools -# STATEDIR:=$(DESTDIR)/var/lib/mandos -# LIBDIR:=$(PREFIX)/lib -## - -## These settings are for a package-type install -PREFIX:=$(DESTDIR)/usr -CONFDIR:=$(DESTDIR)/etc/mandos -KEYDIR:=$(DESTDIR)/etc/keys/mandos -MANDIR:=$(PREFIX)/share/man -INITRAMFSTOOLS:=$(DESTDIR)/usr/share/initramfs-tools -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) -## - -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; \ - 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) +OPTIMIZE=-Os +LANGUAGE=-std=gnu99 +# PREFIX=/usr/local +PREFIX=$(DESTDIR)/usr +# CONFDIR=/usr/local/lib/mandos +CONFDIR=$(DESTDIR)/etc/mandos +# MANDIR=/usr/local/man +MANDIR=$(DESTDIR)/usr/share/man + +GNUTLS_CFLAGS=$(shell libgnutls-config --cflags) +GNUTLS_LIBS=$(shell libgnutls-config --libs) +AVAHI_CFLAGS=$(shell pkg-config --cflags-only-I avahi-core) +AVAHI_LIBS=$(shell pkg-config --libs avahi-core) +GPGME_CFLAGS=$(shell gpgme-config --cflags) +GPGME_LIBS=$(shell gpgme-config --libs) # Do not change these two -CFLAGS+=$(WARN) $(DEBUG) $(FORTIFY) $(SANITIZE) $(COVERAGE) \ - $(OPTIMIZE) $(LANGUAGE) -DVERSION='"$(version)"' -LDFLAGS+=-Xlinker --as-needed $(COVERAGE) $(LINK_FORTIFY) $(foreach flag,$(LINK_FORTIFY_LD),-Xlinker $(flag)) +CFLAGS=$(WARN) $(DEBUG) $(FORTIFY) $(COVERAGE) $(OPTIMIZE) \ + $(LANGUAGE) $(GNUTLS_CFLAGS) $(AVAHI_CFLAGS) $(GPGME_CFLAGS) +LDFLAGS=$(COVERAGE) -# Commands to format a DocBook document into a manual page -DOCBOOKTOMAN=$(strip cd $(dir $<); xsltproc --nonet --xinclude \ +# Commands to format a DocBook refentry document into a manual page +DOCBOOKTOMAN=cd $(dir $<); xsltproc --nonet --xinclude \ --param man.charmap.use.subset 0 \ --param make.year.ranges 1 \ --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) - -DOCBOOKTOHTML=$(strip xsltproc --nonet --xinclude \ - --param make.year.ranges 1 \ - --param make.single.year.ranges 1 \ - --param man.output.quietly 1 \ - --param man.authors.section.enabled 0 \ - --param citerefentry.link 1 \ - --output $@ \ - /usr/share/xml/docbook/stylesheet/nwalsh/xhtml/docbook.xsl \ - $<; $(HTMLPOST) $@) -# Fix citerefentry links -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.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)) - -all: $(PROGS) mandos.lsm + $(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' + +PLUGINS=plugins.d/password-prompt plugins.d/password-request +PROGS=plugin-runner $(PLUGINS) +DOCS=mandos.8 plugin-runner.8mandos mandos-keygen.8 \ + plugins.d/password-request.8mandos \ + plugins.d/password-prompt.8mandos mandos.conf.5 \ + mandos-clients.conf.5 + +objects=$(addsuffix .o,$(PROGS)) + +all: $(PROGS) doc: $(DOCS) -html: $(htmldocs) - -%.5: %.xml common.ent legalnotice.xml - $(DOCBOOKTOMAN) -%.5.xhtml: %.xml common.ent legalnotice.xml - $(DOCBOOKTOHTML) - -%.8: %.xml common.ent legalnotice.xml - $(DOCBOOKTOMAN) -%.8.xhtml: %.xml common.ent legalnotice.xml - $(DOCBOOKTOHTML) - -%.8mandos: %.xml common.ent legalnotice.xml - $(DOCBOOKTOMAN) -%.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) -mandos.8.xhtml: mandos.xml common.ent mandos-options.xml \ - overview.xml legalnotice.xml - $(DOCBOOKTOHTML) - -mandos-keygen.8: mandos-keygen.xml common.ent overview.xml \ - legalnotice.xml - $(DOCBOOKTOMAN) -mandos-keygen.8.xhtml: mandos-keygen.xml common.ent overview.xml \ - 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) -mandos.conf.5.xhtml: mandos.conf.xml common.ent mandos-options.xml \ - legalnotice.xml - $(DOCBOOKTOHTML) - -plugin-runner.8mandos: plugin-runner.xml common.ent overview.xml \ - legalnotice.xml - $(DOCBOOKTOMAN) -plugin-runner.8mandos.xhtml: plugin-runner.xml common.ent \ - overview.xml legalnotice.xml - $(DOCBOOKTOHTML) - -plugins.d/mandos-client.8mandos: plugins.d/mandos-client.xml \ - common.ent \ - mandos-options.xml \ - overview.xml legalnotice.xml - $(DOCBOOKTOMAN) -plugins.d/mandos-client.8mandos.xhtml: plugins.d/mandos-client.xml \ - common.ent \ - mandos-options.xml \ - overview.xml legalnotice.xml - $(DOCBOOKTOHTML) - -# Update all these files with version number $(version) -common.ent: Makefile - $(strip $(SED) --in-place \ - --expression='s/^\($$/\1$(version)">/' \ - $@) - -mandos: Makefile - $(strip $(SED) --in-place \ - --expression='s/^\(version = "\)[^"]*"$$/\1$(version)"/' \ - $@) - -mandos-keygen: Makefile - $(strip $(SED) --in-place \ - --expression='s/^\(VERSION="\)[^"]*"$$/\1$(version)"/' \ - $@) - -mandos-ctl: Makefile - $(strip $(SED) --in-place \ - --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)/' \ - $@) - $(strip $(SED) --in-place \ - --expression='s/^\(Entered-date:\).*/\1\t$(shell date --rfc-3339=date --reference=Makefile)/' \ - $@) - $(strip $(SED) --in-place \ - --expression='s/\(mandos_\)[0-9.]\+\(\.orig\.tar\.gz\)/\1$(version)\2/' \ - $@) - -# Need to add the GnuTLS, Avahi and GPGME libraries, and can't use -# -fsanitize=leak because GnuTLS and GPGME both leak memory. -plugins.d/mandos-client: plugins.d/mandos-client.c - $(CC) $(filter-out -fsanitize=leak,$(CFLAGS)) $(strip\ - ) $(GNUTLS_CFLAGS) $(AVAHI_CFLAGS) $(GPGME_CFLAGS) $(strip\ - ) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) $^ $(strip\ - ) -lrt $(GNUTLS_LIBS) $(AVAHI_LIBS) $(strip\ - ) $(GPGME_LIBS) $(LOADLIBES) $(LDLIBS) -o $@ - -plugin-helpers/mandos-client-iprouteadddel: plugin-helpers/mandos-client-iprouteadddel.c - $(LINK.c) $(LIBNL3_CFLAGS) $^ $(LIBNL3_LIBS) $(strip\ - ) $(LOADLIBES) $(LDLIBS) -o $@ - -.PHONY : all doc html clean distclean mostlyclean maintainer-clean \ - check run-client run-server install install-html \ - install-server install-client-nokey install-client uninstall \ - uninstall-server uninstall-client purge purge-server \ - purge-client +%.5: %.xml + $(DOCBOOKTOMAN) + +%.8: %.xml + $(DOCBOOKTOMAN) + +%.8mandos: %.xml + $(DOCBOOKTOMAN) + +mandos.8: mandos.xml mandos-options.xml + $(DOCBOOKTOMAN) + +mandos.conf.5: mandos.conf.xml mandos-options.xml + $(DOCBOOKTOMAN) + +plugins.d/password-request: plugins.d/password-request.o + $(LINK.o) $(GNUTLS_LIBS) $(AVAHI_LIBS) $(GPGME_LIBS) \ + $(COMMON) $^ $(LOADLIBES) $(LDLIBS) -o $@ + +.PHONY : all doc 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 + -rm --force $(PROGS) $(objects) $(DOCS) core distclean: clean mostlyclean: clean maintainer-clean: clean - -rm --force --recursive keydir confdir statedir + -rm --force --recursive keydir confdir -check: all +check: ./mandos --check - ./mandos-ctl --check -# Run the client with a local config and key -run-client: all keydir/seckey.txt keydir/pubkey.txt keydir/tls-privkey.pem keydir/tls-pubkey.pem - @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 the server with a local key +run-client: all keydir/seckey.txt keydir/pubkey.txt \ + keydir/secring.gpg keydir/pubring.gpg ./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= \ - $(CLIENTARGS) + --options-for=password-request:--keydir=keydir # Used by run-client -keydir/seckey.txt keydir/pubkey.txt keydir/tls-privkey.pem keydir/tls-pubkey.pem: mandos-keygen +keydir/secring.gpg: keydir/seckey.txt + gpg --homedir $(dir $<) --import $^ +keydir/pubring.gpg: keydir/pubkey.txt + gpg --homedir $(dir $<) --import $^ +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 --configdir=confdir # 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 + install $^ $@ +confdir/clients.conf: clients.conf keydir/seckey.txt install --directory confdir - install --mode=u=rw $< $@ + install clients.conf $@ # Add a client password - ./mandos-keygen --dir keydir --password --no-ssh >> $@ -statedir: - install --directory statedir - -install: install-server install-client-nokey - -install-html: html - install --directory $(htmldir) - install --mode=u=rw,go=r --target-directory=$(htmldir) \ - $(htmldocs) + ./mandos-keygen --dir keydir --password >> $@ + +install: install-server install-client 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) \ + install --directory --parents $(CONFDIR) $(MANDIR)/man5 \ + $(MANDIR)/man8 + install --mode=0755 mandos $(PREFIX)/sbin/mandos + install --mode=0644 --target-directory=$(CONFDIR) mandos.conf + install --mode=0640 --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 \ - update-rc.d mandos defaults 25 15;\ - 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 --mode=u=rwx $(KEYDIR) \ - $(LIBDIR)/mandos/plugins.d \ - $(LIBDIR)/mandos/plugin-helpers - if [ "$(CONFDIR)" != "$(LIBDIR)/mandos" ]; then \ - install --mode=u=rwx \ - --directory "$(CONFDIR)/plugins.d" \ - "$(CONFDIR)/plugin-helpers"; \ - 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)/sbin \ +install-client: all doc /usr/share/initramfs-tools/hooks/. + install --directory --parents $(PREFIX)/lib/mandos \ + $(CONFDIR) $(MANDIR)/man8 + install --directory --mode=0700 $(PREFIX)/lib/mandos/plugins.d + chmod u=rwx,g=,o= $(PREFIX)/lib/mandos/plugins.d + install --mode=0755 --target-directory=$(PREFIX)/lib/mandos \ + plugin-runner + install --mode=0755 --target-directory=$(PREFIX)/sbin \ mandos-keygen - install --mode=u=rwx,go=rx \ - --target-directory=$(LIBDIR)/mandos/plugins.d \ + install --mode=0755 \ + --target-directory=$(PREFIX)/lib/mandos/plugins.d \ plugins.d/password-prompt - install --mode=u=rwxs,go=rx \ - --target-directory=$(LIBDIR)/mandos/plugins.d \ - plugins.d/mandos-client - install --mode=u=rwxs,go=rx \ - --target-directory=$(LIBDIR)/mandos/plugins.d \ - plugins.d/usplash - install --mode=u=rwxs,go=rx \ - --target-directory=$(LIBDIR)/mandos/plugins.d \ - plugins.d/splashy - install --mode=u=rwxs,go=rx \ - --target-directory=$(LIBDIR)/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 --mode=4755 \ + --target-directory=$(PREFIX)/lib/mandos/plugins.d \ + plugins.d/password-request install initramfs-tools-hook \ - $(INITRAMFSTOOLS)/hooks/mandos - install --mode=u=rw,go=r initramfs-tools-conf \ - $(INITRAMFSTOOLS)/conf.d/mandos-conf + /usr/share/initramfs-tools/hooks/mandos + install initramfs-tools-hook-conf \ + /usr/share/initramfs-tools/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) + /usr/share/initramfs-tools/scripts/local-top/mandos 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/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 - -$(PREFIX)/sbin/mandos-keygen --dir "$(KEYDIR)" + gzip --best --to-stdout plugins.d/password-request.8mandos \ + > $(MANDIR)/man8/password-request.8mandos.gz + -$(PREFIX)/sbin/mandos-keygen update-initramfs -k all -u - echo "Now run mandos-keygen --password --dir $(KEYDIR)" uninstall: uninstall-server uninstall-client -uninstall-server: +uninstall-server: $(PREFIX)/sbin/mandos -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 -rmdir $(CONFDIR) uninstall-client: # Refuse to uninstall client if /etc/crypttab is explicitly configured # to use it. ! grep --regexp='^ *[^ #].*keyscript=[^,=]*/mandos/' \ - $(DESTDIR)/etc/crypttab + /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 \ - $(INITRAMFSTOOLS)/hooks/mandos \ - $(INITRAMFSTOOLS)/conf-hooks.d/mandos \ - $(INITRAMFSTOOLS)/scripts/init-premount/mandos \ + $(PREFIX)/lib/mandos/plugin-runner \ + $(PREFIX)/lib/mandos/plugins.d/password-prompt \ + $(PREFIX)/lib/mandos/plugins.d/password-request \ + /usr/share/initramfs-tools/hooks/mandos \ + /usr/share/initramfs-tools/conf-hooks.d/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/password-request.8mandos.gz + -rmdir $(PREFIX)/lib/mandos/plugins.d $(CONFDIR)/plugins.d \ + $(PREFIX)/lib/mandos $(CONFDIR) 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 + -rm --force $(CONFDIR)/mandos.conf $(CONFDIR)/clients.conf -rmdir $(CONFDIR) purge-client: uninstall-client - -shred --remove $(KEYDIR)/seckey.txt $(KEYDIR)/tls-privkey.pem - -rm --force $(CONFDIR)/plugin-runner.conf \ - $(KEYDIR)/pubkey.txt $(KEYDIR)/seckey.txt \ - $(KEYDIR)/tls-pubkey.txt $(KEYDIR)/tls-privkey.txt - -rmdir $(KEYDIR) $(CONFDIR)/plugins.d $(CONFDIR) + -rm --force $(CONFDIR)/seckey.txt $(CONFDIR)/pubkey.txt + -rmdir $(CONFDIR) $(CONFDIR)/plugins.d === removed file 'NEWS' --- NEWS 2018-08-19 20:17:48 +0000 +++ NEWS 1970-01-01 00:00:00 +0000 @@ -1,497 +0,0 @@ -This NEWS file records noteworthy changes, very tersely. -See the manual for detailed information. - -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. - -Version 1.0.13 (2009-10-22) -* Client -** Security bug fix: If Mandos server is also installed, do not copy - its config files (with encrypted passwords) into the initrd.img-* - files. - -Version 1.0.12 (2009-09-17) -* Client -** Bug fix: Allow network interface renaming by "udev" by taking down - the network interface after using it. -** Bug fix: User-supplied plugins are now installed correctly. -** Bug fix: If usplash was used but the password was instead provided - by the Mandos server, the usplash daemon used to ignore the first - command passed to it. This has been fixed. -** Bug fix: Make the "--userid" and "--groupid" options in - "plugin-runner.conf" work. -* Server -** Bug fix: Fix the LSB header in the init.d script to make dependency - based booting work. -** A client receiving its password now also counts as if a checker was - run successfully (i.e. the timeout timer is reset). - -Version 1.0.11 (2009-05-23) -* Client -** Bug fix: Use "pkg-config" instead of old "libgnutls-config". - -Version 1.0.10 (2009-05-17) -* Client -** Security bug fix: Fix permissions on initrd.img-*.bak files when - upgrading from older versions. - -Version 1.0.9 (2009-05-17) -* Client -** Security bug fix: Fix permissions on initrd.img file when - installing new linux-image-* packages calling mkinitramfs-kpkg (all - version lower than 2.6.28-1-* does this). - -Version 1.0.8 (2009-02-25) -* Client -** Bug fix: Fix missing quote characters in initramfs-tools-hook. - -Version 1.0.7 (2009-02-24) -* Client -** Bug fix: Do not depend on GNU awk. - -Version 1.0.6 (2009-02-13) -* Server -** Fix bug where server would stop responding, with a zombie checker -** Support for disabling IPv6 (only for advanced users) -** Fix bug which made server not change group ID - -* Client -** Bug fix: Fix permission for /lib64 (on relevant architechtures). -** Add support for IPv4 addresses. -** Add support in mandos-client for not bringing up a network - interface by specifying an empty string to "--interface". -** Make password prompt on boot not be mangled by kernel log messages - about network interface. -** Get network interface from initramfs.conf and/or from kernel - command line. -** If set by "ip=" kernel command line, configure network on boot. -** Support connecting directly using "mandos=connect" kernel command. - line option, provided network is configured using "ip=". -** Fix bug which made plugin-runner and mandos-client not change group - ID. -** Fix bug where the "--options-for" option of plugin-runner would - truncate the value at the first colon character. -** Fix bug where plugin-runner would not go to fallback if all plugins - failed. -** Fix bug where mandos-client would not clean temporary directory on - a signal or on certain file systems. -** Bug fix: remove bashism in /bin/sh script "mandos-keygen". - -Version 1.0.5 (2009-01-17) -* Client -** Fix small memory leak in plugin-runner. - -Version 1.0.4 (2009-01-15) -* Server -** Only find matched user/group pairs when searching for suitable - nonprivileged user/group to switch to. - -* Client -** New kernel parameter "mandos=off" makes client not run at boot. -** Fix linking errors and compilation warnings on AMD64. -** Parse numbers in command line options better. -** The splashy and usplash plugins are more robust while traversing - /proc, and will not abort if a process suddenly disappears. - -Version 1.0.3 (2009-01-06) -* Server -** Now tries to change to user and group "_mandos" before falling back - to trying the old values "mandos", "nobody:nogroup", and "65534". -** Now does not abort on startup even if no clients are defined in - clients.conf. - -* Client -** Plugins named "*.dpkg-bak" are now ignored. -** Hopefully fixed compilation failure on some architectures where the - C compiler does not recognize the "-z" option as a linker option. - -Version 1.0.2 (2008-10-17) -* mandos-keygen now signs the encrypted key blobs. This signature is - not currently verified by mandos-client, but this may change in the - future. - -Version 1.0.1 (2008-10-07) -* Server -** Expand environment variables and ~user in clients.conf's "secfile" - The "secfile" option in /etc/mandos/clients.conf now expands - "~user/foo" and "$ENVVAR" strings. - -* Client (plugin-runner, plugins, etc.) -** Manual pages for the usplash, splashy, and askpass-fifo plugins. - All plugins now have man pages. -** More secure compilation and linking flags. - All programs are now compiled with "-fstack-protector-all -fPIE - -pie", and linked using "-z relro -pie" for additional security. - -* There is now a "NEWS" file (this one), giving a history of - noteworthy changes. === removed file 'README' --- README 2016-03-23 07:11:22 +0000 +++ README 1970-01-01 00:00:00 +0000 @@ -1,11 +0,0 @@ -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. === modified file 'TODO' --- TODO 2019-02-09 23:31:44 +0000 +++ TODO 2008-08-27 01:18:25 +0000 @@ -1,123 +1,150 @@ -*- org -*- -* Testing -** python-nemu - -* mandos-applet - -* mandos-client -** TODO A --server option which only adds to the server list. - (Unlike --connect, which implicitly disables zeroconf.) -** TODO [#B] Use capabilities instead of seteuid(). - https://forums.grsecurity.net/viewtopic.php?f=7&t=2522 -** TODO [#B] Use getaddrinfo(hints=AI_NUMERICHOST) instead of inet_pton() -** TODO [#C] Make start_mandos_communication() take "struct server". -** TODO [#C] --interfaces=regex,eth*,noregex (bridge-utils-interfaces(5)) -** TODO [#A] Detect partial writes to stdout and exit with EX_TEMPFAIL - -* splashy -** TODO [#B] use scandir(3) instead of readdir(3) -** TODO [#A] Detect partial writes to stdout and exit with EX_TEMPFAIL - -* usplash (Deprecated) -** TODO [#B] 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 - -* askpass-fifo -** TODO [#A] Detect partial writes to stdout and exit with EX_TEMPFAIL +* [#A] README file + +* plugin-runner +** [#B] Add more comments to code +** [#B] Add more if(debug) calls +** [#B] Seperate more code to function for more readability +** [#A] Man page: man8/plugin-runner.8mandos +*** EXIT STATUS +*** ENVIRONMENT + Environment is modified according to options and passed to plugins +*** EXAMPLE + Examples of normal usage, debug usage, debugging single or all + plugins, etc. +*** FILES +*** SECURITY + Note the danger of using this program, since you might lock + yourself out of your system without any means of entering the root + file system password. This is, however, very unlikely considering + the fallback to getpass(3). +*** BUGS +*** SEE ALSO + Explaining text on what you can read + +* password-request +** [#A] Man page: man8/password-request.8mandos +*** SYNOPSIS + Document short options +*** DESCRIPTION + State that this command is not meant to be invoked directly, but + is run as a plugin from mandos-client(8) and only run in the + initrd environment, not the real system. +*** PURPOSE + As in mandos.xml +*** OVERVIEW + As in mandos.xml +*** EXIT STATUS +*** ENVIRONMENT + Note that it does *not* currently use cryptsource or crypttarget. +*** FILES + Describe the key files and the key ring files. Also note that + they should normally have been automatically created. +*** BUGS +*** EXAMPLE + Examples of normal usage, debug usage, debugging by connecting + directly, etc. +*** SECURITY +*** SEE ALSO + Update from mandos.xml +** [#B] Temporarily lower kernel log level + for less printouts during sucessfull boot. +** IPv4 support +** use strsep instead of strtok? +** Do not depend on GnuPG key rings on disk + This would mean creating new GnuPG key rings with GPGME by + importing the key files from scratch on every program start. +** Keydir move: /etc/mandos -> /etc/keys/mandos + Must create in preinst if not pre-depending on cryptsetup * password-prompt -** 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] passdev - -* plugin-runner -** TODO handle printing for errors for plugins -*** Hook up stderr of plugins, buffer them, and prepend "Mandos Plugin [plugin name]" -** TODO [#C] use same file name rules as run-parts(8) -** kernel command line option for debug info -** TODO [#A] Restart plugins which exit with EX_TEMPFAIL +** [#A] Man page: man8/password-prompt.8mandos +*** SYNOPSIS + Document short options +*** DESCRIPTION + Note that this is more or less a simple getpass(3) wrapper, even + though actual use of getpass(3) is not guaranteed. +*** EXIT STATUS +*** ENVIRONMENT + Document use of "cryptsource" and "crypttarget". +*** FILES +*** BUGS +*** EXAMPLE + Examples of normal usage, debug usage, with a prefix, etc. +*** SECURITY + Not much to do here but it is noteworthy to state the danger of + not having a fallback option. +*** SEE ALSO + Refer to mandos-client(8mandos) and password-request(8mandos) + and also, perhaps, to cryptsetup(8)? +** Use getpass(3)? + Man page says "obsolete", but [[info:libc:getpass][GNU LibC Manual: Reading Passwords]] + does not. See also [[http://sources.redhat.com/ml/libc-alpha/2003-05/msg00251.html][Marcus Brinkmann: Re: getpass obsolete?]] and + [[http://article.gmane.org/gmane.comp.lib.glibc.alpha/4906][Petter Reinholdtsen: Re: getpass obsolete?]], and especially also + [[http://www.steve.org.uk/Reference/Unix/faq_4.html#SEC48][Unix Programming FAQ 3.1 How can I make my program not echo input?]] * 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 /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 [#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 - + SetPass(u"gazonk", True) -> Approval, persistent - + Approve(False) -> Close client connection immediately -** TODO [#C] python-parsedatetime -** TODO Separate logging logic to own object -** TODO [#B] Limit approval_delay to max gnutls/tls timeout value -** TODO [#B] break the wait on approval_delay if connection dies -** TODO Generate Client.runtime_expansions from client options + extra -** TODO 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() - -* mandos-ctl -*** Handle "no D-Bus server" and/or "no Mandos server found" better -** TODO Remove old string_to_delta format :2: - -* TODO mandos-dispatch - 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 "--secfile" option - Using the "secfile" option instead of "secret" -** TODO [#B] "--test" option - For testing decryption before rebooting. - -* Package +** [#A] /etc/init.d/mandos-server :teddy: +** [#B] Log level :bugs: +** /etc/mandos/clients.d/*.conf + Watch this directory and add/remove/update clients? +** config for TXT record +** [#B] Run-time communication with server :bugs: + Probably using D-Bus + See also [[*Mandos-tools]] +** Implement --foreground :bugs: + [[info:standards:Option%20Table][Table of Long Options]] +** Implement --socket + [[info:standards:Option%20Table][Table of Long Options]] +** Date+time on console log messages :bugs: + Is this the default? + +* Mandos-tools/utilities + All of this probably using D-Bus +** List clients +** Disable client +** Enable client + +* Man pages tags + Go through all man pages to conform to the style of tags chosen in + [[http://svn.debian.org/wsvn/debian-xml-sgml/packages/docbook-xsl/trunk/debian/examples/foo.1.example_manpage.xml?op=file&rev=0&sc=0][foo.1.example_manpage.xml]]. + +* Installer +** Client-side +*** Update initrd.img after installation + This seems to use some kind of "trigger" system + [[file:/usr/share/doc/dpkg/triggers.txt.gz]] + dpkg-trigger(1), deb-triggers(5) +*** Keydir move: /etc/mandos -> /etc/keys/mandos + Must create in preinst if not pre-depending on cryptsetup +*** mandos-keygen +**** "--passfile" option + Using the "secfile" option instead of "secret" +**** [#A] "--test" option + For testing decryption before rebooting. +** Server-side +*** [#A] Create mandos user and group for server +*** [#A] Create /var/run/mandos directory with perm and ownership + +* [#A] 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) +*** Do not install in initrd.img if configured not to. + Use "/etc/initramfs-tools/conf.d/mandos"? Definitely a debconf + question. +** /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]] +** unperish +** bzr-builddeb + +* INSTALL file + +* Web site + +* Mailing list + +* Announce project on news + [[news:comp.os.linux.announce]] #+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 2008-08-27 01:18:25 +0000 @@ -2,45 +2,25 @@ # values, so uncomment and change them if you want different ones. [DEFAULT] -# How long until a client is disabled and not be allowed to get the -# data this server holds. -;timeout = PT5M +# How long until a client is considered invalid - that is, ineligible +# to get the data this server holds. +;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 +# above "timeout" occurs, at which time the client will be marked +# invalid, and any running checker killed. +;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 +;checker = fping -q -- %(host)s ;#### ;# 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.txt.asc ; ;# 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 ;#### === removed file 'common.ent' --- common.ent 2018-08-19 20:17:48 +0000 +++ common.ent 1970-01-01 00:00:00 +0000 @@ -1,3 +0,0 @@ - - - === removed file 'dbus-mandos.conf' --- dbus-mandos.conf 2011-10-02 19:18:24 +0000 +++ dbus-mandos.conf 1970-01-01 00:00:00 +0000 @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - === removed directory 'debian' === removed file 'debian/changelog' --- debian/changelog 2018-08-19 20:17:48 +0000 +++ debian/changelog 1970-01-01 00:00:00 +0000 @@ -1,735 +0,0 @@ -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. - * debian/rules: Build with BROKEN_PIE set on mips and mipsel - architectures - fixes FTBFS there. - - -- Teddy Hogeborn Sun, 25 Oct 2009 20:10:09 +0100 - -mandos (1.0.13-1) unstable; urgency=high - - * New upstream release. - * Do not copy unnecessary files to initrd (Closes: #551907) - - -- Teddy Hogeborn Thu, 22 Oct 2009 00:53:21 +0200 - -mandos (1.0.12-1) unstable; urgency=low - - * New upstream release. - * init.d-mandos: Correct dependencies (Closes: #546928) - * debian/control (Standards-Version): Changed to "3.8.3". - * debian/mandos-client.README.Debian: Improved wording and formatting. - Updated location of nfsroot.txt. - * debian/mandos.README.Debian: Improved wording and formatting. - * debian/mandos-client.postinst (configure): Don't look for user and - group with the old name if upgrading from a new enough version. - * debian/mandos.postinst (configure): - '' - - * debian/mandos-client.README.Debian: Added text about non-usability of - pseudo-network interfaces. - - -- Teddy Hogeborn Thu, 17 Sep 2009 15:03:59 +0200 - -mandos (1.0.11-1) unstable; urgency=low - - * debian/control (Standards-Version): Changed to "3.8.1". - * Makefile (GNUTLS_CFLAGS, GNUTLS_CFLAGS): Use "pkg-config" instead of - the old "libgnutls-config" script. (Closes: #529836) - - -- Teddy Hogeborn Sat, 23 May 2009 07:12:20 +0200 - -mandos (1.0.10-1) unstable; urgency=low - - * New upstream release. - * debian/mandos-client.postinst (update_initramfs): Fix permissions of - old initrd.img-*.bak files. - - -- Teddy Hogeborn Sun, 17 May 2009 04:56:35 +0200 - -mandos (1.0.9-1) unstable; urgency=low - - * New upstream release. - - -- Teddy Hogeborn Sun, 17 May 2009 02:59:45 +0200 - -mandos (1.0.8-1) unstable; urgency=low - - * New upstream release. - - -- Teddy Hogeborn Wed, 25 Feb 2009 02:26:57 +0100 - -mandos (1.0.7-1) unstable; urgency=low - - * New upstream release. - - -- Teddy Hogeborn Tue, 24 Feb 2009 12:58:06 +0100 - -mandos (1.0.6-1) unstable; urgency=low - - * New upstream release. - * debian/mandos-client.postinst: Converted to Bourne shell. Also - minor message change. - * debian/mandos-client.postrm: Minor message change. - * debian/mandos.postinst: Converted to Bourne shell. Also minor - message change. - * debian/mandos.prerm: Minor message change. - * debian/rules (install-indep): Removed "--no-start" from - dh_installinit. - * debian/mandos-client.lintian-overrides: Remove obsolete override for - unbreakable line in plugin-runner manual page. - * debian/control (mandos/Depends): Added "python-gobject". - * debian/mandos-client.dirs: Change - "usr/share/initramfs-tools/scripts/local-top" to - "usr/share/initramfs-tools/scripts/init-premount". - * debian/mandos-client.README.Debian: Add reference to initramfs.conf - and nfsroot.txt. New section about the new non-local connection - feature. - - -- Teddy Hogeborn Fri, 13 Feb 2009 09:27:25 +0100 - -mandos (1.0.5-1) unstable; urgency=low - - * New upstream release. - - -- Teddy Hogeborn Sat, 17 Jan 2009 02:26:00 +0100 - -mandos (1.0.4-1) unstable; urgency=low - - * New upstream release. - * debian/watch: New file. - * debian/mandos-client.README.Debian: Document new "mandos=off" kernel - parameter. - - -- Teddy Hogeborn Thu, 15 Jan 2009 05:49:22 +0100 - -mandos (1.0.3-2) unstable; urgency=low - - * Removed some now-unused debconf files. - * Changed postinst scripts to not source debconf/confmodule. - * Removed po-debconf from build-depends. - - -- Teddy Hogeborn Tue, 06 Jan 2009 21:28:20 +0100 - -mandos (1.0.3-1) unstable; urgency=low - - * New upstream release. - * Add -Xlinker to linker flags to fix FTBFS for some architectures. - Thanks to Thiemo Seufer for the report and - fix. (Closes: #509398) - * Remove debconf use altogether, thereby stopping debconf abuse. Thanks - to Christian Perrier . (Closes: #509653) - * Add NEWS file to /usr/share/doc directories. - * Use and create "_mandos" user+group. Rename old user+group created by - older versions of this package. - * Fix manual pages by adding build-depend on "docbook-xml". - - -- Teddy Hogeborn Tue, 06 Jan 2009 01:21:20 +0100 - -mandos (1.0.2-1) unstable; urgency=low - - * New upstream release. - * debian/copyright: Rewritten to conform to - . - - -- Teddy Hogeborn Fri, 17 Oct 2008 20:42:12 +0200 - -mandos (1.0.1-1) unstable; urgency=low - - * New upstream release. - * Separate /usr/share/doc/mandos-client/README.Debian into sections with - headlines. Add instructions on how to test the server and verify the - password. - - -- Teddy Hogeborn Tue, 07 Oct 2008 23:07:23 +0200 - -mandos (1.0-2) unstable; urgency=low - - * Added comments in debian/*.lintian-overrides files. Added Debian - revison number to version number. - - -- Teddy Hogeborn Wed, 01 Oct 2008 17:23:35 +0200 - -mandos (1.0-1) unstable; urgency=low - - * Initial Release. (Closes: #500727). - - -- Teddy Hogeborn Tue, 30 Sep 2008 21:58:43 +0200 === removed file 'debian/compat' --- debian/compat 2018-02-06 20:03:50 +0000 +++ debian/compat 1970-01-01 00:00:00 +0000 @@ -1,1 +0,0 @@ -10 === removed file 'debian/control' --- debian/control 2019-02-09 23:23:26 +0000 +++ debian/control 1970-01-01 00:00:00 +0000 @@ -1,67 +0,0 @@ -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) | gnutls-dev (>= 3.3.0), - libgnutls28-dev (<< 3.6.0) | libgnutls30 (<< 3.6.0) - | libgnutls30 (>= 3.6.6), - xsltproc, pkg-config, libnl-route-3-dev -Build-Depends-Indep: systemd, python (>= 2.7), python (<< 3), - python-dbus, python-gi -Standards-Version: 4.2.1 -Vcs-Bzr: https://ftp.recompile.se/pub/mandos/trunk -Vcs-Browser: https://bzr.recompile.se/loggerhead/mandos/trunk/files -Homepage: https://www.recompile.se/mandos -Rules-Requires-Root: binary-targets - -Package: mandos -Architecture: all -Depends: ${misc:Depends}, python (>= 2.7), python (<< 3), - libgnutls28-dev (>= 3.3.0) | libgnutls30 (>= 3.3.0), - libgnutls28-dev (<< 3.6.0) | libgnutls30 (<< 3.6.0) - | libgnutls30 (>= 3.6.6), - python-dbus, python-gi, avahi-daemon, adduser, python-urwid, - gnupg2 | gnupg, systemd-sysv | lsb-base (>= 3.0-6) -Recommends: ssh-client | fping -Description: server giving encrypted passwords to Mandos clients - This is the server part of the Mandos system, which allows - computers to have encrypted root file systems and at the - 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 an 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) | openssl (>= 1.1.0) -Recommends: ssh -Breaks: dropbear (<= 0.53.1-1) -Enhances: cryptsetup -Description: do unattended reboots with an encrypted root file system - This is the client part of the Mandos system, which allows - computers to have encrypted root file systems and at the - 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 an OpenPGP key, and the - password is then used to unlock the root file system, - whereupon the computers can continue booting normally. === removed file 'debian/copyright' --- debian/copyright 2018-02-08 10:23:55 +0000 +++ debian/copyright 1970-01-01 00:00:00 +0000 @@ -1,26 +0,0 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: Mandos -Upstream-Contact: Mandos -Source: - -Files: * -Copyright: Copyright © 2008-2018 Teddy Hogeborn - Copyright © 2008-2018 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 - 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 . - . - On Debian systems, the complete text of the GNU General Public - License can be found in "/usr/share/common-licenses/GPL-3". === removed file 'debian/mandos-client.README.Debian' --- debian/mandos-client.README.Debian 2019-02-09 23:23:26 +0000 +++ debian/mandos-client.README.Debian 1970-01-01 00:00:00 +0000 @@ -1,111 +0,0 @@ -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. - -* Adding a Client Password to the Server - - The server must be given a password to give back to the client on - boot time. This password must be a one which can be used to unlock - the root file system device. On the *client*, run this command: - - mandos-keygen --password - - It will prompt for a password and output a config file section. - This output should be copied to the Mandos server and added to the - file "/etc/mandos/clients.conf" there. - -* Testing that it Works (Without Rebooting) - - After the server has been started with this client's key added, it - 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 \ - --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 - - 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 "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 - to the normal Mandos plugins. When adding or changing plugins, do - not forget to update the initital RAM disk image: - - update-initramfs -k all -u - -* Do *NOT* Edit "/etc/crypttab" - - It is NOT necessary to edit "/etc/crypttab" to specify - "/usr/lib/mandos/plugin-runner" as a keyscript for the root file - system; if no keyscript is given for the root file system, the - Mandos client will be the new default way for getting a password for - the root file system when booting. - -* 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" 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 , Sat, 9 Feb 2019 15:08:04 +0100 === removed file 'debian/mandos-client.dirs' --- debian/mandos-client.dirs 2018-08-19 14:32:00 +0000 +++ debian/mandos-client.dirs 1970-01-01 00:00:00 +0000 @@ -1,6 +0,0 @@ -usr/share/man/man8 -usr/sbin -usr/share/initramfs-tools/hooks -usr/share/initramfs-tools/conf.d -usr/share/initramfs-tools/scripts/init-premount -usr/share/initramfs-tools/scripts/local-premount === removed file 'debian/mandos-client.docs' --- debian/mandos-client.docs 2012-06-01 21:48:12 +0000 +++ debian/mandos-client.docs 1970-01-01 00:00:00 +0000 @@ -1,3 +0,0 @@ -NEWS -README -TODO === 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 === removed file 'debian/mandos-client.links' --- debian/mandos-client.links 2008-09-19 13:50:22 +0000 +++ debian/mandos-client.links 1970-01-01 00:00:00 +0000 @@ -1,1 +0,0 @@ -usr/share/man/man8/plugin-runner.8mandos.gz usr/share/man/man5/plugin-runner.conf.5mandos.gz === removed file 'debian/mandos-client.lintian-overrides' --- debian/mandos-client.lintian-overrides 2016-03-19 04:21:00 +0000 +++ debian/mandos-client.lintian-overrides 1970-01-01 00:00:00 +0000 @@ -1,32 +0,0 @@ -# This directory contains secret client key files. -# -mandos-client binary: non-standard-dir-perm etc/keys/mandos/ 0700 != 0755 - -# The directory /usr/lib//mandos/plugins.d contains setuid -# binaries which are not meant to be run outside an initial RAM disk -# environment (except for test purposes). It would be insecure to -# allow anyone to run them. -# -mandos-client binary: non-standard-dir-perm usr/lib/*/mandos/plugins.d/ 0700 != 0755 -# Likewise for helper executables for plugins -mandos-client binary: non-standard-dir-perm usr/lib/*/mandos/plugin-helpers/ 0700 != 0755 - -# These binaries must be setuid root, since they need root powers, but -# are started by plugin-runner(8mandos), which runs all plugins as -# user/group "_mandos". These binaries are not run in a running -# system, but in an initial RAM disk environment. Here they are -# protected from non-root access by the directory permissions, above. -# -mandos-client binary: setuid-binary usr/lib/*/mandos/plugins.d/mandos-client 4755 root/root -mandos-client binary: setuid-binary usr/lib/*/mandos/plugins.d/askpass-fifo 4755 root/root -mandos-client binary: setuid-binary usr/lib/*/mandos/plugins.d/splashy 4755 root/root -mandos-client binary: setuid-binary usr/lib/*/mandos/plugins.d/usplash 4755 root/root -mandos-client binary: setuid-binary usr/lib/*/mandos/plugins.d/plymouth 4755 root/root - -# The directory /etc/mandos/plugins.d can be used by local system -# administrators to place plugins in, overriding and complementing -# /usr/lib//mandos/plugins.d, and must be likewise protected. -# -mandos-client binary: non-standard-dir-perm etc/mandos/plugins.d/ 0700 != 0755 -# Likewise for plugin-helpers directory -mandos-client binary: non-standard-dir-perm etc/mandos/plugin-helpers/ 0700 != 0755 === removed file 'debian/mandos-client.postinst' --- debian/mandos-client.postinst 2019-02-09 23:23:26 +0000 +++ debian/mandos-client.postinst 1970-01-01 00:00:00 +0000 @@ -1,160 +0,0 @@ -#!/bin/sh -# This script can be called in the following ways: -# -# After the package was installed: -# configure -# -# -# If prerm fails during upgrade or fails on failed upgrade: -# abort-upgrade -# -# If prerm fails during deconfiguration of a package: -# abort-deconfigure in-favour -# removing -# -# If prerm fails during replacement due to conflict: -# abort-remove in-favour - -set -e - -# Update the initial RAM file system image -update_initramfs() -{ - update-initramfs -u -k all - - if dpkg --compare-versions "$2" lt-nl "1.0.10-1"; then - # Make old initrd.img files unreadable too, in case they were - # created with mandos-client 1.0.8 or older. - find /boot -maxdepth 1 -type f -name "initrd.img-*.bak" \ - -print0 | xargs --null --no-run-if-empty chmod o-r - fi -} - -# Add user and group -add_mandos_user(){ - # Rename old "mandos" user and group - if dpkg --compare-versions "$2" lt "1.0.3-1"; then - case "`getent passwd mandos`" in - *:Mandos\ password\ system,,,:/nonexistent:/bin/false) - usermod --login _mandos mandos - groupmod --new-name _mandos mandos - return - ;; - esac - fi - # Create new user and group - if ! getent passwd _mandos >/dev/null; then - adduser --system --force-badname --quiet --home /nonexistent \ - --no-create-home --group --disabled-password \ - --gecos "Mandos password system" _mandos - fi -} - -# Create client key pairs -create_keys(){ - # If the OpenPGP key files do not exist, generate all keys using - # mandos-keygen - if ! [ -r /etc/keys/mandos/pubkey.txt \ - -a -r /etc/keys/mandos/seckey.txt ]; then - mandos-keygen - gpg-connect-agent KILLAGENT /bye || : - return 0 - fi - - # 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 - - # If this is an upgrade from an old installation, the TLS keys - # will not exist; create them. - - # First try certtool from GnuTLS - if ! certtool --generate-privkey --password='' \ - --outfile /etc/keys/mandos/tls-privkey.pem \ - --sec-param ultra --key-type=ed25519 --pkcs8 --no-text \ - 2>/dev/null; then - # Otherwise try OpenSSL - if ! openssl genpkey -algorithm X25519 \ - -out /etc/keys/mandos/tls-privkey.pem; then - rm --force /etc/keys/mandos/tls-privkey.pem - # None of the commands succeded; give up - return 1 - fi - fi - - local umask=$(umask) - umask 077 - # 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 -} - -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" -} - -case "$1" in - configure) - add_mandos_user "$@" - create_keys "$@" - create_dh_params "$@" || : - 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) - ;; - - *) - echo "$0 called with unknown argument '$1'" 1>&2 - exit 1 - ;; -esac - -#DEBHELPER# - -exit 0 === removed file 'debian/mandos-client.postrm' --- debian/mandos-client.postrm 2019-02-09 23:23:26 +0000 +++ debian/mandos-client.postrm 1970-01-01 00:00:00 +0000 @@ -1,63 +0,0 @@ -#!/bin/sh -# This script can be called in the following ways: -# -# After the package was removed: -# remove -# -# After the package was purged: -# purge -# -# After the package was upgraded: -# upgrade -# if that fails: -# failed-upgrade -# -# -# After all of the packages files have been replaced: -# disappear -# -# -# If preinst fails during install: -# abort-install -# -# If preinst fails during upgrade of removed package: -# abort-install -# -# If preinst fails during upgrade: -# abort-upgrade - -set -e - -# Update the initial RAM file system image -update_initramfs() -{ - update-initramfs -u -k all || : -} - -case "$1" in - remove) - update_initramfs - ;; - - purge) - 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 - ;; - upgrade|failed-upgrade|disappear|abort-install|abort-upgrade) - ;; - - *) - echo "$0 called with unknown argument '$1'" 1>&2 - exit 1 - ;; -esac - -#DEBHELPER# - -exit 0 === removed file 'debian/mandos.README.Debian' --- debian/mandos.README.Debian 2011-10-05 16:00:56 +0000 +++ debian/mandos.README.Debian 1970-01-01 00:00:00 +0000 @@ -1,10 +0,0 @@ -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 - - # 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. - - -- Teddy Hogeborn , Wed, 5 Oct 2011 17:51:22 +0200 === removed file 'debian/mandos.dirs' --- debian/mandos.dirs 2016-03-19 12:10:15 +0000 +++ debian/mandos.dirs 1970-01-01 00:00:00 +0000 @@ -1,9 +0,0 @@ -usr/share/man/man5 -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 === removed file 'debian/mandos.docs' --- debian/mandos.docs 2010-09-12 03:00:40 +0000 +++ debian/mandos.docs 1970-01-01 00:00:00 +0000 @@ -1,4 +0,0 @@ -NEWS -README -TODO -DBUS-API === removed file 'debian/mandos.lintian-overrides' --- debian/mandos.lintian-overrides 2018-02-10 18:58:32 +0000 +++ debian/mandos.lintian-overrides 1970-01-01 00:00:00 +0000 @@ -1,5 +0,0 @@ -# This config file will normally have encrypted secret client keys in -# 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) === removed file 'debian/mandos.postinst' --- debian/mandos.postinst 2016-03-19 03:48:56 +0000 +++ debian/mandos.postinst 1970-01-01 00:00:00 +0000 @@ -1,79 +0,0 @@ -#!/bin/sh -# This script can be called in the following ways: -# -# After the package was installed: -# configure -# -# -# If prerm fails during upgrade or fails on failed upgrade: -# abort-upgrade -# -# If prerm fails during deconfiguration of a package: -# abort-deconfigure in-favour -# removing -# -# If prerm fails during replacement due to conflict: -# abort-remove in-favour - -set -e - -case "$1" in - configure) - # Rename old "mandos" user and group - if dpkg --compare-versions "$2" lt "1.0.3-1"; then - case "`getent passwd mandos`" in - *:Mandos\ password\ system,,,:/nonexistent:/bin/false) - usermod --login _mandos mandos - groupmod --new-name _mandos mandos - ;; - esac - fi - # Create new user and group - if ! getent passwd _mandos >/dev/null; then - adduser --system --force-badname --quiet \ - --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 - ;; - - 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 === removed file 'debian/mandos.prerm' --- debian/mandos.prerm 2015-07-12 01:57:54 +0000 +++ debian/mandos.prerm 1970-01-01 00:00:00 +0000 @@ -1,32 +0,0 @@ -#!/bin/sh -# prerm script for mandos -# -# see: dh_installdeb(1) - -set -e - -# summary of how this script can be called: -# * 'remove' -# * 'upgrade' -# * 'failed-upgrade' -# * 'remove' 'in-favour' -# * 'deconfigure' 'in-favour' -# 'removing' -# -# for details, see /usr/share/doc/packaging-manual/ - -case "$1" in - remove|deconfigure) - invoke-rc.d mandos stop || : - ;; - upgrade|failed-upgrade) - ;; - *) - echo "prerm called with unknown argument '$1'" >&2 - exit 0 - ;; -esac - -#DEBHELPER# - -exit 0 === removed directory 'debian/po' === removed file 'debian/rules' --- debian/rules 2018-02-08 12:25:31 +0000 +++ debian/rules 1970-01-01 00:00:00 +0000 @@ -1,45 +0,0 @@ -#!/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: - 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_fixperms --exclude etc/keys/mandos \ - --exclude etc/mandos/plugins.d \ - --exclude etc/mandos/plugin-helpers \ - --exclude usr/lib/$(DEB_HOST_MULTIARCH)/mandos/plugins.d \ - --exclude usr/lib/$(DEB_HOST_MULTIARCH)/mandos/plugin-helpers \ - --exclude usr/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: ; === 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/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----- === removed file 'debian/watch' --- debian/watch 2018-02-08 10:02:51 +0000 +++ debian/watch 1970-01-01 00:00:00 +0000 @@ -1,3 +0,0 @@ -version=4 -opts=pgpmode=auto \ - https://ftp.recompile.se/pub/@PACKAGE@/@PACKAGE@@ANY_VERSION@(?:\.orig)?@ARCHIVE_EXT@ === removed file 'default-mandos' --- default-mandos 2008-09-17 00:34:09 +0000 +++ default-mandos 1970-01-01 00:00:00 +0000 @@ -1,7 +0,0 @@ -# Directory where configuration files are located. Default is -# "/etc/mandos". -# -#CONFIGDIR=/etc/mandos - -# Additional options that are passed to the Daemon. -DAEMON_ARGS="" === removed file 'init.d-mandos' --- init.d-mandos 2018-02-10 13:23:58 +0000 +++ init.d-mandos 1970-01-01 00:00:00 +0000 @@ -1,164 +0,0 @@ -#! /bin/sh -### BEGIN INIT INFO -# Provides: mandos -# Required-Start: $remote_fs $syslog avahi-daemon -# Required-Stop: $remote_fs $syslog avahi-daemon -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: Mandos server -# Description: Server of encrypted passwords to Mandos clients -### END INIT INFO - -# Author: Teddy Hogeborn -# Author: Björn Påhlsson - -# Do NOT "set -e" - -# PATH should only include /usr/* if it runs after the mountnfs.sh script -PATH=/sbin:/usr/sbin:/bin:/usr/bin -DESC="Mandos root file system password server" -NAME=mandos -DAEMON=/usr/sbin/$NAME -DAEMON_ARGS="" -if [ -d /run/. ]; then - PIDFILE=/run/$NAME.pid -else - PIDFILE=/var/run/$NAME.pid -fi -SCRIPTNAME=/etc/init.d/$NAME - -# Exit if the package is not installed -[ -x "$DAEMON" ] || exit 0 - -# Read configuration variable file if it is present -[ -r /etc/default/$NAME ] && . /etc/default/$NAME - -if [ -n "$CONFIGDIR" ]; then - DAEMON_ARGS="$DAEMON_ARGS --configdir $CONFIGDIR" -fi - -# Load the VERBOSE setting and other rcS variables -. /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. -. /lib/lsb/init-functions - -# -# Function that starts the daemon/service -# -do_start() -{ - # Return - # 0 if daemon has been started - # 1 if daemon was already running - # 2 if daemon could not be started - start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ - || return 1 - start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ - $DAEMON_ARGS \ - || return 2 - # Add code here, if necessary, that waits for the process to be ready - # to handle requests from services started subsequently which depend - # on this one. As a last resort, sleep for some time. -} - -# -# Function that stops the daemon/service -# -do_stop() -{ - # Return - # 0 if daemon has been stopped - # 1 if daemon was already stopped - # 2 if daemon could not be stopped - # other if a failure occurred - start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME - RETVAL="$?" - [ "$RETVAL" = 2 ] && return 2 - # Wait for children to finish too if this is a daemon that forks - # and if the daemon is only ever run from this initscript. - # If the above conditions are not satisfied then add some other code - # that waits for the process to drop all resources that could be - # needed by services started subsequently. A last resort is to - # sleep for some time. - start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON - [ "$?" = 2 ] && return 2 - # Many daemons don't delete their pidfiles when they exit. - rm -f $PIDFILE - return "$RETVAL" -} - -# -# Function that sends a SIGHUP to the daemon/service -# -do_reload() { - # - # If the daemon can reload its configuration without - # restarting (for example, when it is sent a SIGHUP), - # then implement that here. - # - start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME - return 0 -} - -case "$1" in - start) - [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" - do_start - case "$?" in - 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; - 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; - esac - ;; - stop) - [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" - do_stop - case "$?" in - 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; - 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 - # and leave 'force-reload' as an alias for 'restart'. - # - #log_daemon_msg "Reloading $DESC" "$NAME" - #do_reload - #log_end_msg $? - #;; - restart|force-reload) - # - # If the "reload" option is implemented then remove the - # 'force-reload' alias - # - log_daemon_msg "Restarting $DESC" "$NAME" - do_stop - case "$?" in - 0|1) - do_start - case "$?" in - 0) log_end_msg 0 ;; - 1) log_end_msg 1 ;; # Old process is still running - *) log_end_msg 1 ;; # Failed to start - esac - ;; - *) - # 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 - 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 === modified file 'initramfs-tools-hook' --- initramfs-tools-hook 2018-08-19 14:06:55 +0000 +++ initramfs-tools-hook 2008-08-24 23:18:18 +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,38 +29,15 @@ . /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" - break - fi -done -if [ -z "$libdir" ]; then +if [ -d /usr/lib/mandos ]; then + prefix=/usr +elif [ -d /usr/local/lib/mandos ]; then + prefix=/usr/local +else # Mandos not found exit 1 fi -for d in /etc/keys/mandos /etc/mandos/keys; do - if [ -d "$d" ]; then - keydir="$d" - break - fi -done -if [ -z "$keydir" ]; then - # Mandos key directory not found - exit 1 -fi - -set `{ getent passwd _mandos \ - || getent passwd nobody \ - || echo ::65534:65534:::; } \ - | cut --delimiter=: --fields=3,4 --only-delimited \ - --output-delimiter=" "` -mandos_user="$1" -mandos_group="$2" - # The Mandos network client uses the network auto_add_modules net # The Mandos network client uses IPv6 @@ -70,50 +47,26 @@ 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}" -install --owner=${mandos_user} --group=${mandos_group} --directory \ - --mode=u=rwx "${DESTDIR}${PLUGINDIR}" \ - "${DESTDIR}${PLUGINHELPERDIR}" - -copy_exec "$libdir"/mandos/mandos-to-cryptroot-unlock "${MANDOSDIR}" +mkdir --parents "${DESTDIR}${CONFDIR}" +mkdir --parents "${DESTDIR}${PLUGINDIR}" # Copy the Mandos plugin runner -copy_exec "$libdir"/mandos/plugin-runner "${MANDOSDIR}" +copy_exec "$prefix"/lib/mandos/plugin-runner "${DESTDIR}${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 continue fi case "$base" in - *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) - : ;; - "*") echo "W: Mandos client plugin directory is empty." >&2 ;; - *) copy_exec "$file" "${PLUGINDIR}" ;; - 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}" ;; + *~|.*|\#*\#|*.dpkg-old|*.dpkg-new|*.dpkg-divert) : ;; + *) copy_exec "$file" "${PLUGINDIR}";; esac done @@ -121,127 +74,31 @@ for file in /etc/mandos/plugins.d/*; do base="`basename \"$file\"`" case "$base" in - *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) - : ;; - "*") : ;; - *) copy_exec "$file" "${PLUGINDIR}" ;; - 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 - -# Config files -for file in /etc/mandos/plugin-runner.conf; do + *~|.*|*.dpkg-old|*.dpkg-new|*.dpkg-divert) : ;; + *) copy_exec "$file" "${PLUGINDIR}";; + esac +done + +# GPGME needs /usr/bin/gpg +if ! [ -e "${DESTDIR}/usr/bin/gpg" ] \ + && [ -n "`ls \"${DESTDIR}\"/usr/lib/libgpgme.so* 2>/dev/null`" ]; then + copy_exec /usr/bin/gpg +fi + +# Key files +for file in /etc/mandos/*; do if [ -d "$file" ]; then continue fi cp --archive --sparse=always "$file" "${DESTDIR}${CONFDIR}" done - -if [ ${mandos_user} != 65534 ]; then - sed --in-place --expression="1i--userid=${mandos_user}" \ - "${DESTDIR}${CONFDIR}/plugin-runner.conf" -fi - -if [ ${mandos_group} != 65534 ]; then - sed --in-place --expression="1i--groupid=${mandos_group}" \ - "${DESTDIR}${CONFDIR}/plugin-runner.conf" -fi - -# Key files -for file in "$keydir"/*; do - if [ -d "$file" ]; then - continue - fi - case "$file" in - *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) - : ;; - "*") : ;; - *) - cp --archive --sparse=always "$file" \ - "${DESTDIR}${CONFDIR}" - chown ${mandos_user}:${mandos_group} \ - "${DESTDIR}${CONFDIR}/`basename \"$file\"`" - ;; - esac -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 +# Create key ring files +gpg --no-random-seed-file --quiet --batch --no-tty --armor \ + --no-default-keyring --no-options --enable-dsa2 \ + --homedir "${DESTDIR}${CONFDIR}" --no-permission-warning \ + --trust-model always --import-options import-minimal \ + --import "${DESTDIR}${CONFDIR}/seckey.txt" +chown nobody "${DESTDIR}${CONFDIR}/secring.gpg" # /lib/mandos/plugin-runner will drop priviliges, but needs access to # its plugin directory and its config file. However, since almost all @@ -252,10 +109,10 @@ # 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 +for full in "${PLUGINDIR}" "${CONFDIR}"; do while [ "$full" != "/" ]; do chmod a+rX "${DESTDIR}$full" full="`dirname \"$full\"`" @@ -265,13 +122,8 @@ # Reset some other things to sane permissions which we have # inadvertently affected with our umask setting. for dir in / /bin /etc /keyscripts /sbin /scripts /usr /usr/bin; do - if [ -d "${DESTDIR}$dir" ]; then - chmod a+rX "${DESTDIR}$dir" - fi + chmod a+rX "${DESTDIR}$dir" 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 -- - fi +for dir in /lib /usr/lib; do + chmod --recursive a+rX "${DESTDIR}$dir" done === added file 'initramfs-tools-hook-conf' --- initramfs-tools-hook-conf 1970-01-01 00:00:00 +0000 +++ initramfs-tools-hook-conf 2008-08-12 19:22:34 +0000 @@ -0,0 +1,1 @@ +UMASK=027 === modified file 'initramfs-tools-script' --- initramfs-tools-script 2018-08-19 01:35:11 +0000 +++ initramfs-tools-script 2008-08-14 02:24:59 +0000 @@ -6,176 +6,69 @@ # # This script should be installed as -# "/usr/share/initramfs-tools/scripts/init-premount/mandos" which will -# eventually be "/scripts/init-premount/mandos" in the initrd.img -# file. +# "/usr/share/initramfs-tools/scripts/local-top/mandos" which will +# eventually be "/scripts/local-top/mandos" in the initrd.img file. -PREREQ="udev" +# No initramfs pre-requirements; we must instead run BEFORE cryptroot. +# This is not a problem, since cryptroot forces itself to run LAST. +PREREQ="" prereqs() { - echo "$PREREQ" + echo "$PREREQ" } case $1 in prereqs) - prereqs - exit 0 - ;; -esac - -. /scripts/functions - -for param in `cat /proc/cmdline`; do - case "$param" in - ip=*) IPOPTS="${param#ip=}" ;; - mandos=*) - # Split option line on commas - old_ifs="$IFS" - IFS="$IFS," - for mpar in ${param#mandos=}; do - IFS="$old_ifs" - case "$mpar" in - off) exit 0 ;; - connect) connect="" ;; - connect:*) connect="${mpar#connect:}" ;; - *) log_warning_msg "$0: Bad option ${mpar}" ;; - esac - done - unset mpar - IFS="$old_ifs" - unset old_ifs - ;; - esac -done -unset param - -chmod a=rwxt /tmp - -# Get DEVICE from /conf/initramfs.conf and other files -. /conf/initramfs.conf -for conf in /conf/conf.d/*; do - [ -f "${conf}" ] && . "${conf}" -done -if [ -e /conf/param.conf ]; then - . /conf/param.conf -fi - -# Override DEVICE from sixth field of ip= kernel option, if passed -case "$IPOPTS" in - *:*:*:*:*:*) # At least six fields - # Remove the first five fields - device="${IPOPTS#*:*:*:*:*:}" - # Remove all fields except the first one - DEVICE="${device%%:*}" - ;; -esac - -# Add device setting (if any) to plugin-runner.conf -if [ "${DEVICE+set}" = set ]; then - # Did we get the device from an ip= option? - if [ "${device+set}" = set ]; then - # Let ip= option override local config; append: - cat <<-EOF >>/conf/conf.d/mandos/plugin-runner.conf - - --options-for=mandos-client:--interface=${DEVICE} -EOF - else - # Prepend device setting so any later options would override: - sed -i -e \ - '1i--options-for=mandos-client:--interface='"${DEVICE}" \ - /conf/conf.d/mandos/plugin-runner.conf - fi -fi -unset device - -# 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 - - --options-for=mandos-client:--connect=${connect} -EOF - 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 + prereqs + exit 0 + ;; +esac + +test -w /conf/conf.d/cryptroot + +# 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-02-09 23:23:26 +0000 +++ intro.xml 1970-01-01 00:00:00 +0000 @@ -1,439 +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 - 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 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.) - - - - - 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. - - - - - -
- - - - - === removed file 'legalnotice.xml' --- legalnotice.xml 2017-08-20 16:20:54 +0000 +++ legalnotice.xml 1970-01-01 00:00:00 +0000 @@ -1,28 +0,0 @@ - - - - - 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. - - - - 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-02-09 23:23:26 +0000 +++ mandos 2008-08-27 01:18:25 +0000 @@ -1,330 +1,103 @@ #!/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". -# +# methods "add" and "remove" in the "AvahiService" class, the +# "server_state_changed" and "entry_group_state_changed" functions, +# and some lines in "main". +# # Everything else is -# 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 +# Copyright © 2007-2008 Teddy Hogeborn & 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 + +import SocketServer import socket -import argparse +import select +from optparse import OptionParser 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 import sys import re import os import signal +from sets import Set import subprocess import atexit import stat import logging import logging.handlers -import pwd -import contextlib -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 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.7.20" -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. - It is instantiated once, and simulates having an Avahi module.""" - IF_UNSPEC = -1 # avahi-common/address.h - PROTO_UNSPEC = -1 # avahi-common/address.h - PROTO_INET = 0 # avahi-common/address.h - PROTO_INET6 = 1 # avahi-common/address.h - DBUS_NAME = "org.freedesktop.Avahi" - DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup" - DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server" - DBUS_PATH_SERVER = "/" - - def string_array_to_txt_array(self, t): - return dbus.Array((dbus.ByteArray(s.encode("utf-8")) - for s in t), signature="ay") - ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h - ENTRY_GROUP_COLLISION = 3 # avahi-common/defs.h - ENTRY_GROUP_FAILURE = 4 # avahi-common/defs.h - SERVER_INVALID = 0 # avahi-common/defs.h - SERVER_REGISTERING = 1 # avahi-common/defs.h - SERVER_RUNNING = 2 # avahi-common/defs.h - SERVER_COLLISION = 3 # avahi-common/defs.h - SERVER_FAILURE = 4 # avahi-common/defs.h -avahi = Avahi() - + +version = "1.0" + +logger = logging.Logger('mandos') +syslogger = logging.handlers.SysLogHandler\ + (facility = logging.handlers.SysLogHandler.LOG_DAEMON, + address = "/dev/log") +syslogger.setFormatter(logging.Formatter\ + ('Mandos: %(levelname)s: %(message)s')) +logger.addHandler(syslogger) + +console = logging.StreamHandler() +console.setFormatter(logging.Formatter('%(name)s: %(levelname)s:' + ' %(message)s')) +logger.addHandler(console) class AvahiError(Exception): - def __init__(self, value, *args, **kwargs): + def __init__(self, value): self.value = value - return super(AvahiError, self).__init__(value, *args, - **kwargs) - + def __str__(self): + return 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 + 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. @@ -332,2524 +105,463 @@ max_renames: integer; maximum number of renames rename_count: integer; counter so we only rename after collisions a sensible number of times - group: D-Bus Entry Group - 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, + type = None, port = None, TXT = None, domain = "", + host = "", max_renames = 32768): self.interface = interface self.name = name - self.type = servicetype + self.type = type self.port = port - self.TXT = TXT if TXT is not None else [] + if TXT is None: + self.TXT = [] + else: + self.TXT = TXT self.domain = domain self.host = host self.rename_count = 0 self.max_renames = max_renames - self.protocol = protocol - 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.", - self.rename_count) + logger.critical(u"No suitable Zeroconf service name found" + u" after %i retries, exiting.", + rename_count) raise AvahiServiceError("Too many renames") - self.name = str( - self.server.GetAlternativeServiceName(self.name)) + self.name = server.GetAlternativeServiceName(self.name) + logger.info(u"Changing Zeroconf service name to %r ...", + str(self.name)) + syslogger.setFormatter(logging.Formatter\ + ('Mandos (%s): %%(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() - + if group is not None: + 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.name, self.type) - self.group.AddService( - self.interface, - self.protocol, - dbus.UInt32(0), # flags - self.name, self.type, - self.domain, self.host, - 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) - - if state == avahi.ENTRY_GROUP_ESTABLISHED: - logger.debug("Zeroconf service established.") - elif state == avahi.ENTRY_GROUP_COLLISION: - logger.info("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)) - - 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 = None - self.remove() - - def server_state_changed(self, state, error=None): - """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() - 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) - - 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_INTERFACE_SERVER) - self.server.connect_to_signal("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. - It is instantiated once, and simulates having a GnuTLS module.""" - - library = ctypes.util.find_library("gnutls") - if library is None: - library = ctypes.util.find_library("gnutls-deb0") - _library = ctypes.cdll.LoadLibrary(library) - del library - _need_version = b"3.3.0" - _tls_rawpk_version = b"3.6.6" - - def __init__(self): - # Need to use "self" here, since this method is called before - # the assignment to the "gnutls" global variable happens. - if self.check_version(self._need_version) is None: - raise self.Error("Needs GnuTLS {} or later" - .format(self._need_version)) - - # Unless otherwise indicated, the constants and types below are - # all from the gnutls/gnutls.h C header file. - - # 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): - # We need to use the class name "GnuTLS" here, since this - # exception might be raised from within GnuTLS.__init__, - # which is called before the assignment to the "gnutls" - # global variable has happened. - def __init__(self, message=None, code=None, args=()): - # Default usage is by a message string, but if a return - # code is passed, convert it to a string with - # gnutls.strerror() - self.code = code - if message is None and code is not None: - message = GnuTLS.strerror(code) - return super(GnuTLS.Error, self).__init__( - message, *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 - - 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 -# Create the global "gnutls" object, simulating a module -gnutls = GnuTLS() - - -def call_pipe(connection, # : multiprocessing.Connection - func, *args, **kwargs): - """This function is meant to be called by multiprocessing.Process - - This function runs func(*args, **kwargs), and writes the resulting - return value on the provided multiprocessing.Connection. - """ - connection.send(func(*args, **kwargs)) - connection.close() + global group + if group is None: + group = dbus.Interface\ + (bus.get_object(avahi.DBUS_NAME, + server.EntryGroupNew()), + avahi.DBUS_INTERFACE_ENTRY_GROUP) + group.connect_to_signal('StateChanged', + entry_group_state_changed) + logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...", + service.name, service.type) + group.AddService( + self.interface, # interface + avahi.PROTO_INET6, # protocol + dbus.UInt32(0), # flags + self.name, self.type, + self.domain, self.host, + dbus.UInt16(self.port), + avahi.string_array_to_txt_array(self.TXT)) + group.Commit() + +# From the Avahi example code: +group = None # our entry group +# End of Avahi example code 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 - 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 + name: string; from the config file, used in log messages + 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(); object creation, not client host + last_checked_ok: datetime.datetime() or None if not yet checked OK + timeout: datetime.timedelta(); How long from last_checked_ok + until this client is invalid + interval: datetime.timedelta(); How often to start a new checker + stop_hook: If set, called by stop() as stop_hook(self) + checker: subprocess.Popen(); a running checker process used + to see if the client lives. + 'None' if no process is running. + checker_initiator_tag: a gobject event source tag, or None + stop_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() + Private attibutes: + _timeout: Real variable for 'timeout' + _interval: Real variable for 'interval' + _timeout_milliseconds: Used when calling gobject.timeout_add() + _interval_milliseconds: - '' - """ - - 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 _set_timeout(self, timeout): + "Setter function for 'timeout' attribute" + self._timeout = timeout + self._timeout_milliseconds = ((self.timeout.days + * 24 * 60 * 60 * 1000) + + (self.timeout.seconds * 1000) + + (self.timeout.microseconds + // 1000)) + timeout = property(lambda self: self._timeout, + _set_timeout) + del _set_timeout + def _set_interval(self, interval): + "Setter function for 'interval' attribute" + self._interval = interval + self._interval_milliseconds = ((self.interval.days + * 24 * 60 * 60 * 1000) + + (self.interval.seconds + * 1000) + + (self.interval.microseconds + // 1000)) + interval = property(lambda self: self._interval, + _set_interval) + del _set_interval + def __init__(self, name = None, stop_hook=None, config={}): + """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) + 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["fingerprint"].upper()\ + .replace(u" ", u"") + logger.debug(u" Fingerprint: %s", self.fingerprint) + if "secret" in config: + self.secret = config["secret"].decode(u"base64") + elif "secfile" in config: + sf = open(config["secfile"]) + self.secret = sf.read() + sf.close() 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("host", "") + self.created = datetime.datetime.now() + self.last_checked_ok = None + self.timeout = string_to_delta(config["timeout"]) + self.interval = string_to_delta(config["interval"]) + self.stop_hook = stop_hook self.checker = None self.checker_initiator_tag = None - self.disable_initiator_tag = None + self.stop_initiator_tag = None self.checker_callback_tag = None - 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() - - def enable(self): + self.check_command = config["checker"] + def start(self): """Start this client's checker and timeout hooks""" - if getattr(self, "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() - - 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) - 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) - self.checker_initiator_tag = None - self.stop_checker() - self.enabled = False - if not quiet: - self.send_changedstate() - # Do not run this again if called by a GLib.timeout_add - return False - - def __del__(self): - self.disable() - - def init_checker(self): # Schedule a new checker to be started an 'interval' from now, # and every interval from then on. - if self.checker_initiator_tag is not None: - GLib.source_remove(self.checker_initiator_tag) - self.checker_initiator_tag = GLib.timeout_add( - 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) + self.checker_initiator_tag = gobject.timeout_add\ + (self._interval_milliseconds, + self.start_checker) # Also start a new checker *right now*. self.start_checker() - - def checker_callback(self, source, condition, connection, - command): + # Schedule a stop() when 'timeout' has passed + self.stop_initiator_tag = gobject.timeout_add\ + (self._timeout_milliseconds, + self.stop) + def stop(self): + """Stop this client. + The possibility that a client might be restarted is left open, + but not currently used.""" + # If this client doesn't have a secret, it is already stopped. + if hasattr(self, "secret") and self.secret: + logger.info(u"Stopping client %s", self.name) + self.secret = None + else: + return False + if getattr(self, "stop_initiator_tag", False): + gobject.source_remove(self.stop_initiator_tag) + self.stop_initiator_tag = None + if getattr(self, "checker_initiator_tag", False): + gobject.source_remove(self.checker_initiator_tag) + self.checker_initiator_tag = None + self.stop_checker() + if self.stop_hook: + self.stop_hook(self) + # Do not run this again if called by a gobject.timeout_add + return False + def __del__(self): + self.stop_hook = None + self.stop() + def checker_callback(self, pid, condition): """The checker has completed, so take appropriate actions.""" + now = datetime.datetime.now() 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", - vars(self)) - self.checked_ok() - else: - logger.info("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?", + if os.WIFEXITED(condition) \ + and (os.WEXITSTATUS(condition) == 0): + logger.info(u"Checker for %(name)s succeeded", + vars(self)) + self.last_checked_ok = now + gobject.source_remove(self.stop_initiator_tag) + self.stop_initiator_tag = gobject.timeout_add\ + (self._timeout_milliseconds, + self.stop) + elif not os.WIFEXITED(condition): + 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.""" - 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() - + else: + logger.info(u"Checker for %(name)s failed", + vars(self)) 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 - # Start a new checker if needed + # than 'timeout' for the client to be declared invalid, which + # is as it should be. 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 - 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: + # In case check_command has exactly one % operator + command = self.check_command % self.host + except TypeError: + # Escape attributes for the shell + escaped_attrs = dict((key, re.escape(str(val))) + for key, val in + vars(self).iteritems()) + try: + command = self.check_command % escaped_attrs + except TypeError, error: + logger.error(u'Could not format string "%s":' + u' %s', self.check_command, error) + return True # Try again later + 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="/") + self.checker_callback_tag = gobject.child_watch_add\ + (self.checker.pid, + self.checker_callback) + 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: 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) + #os.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): - """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)) - - 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"): - 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 - - return decorator - - -class DBusPropertyException(dbus.exceptions.DBusException): - """A base class for D-Bus property-related exceptions - """ - pass - - -class DBusPropertyAccessException(DBusPropertyException): - """A property's access permissions disallows an operation. - """ - pass - - -class DBusPropertyNotFound(DBusPropertyException): - """An attempt was made to access a non-existing property. - """ - 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): - """A D-Bus object with properties. - - Classes inheriting from this can use the dbus_service_property - decorator to expose methods as D-Bus properties. It exposes the - standard Get(), Set(), and GetAll() methods on the D-Bus. - """ - - def _get_dbus_property(self, interface_name, property_name): - """Returns a bound method if one exists which is a D-Bus - property with the specified name and interface. - """ - 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) - - # 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") - 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": - raise DBusPropertyAccessException(property_name) - value = prop() - if not hasattr(value, "variant_level"): - return value - return type(value)(value, variant_level=value.variant_level+1) - - @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv") - def Set(self, interface_name, property_name, value): - """Standard D-Bus property Set() method, see D-Bus standard. - """ - prop = self._get_dbus_property(interface_name, property_name) - if prop._dbus_access == "read": - raise DBusPropertyAccessException(property_name) - if prop._dbus_get_args_options["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)) - prop(value) - - @dbus.service.method(dbus.PROPERTIES_IFACE, - in_signature="s", - out_signature="a{sv}") - def GetAll(self, interface_name): - """Standard D-Bus property GetAll() method, see D-Bus - standard. - - Note: Will not include properties with access="write". - """ - properties = {} - for name, prop in self._get_all_dbus_things("property"): - 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": - continue - value = prop() - if not hasattr(value, "variant_level"): - properties[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 - - @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 property tags and interface annotation tags. - """ - xmlstring = DBusObjectWithAnnotations.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) - return e - - for if_tag in document.getElementsByTagName("interface"): - # Add property tags - for tag in (make_tag(document, name, prop) - for name, prop - in self._get_all_dbus_things("property") - if prop._dbus_interface - == if_tag.getAttribute("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"}) -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): - 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) - 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 - - def __del__(self, *args, **kwargs): - try: - self.remove_from_connection() - except LookupError: - pass - if hasattr(DBusObjectWithProperties, "__del__"): - DBusObjectWithProperties.__del__(self, *args, **kwargs) - Client.__del__(self, *args, **kwargs) - - def checker_callback(self, source, condition, - connection, command, *args, **kwargs): - ret = Client.checker_callback(self, source, condition, - connection, command, *args, - **kwargs) - exitstatus = self.last_checker_status - if exitstatus >= 0: - # Emit D-Bus signal - self.CheckerCompleted(dbus.Int16(exitstatus), - # This is specific to GNU libC - dbus.Int64(exitstatus << 8), - 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.String(command)) - return ret - - def start_checker(self, *args, **kwargs): - old_checker_pid = getattr(self.checker, "pid", None) - r = Client.start_checker(self, *args, **kwargs) - # 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 - - # CheckerCompleted - signal - @dbus.service.signal(_interface, signature="nxs") - def CheckerCompleted(self, exitcode, waitstatus, command): - "D-Bus signal" - pass - - # CheckerStarted - signal - @dbus.service.signal(_interface, signature="s") - def CheckerStarted(self, command): - "D-Bus signal" - pass - - # PropertyChanged - signal - @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"}) - @dbus.service.signal(_interface, signature="sv") - def PropertyChanged(self, property, value): - "D-Bus signal" - pass - - # GotSecret - signal - @dbus.service.signal(_interface) - def GotSecret(self): - """D-Bus signal - Is sent after a successful transfer of secret from the Mandos - server to mandos-client - """ - pass - - # Rejected - signal - @dbus.service.signal(_interface, signature="s") - def Rejected(self, reason): - "D-Bus signal" - pass - - # NeedApproval - signal - @dbus.service.signal(_interface, signature="tb") - def NeedApproval(self, timeout, default): - "D-Bus signal" - return self.need_approval() - - # Methods - - # Approve - method - @dbus.service.method(_interface, in_signature="b") - def Approve(self, value): - self.approve(value) - - # CheckedOK - method - @dbus.service.method(_interface) - def CheckedOK(self): - self.checked_ok() - - # Enable - method - @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"}) - @dbus.service.method(_interface) - def Enable(self): - "D-Bus method" - self.enable() - - # StartChecker - method - @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"}) - @dbus.service.method(_interface) - def StartChecker(self): - "D-Bus method" - self.start_checker() - - # Disable - method - @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"}) - @dbus.service.method(_interface) - def Disable(self): - "D-Bus method" - self.disable() - - # StopChecker - method - @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"}) - @dbus.service.method(_interface) - def StopChecker(self): - self.stop_checker() - - # Properties - - # 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): - return dbus.String(self.name) - - # KeyID - property - @dbus_annotations( - {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"}) - @dbus_service_property(_interface, signature="s", access="read") - def KeyID_dbus_property(self): - return dbus.String(self.key_id) - - # Fingerprint - property - @dbus_annotations( - {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"}) - @dbus_service_property(_interface, signature="s", access="read") - def Fingerprint_dbus_property(self): - return dbus.String(self.fingerprint) - - # Host - property - @dbus_service_property(_interface, - signature="s", - access="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): - 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): - 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 value is None: # get - return dbus.UInt64(self.timeout.total_seconds() * 1000) - old_timeout = self.timeout - 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) - self.interval = datetime.timedelta(0, 0, 0, value) - if getattr(self, "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 - - # Checker - property - @dbus_service_property(_interface, - signature="s", - access="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): - 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) - - 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. + def still_valid(self): + """Has the timeout not yet passed for this client?""" + now = datetime.datetime.now() + if self.last_checked_ok is None: + return now < (self.created + self.timeout) + else: + return now < (self.last_checked_ok + self.timeout) + + +def peer_certificate(session): + "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() + cert_list = gnutls.library.functions.gnutls_certificate_get_peers\ + (session._c_object, ctypes.byref(list_size)) + if list_size.value == 0: + return None + cert = cert_list[0] + return ctypes.string_at(cert.data, cert.size) + + +def fingerprint(openpgp): + "Convert an OpenPGP bytestring to a hexdigit fingerprint string" + # New GnuTLS "datum" with the OpenPGP public key + 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.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.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.library.functions.gnutls_openpgp_crt_verify_self\ + (crt, 0, ctypes.byref(crtverify)) + if crtverify.value != 0: + gnutls.library.functions.gnutls_openpgp_crt_deinit(crt) + raise gnutls.errors.CertificateSecurityError("Verify failed") + # New buffer for the fingerprint + buffer = ctypes.create_string_buffer(20) + buffer_length = ctypes.c_size_t() + # Get the fingerprint from the certificate into the buffer + gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\ + (crt, ctypes.byref(buffer), ctypes.byref(buffer_length)) + # Deinit the certificate + gnutls.library.functions.gnutls_openpgp_crt_deinit(crt) + # Convert the buffer to a Python bytestring + fpr = ctypes.string_at(buffer, buffer_length.value) + # Convert the bytestring to hexadecimal notation + hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr) + return hex_fpr + + +class tcp_handler(SocketServer.BaseRequestHandler, object): + """A TCP request handler class. + Instantiated by IPv6_TCPServer for each request 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")) - # 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) - - # Start communication using the Mandos protocol - # Get protocol number - line = self.request.makefile().readline() - logger.debug("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) - return - - # Start GnuTLS connection - try: - session.handshake() - except gnutls.Error as error: - logger.warning("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 - 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: - 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 - 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() - - finally: - if approval_required: - client.approvals_pending -= 1 - try: - session.bye() - except gnutls.Error as error: - logger.warning("GnuTLS bye failed", - exc_info=error) - - @staticmethod - def peer_certificate(session): - "Return the peer's certificate as a bytestring" - try: - cert_type = gnutls.certificate_type_get2(session._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"" - list_size = ctypes.c_uint(1) - cert_list = (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") - 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))) - # New empty GnuTLS certificate - crt = gnutls.openpgp_crt_t() - 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) - # Verify the self signature in the key - crtverify = ctypes.c_uint() - 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) - # 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)) - # Deinit the certificate - 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() - 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 """ - - def process_request(self, request, client_address): - """Overrides and wraps the original process_request(). - - This function creates a new pipe in self.pipe - """ - parent_pipe, self.child_pipe = multiprocessing.Pipe() - - proc = MultiprocessingMixIn.process_request(self, request, - client_address) - self.child_pipe.close() - self.add_pipe(parent_pipe, proc) - - def add_pipe(self, parent_pipe, proc): - """Dummy function; override as necessary""" - raise NotImplementedError() - - -class IPv6_TCPServer(MultiprocessingMixInWithPipe, - socketserver.TCPServer, object): - """IPv6-capable TCP server. Accepts 'None' as address and/or port - + logger.info(u"TCP connection from: %s", + unicode(self.client_address)) + session = gnutls.connection.ClientSession\ + (self.request, gnutls.connection.X509Credentials()) + + line = self.request.makefile().readline() + logger.debug(u"Protocol version: %r", line) + try: + if int(line.strip().split()[0]) > 1: + raise RuntimeError + except (ValueError, IndexError, RuntimeError), error: + logger.error(u"Unknown protocol version: %s", error) + return + + # 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 = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC", + # "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP", + # "+DHE-DSS")) + priority = "NORMAL" # Fallback default, since this + # MUST be set. + if self.server.settings["priority"]: + priority = self.server.settings["priority"] + gnutls.library.functions.gnutls_priority_set_direct\ + (session._c_object, priority, None); + + try: + session.handshake() + 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 + try: + fpr = fingerprint(peer_certificate(session)) + except (TypeError, gnutls.errors.GNUTLSError), error: + logger.warning(u"Bad certificate: %s", error) + session.bye() + return + logger.debug(u"Fingerprint: %s", fpr) + client = None + for c in self.server.clients: + if c.fingerprint == fpr: + client = c + break + if not client: + logger.warning(u"Client not found for fingerprint: %s", + fpr) + session.bye() + return + # Have to check if client.still_valid(), since it is possible + # that the client timed out while establishing the GnuTLS + # session. + if not client.still_valid(): + logger.warning(u"Client %(name)s is invalid", + vars(client)) + session.bye() + return + 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 + session.bye() + + +class IPv6_TCPServer(SocketServer.ForkingTCPServer, object): + """IPv6 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 + settings: Server settings + clients: Set() of Client objects """ - - 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(). - """ - 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) - + address_family = socket.AF_INET6 + def __init__(self, *args, **kwargs): + if "settings" in kwargs: + self.settings = kwargs["settings"] + del kwargs["settings"] + if "clients" in kwargs: + self.clients = kwargs["clients"] + del kwargs["clients"] + return super(type(self), self).__init__(*args, **kwargs) 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 + if self.settings["interface"]: + # 25 is from /usr/include/asm-i486/socket.h + SO_BINDTODEVICE = getattr(socket, "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) + self.socket.setsockopt(socket.SOL_SOCKET, + SO_BINDTODEVICE, + self.settings["interface"]) + except socket.error, error: + if error[0] == errno.EPERM: + logger.error(u"No permission to" + u" bind to interface %s", + self.settings["interface"]) else: - raise + raise error # Only bind(2) the socket if we really need to. if self.server_address[0] or self.server_address[1]: if not self.server_address[0]: - if self.address_family == socket.AF_INET6: - any_address = "::" # in6addr_any - else: - any_address = "0.0.0.0" # INADDR_ANY - self.server_address = (any_address, + in6addr_any = "::" + self.server_address = (in6addr_any, self.server_address[1]) elif not self.server_address[1]: - self.server_address = (self.server_address[0], 0) -# if self.interface: + self.server_address = (self.server_address[0], + 0) +# if self.settings["interface"]: # self.server_address = (self.server_address[0], # 0, # port # 0, # flowinfo # if_nametoindex -# (self.interface)) - return socketserver.TCPServer.server_bind(self) - - -class MandosServer(IPv6_TCPServer): - """Mandos server. - - Attributes: - clients: set of Client objects - gnutls_priority GnuTLS priority string - use_dbus: Boolean; to emit D-Bus signals or not - - Assumes a GLib.MainLoop event loop. - """ - - def __init__(self, server_address, RequestHandlerClass, - interface=None, - use_ipv6=True, - clients=None, - gnutls_priority=None, - use_dbus=True, - socketfd=None): - self.enabled = False - self.clients = clients - if self.clients is None: - self.clients = {} - 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) - - def server_activate(self): - if self.enabled: - return socketserver.TCPServer.server_activate(self) - - def enable(self): - self.enabled = True - - def add_pipe(self, parent_pipe, proc): - # Call "handle_ipc" for both data and EOF events - GLib.io_add_watch( - 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 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) - - 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 +# (self.settings +# ["interface"])) + return super(type(self), self).server_bind() def string_to_delta(interval): @@ -2863,43 +575,84 @@ datetime.timedelta(0, 3600) >>> string_to_delta('24h') datetime.timedelta(1) - >>> string_to_delta('1w') + >>> string_to_delta(u'1w') datetime.timedelta(7) >>> string_to_delta('5m 30s') datetime.timedelta(0, 330) """ - - try: - return rfc3339_duration_to_delta(interval) - except ValueError: - pass - timevalue = datetime.timedelta(0) for s in interval.split(): try: - suffix = s[-1] - value = int(s[:-1]) - if suffix == "d": + suffix=unicode(s[-1]) + value=int(s[:-1]) + 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 + except (ValueError, IndexError): + raise ValueError timevalue += delta return timevalue -def daemon(nochdir=False, noclose=False): +def server_state_changed(state): + """Derived from the Avahi example code""" + if state == avahi.SERVER_COLLISION: + logger.error(u"Zeroconf server name collision") + service.remove() + elif state == avahi.SERVER_RUNNING: + service.add() + + +def entry_group_state_changed(state, error): + """Derived from the Avahi example code""" + logger.debug(u"Avahi state change: %i", state) + + if state == avahi.ENTRY_GROUP_ESTABLISHED: + logger.debug(u"Zeroconf service established.") + elif state == avahi.ENTRY_GROUP_COLLISION: + logger.warning(u"Zeroconf service name collision.") + service.rename() + elif state == avahi.ENTRY_GROUP_FAILURE: + logger.critical(u"Avahi: Error in group state changed %s", + unicode(error)) + raise AvahiGroupError("State changed: %s", str(error)) + +def if_nametoindex(interface): + """Call the C function if_nametoindex(), or equivalent""" + global if_nametoindex + try: + if "ctypes.util" not in sys.modules: + import ctypes.util + if_nametoindex = ctypes.cdll.LoadLibrary\ + (ctypes.util.find_library("c")).if_nametoindex + except (OSError, AttributeError): + if "struct" not in sys.modules: + import struct + if "fcntl" not in sys.modules: + import fcntl + def if_nametoindex(interface): + "Get an interface index the hard way, i.e. using fcntl()" + SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h + s = socket.socket() + ifreq = fcntl.ioctl(s, SIOCGIFINDEX, + struct.pack("16s16x", interface)) + s.close() + interface_index = struct.unpack("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() @@ -2910,11 +663,10 @@ 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)) + "/dev/null not a character device") os.dup2(null, sys.stdin.fileno()) os.dup2(null, sys.stdout.fileno()) os.dup2(null, sys.stderr.fileno()) @@ -2923,701 +675,207 @@ 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() - + global main_loop_started + main_loop_started = False + + parser = OptionParser(version = "%%prog %s" % version) + parser.add_option("-i", "--interface", type="string", + metavar="IF", help="Bind to interface IF") + parser.add_option("-a", "--address", type="string", + help="Address to listen for requests on") + parser.add_option("-p", "--port", type="int", + help="Port number to receive requests on") + parser.add_option("--check", action="store_true", default=False, + help="Run self-test") + parser.add_option("--debug", action="store_true", + help="Debug mode; run in foreground and log to" + " terminal") + parser.add_option("--priority", type="string", help="GnuTLS" + " priority string (see GnuTLS documentation)") + parser.add_option("--servicename", type="string", metavar="NAME", + help="Zeroconf service name") + parser.add_option("--configdir", type="string", + default="/etc/mandos", metavar="DIR", + help="Directory to search for configuration" + " files") + (options, args) = parser.parse_args() + if options.check: import doctest - fail_count, test_count = doctest.testmod() - sys.exit(os.EX_OK if fail_count == 0 else 1) - + 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 = { "interface": "", + "address": "", + "port": "", + "debug": "False", + "priority": + "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP", + "servicename": "Mandos", + } + # Parse config file for server-global settings - server_config = configparser.SafeConfigParser(server_defaults) + server_config = ConfigParser.SafeConfigParser(server_defaults) del server_defaults server_config.read(os.path.join(options.configdir, "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", - 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"]) + # Use getboolean on the boolean config option + server_settings["debug"] = server_config.getboolean\ + ("DEFAULT", "debug") 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"): + "priority", "servicename", "configdir"): 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 # 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 not debug: + syslogger.setLevel(logging.WARNING) + console.setLevel(logging.WARNING) + if server_settings["servicename"] != "Mandos": - syslogger.setFormatter( - logging.Formatter('Mandos ({}) [%(process)d]:' - ' %(levelname)s: %(message)s'.format( - server_settings["servicename"]))) - + syslogger.setFormatter(logging.Formatter\ + ('Mandos (%s): %%(levelname)s:' + ' %%(message)s' + % server_settings["servicename"])) + # Parse config file with clients - client_config = configparser.SafeConfigParser(Client - .client_defaults) + client_defaults = { "timeout": "1h", + "interval": "5m", + "checker": "fping -q -- %(host)s", + "host": "", + } + client_config = ConfigParser.SafeConfigParser(client_defaults) client_config.read(os.path.join(server_settings["configdir"], "clients.conf")) - - global mandos_dbus_service - mandos_dbus_service = None - - socketfd = None - if server_settings["socket"] != "": - socketfd = server_settings["socket"] - 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 - except KeyError: - continue - else: - 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 - - if debug: - # Enable all possible GnuTLS debugging - - # "Use a log level over 10 to enable all debugging options." - # - GnuTLS manual - gnutls.global_set_log_level(11) - - @gnutls.log_func - def debug_gnutls(level, string): - 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() - + + global service + service = AvahiService(name = server_settings["servicename"], + type = "_mandos._tcp", ); + if server_settings["interface"]: + service.interface = if_nametoindex\ + (server_settings["interface"]) + global main_loop + global bus + global server # 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() + server = dbus.Interface(bus.get_object(avahi.DBUS_NAME, + avahi.DBUS_PATH_SERVER), + avahi.DBUS_INTERFACE_SERVER) # 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) - use_dbus = False - server_settings["use_dbus"] = False - tcp_server.use_dbus = False - if zeroconf: - protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET - service = AvahiServiceToSyslog( - name=server_settings["servicename"], - servicetype="_mandos._tcp", - protocol=protocol, - bus=bus) - if server_settings["interface"]: - service.interface = if_nametoindex( - server_settings["interface"].encode("utf-8")) - - global multiprocessing_manager - multiprocessing_manager = multiprocessing.Manager() - - client_class = Client - if use_dbus: - client_class = functools.partial(ClientDBus, bus=bus) - - client_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) - - if not tcp_server.clients: - logger.warning("No clients defined") - - if not foreground: - if pidfile is not None: - 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) + + clients = Set() + def remove_from_clients(client): + clients.remove(client) + if not clients: + logger.critical(u"No clients left, exiting") + sys.exit() + + clients.update(Set(Client(name = section, + stop_hook = remove_from_clients, + config + = dict(client_config.items(section))) + for section in client_config.sections())) + if not clients: + logger.critical(u"No clients defined") + sys.exit(1) + + 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() + + pidfilename = "/var/run/mandos/mandos.pid" + pid = os.getpid() + try: + pidfile = open(pidfilename, "w") + pidfile.write(str(pid) + "\n") + pidfile.close() del pidfile - del pidfilename - - for termsig in (signal.SIGHUP, signal.SIGTERM): - GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig, - lambda: main_loop.quit() and False) - - if use_dbus: - - @alternate_dbus_interfaces( - {"se.recompile.Mandos": "se.bsnet.fukt.Mandos"}) - class MandosDBusService(DBusObjectWithObjectManager): - """A D-Bus proxy object""" - - def __init__(self): - dbus.service.Object.__init__(self, bus, "/") - - _interface = "se.recompile.Mandos" - - @dbus.service.signal(_interface, signature="o") - def ClientAdded(self, objpath): - "D-Bus signal" - pass - - @dbus.service.signal(_interface, signature="ss") - def ClientNotFound(self, key_id, address): - "D-Bus signal" - pass - - @dbus_annotations({"org.freedesktop.DBus.Deprecated": - "true"}) - @dbus.service.signal(_interface, signature="os") - def ClientRemoved(self, objpath, name): - "D-Bus signal" - pass - - @dbus_annotations({"org.freedesktop.DBus.Deprecated": - "true"}) - @dbus.service.method(_interface, out_signature="ao") - 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"}) - @dbus.service.method(_interface, - out_signature="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") - def RemoveClient(self, object_path): - "D-Bus method" - for c in tcp_server.clients.values(): - if c.dbus_object_path == object_path: - del tcp_server.clients[c.name] - c.remove_from_connection() - # Don't signal the disabling - c.disable(quiet=True) - # Emit D-Bus signal for removal - self.client_removed_signal(c) - return - raise KeyError(object_path) - - del _interface - - @dbus.service.method(dbus.OBJECT_MANAGER_IFACE, - out_signature="a{oa{sa{sv}}}") - 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 - + except IOError, err: + logger.error(u"Could not write %s file with PID %d", + pidfilename, os.getpid()) + 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 - while tcp_server.clients: - name, client = tcp_server.clients.popitem() - if use_dbus: - client.remove_from_connection() - # Don't signal the disabling - client.disable(quiet=True) - # Emit D-Bus signal for removal - if use_dbus: - mandos_dbus_service.client_removed_signal(client) - client_settings.clear() - + global group + # From the Avahi example code + if not group is None: + group.Free() + group = None + # End of Avahi example code + + while clients: + client = clients.pop() + client.stop_hook = None + client.stop() + atexit.register(cleanup) - - for client in tcp_server.clients.values(): - 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() - - tcp_server.enable() - tcp_server.server_activate() - + + 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()) + + for client in clients: + client.start() + + tcp_server = IPv6_TCPServer((server_settings["address"], + server_settings["port"]), + tcp_handler, + settings=server_settings, + clients=clients) # Find out what port we got - if zeroconf: - 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()) - else: # IPv4 - logger.info("Now listening on address %r, port %d", - *tcp_server.socket.getsockname()) - - # service.interface = tcp_server.socket.getsockname()[3] - + service.port = tcp_server.socket.getsockname()[1] + logger.info(u"Now listening on address %r, port %d, flowinfo %d," + u" scope_id %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 + server.connect_to_signal("StateChanged", server_state_changed) + try: + server_state_changed(server.GetState()) + except dbus.exceptions.DBusException, error: + logger.critical(u"DBusException: %s", error) + 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_started = True main_loop.run() - except AvahiError as error: - logger.critical("Avahi Error", exc_info=error) - cleanup() + except AvahiError, error: + logger.critical(u"AvahiError: %s" + unicode(error)) sys.exit(1) except KeyboardInterrupt: if debug: - print("", file=sys.stderr) - logger.debug("Server received KeyboardInterrupt") - logger.debug("Server exiting") - # Must run before the D-Bus bus name gets deregistered - cleanup() - + print if __name__ == '__main__': main() === modified file 'mandos-clients.conf.xml' --- mandos-clients.conf.xml 2019-02-09 23:23:26 +0000 +++ mandos-clients.conf.xml 2008-08-25 07:52:35 +0000 @@ -1,54 +1,63 @@ - + /etc/mandos/clients.conf"> - - -%common; ]> - + - Mandos Manual + &CONFNAME; - Mandos - &version; - &TIMESTAMP; + &CONFNAME; + &VERSION; 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 Teddy Hogeborn Björn Påhlsson - + + + 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 this program; If not, see + . + +
- + &CONFNAME; 5 @@ -60,11 +69,13 @@ Configuration file for the Mandos server - + - &CONFPATH; + + &CONFPATH; + - + DESCRIPTION @@ -72,13 +83,9 @@ >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 valid, even + if a client was declared invalid in a previous run of the + server. The format starts with a [section @@ -108,88 +115,79 @@ start time expansion, see . - Unknown options are ignored. The used options are as follows: + Uknown options are ignored. The used options are as follows: - + - - - - - - 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. - - - 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 + + + timeout + + timeout = TIME + + + The timeout is how long the server will wait for a + successful checker run until a client is considered + invalid - that is, ineligible 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. + + + + + + interval + + interval = TIME + + + 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 marked invalid, and any running + checker killed. The default interval is 5 minutes. + + + The format of TIME is the same + as for timeout above. + + + + + + checker + + checker = COMMAND + + + 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 find one, outputs - a option to check for the - client’s key fingerprint – this is more secure against - spoofing. + >-- %(host)s. In addition to normal start time expansion, this option @@ -200,139 +198,32 @@ - - - - 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. - - - - - - - - - This option is required. - + fingerprint + + fingerprint = HEXSTRING + 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. - - - - - - - - - If this option is not specified, the option is required - to be present. - + through TLS. The string needs to be in hexidecimal form, + but spaces or upper/lower case are not significant. + + + + + + secret + + secret = BASE64_ENCODED_DATA + 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 @@ -345,50 +236,49 @@ lines is that a line beginning with white space adds to the value of the previous line, RFC 822-style. - - - - - - - - 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. - - - - - - - - - Whether this client should be enabled by default. The - default is true. + + If this option is not specified, the option is used instead, but one of them + must be present. + + + + + + secfile + + secfile = FILENAME + + + The same as , but 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. + + + This option is only used, and must be + present, if is not specified. + + + + + + host + + host = STRING + + + 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. - + EXPANSION @@ -425,30 +315,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 @@ -457,11 +327,11 @@ percent characters in a row (%%%%) must be entered. Also, a bad format here will lead to an immediate but silent run-time fatal exit; debug - mode is needed to expose an error of this kind. + mode is needed to track down an error of this kind. - - + + FILES @@ -482,7 +352,6 @@ %(foo)s is obscure. - @@ -490,13 +359,12 @@ [DEFAULT] -timeout = PT5M -interval = PT2M -checker = fping -q -- %%(host)s +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 @@ -515,52 +383,28 @@ 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 + - + SEE ALSO - intro - 8mandos, - mandos-keygen - 8, - mandos.conf - 5, - mandos - 8, - fping - 8 + + mandos + 8, + mandos-keygen + 8, + mandos.conf + 5 - - - - 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. - - - -
- - - - - === removed file 'mandos-ctl' --- mandos-ctl 2019-02-09 23:23:26 +0000 +++ mandos-ctl 1970-01-01 00:00:00 +0000 @@ -1,498 +0,0 @@ -#!/usr/bin/python -# -*- mode: python; coding: utf-8 -*- -# -# Mandos Monitor - Control and monitor the Mandos server -# -# 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 -# WITHOUT 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 - -import sys -import argparse -import locale -import datetime -import re -import os -import collections -import json - -import dbus - -if sys.version_info.major == 2: - str = unicode - -locale.setlocale(locale.LC_ALL, "") - -tablewords = { - "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", -} -defaultkeywords = ("Name", "Enabled", "Timeout", "LastCheckedOK") -domain = "se.recompile" -busname = domain + ".Mandos" -server_path = "/" -server_interface = domain + ".Mandos" -client_interface = domain + ".Mandos.Client" -version = "1.7.20" - - -try: - dbus.OBJECT_MANAGER_IFACE -except AttributeError: - dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager" - - -def milliseconds_to_string(ms): - td = datetime.timedelta(0, 0, 0, ms) - return ("{days}{hours:02}:{minutes:02}:{seconds:02}" - .format(days="{}T".format(td.days) if td.days else "", - hours=td.seconds // 3600, - minutes=(td.seconds % 3600) // 60, - seconds=td.seconds % 60)) - - -def rfc3339_duration_to_delta(duration): - """Parse an RFC 3339 "duration" and return a datetime.timedelta - - >>> rfc3339_duration_to_delta("P7D") - datetime.timedelta(7) - >>> 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') - datetime.timedelta(7) - >>> string_to_delta('60s') - datetime.timedelta(0, 60) - >>> string_to_delta('60m') - datetime.timedelta(0, 3600) - >>> string_to_delta('24h') - datetime.timedelta(1) - >>> string_to_delta('1w') - datetime.timedelta(7) - >>> string_to_delta('5m 30s') - datetime.timedelta(0, 330) - """ - - try: - return rfc3339_duration_to_delta(interval) - except ValueError: - pass - - value = datetime.timedelta(0) - regexp = re.compile(r"(\d+)([dsmhw]?)") - - for num, suffix in regexp.findall(interval): - if suffix == "d": - value += datetime.timedelta(int(num)) - 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 print_clients(clients, keywords): - def valuetostring(value, keyword): - if type(value) is dbus.Boolean: - return "Yes" if value else "No" - if keyword in ("Timeout", "Interval", "ApprovalDelay", - "ApprovalDuration", "ExtendedTimeout"): - return milliseconds_to_string(value) - return str(value) - - # Create format string to print table rows - format_string = " ".join("{{{key}:{width}}}".format( - width=max(len(tablewords[key]), - max(len(valuetostring(client[key], key)) - for client in clients)), - key=key) - for key in keywords) - # Print header line - print(format_string.format(**tablewords)) - for client in clients: - print(format_string - .format(**{key: valuetostring(client[key], key) - for key in keywords})) - - -def has_actions(options): - return any((options.enable, - options.disable, - options.bump_timeout, - options.start_checker, - options.stop_checker, - options.is_enabled, - options.remove, - options.checker is not None, - options.timeout is not None, - options.extended_timeout is not None, - options.interval is not None, - options.approved_by_default is not None, - options.approval_delay is not None, - options.approval_duration is not None, - options.host is not None, - options.secret is not None, - options.approve, - options.deny)) - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("--version", action="version", - version="%(prog)s {}".format(version), - help="show version number and exit") - parser.add_argument("-a", "--all", action="store_true", - help="Select all clients") - parser.add_argument("-v", "--verbose", action="store_true", - help="Print all fields") - parser.add_argument("-j", "--dump-json", action="store_true", - help="Dump client data in JSON format") - parser.add_argument("-e", "--enable", action="store_true", - help="Enable client") - parser.add_argument("-d", "--disable", action="store_true", - help="disable client") - parser.add_argument("-b", "--bump-timeout", action="store_true", - help="Bump timeout for client") - parser.add_argument("--start-checker", action="store_true", - help="Start checker for client") - parser.add_argument("--stop-checker", action="store_true", - help="Stop checker for client") - parser.add_argument("-V", "--is-enabled", action="store_true", - help="Check if client is enabled") - parser.add_argument("-r", "--remove", action="store_true", - help="Remove client") - parser.add_argument("-c", "--checker", - help="Set checker command for client") - parser.add_argument("-t", "--timeout", - help="Set timeout for client") - parser.add_argument("--extended-timeout", - help="Set extended timeout for client") - parser.add_argument("-i", "--interval", - help="Set checker interval for client") - parser.add_argument("--approve-by-default", action="store_true", - default=None, dest="approved_by_default", - help="Set client to be approved by default") - parser.add_argument("--deny-by-default", action="store_false", - dest="approved_by_default", - help="Set client to be denied by default") - parser.add_argument("--approval-delay", - help="Set delay before client approve/deny") - parser.add_argument("--approval-duration", - help="Set duration of one client approval") - parser.add_argument("-H", "--host", help="Set host for client") - parser.add_argument("-s", "--secret", - type=argparse.FileType(mode="rb"), - help="Set password blob (file) for client") - parser.add_argument("-A", "--approve", action="store_true", - help="Approve any current client request") - parser.add_argument("-D", "--deny", action="store_true", - help="Deny any current client request") - parser.add_argument("--check", action="store_true", - help="Run self-test") - parser.add_argument("client", nargs="*", help="Client name") - options = parser.parse_args() - - if has_actions(options) and not (options.client or options.all): - parser.error("Options require clients names or --all.") - if options.verbose and has_actions(options): - parser.error("--verbose can only be used alone.") - if options.dump_json and (options.verbose - or has_actions(options)): - parser.error("--dump-json can only be used alone.") - if options.all and not has_actions(options): - parser.error("--all requires an action.") - - if options.check: - import doctest - fail_count, test_count = doctest.testmod() - sys.exit(os.EX_OK if fail_count == 0 else 1) - - try: - bus = dbus.SystemBus() - mandos_dbus_objc = bus.get_object(busname, server_path) - except dbus.exceptions.DBusException: - print("Could not connect to Mandos server", file=sys.stderr) - sys.exit(1) - - mandos_serv = dbus.Interface(mandos_dbus_objc, - dbus_interface=server_interface) - mandos_serv_object_manager = dbus.Interface( - mandos_dbus_objc, dbus_interface=dbus.OBJECT_MANAGER_IFACE) - - # block stderr since dbus library prints to stderr - null = os.open(os.path.devnull, os.O_RDWR) - stderrcopy = os.dup(sys.stderr.fileno()) - os.dup2(null, sys.stderr.fileno()) - os.close(null) - try: - try: - mandos_clients = {path: ifs_and_props[client_interface] - for path, ifs_and_props in - mandos_serv_object_manager - .GetManagedObjects().items() - if client_interface in ifs_and_props} - finally: - # restore stderr - os.dup2(stderrcopy, sys.stderr.fileno()) - os.close(stderrcopy) - except dbus.exceptions.DBusException as e: - print("Access denied: " - "Accessing mandos server through D-Bus: {}".format(e), - file=sys.stderr) - sys.exit(1) - - # Compile dict of (clients: properties) to process - clients = {} - - if options.all or not options.client: - clients = {bus.get_object(busname, path): properties - for path, properties in mandos_clients.items()} - else: - for name in options.client: - for path, client in mandos_clients.items(): - if client["Name"] == name: - client_objc = bus.get_object(busname, path) - clients[client_objc] = client - break - else: - print("Client not found on server: {!r}" - .format(name), file=sys.stderr) - sys.exit(1) - - if not has_actions(options) and clients: - if options.verbose or options.dump_json: - keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK", - "Created", "Interval", "Host", "KeyID", - "Fingerprint", "CheckerRunning", - "LastEnabled", "ApprovalPending", - "ApprovedByDefault", "LastApprovalRequest", - "ApprovalDelay", "ApprovalDuration", - "Checker", "ExtendedTimeout", "Expires", - "LastCheckerStatus") - else: - keywords = defaultkeywords - - if options.dump_json: - json.dump({client["Name"]: {key: - bool(client[key]) - if isinstance(client[key], - dbus.Boolean) - else client[key] - for key in keywords} - for client in clients.values()}, - fp=sys.stdout, indent=4, - separators=(',', ': ')) - print() - else: - print_clients(clients.values(), keywords) - else: - # Process each client in the list by all selected options - for client in clients: - - def set_client_prop(prop, value): - """Set a Client D-Bus property""" - client.Set(client_interface, prop, value, - dbus_interface=dbus.PROPERTIES_IFACE) - - def set_client_prop_ms(prop, value): - """Set a Client D-Bus property, converted - from a string to milliseconds.""" - set_client_prop(prop, - string_to_delta(value).total_seconds() - * 1000) - - if options.remove: - mandos_serv.RemoveClient(client.__dbus_object_path__) - if options.enable: - set_client_prop("Enabled", dbus.Boolean(True)) - if options.disable: - set_client_prop("Enabled", dbus.Boolean(False)) - if options.bump_timeout: - set_client_prop("LastCheckedOK", "") - if options.start_checker: - set_client_prop("CheckerRunning", dbus.Boolean(True)) - if options.stop_checker: - set_client_prop("CheckerRunning", dbus.Boolean(False)) - if options.is_enabled: - if client.Get(client_interface, "Enabled", - dbus_interface=dbus.PROPERTIES_IFACE): - sys.exit(0) - else: - sys.exit(1) - if options.checker is not None: - set_client_prop("Checker", options.checker) - if options.host is not None: - set_client_prop("Host", options.host) - if options.interval is not None: - set_client_prop_ms("Interval", options.interval) - if options.approval_delay is not None: - set_client_prop_ms("ApprovalDelay", - options.approval_delay) - if options.approval_duration is not None: - set_client_prop_ms("ApprovalDuration", - options.approval_duration) - if options.timeout is not None: - set_client_prop_ms("Timeout", options.timeout) - if options.extended_timeout is not None: - set_client_prop_ms("ExtendedTimeout", - options.extended_timeout) - if options.secret is not None: - set_client_prop("Secret", - dbus.ByteArray(options.secret.read())) - if options.approved_by_default is not None: - set_client_prop("ApprovedByDefault", - dbus.Boolean(options - .approved_by_default)) - if options.approve: - client.Approve(dbus.Boolean(True), - dbus_interface=client_interface) - elif options.deny: - client.Approve(dbus.Boolean(False), - dbus_interface=client_interface) - - -if __name__ == "__main__": - main() === removed file 'mandos-ctl.xml' --- mandos-ctl.xml 2018-02-08 10:23:55 +0000 +++ mandos-ctl.xml 1970-01-01 00:00:00 +0000 @@ -1,626 +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 - 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; - - - - - - - &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. - - - - - - - - - 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="5m" --interval="1m" 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-02-09 23:23:26 +0000 +++ mandos-keygen 2008-08-25 03:53:42 +0000 @@ -1,56 +1,47 @@ #!/bin/sh -e # -# Mandos key generator - create new keys for a Mandos client -# -# 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 +# Mandos key generator - create a new OpenPGP key for a Mandos client +# +# Copyright © 2007-2008 Teddy Hogeborn & 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 . -# - -VERSION="1.7.20" - -KEYDIR="/etc/keys/mandos" -KEYTYPE=RSA -KEYLENGTH=4096 -SUBKEYTYPE=RSA -SUBKEYLENGTH=4096 -KEYNAME="`hostname --fqdn 2>/dev/null || hostname`" +# along with this program. If not, see . +# +# Contact the authors at . +# + +VERSION="1.0" + +KEYDIR="/etc/mandos" +KEYTYPE=DSA +KEYLENGTH=2048 +SUBKEYTYPE=ELG-E +SUBKEYLENGTH=2048 +KEYNAME="`hostname --fqdn`" KEYEMAIL="" -KEYCOMMENT="" +KEYCOMMENT="Mandos client key" KEYEXPIRE=0 -TLS_KEYTYPE=ed25519 FORCE=no -SSH=yes KEYCOMMENT_ORIG="$KEYCOMMENT" mode=keygen -if [ ! -d "$KEYDIR" ]; then - KEYDIR="/etc/mandos/keys" -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 vhd:t:l:n:e:c:x:f \ + --longoptions version,help,password,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 +if [ -d "$KEYDIR" ]; then :; else echo "$KEYDIR not a directory" >&2 exit 1 fi -if [ ! -r "$KEYDIR" ]; then - echo "Directory $KEYDIR not readable" >&2 +if [ -w "$KEYDIR" ]; then :; else + echo "Directory $KEYDIR not writeable" >&2 + exit 1 +fi + +if [ "$mode" = password -a -e "$KEYDIR/trustdb.gpg.lock" ]; then + echo "Key directory has locked trustdb; aborting." >&2 exit 1 fi if [ "$mode" = keygen ]; then - if [ ! -w "$KEYDIR" ]; then - echo "Directory $KEYDIR not writeable" >&2 - exit 1 - fi if [ -z "$KEYTYPE" ]; then echo "Empty key type" >&2 exit 1 @@ -157,7 +137,7 @@ echo "Invalid key length" >&2 exit 1 fi - + if [ -z "$KEYEXPIRE" ]; then echo "Empty key expiration" >&2 exit 1 @@ -169,9 +149,7 @@ [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|*) FORCE=0;; esac - if { [ -e "$SECKEYFILE" ] || [ -e "$PUBKEYFILE" ] \ - || [ -e "$TLS_PRIVKEYFILE" ] \ - || [ -e "$TLS_PUBKEYFILE" ]; } \ + if { [ -e "$SECKEYFILE" ] || [ -e "$PUBKEYFILE" ]; } \ && [ "$FORCE" -eq 0 ]; then echo "Refusing to overwrite old key files; use --force" >&2 exit 1 @@ -184,120 +162,65 @@ if [ -n "$KEYEMAIL" ]; then KEYEMAILLINE="Name-Email: $KEYEMAIL" fi - + # Create temporary gpg batch file - BATCHFILE="`mktemp -t mandos-keygen-batch.XXXXXXXXXX`" + BATCHFILE="`mktemp -t mandos-gpg-batch.XXXXXXXXXX`" fi if [ "$mode" = password ]; then # Create temporary encrypted password file - SECFILE="`mktemp -t mandos-keygen-secfile.XXXXXXXXXX`" -fi - -# Create temporary key ring directory -RINGDIR="`mktemp -d -t mandos-keygen-keyrings.XXXXXXXXXX`" + SECFILE="`mktemp -t mandos-gpg-secfile.XXXXXXXXXX`" +fi + +# Create temporary key rings +SECRING="`mktemp -t mandos-gpg-secring.XXXXXXXXXX`" +PUBRING="`mktemp -t mandos-gpg-pubring.XXXXXXXXXX`" + +if [ "$mode" = password ]; then + # If a trustdb.gpg file does not already exist, schedule it for + # deletion when we are done. + if ! [ -e "$KEYDIR/trustdb.gpg" ]; then + TRUSTDB="$KEYDIR/trustdb.gpg" + fi +fi # Remove temporary files on exit trap " set +e; \ -test -n \"$SECFILE\" && shred --remove \"$SECFILE\"; \ -shred --remove \"$RINGDIR\"/sec* 2>/dev/null; -test -n \"$BATCHFILE\" && rm --force \"$BATCHFILE\"; \ -rm --recursive --force \"$RINGDIR\"; -tty --quiet && stty echo; \ +rm --force $PUBRING ${PUBRING}~ $BATCHFILE $TRUSTDB; \ +shred --remove $SECRING $SECFILE; \ +stty echo; \ " EXIT -set -e - -umask 077 +umask 027 if [ "$mode" = keygen ]; then # Create batch file for GnuPG 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 Expire-Date: $KEYEXPIRE #Preferences: #Handle: - #%pubring pubring.gpg - #%secring secring.gpg - %no-protection + %pubring $PUBRING + %secring $SECRING %commit EOF - if tty --quiet; then - cat <<-EOF - Note: Due to entropy requirements, key generation could take - anything from a few minutes to SEVERAL HOURS. Please be - patient and/or supply the system with more entropy if needed. - EOF - echo -n "Started: " - date - fi - - # Backup any old key files - if cp --backup=numbered --force "$TLS_PRIVKEYFILE" "$TLS_PRIVKEYFILE" \ - 2>/dev/null; then - shred --remove "$TLS_PRIVKEYFILE" - fi - if cp --backup=numbered --force "$TLS_PUBKEYFILE" "$TLS_PUBKEYFILE" \ - 2>/dev/null; then - rm --force "$TLS_PUBKEYFILE" - fi - - ## Generate TLS private key - - # First try certtool from GnuTLS - if ! certtool --generate-privkey --password='' \ - --outfile "$TLS_PRIVKEYFILE" --sec-param ultra \ - --key-type="$TLS_KEYTYPE" --pkcs8 --no-text 2>/dev/null; then - # Otherwise try OpenSSL - if ! openssl genpkey -algorithm X25519 -out \ - /etc/keys/mandos/tls-privkey.pem; then - rm --force /etc/keys/mandos/tls-privkey.pem - # None of the commands succeded; give up - return 1 - fi - fi - - ## 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 - - # 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 \ + gpg --no-random-seed-file --quiet --batch --no-tty \ + --no-default-keyring --no-options --enable-dsa2 \ + --secret-keyring "$SECRING" --keyring "$PUBRING" \ --gen-key "$BATCHFILE" rm --force "$BATCHFILE" - - if tty --quiet; then - echo -n "Finished: " - date - fi - + # Backup any old key files if cp --backup=numbered --force "$SECKEYFILE" "$SECKEYFILE" \ 2>/dev/null; then @@ -317,113 +240,64 @@ FILECOMMENT="$FILECOMMENT <$KEYEMAIL>" fi - # Export key from key rings to key files - gpg --quiet --batch --no-tty --no-options --enable-dsa2 \ - --homedir "$RINGDIR" --armor --export-options export-minimal \ - --comment "$FILECOMMENT" --output "$SECKEYFILE" \ - --export-secret-keys - gpg --quiet --batch --no-tty --no-options --enable-dsa2 \ - --homedir "$RINGDIR" --armor --export-options export-minimal \ - --comment "$FILECOMMENT" --output "$PUBKEYFILE" --export + # Export keys from key rings to key files + gpg --no-random-seed-file --quiet --batch --no-tty --armor \ + --no-default-keyring --no-options --enable-dsa2 \ + --secret-keyring "$SECRING" --keyring "$PUBRING" \ + --export-options export-minimal --comment "$FILECOMMENT" \ + --output "$SECKEYFILE" --export-secret-keys + gpg --no-random-seed-file --quiet --batch --no-tty --armor \ + --no-default-keyring --no-options --enable-dsa2 \ + --secret-keyring "$SECRING" --keyring "$PUBRING" \ + --export-options export-minimal --comment "$FILECOMMENT" \ + --output "$PUBKEYFILE" --export 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 \ - --import "$SECKEYFILE" - gpg --quiet --batch --no-tty --no-options --enable-dsa2 \ - --homedir "$RINGDIR" --trust-model always --armor \ - --import "$PUBKEYFILE" - + # Import keys into temporary key rings + gpg --no-random-seed-file --quiet --batch --no-tty --armor \ + --no-default-keyring --no-options --enable-dsa2 \ + --homedir "$KEYDIR" --no-permission-warning \ + --secret-keyring "$SECRING" --keyring "$PUBRING" \ + --trust-model always --import "$SECKEYFILE" + gpg --no-random-seed-file --quiet --batch --no-tty --armor \ + --no-default-keyring --no-options --enable-dsa2 \ + --homedir "$KEYDIR" --no-permission-warning \ + --secret-keyring "$SECRING" --keyring "$PUBRING" \ + --trust-model always --import "$PUBKEYFILE" + # Get fingerprint of key - FINGERPRINT="`gpg --quiet --batch --no-tty --no-options \ - --enable-dsa2 --homedir "$RINGDIR" --trust-model always \ - --fingerprint --with-colons \ - | sed --quiet \ - --expression='/^fpr:/{s/^fpr:.*:\\([0-9A-Z]*\\):\$/\\1/p;q}'`" + FINGERPRINT="`gpg --no-random-seed-file --quiet --batch --no-tty \ + --armor --no-default-keyring --no-options --enable-dsa2 \ + --homedir \"$KEYDIR\" --no-permission-warning \ + --secret-keyring \"$SECRING\" --keyring \"$PUBRING\" \ + --trust-model always --fingerprint --with-colons \ + | sed -n -e '/^fpr:/{s/^fpr:.*:\\([0-9A-Z]*\\):\$/\\1/p;q}'`" test -n "$FINGERPRINT" - KEY_ID="$(certtool --key-id --hash=sha256 \ - --infile="$TLS_PUBKEYFILE" 2>/dev/null || :)" - - if [ -z "$KEY_ID" ]; then - KEY_ID=$(openssl pkey -pubin -in /tmp/tls-pubkey.pem \ - -outform der \ - | openssl sha256 \ - | sed --expression='s/^.*[^[:xdigit:]]//') - fi - test -n "$KEY_ID" - FILECOMMENT="Encrypted password for a Mandos client" - while [ ! -s "$SECFILE" ]; do - if [ -n "$PASSFILE" ]; then - cat "$PASSFILE" - 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 - fi - done + stty -echo + echo -n "Enter passphrase: " >&2 + sed -e '1q' \ + | gpg --no-random-seed-file --batch --no-tty --armor \ + --no-default-keyring --no-options --enable-dsa2 \ + --homedir "$KEYDIR" --no-permission-warning \ + --secret-keyring "$SECRING" --keyring "$PUBRING" \ + --trust-model always --encrypt --recipient "$FINGERPRINT" \ + --comment "$FILECOMMENT" \ + > "$SECFILE" + echo >&2 + stty echo cat <<-EOF [$KEYNAME] host = $KEYNAME - key_id = $KEY_ID fingerprint = $FINGERPRINT secret = - EOF - sed --quiet --expression=' +EOF + sed -n -e ' /^-----BEGIN PGP MESSAGE-----$/,/^-----END PGP MESSAGE-----$/{ /^$/,${ # Remove 24-bit Radix-64 checksum @@ -432,10 +306,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 @@ -446,5 +316,9 @@ shred --remove "$SECFILE" fi # Remove the key rings -shred --remove "$RINGDIR"/sec* 2>/dev/null -rm --recursive --force "$RINGDIR" +shred --remove "$SECRING" +rm --force "$PUBRING" "${PUBRING}~" +# Remove the trustdb, if one did not exist when we started +if [ -n "$TRUSTDB" ]; then + rm --force "$TRUSTDB" +fi === modified file 'mandos-keygen.xml' --- mandos-keygen.xml 2019-02-09 23:34:15 +0000 +++ mandos-keygen.xml 2008-08-25 03:53:42 +0000 @@ -1,53 +1,62 @@ - - -%common; ]> - Mandos Manual + &COMMANDNAME; - Mandos - &version; - &TIMESTAMP; + &COMMANDNAME; + &VERSION; 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 Teddy Hogeborn Björn Påhlsson - + + + 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 this program; If not, see + . + +
- + &COMMANDNAME; 8 @@ -56,268 +65,247 @@ &COMMANDNAME; - Generate key and password for Mandos client and server. + Generate keys for password-request + 8mandos - + &COMMANDNAME; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + directory + + + + type + + + + bits + + + + type + + + + bits + + + + NAME + + + + EMAIL + + + + COMMENT + + + + TIME + + + + + + &COMMANDNAME; + + + directory + + + + type + + + + bits + + + + type + + + + bits + + + + NAME + + + + EMAIL + + + + COMMENT + + + + TIME + + &COMMANDNAME; + - - - - FILE - - - - - - - - - - - - - - + + + + directory + + + + NAME &COMMANDNAME; + - &COMMANDNAME; + - - + DESCRIPTION &COMMANDNAME; is a program to generate the - TLS and OpenPGP keys used by - mandos-client + OpenPGP keys used by + password-request 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. + normally written to /etc/mandos for later installation into the + initrd image, but this, like most things, can be changed with + command line options. - This program can also be used with the - or - options to generate a ready-made section for - clients.conf (see + It can also be used to generate ready-made sections for mandos-clients.conf - 5). + 5 using the + option. 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 - + - - + -h, --help Show a help message and exit - - - - - - - Target directory for key files. Default is /etc/keys/mandos. - - - - - - - - - - OpenPGP key type. Default is RSA. - - - - - - - - - - OpenPGP key length in bits. Default is 4096. - - - - - - - - - - OpenPGP subkey type. Default is RSA - - - - - - - - - - OpenPGP subkey length in bits. Default is 4096. - - - - - - - + + + -d, --dir + directory + + + Target directory for key files. Default is + /etc/mandos. + + + + + + -t, --type + type + + + Key type. Default is DSA. + + + + + + -l, --length + bits + + + Key length in bits. Default is 2048. + + + + + + -s, --subtype + type + + + Subkey type. Default is ELG-E (Elgamal + encryption-only). + + + + + + -L, --sublength + bits + + + Subkey length in bits. Default is 2048. + + + + + + -e, --email + address Email address of key. Default is empty. - + - - + -c, --comment + comment - Comment field for key. Default is empty. + Comment field for key. The default value is + Mandos client key. - + - - + -x, --expire + time Key expire time. Default is no expiration. See @@ -326,93 +314,50 @@ - - - - - - - TLS key type. Default is ed25519 - - - - - - - - - - Force overwriting old key. - - - - - - + + + -f, --force + + + Force overwriting old keys. + + + + + -p, --password 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. - - - - - - - - - The same as , but read from - FILE, not the terminal. - - - - - - - - - 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. + and no keys are created. - + 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. - + EXIT STATUS - The exit status will be 0 if a new key (or password, if the - option was used) was successfully - created, otherwise not. + The exit status will be 0 if new keys were successfully created, + otherwise not. @@ -420,7 +365,7 @@ ENVIRONMENT - TMPDIR + TMPDIR If set, temporary files will be created here. See @@ -432,7 +377,7 @@ - + FILES Use the option to change where @@ -441,7 +386,7 @@ - /etc/keys/mandos/seckey.txt + /etc/mandos/seckey.txt OpenPGP secret key file which will be created or @@ -450,7 +395,7 @@ - /etc/keys/mandos/pubkey.txt + /etc/mandos/pubkey.txt OpenPGP public key file which will be created or @@ -459,23 +404,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 @@ -485,12 +414,14 @@ - + BUGS - + + None are known at this time. + - + EXAMPLE @@ -498,82 +429,48 @@ Normal invocation needs no options: - &COMMANDNAME; + mandos-keygen - Create key in another directory and of another type. Force + Create keys in another directory and of another type. Force overwriting old key files: -&COMMANDNAME; --dir ~/keydir --type RSA --force - - - - - - Prompt for a password, encrypt it with the keys in /etc/keys/mandos and output a - section suitable for clients.conf. - - - &COMMANDNAME; --password - - - - - Prompt for a password, encrypt it with the keys in the - client-key directory and output a section - suitable for clients.conf. - - - - -&COMMANDNAME; --password --dir client-key +mandos-keygen --dir ~/keydir --type RSA --force - + SECURITY The , , , and - options can be used to create keys of low security. If in - doubt, leave them to the default values. + options can be used to create keys of insufficient security. If + in doubt, leave them to the default values. - The key expire time is not guaranteed to be - honored by mandos + The key expire time is not guaranteed to be honored by + mandos 8. - + SEE ALSO - intro + password-request 8mandos, + mandos + 8, gpg - 1, - mandos-clients.conf - 5, - mandos - 8, - mandos-client - 8mandos, - ssh-keyscan 1
- - - - - === removed file 'mandos-monitor' --- mandos-monitor 2018-08-19 20:17:48 +0000 +++ mandos-monitor 1970-01-01 00:00:00 +0000 @@ -1,746 +0,0 @@ -#!/usr/bin/python -# -*- mode: python; coding: utf-8 -*- -# -# Mandos Monitor - Control and monitor the Mandos server -# -# Copyright © 2009-2018 Teddy Hogeborn -# Copyright © 2009-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 . -# - -from __future__ import (division, absolute_import, print_function, - unicode_literals) -try: - from future_builtins import * -except ImportError: - pass - -import sys -import os - -import datetime - -import urwid.curses_display -import urwid - -from dbus.mainloop.glib import DBusGMainLoop -from gi.repository import GLib - -import dbus - -import locale - -import logging - -if sys.version_info.major == 2: - str = unicode - -locale.setlocale(locale.LC_ALL, '') - -logging.getLogger('dbus.proxies').setLevel(logging.CRITICAL) - -# Some useful constants -domain = 'se.recompile' -server_interface = domain + '.Mandos' -client_interface = domain + '.Mandos.Client' -version = "1.7.20" - -try: - dbus.OBJECT_MANAGER_IFACE -except AttributeError: - dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager" - - -def isoformat_to_datetime(iso): - "Parse an ISO 8601 date string to a datetime.datetime()" - if not iso: - return None - d, t = iso.split("T", 1) - year, month, day = d.split("-", 2) - hour, minute, second = t.split(":", 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 - - -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. - """ - # Update properties dict with new value - if interface == client_interface: - self.properties.update(properties) - - def delete(self): - self.property_changed_match.remove() - - -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): - # Called on update - self.update_hook = update_hook - # Called on delete - self.delete_hook = delete_hook - # Mandos Server proxy object - self.server_proxy_object = server_proxy_object - # Logger - self.logger = logger - - self._update_timer_callback_tag = None - - # The widget shown normally - self._text_widget = urwid.Text("") - # The widget shown when we have focus - self._focus_text_widget = urwid.Text("") - super(MandosClientWidget, self).__init__(**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 - (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) - self.update() - return - # Checker failed - if os.WIFEXITED(condition): - self.logger('Checker for client {} (command "{}") failed' - ' with exit code {}' - .format(self.properties["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.update() - - def checker_started(self, command): - """Server signals that a checker started.""" - self.logger('Client {} started checker "{}"' - .format(self.properties["Name"], - command), level=0) - - 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)) - - def selectable(self): - """Make this a "selectable" widget. - This overrides the method from urwid.FlowWidget.""" - return True - - def rows(self, maxcolrow, focus=False): - """How many rows this widget will occupy might depend on - whether we have focus or not. - This overrides the method from urwid.FlowWidget""" - return self.current_widget(focus).rows(maxcolrow, focus=focus) - - def current_widget(self, focus=False): - if focus or self.opened: - return self._focus_widget - return self._widget - - def update(self): - "Called when what is visible on the screen should be updated." - # How to add standout mode to a style - with_standout = {"normal": "standout", - "bold": "bold-standout", - "underline-blink": - "underline-blink-standout", - "bold-underline-blink": - "bold-underline-blink-standout", - } - - # 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) - - if not urwid.supports_unicode(): - self._text = self._text.encode("ascii", "replace") - textlist = [("normal", self._text)] - self._text_widget.set_text(textlist) - self._focus_text_widget.set_text([(with_standout[text[0]], - text[1]) - if isinstance(text, tuple) - else text - for text in textlist]) - self._widget = self._text_widget - self._focus_widget = urwid.AttrWrap(self._focus_text_widget, - "standout") - # Run update hook, if any - if self.update_hook is not None: - self.update_hook() - - def update_timer(self): - """called by GLib. Will indefinitely loop until - GLib.source_remove() on tag is called - """ - self.update() - return True # Keep calling this - - def delete(self, **kwargs): - if self._update_timer_callback_tag is not None: - GLib.source_remove(self._update_timer_callback_tag) - 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): - """Render differently if we have focus. - This overrides the method from urwid.FlowWidget""" - return self.current_widget(focus).render(maxcolrow, - focus=focus) - - def keypress(self, maxcolrow, key): - """Handle keys. - This overrides the method from urwid.FlowWidget""" - if key == "+": - self.proxy.Set(client_interface, "Enabled", - dbus.Boolean(True), ignore_reply=True, - dbus_interface=dbus.PROPERTIES_IFACE) - 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": - 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) - # xxx -# elif key == "p" or key == "=": -# self.proxy.pause() -# elif key == "u" or key == ":": -# self.proxy.unpause() -# elif key == "RET": -# self.open() - else: - return key - - def properties_changed(self, interface, properties, invalidated): - """Call self.update() if any properties changed. - This overrides the method from MandosClientPropertyCache""" - old_values = {key: self.properties.get(key) - for key in properties.keys()} - super(MandosClientWidget, self).properties_changed( - interface, properties, invalidated) - if any(old_values[key] != self.properties.get(key) - for key in old_values): - self.update() - - -class ConstrainedListBox(urwid.ListBox): - """Like a normal urwid.ListBox, but will consume all "up" or - "down" key presses, thus not allowing any containing widgets to - use them as an excuse to shift focus away from this widget. - """ - def keypress(self, *args, **kwargs): - ret = (super(ConstrainedListBox, self) - .keypress(*args, **kwargs)) - if ret in ("up", "down"): - return - return ret - - -class UserInterface(object): - """This is the entire user interface - the whole screen - with boxes, lists of client widgets, etc. - """ - def __init__(self, max_log_length=1000, log_level=1): - 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"), - )) - - if urwid.supports_unicode(): - self.divider = "─" # \u2500 - else: - self.divider = "_" # \u005f - - self.screen.start() - - self.size = self.screen.get_cols_rows() - - self.clients = urwid.SimpleListWalker([]) - self.clients_dict = {} - - # We will add Text widgets to this list - self.log = [] - 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.rebuild() - self.log_message_raw(("bold", - "Mandos Monitor version " + version)) - self.log_message_raw(("bold", - "q: Quit ?: Help")) - - self.busname = domain + '.Mandos' - self.main_loop = GLib.MainLoop() - - def client_not_found(self, fingerprint, address): - self.log_message("Client with address {} and fingerprint {}" - " could not be found" - .format(address, fingerprint)) - - def rebuild(self): - """This rebuilds the User Interface. - Call this when the widget layout needs to change""" - self.uilist = [] - # self.uilist.append(urwid.ListBox(self.clients)) - self.uilist.append(urwid.Frame(ConstrainedListBox(self. - clients), - # header=urwid.Divider(), - header=None, - footer=urwid.Divider( - div_char=self.divider))) - if self.log_visible: - self.uilist.append(self.logbox) - self.topwidget = urwid.Pile(self.uilist) - - def log_message(self, message, level=1): - """Log message formatted with timestamp""" - if level < self.log_level: - return - timestamp = datetime.datetime.now().isoformat() - self.log_message_raw("{}: {}".format(timestamp, message), - level=level) - - def log_message_raw(self, markup, level=1): - """Add a log message to the log buffer.""" - if level < self.log_level: - return - self.log.append(urwid.Text(markup, wrap=self.log_wrap)) - if self.max_log_length: - if len(self.log) > self.max_log_length: - del self.log[0:len(self.log)-self.max_log_length-1] - self.logbox.set_focus(len(self.logbox.body.contents), - coming_from="above") - self.refresh() - - def toggle_log_display(self): - """Toggle visibility of the log buffer.""" - self.log_visible = not self.log_visible - self.rebuild() - self.log_message("Log visibility changed to: {}" - .format(self.log_visible), level=0) - - def change_log_display(self): - """Change type of log display. - Currently, this toggles wrapping of text lines.""" - if self.log_wrap == "clip": - self.log_wrap = "any" - else: - self.log_wrap = "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 - 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 - 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])), - 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.refresh() - - def remove_client(self, client, path=None): - self.clients.remove(client) - if path is None: - path = client.proxy.object_path - del self.clients_dict[path] - self.refresh() - - def refresh(self): - """Redraw the screen""" - canvas = self.topwidget.render(self.size, focus=True) - self.screen.draw_screen(self.size, canvas) - - def run(self): - """Start the main loop and exit when it's done.""" - 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 - (sys.stdin.fileno(), - GLib.IO_IN, - self.process_input)) - self.main_loop.run() - # Main loop has finished, we should close everything now - GLib.source_remove(self._input_callback_tag) - 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 - } - for key in keys: - try: - key = translations[key] - except KeyError: # :-) - pass - - if key == "q" or key == "Q": - self.stop() - break - elif key == "window resize": - self.size = self.screen.get_cols_rows() - self.refresh() - elif key == "ctrl l": - self.screen.clear() - self.refresh() - elif key == "l" or key == "D": - self.toggle_log_display() - self.refresh() - elif key == "w" or key == "i": - self.change_log_display() - self.refresh() - elif key == "?" or key == "f1" or key == "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.refresh() - elif key == "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 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)) - ui.screen.stop() - raise === removed file 'mandos-monitor.xml' --- mandos-monitor.xml 2018-02-08 10:23:55 +0000 +++ mandos-monitor.xml 1970-01-01 00:00:00 +0000 @@ -1,248 +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 - 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 2008-08-25 10:41:16 +0000 @@ -5,8 +5,6 @@
@@ -17,8 +15,8 @@ and listen to requests on the specified network interface. Default is to use all available interfaces. Note: a failure to bind to the specified - interface is not considered critical, and the server will not - exit, but instead continue normally. + interface is not considered critical, and the server does not + exit, but will instead continue normally. @@ -26,12 +24,10 @@ specified IPv6 address. If a link-local address is specified, an interface should be set, since a link-local address is only valid on a single interface. By default, the server will listen to all - available addresses. If set, this must normally be an IPv6 - address; an IPv4 address can only be specified using IPv4-mapped - IPv6 address syntax: ::FFFF:192.0.2.3. (Only if IPv6 usage is - disabled (see below) must this be an IPv4 - address.) + available addresses. If set, this must be an IPv6 address; an + IPv4 address can only be specified using IPv4-mapped IPv6 address + syntax: ::FFFF:192.0.2.3. @@ -47,78 +43,24 @@ - GnuTLS priority string for the TLS handshake. + GnuTLS priority string for the TLS handshake with the clients. 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 - 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. + SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP. + See gnutls_priority_init + 3 for the + syntax. Warning: changing this may make the + TLS handshake fail, making communication with clients impossible. Zeroconf service name. The default is Mandos. This only needs to be - changed if for some reason is would be necessary to run more than - one server on the same host. This would not + changed this if it, for some reason, is necessary to run more than + one server on the same host, which would not normally be useful. If there are name collisions on the same network, the newer server will automatically rename itself to Mandos #2, and so on; therefore, this option is not needed in that case. - - This option controls whether the server will provide a D-Bus - system bus interface. The default is to provide such an - interface. - - - - This option controls whether the server will use IPv6 sockets and - addresses. The default is to use IPv6. This option should - never normally be turned off, even in - IPv4-only environments. This is because - mandos-client - 8mandos will normally use - IPv6 link-local addresses, and will not be able to find or connect - to the server if this option is turned off. Only - 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 2008-08-18 23:55:28 +0000 @@ -4,50 +4,35 @@ # 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*. # If there are name collisions on the same *network*, the server will # rename itself to "Mandos #2", etc. ;servicename = Mandos - -# Whether to provide a D-Bus system bus interface or not -;use_dbus = True - -# 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 2018-02-08 10:23:55 +0000 +++ mandos.conf.xml 2008-08-25 10:41:16 +0000 @@ -1,54 +1,63 @@ - + /etc/mandos/mandos.conf"> - - -%common; ]> - Mandos Manual + &CONFNAME; - Mandos - &version; - &TIMESTAMP; + &CONFNAME; + &VERSION; 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 Teddy Hogeborn Björn Påhlsson - + + + 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 this program; If not, see + . + +
- + &CONFNAME; 5 @@ -60,11 +69,13 @@ Configuration file for the Mandos server - + - &CONFPATH; + + &CONFPATH; + - + DESCRIPTION @@ -82,113 +93,76 @@ # or ; are ignored and may be used to provide comments. - + OPTIONS - + interface + interface = NAME + - + - + address + address = ADDRESS + - + - + port + port = NUMBER + - + - + + debug = { 1 | yes | true | on | 0 | no | false | off } - + >false | off } + - + - + priority + priority = STRING + - + - + servicename + servicename = NAME + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -204,9 +178,8 @@ The [DEFAULT] is necessary because the Python built-in module ConfigParser - requires it. + requres it. - @@ -227,15 +200,11 @@ [DEFAULT] # A configuration example interface = eth0 -address = fe80::aede:48ff:fe71:f6f2 +address = 2001:db8:f983:bd0b:30de:ae4a:71f2:f672 port = 1025 -debug = True -priority = SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA +debug = true +priority = SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP servicename = Daena -use_dbus = False -use_ipv6 = True -restore = True -statedir = /var/lib/mandos @@ -243,16 +212,15 @@ SEE ALSO - intro - 8mandos, - gnutls_priority_init3, - mandos - 8, - mandos-clients.conf - 5 + + mandos + 8, + mandos-clients.conf + 5, + gnutls_priority_init + 3 - + @@ -278,7 +246,7 @@ The clients use 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. @@ -300,8 +268,3 @@
- - - - - === removed file 'mandos.lsm' --- mandos.lsm 2018-08-19 20:17:48 +0000 +++ mandos.lsm 1970-01-01 00:00:00 +0000 @@ -1,23 +0,0 @@ -Begin4 -Title: Mandos -Version: 1.7.20 -Entered-date: 2018-08-19 -Description: The Mandos system allows computers to have encrypted - root file systems and at the same time be capable of - remote and/or unattended reboots. -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 - 177K mandos_1.7.20.orig.tar.gz -Alternate-site: ftp://ftp.recompile.se/pub/mandos - 177K mandos_1.7.20.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 -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-09 23:23:26 +0000 +++ mandos.xml 2008-08-25 10:41:16 +0000 @@ -1,53 +1,62 @@ - - -%common; ]> - - Mandos Manual + + &COMMANDNAME; - Mandos - &version; - &TIMESTAMP; + &COMMANDNAME; + &VERSION; 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 Teddy Hogeborn Björn Påhlsson - + + + 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 this program; If not, see + . + +
- + &COMMANDNAME; 8 @@ -56,154 +65,116 @@ &COMMANDNAME; - Gives encrypted passwords to authenticated Mandos clients + Sends encrypted passwords to authenticated Mandos clients - + &COMMANDNAME; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + --interfaceNAME + --addressADDRESS + --portPORT + --priorityPRIORITY + --servicenameNAME + --configdirDIRECTORY + --debug + + + &COMMANDNAME; + -iNAME + -aADDRESS + -pPORT + --priorityPRIORITY + --servicenameNAME + --configdirDIRECTORY + --debug &COMMANDNAME; - - + -h + --help &COMMANDNAME; - + --version &COMMANDNAME; - + --check - + DESCRIPTION &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. + 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 + - - + -h, --help Show a help message and exit - + - - NAME - - NAME + -i, --interface NAME - + - - + -a, --address + ADDRESS - + - - + -p, --port + PORT - + - + --check Run the server’s self-tests. This includes any unit @@ -211,52 +182,34 @@ - + - + --debug - - - - - - 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. - - - - - - + + + --priority + PRIORITY - + - + --servicename NAME + - + - + --configdir DIR + Directory to search for configuration files. Default is @@ -268,87 +221,28 @@ - + - + --version Prints the program version and exit. - - - - - - - See also . - - - - - - - - - - - - - - - - - See also . - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + OVERVIEW This program is the server part. It is a normal server program and will run in a normal system environment, not in an initial - RAM disk environment. + RAM disk environment. - + NETWORK PROTOCOL @@ -361,11 +255,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) @@ -391,7 +285,7 @@ - Public key (part of TLS handshake) + OpenPGP public key (part of TLS handshake) -> @@ -406,74 +300,31 @@
- + CHECKING The server will, by default, continually check that the clients are still up. If a client has not been confirmed as being up 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 + longer eligible to receive the encrypted password. The 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 - 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. - - - - + 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. - - - + EXIT STATUS @@ -481,12 +332,12 @@ critical error is encountered. - + ENVIRONMENT - PATH + PATH To start the configured checker (see - - + + FILES Use the option to change where @@ -531,31 +382,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/mandos.pid + + + The file containing the process id of + &COMMANDNAME;. + + + + + /dev/log The Unix domain socket to where local syslog messages are @@ -584,9 +420,25 @@ backtrace. This could be considered a feature. + Currently, if a client is declared invalid 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 does not show a timestamp. + @@ -596,20 +448,20 @@ Normal invocation needs no options: - &COMMANDNAME; + mandos 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: -&COMMANDNAME; --debug --configdir ~/mandos --servicename Test +mandos --debug --configdir ~/mandos --servicename Test @@ -621,37 +473,37 @@ -&COMMANDNAME; --interface eth7 --address fe80::aede:48ff:fe71:f6f2 +mandos --interface eth7 --address fe80::aede:48ff:fe71:f6f2 - + SECURITY - + SERVER Running this &COMMANDNAME; server program should not in itself present any security risk to the host - computer running it. The program switches to a non-root user - soon after startup. + computer running it. The program does not need any special + privileges to run, and is designed to run as a non-root user. - + 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 - except the user starting the server (usually root). + except the user running the server. As detailed in , the status of all @@ -659,26 +511,41 @@ 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 declare the client + invalid. 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 valid, 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 + password-request 8mandos. - + SEE ALSO - intro - 8mandos, - mandos-clients.conf - 5, - mandos.conf - 5, - mandos-client - 8mandos, - sh - 1 + + mandos.conf + 5, + mandos-clients.conf + 5, + password-request + 8mandos, + sh1 + @@ -705,13 +572,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. @@ -749,12 +617,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. @@ -770,36 +638,16 @@ - 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-08-23 07:17:28 +0000 @@ -1,17 +1,15 @@ - This is part of the Mandos system for allowing 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 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. + encrypted root file systems and also be capable of remote and + unattended reboots. The computers run a small client program in the + initial RAM disk environment which will communicate with a server + over a network. The clients are identified by the server using a + 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 2018-08-19 01:03:28 +0000 +++ plugin-runner.c 2008-08-25 07:53:43 +0000 @@ -1,59 +1,57 @@ -/* -*- coding: utf-8; mode: c; mode: orgtbl -*- */ +/* -*- coding: utf-8 -*- */ /* * 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 © 2007-2008 Teddy Hogeborn & 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() */ #include /* size_t, NULL */ -#include /* malloc(), exit(), EXIT_SUCCESS, - realloc() */ +#include /* malloc(), exit(), EXIT_FAILURE, + 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, popen(), fileno(), + fprintf(), stderr, STDOUT_FILENO */ +#include /* DIR, opendir(), 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() */ +#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, opendir(), + 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() */ #include /* errno */ #include /* struct argp_option, struct argp_state, struct argp, @@ -63,24 +61,30 @@ #include /* struct sigaction, sigemptyset(), sigaddset(), sigaction(), sigprocmask(), SIG_BLOCK, SIGCHLD, - SIG_UNBLOCK, kill(), sig_atomic_t - */ + SIG_UNBLOCK, kill() */ #include /* errno, EBADF */ -#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_version = "plugin-runner 1.0"; +const char *argp_program_bug_address = ""; + +struct process; + +typedef struct process{ + pid_t pid; + int fd; + char *buffer; + size_t buffer_size; + size_t buffer_length; + bool eof; + volatile bool completed; + volatile int status; + struct process *next; +} process; typedef struct plugin{ char *name; /* can be NULL or any plugin name */ @@ -89,109 +93,69 @@ char **environ; int envc; bool disabled; - - /* Variables used for running processes*/ - pid_t pid; - int fd; - char *buffer; - size_t buffer_size; - size_t buffer_length; - bool eof; - volatile sig_atomic_t completed; - int status; struct plugin *next; } plugin; -static plugin *plugin_list = NULL; - -/* 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){ - if((p->name == name) - or (p->name and name and (strcmp(p->name, name) == 0))){ +static plugin *getplugin(char *name, plugin **plugin_list){ + for (plugin *p = *plugin_list; p != NULL; p = p->next){ + if ((p->name == name) + or (p->name and name and (strcmp(p->name, name) == 0))){ return p; } } /* Create a new plugin */ - plugin *new_plugin = NULL; - do { - new_plugin = malloc(sizeof(plugin)); - } while(new_plugin == NULL and errno == EINTR); - if(new_plugin == NULL){ + plugin *new_plugin = malloc(sizeof(plugin)); + if (new_plugin == NULL){ return NULL; } char *copy_name = NULL; if(name != NULL){ - do { - copy_name = strdup(name); - } while(copy_name == NULL and errno == EINTR); + copy_name = strdup(name); if(copy_name == NULL){ - int e = errno; - free(new_plugin); - errno = e; return NULL; } } - *new_plugin = (plugin){ .name = copy_name, - .argc = 1, - .disabled = false, - .next = plugin_list }; + *new_plugin = (plugin) { .name = copy_name, + .argc = 1, + .envc = 0, + .disabled = false, + .next = *plugin_list }; - do { - new_plugin->argv = malloc(sizeof(char *) * 2); - } while(new_plugin->argv == NULL and errno == EINTR); - if(new_plugin->argv == NULL){ - int e = errno; + new_plugin->argv = malloc(sizeof(char *) * 2); + if (new_plugin->argv == NULL){ free(copy_name); free(new_plugin); - errno = e; return NULL; } new_plugin->argv[0] = copy_name; new_plugin->argv[1] = NULL; - - do { - new_plugin->environ = malloc(sizeof(char *)); - } while(new_plugin->environ == NULL and errno == EINTR); + + new_plugin->environ = malloc(sizeof(char *)); if(new_plugin->environ == NULL){ - int e = errno; free(copy_name); free(new_plugin->argv); free(new_plugin); - errno = e; return NULL; } new_plugin->environ[0] = NULL; - /* Append the new plugin to the list */ - plugin_list = new_plugin; + *plugin_list = new_plugin; return new_plugin; } /* 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)); /* Malloc check */ - if(new_array == NULL){ + if(*array == NULL){ return false; } - *array = new_array; /* Make a copy of the new string */ - char *copy; - do { - copy = strdup(new); - } while(copy == NULL and errno == EINTR); + char *copy = strdup(new); if(copy == NULL){ return false; } @@ -204,7 +168,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,60 +176,37 @@ } /* Add to a plugin's environment */ -__attribute__((nonnull(2), warn_unused_result)) -static bool add_environment(plugin *p, const char *def, bool replace){ +static bool add_environment(plugin *p, const char *def){ if(p == NULL){ return false; } - /* 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){ - /* It already exists */ - if(replace){ - char *new_envdef; - do { - new_envdef = realloc(*envdef, strlen(def) + 1); - } while(new_envdef == NULL and errno == EINTR); - if(new_envdef == NULL){ - return false; - } - *envdef = new_envdef; - strcpy(*envdef, def); - } - return true; - } - } 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]] | + * *Note File Descriptor Flags:(libc)Descriptor Flags. */ -__attribute__((warn_unused_result)) -static int set_cloexec_flag(int fd){ - int ret = (int)TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD, 0)); +static int set_cloexec_flag(int fd) +{ + int ret = fcntl(fd, F_GETFD, 0); /* If reading the flags failed, return error indication now. */ if(ret < 0){ return ret; } /* Store modified flag word in the descriptor. */ - return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD, - ret | FD_CLOEXEC)); + return fcntl(fd, F_SETFD, ret | FD_CLOEXEC); } -#endif /* not O_CLOEXEC */ +process *process_list = NULL; /* Mark processes as completed when they exit, and save their exit status. */ -static void handle_sigchld(__attribute__((unused)) int sig){ - int old_errno = errno; +void handle_sigchld(__attribute__((unused)) int sig){ while(true){ - plugin *proc = plugin_list; + process *proc = process_list; int status; pid_t pid = waitpid(-1, &status, WNOHANG); if(pid == 0){ @@ -274,13 +214,13 @@ break; } if(pid == -1){ - if(errno == ECHILD){ - /* No child processes */ - break; + if (errno != ECHILD){ + perror("waitpid"); } - error(0, errno, "waitpid"); + /* No child processes */ + break; } - + /* A child exited, find it in process_list */ while(proc != NULL and proc->pid != pid){ proc = proc->next; @@ -290,15 +230,15 @@ continue; } proc->status = status; - proc->completed = 1; + proc->completed = true; } - errno = old_errno; } -/* Prints out a password to stdout */ -__attribute__((nonnull, warn_unused_result)) -static bool print_out_password(const char *buffer, size_t length){ +bool print_out_password(const char *buffer, size_t length){ ssize_t ret; + if(length>0 and buffer[length-1] == '\n'){ + length--; + } for(size_t written = 0; written < length; written += (size_t)ret){ ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buffer + written, length - written)); @@ -309,53 +249,31 @@ return true; } -/* Removes and free a plugin from the plugin list */ -__attribute__((nonnull)) -static void free_plugin(plugin *plugin_node){ - - for(char **arg = plugin_node->argv; *arg != NULL; arg++){ - free(*arg); - } - free(plugin_node->argv); - for(char **env = plugin_node->environ; *env != NULL; env++){ - free(*env); - } - free(plugin_node->environ); - free(plugin_node->buffer); - - /* Removes the plugin from the singly-linked list */ - if(plugin_node == plugin_list){ - /* First one - simple */ - plugin_list = plugin_list->next; - } else { - /* Second one or later */ - for(plugin *p = plugin_list; p != NULL; p = p->next){ - if(p->next == plugin_node){ - p->next = plugin_node->next; - break; - } - } - } - - free(plugin_node); -} - -static void free_plugin_list(void){ - while(plugin_list != NULL){ - free_plugin(plugin_list); +static void free_plugin_list(plugin *plugin_list){ + for(plugin *next; plugin_list != NULL; plugin_list = next){ + next = plugin_list->next; + for(char **arg = plugin_list->argv; *arg != NULL; arg++){ + free(*arg); + } + free(plugin_list->argv); + for(char **env = plugin_list->environ; *env != NULL; env++){ + free(*env); + } + free(plugin_list->environ); + free(plugin_list); } } 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; - ssize_t sret; uid_t uid = 65534; gid_t gid = 65534; bool debug = false; @@ -365,20 +283,19 @@ .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"); - exitstatus = EX_OSERR; + perror("sigaddset"); + exitstatus = EXIT_FAILURE; goto fallback; } ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action); if(ret == -1){ - error(0, errno, "sigaction"); - exitstatus = EX_OSERR; + perror("sigaction"); + exitstatus = EXIT_FAILURE; goto fallback; } @@ -387,21 +304,18 @@ { .name = "global-options", .key = 'g', .arg = "OPTION[,OPTION[,...]]", .doc = "Options passed to all plugins" }, - { .name = "global-env", .key = 'G', + { .name = "global-envs", .key = 'e', .arg = "VAR=value", .doc = "Environment variable passed to all plugins" }, { .name = "options-for", .key = 'o', .arg = "PLUGIN:OPTION[,OPTION[,...]]", .doc = "Options passed only to specified plugin" }, - { .name = "env-for", .key = 'E', + { .name = "envs-for", .key = 'f', .arg = "PLUGIN:ENV=value", .doc = "Environment variable passed to specified plugin" }, { .name = "disable", .key = 'd', .arg = "PLUGIN", .doc = "Disable a specific plugin", .group = 1 }, - { .name = "enable", .key = 'e', - .arg = "PLUGIN", - .doc = "Enable a specific plugin", .group = 1 }, { .name = "plugin-dir", .key = 128, .arg = "DIRECTORY", .doc = "Specify a different plugin directory", .group = 2 }, @@ -416,275 +330,169 @@ .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 - */ - { .name = "help", .key = '?', - .doc = "Give this help list", .group = -1 }, - { .name = "usage", .key = -3, - .doc = "Give a short usage message", .group = -1 }, - { .name = "version", .key = 'V', - .doc = "Print program version", .group = -1 }, { .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; - case 'g': /* --global-options */ - { - char *plugin_option; - while((plugin_option = strsep(&arg, ",")) != NULL){ - if(not add_argument(getplugin(NULL), plugin_option)){ - break; - } - } - errno = 0; - } - break; - case 'G': /* --global-env */ - if(add_environment(getplugin(NULL), arg, true)){ - errno = 0; - } - break; - case 'o': /* --options-for */ - { - char *option_list = strchr(arg, ':'); - if(option_list == NULL){ - argp_error(state, "No colon in \"%s\"", arg); - errno = EINVAL; - break; - } - *option_list = '\0'; - option_list++; - if(arg[0] == '\0'){ - argp_error(state, "Empty plugin name"); - errno = EINVAL; - break; - } - char *option; - while((option = strsep(&option_list, ",")) != NULL){ - if(not add_argument(getplugin(arg), option)){ - break; - } - } - errno = 0; - } - break; - case 'E': /* --env-for */ + error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the INPUT argument from `argp_parse', which we know is a + pointer to our plugin list pointer. */ + plugin **plugins = state->input; + switch (key) { + case 'g': + if (arg != NULL){ + char *p; + while((p = strsep(&arg, ",")) != NULL){ + if(p[0] == '\0'){ + continue; + } + if(not add_argument(getplugin(NULL, plugins), p)){ + perror("add_argument"); + return ARGP_ERR_UNKNOWN; + } + } + } + break; + case 'e': + if(arg == NULL){ + break; + } + { + char *envdef = strdup(arg); + if(envdef == NULL){ + break; + } + if(not add_environment(getplugin(NULL, plugins), envdef)){ + perror("add_environment"); + } + } + break; + case 'o': + if (arg != NULL){ + char *p_name = strsep(&arg, ":"); + if(p_name[0] == '\0'){ + break; + } + char *opt = strsep(&arg, ":"); + if(opt[0] == '\0'){ + break; + } + if(opt != NULL){ + char *p; + while((p = strsep(&opt, ",")) != NULL){ + if(p[0] == '\0'){ + continue; + } + if(not add_argument(getplugin(p_name, plugins), p)){ + perror("add_argument"); + return ARGP_ERR_UNKNOWN; + } + } + } + } + break; + case 'f': + if(arg == NULL){ + break; + } { char *envdef = strchr(arg, ':'); if(envdef == NULL){ - argp_error(state, "No colon in \"%s\"", arg); - errno = EINVAL; - break; - } - *envdef = '\0'; + break; + } + char *p_name = strndup(arg, (size_t) (envdef-arg)); + if(p_name == NULL){ + break; + } envdef++; - if(arg[0] == '\0'){ - argp_error(state, "Empty plugin name"); - errno = EINVAL; - break; - } - if(add_environment(getplugin(arg), envdef, true)){ - errno = 0; - } - } - break; - case 'd': /* --disable */ - { - plugin *p = getplugin(arg); - if(p != NULL){ - p->disabled = true; - errno = 0; - } - } - break; - case 'e': /* --enable */ - { - plugin *p = getplugin(arg); - if(p != NULL){ - p->disabled = false; - errno = 0; - } - } - break; - case 128: /* --plugin-dir */ - free(plugindir); + if(not add_environment(getplugin(p_name, plugins), envdef)){ + perror("add_environment"); + } + } + break; + case 'd': + if (arg != NULL){ + plugin *p = getplugin(arg, plugins); + if(p == NULL){ + return ARGP_ERR_UNKNOWN; + } + p->disabled = true; + } + break; + case 128: 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); - if(errno != 0 or tmp == arg or *tmp != '\0' - or tmp_id != (uid_t)tmp_id){ - argp_error(state, "Bad user ID number: \"%s\", using %" - PRIdMAX, arg, (intmax_t)uid); - break; - } - uid = (uid_t)tmp_id; - errno = 0; - break; - case 131: /* --groupid */ - tmp_id = strtoimax(arg, &tmp, 10); - if(errno != 0 or tmp == arg or *tmp != '\0' - or tmp_id != (gid_t)tmp_id){ - argp_error(state, "Bad group ID number: \"%s\", using %" - PRIdMAX, arg, (intmax_t)gid); - break; - } - gid = (gid_t)tmp_id; - errno = 0; - break; - case 132: /* --debug */ + if(plugindir == NULL){ + perror("strdup"); + } + break; + case 129: + argfile = strdup(arg); + if(argfile == NULL){ + perror("strdup"); + } + break; + case 130: + uid = (uid_t)strtol(arg, NULL, 10); + break; + case 131: + gid = (gid_t)strtol(arg, NULL, 10); + break; + case 132: 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); - 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); - case 'V': /* --version */ - fprintf(state->out_stream, "%s\n", argp_program_version); - exit(EXIT_SUCCESS); - break; -/* - * When adding more options before this line, remember to also add a - * "case" to the "parse_opt_config_file" function below. - */ - case ARGP_KEY_ARG: - /* Cryptsetup always passes an argument, which is an empty - string if "none" was specified in /etc/crypttab. So if - argument was empty, we ignore it silently. */ - if(arg[0] == '\0'){ - break; - } - /* FALLTHROUGH */ - default: - return ARGP_ERR_UNKNOWN; - } - return errno; /* Set to 0 at start */ - } - - /* This option parser is the same as parse_opt() above, except it - ignores everything but the --config-file option. */ - error_t parse_opt_config_file(int key, char *arg, - __attribute__((unused)) - struct argp_state *state){ - errno = 0; - switch(key){ - case 'g': /* --global-options */ - case 'G': /* --global-env */ - case 'o': /* --options-for */ - case 'E': /* --env-for */ - case 'd': /* --disable */ - case 'e': /* --enable */ - case 128: /* --plugin-dir */ - break; - 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 */ - case ARGP_KEY_ARG: - break; - default: - return ARGP_ERR_UNKNOWN; - } - return errno; - } - - struct argp argp = { .options = options, - .parser = parse_opt_config_file, - .args_doc = "", + case ARGP_KEY_ARG: + fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg); + break; + case ARGP_KEY_END: + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; + } + + plugin *plugin_list = NULL; + + struct argp argp = { .options = options, .parser = parse_opt, + .args_doc = "[+PLUS_SEPARATED_OPTIONS]", .doc = "Mandos plugin runner -- Run plugins" }; - /* Parse using parse_opt_config_file() in order to get the custom - config file location, if any. */ - ret = argp_parse(&argp, argc, argv, - ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP, - NULL, NULL); - switch(ret){ - case 0: - break; - case ENOMEM: - default: - errno = ret; - error(0, errno, "argp_parse"); - exitstatus = EX_OSERR; - goto fallback; - case EINVAL: - exitstatus = EX_USAGE; + ret = argp_parse (&argp, argc, argv, 0, 0, &plugin_list); + if (ret == ARGP_ERR_UNKNOWN){ + fprintf(stderr, "Unknown error while parsing arguments\n"); + exitstatus = EXIT_FAILURE; goto fallback; } - - /* Reset to the normal argument parser */ - argp.parser = parse_opt; - - /* Open the configfile if available */ - if(argfile == NULL){ + + if (argfile == NULL){ conffp = fopen(AFILE, "r"); } else { conffp = fopen(argfile, "r"); } + if(conffp != NULL){ char *org_line = NULL; char *p, *arg, *new_arg, *line; size_t size = 0; + ssize_t sret; const char whitespace_delims[] = " \r\t\f\v\n"; const char comment_delim[] = "#"; - + custom_argc = 1; custom_argv = malloc(sizeof(char*) * 2); if(custom_argv == NULL){ - error(0, errno, "malloc"); - exitstatus = EX_OSERR; + perror("malloc"); + exitstatus = EXIT_FAILURE; goto fallback; } custom_argv[0] = argv[0]; custom_argv[1] = NULL; - /* for each line in the config file, strip whitespace and ignore - commented text */ while(true){ sret = getline(&org_line, &size, conffp); if(sret == -1){ break; } - + line = org_line; arg = strsep(&line, comment_delim); while((p = strsep(&arg, whitespace_delims)) != NULL){ @@ -693,631 +501,507 @@ } new_arg = strdup(p); if(new_arg == NULL){ - error(0, errno, "strdup"); - exitstatus = EX_OSERR; + perror("strdup"); + exitstatus = EXIT_FAILURE; 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 = EXIT_FAILURE; + free(org_line); + goto fallback; } custom_argv[custom_argc-1] = new_arg; - custom_argv[custom_argc] = NULL; + custom_argv[custom_argc] = NULL; } } - do { - ret = fclose(conffp); - } while(ret == EOF and errno == EINTR); - if(ret == EOF){ - error(0, errno, "fclose"); - exitstatus = EX_IOERR; - goto fallback; - } free(org_line); - } else { + } else{ /* 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"); - exitstatus = EX_OSERR; + if (errno == EMFILE or errno == ENFILE or errno == ENOMEM){ + perror("fopen"); + exitstatus = EXIT_FAILURE; goto fallback; } } - /* If there were any arguments from the configuration file, pass - them to parser as command line arguments */ + if(custom_argv != NULL){ - ret = argp_parse(&argp, custom_argc, custom_argv, - ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP, - NULL, NULL); - switch(ret){ - case 0: - break; - case ENOMEM: - default: - errno = ret; - error(0, errno, "argp_parse"); - exitstatus = EX_OSERR; - goto fallback; - case EINVAL: - exitstatus = EX_CONFIG; - goto fallback; - } - } - - /* Parse actual command line arguments, to let them override the - config file */ - ret = argp_parse(&argp, argc, argv, - ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP, - NULL, NULL); - switch(ret){ - case 0: - break; - case ENOMEM: - default: - errno = ret; - error(0, errno, "argp_parse"); - exitstatus = EX_OSERR; - goto fallback; - case EINVAL: - exitstatus = EX_USAGE; - 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); + ret = argp_parse (&argp, custom_argc, custom_argv, 0, 0, &plugin_list); + if (ret == ARGP_ERR_UNKNOWN){ + fprintf(stderr, "Unknown error while parsing arguments\n"); + exitstatus = EXIT_FAILURE; + goto fallback; } } 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++){ fprintf(stderr, "\tArg: %s\n", *a); } - fprintf(stderr, "...and %d environment variables\n", p->envc); + fprintf(stderr, "...and %u environment variables\n", p->envc); for(char **a = p->environ; *a != NULL; a++){ fprintf(stderr, "\t%s\n", *a); } } } - 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); - if(ret == -1){ - error(0, errno, "setgid"); - } ret = setuid(uid); - if(ret == -1){ - error(0, errno, "setuid"); - } - - /* Open plugin directory with close_on_exec flag */ + if (ret == -1){ + perror("setuid"); + } + + setgid(gid); + if (ret == -1){ + perror("setgid"); + } + + if (plugindir == NULL){ + dir = opendir(PDIR); + } else { + dir = opendir(plugindir); + } + + if(dir == NULL){ + perror("Could not open plugin dir"); + exitstatus = EXIT_FAILURE; + goto fallback; + } + + /* Set the FD_CLOEXEC flag on the directory, if possible */ { - dir_fd = open(plugindir != NULL ? plugindir : PDIR, 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"); - exitstatus = EX_UNAVAILABLE; - goto fallback; - } - -#ifndef O_CLOEXEC - /* Set the FD_CLOEXEC flag on the directory */ - ret = set_cloexec_flag(dir_fd); - if(ret < 0){ - error(0, errno, "set_cloexec_flag"); - 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; + int dir_fd = dirfd(dir); + if(dir_fd >= 0){ + ret = set_cloexec_flag(dir_fd); + if(ret < 0){ + perror("set_cloexec_flag"); + exitstatus = EXIT_FAILURE; + 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]); - continue; - } - ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st)); - if(ret == -1){ - error(0, errno, "stat"); - close(plugin_fd); - free(direntries[i]); - 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)){ + while(true){ + dirst = readdir(dir); + + // All directory entries have been processed + if(dirst == NULL){ + if (errno == EBADF){ + perror("readdir"); + exitstatus = EXIT_FAILURE; + 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-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; + ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name); + if(ret < 0){ + perror("asprintf"); + continue; + } + + ret = stat(filename, &st); + if (ret == -1){ + perror("stat"); + free(filename); + continue; + } + + if (not S_ISREG(st.st_mode) or (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, &plugin_list); 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; } { /* Add global arguments to argument list for this plugin */ - plugin *g = getplugin(NULL); + plugin *g = getplugin(NULL, &plugin_list); 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"); + if(not add_environment(p, *e)){ + 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"); + char *copy = strdup(*e); + if(copy == NULL){ + perror("strdup"); + continue; + } + if(not add_environment(p, copy)){ + 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 - /* Ask OS to automatic close the pipe on exec */ + ret = pipe(pipefd); + if (ret == -1){ + perror("pipe"); + exitstatus = EXIT_FAILURE; + goto fallback; + } ret = set_cloexec_flag(pipefd[0]); if(ret < 0){ - error(0, errno, "set_cloexec_flag"); - close(pipefd[0]); - close(pipefd[1]); - exitstatus = EX_OSERR; - free(direntries[i]); + perror("set_cloexec_flag"); + exitstatus = EXIT_FAILURE; goto fallback; } ret = set_cloexec_flag(pipefd[1]); if(ret < 0){ - error(0, errno, "set_cloexec_flag"); - close(pipefd[0]); - close(pipefd[1]); - exitstatus = EX_OSERR; - free(direntries[i]); + perror("set_cloexec_flag"); + exitstatus = EXIT_FAILURE; 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)); + ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL); if(ret < 0){ - error(0, errno, "sigprocmask"); - exitstatus = EX_OSERR; - free(direntries[i]); + perror("sigprocmask"); + exitstatus = EXIT_FAILURE; goto fallback; } - /* Starting a new process to be watched */ - pid_t pid; - do { - pid = fork(); - } while(pid == -1 and errno == EINTR); + // Starting a new process to be watched + pid_t pid = fork(); if(pid == -1){ - error(0, errno, "fork"); - TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK, - &sigchld_action.sa_mask, NULL)); - close(pipefd[0]); - close(pipefd[1]); - exitstatus = EX_OSERR; - free(direntries[i]); + perror("fork"); + exitstatus = EXIT_FAILURE; goto fallback; } if(pid == 0){ /* this is the child process */ ret = sigaction(SIGCHLD, &old_sigchld_action, NULL); if(ret < 0){ - error(0, errno, "sigaction"); - _exit(EX_OSERR); + perror("sigaction"); + _exit(EXIT_FAILURE); } - ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL); + ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL); if(ret < 0){ - error(0, errno, "sigprocmask"); - _exit(EX_OSERR); + perror("sigprocmask"); + _exit(EXIT_FAILURE); } - + ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */ if(ret == -1){ - error(0, errno, "dup2"); - _exit(EX_OSERR); + perror("dup2"); + _exit(EXIT_FAILURE); } - 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(EXIT_FAILURE); + } + } else { + if(execve(filename, p->argv, p->environ) < 0){ + perror("execve"); + _exit(EXIT_FAILURE); + } } /* no return */ } - /* Parent process */ - close(pipefd[1]); /* Close unused write end of pipe */ - close(plugin_fd); - plugin *new_plugin = getplugin(direntries[i]->d_name); - if(new_plugin == NULL){ - error(0, errno, "getplugin"); - ret = (int)(TEMP_FAILURE_RETRY - (sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, - NULL))); + /* parent process */ + free(filename); + close(pipefd[1]); /* close unused write end of pipe */ + process *new_process = malloc(sizeof(process)); + if (new_process == NULL){ + perror("malloc"); + ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL); if(ret < 0){ - error(0, errno, "sigprocmask"); + perror("sigprocmask"); } - exitstatus = EX_OSERR; - free(direntries[i]); + exitstatus = EXIT_FAILURE; 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)); - } - + *new_process = (struct process){ .pid = pid, + .fd = pipefd[0], + .next = process_list }; + // List handling + process_list = new_process; /* 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)); + ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL); if(ret < 0){ - error(0, errno, "sigprocmask"); - exitstatus = EX_OSERR; + perror("sigprocmask"); + exitstatus = EXIT_FAILURE; goto fallback; } - FD_SET(new_plugin->fd, &rfds_all); - - if(maxfd < new_plugin->fd){ - maxfd = new_plugin->fd; - } - } - - free(direntries); - direntries = NULL; - close(dir_fd); - dir_fd = -1; - free_plugin(getplugin(NULL)); - - for(plugin *p = plugin_list; p != NULL; p = p->next){ - if(p->pid != 0){ - break; - } - if(p->next == NULL){ - fprintf(stderr, "No plugin processes started. Incorrect plugin" - " directory?\n"); - free_plugin_list(); - } - } - - /* Main loop while running plugins exist */ - while(plugin_list){ + FD_SET(new_process->fd, &rfds_all); + + if (maxfd < new_process->fd){ + maxfd = new_process->fd; + } + + } + + free_plugin_list(plugin_list); + plugin_list = NULL; + + closedir(dir); + dir = NULL; + + if (process_list == NULL){ + fprintf(stderr, "No plugin processes started. Incorrect plugin" + " directory?\n"); + process_list = NULL; + } + while(process_list){ 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"); - exitstatus = EX_OSERR; + if (select_ret == -1){ + perror("select"); + exitstatus = EXIT_FAILURE; goto fallback; } /* OK, now either a process completed, or something can be read from one of them */ - for(plugin *proc = plugin_list; proc != NULL;){ + for(process *proc = process_list; proc ; proc = proc->next){ /* Is this process completely done? */ - if(proc->completed and proc->eof){ + if(proc->eof and proc->completed){ /* Only accept the plugin output if it exited cleanly */ if(not WIFEXITED(proc->status) or WEXITSTATUS(proc->status) != 0){ /* Bad exit by plugin */ - if(debug){ if(WIFEXITED(proc->status)){ - fprintf(stderr, "Plugin %s [%" PRIdMAX "] exited with" - " status %d\n", proc->name, - (intmax_t) (proc->pid), + fprintf(stderr, "Plugin %u exited with status %d\n", + (unsigned int) (proc->pid), WEXITSTATUS(proc->status)); - } else if(WIFSIGNALED(proc->status)){ - fprintf(stderr, "Plugin %s [%" PRIdMAX "] killed by" - " signal %d: %s\n", proc->name, - (intmax_t) (proc->pid), - WTERMSIG(proc->status), - strsignal(WTERMSIG(proc->status))); + } else if(WIFSIGNALED(proc->status)) { + fprintf(stderr, "Plugin %u killed by signal %d\n", + (unsigned int) (proc->pid), + WTERMSIG(proc->status)); + } else if(WCOREDUMP(proc->status)){ + fprintf(stderr, "Plugin %d dumped core\n", + (unsigned int) (proc->pid)); } } - /* Remove the plugin */ FD_CLR(proc->fd, &rfds_all); - /* Block signal while modifying process_list */ - ret = (int)TEMP_FAILURE_RETRY(sigprocmask - (SIG_BLOCK, - &sigchld_action.sa_mask, - NULL)); + ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL); if(ret < 0){ - error(0, errno, "sigprocmask"); - exitstatus = EX_OSERR; + perror("sigprocmask"); + exitstatus = EXIT_FAILURE; goto fallback; } - - plugin *next_plugin = proc->next; - free_plugin(proc); - proc = next_plugin; - + /* Delete this process entry from the list */ + if(process_list == proc){ + /* First one - simple */ + process_list = proc->next; + } else { + /* Second one or later */ + for(process *p = process_list; p != NULL; p = p->next){ + if(p->next == proc){ + p->next = proc->next; + break; + } + } + } /* We are done modifying process list, so unblock signal */ - ret = (int)(TEMP_FAILURE_RETRY - (sigprocmask(SIG_UNBLOCK, - &sigchld_action.sa_mask, NULL))); + ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, + NULL); if(ret < 0){ - error(0, errno, "sigprocmask"); - exitstatus = EX_OSERR; - goto fallback; - } - - if(plugin_list == NULL){ - break; - } - - continue; + perror("sigprocmask"); + } + free(proc->buffer); + free(proc); + /* We deleted this process from the list, so we can't go + proc->next. Therefore, start over from the beginning of + the process list */ + break; } - /* This process exited nicely, so print its buffer */ - + bool bret = print_out_password(proc->buffer, proc->buffer_length); if(not bret){ - error(0, errno, "print_out_password"); - exitstatus = EX_IOERR; + perror("print_out_password"); + exitstatus = EXIT_FAILURE; } goto fallback; } - /* This process has not completed. Does it have any output? */ if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ /* 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"); - exitstatus = EX_OSERR; + proc->buffer = realloc(proc->buffer, proc->buffer_size + + (size_t) BUFFER_SIZE); + if (proc->buffer == NULL){ + perror("malloc"); + exitstatus = EXIT_FAILURE; goto fallback; } - proc->buffer = new_buffer; proc->buffer_size += BUFFER_SIZE; } /* Read from the process */ - sret = TEMP_FAILURE_RETRY(read(proc->fd, - proc->buffer - + proc->buffer_length, - BUFFER_SIZE)); - if(sret < 0){ + ret = read(proc->fd, proc->buffer + proc->buffer_length, + BUFFER_SIZE); + if(ret < 0){ /* Read error from this process; ignore the error */ - proc = proc->next; continue; } - if(sret == 0){ + if(ret == 0){ /* got EOF */ proc->eof = true; } else { - proc->buffer_length += (size_t) sret; + proc->buffer_length += (size_t) ret; } } } - - + + fallback: - if(plugin_list == NULL or (exitstatus != EXIT_SUCCESS - and exitstatus != EX_OK)){ + if(process_list == NULL or exitstatus != EXIT_SUCCESS){ /* Fallback if all plugins failed, none are found or an error occured */ bool bret; fprintf(stderr, "Going to fallback mode using getpass(3)\n"); char *passwordbuffer = getpass("Password: "); - size_t len = strlen(passwordbuffer); - /* Strip trailing newline */ - if(len > 0 and passwordbuffer[len-1] == '\n'){ - passwordbuffer[len-1] = '\0'; /* not strictly necessary */ - len--; - } - bret = print_out_password(passwordbuffer, len); + bret = print_out_password(passwordbuffer, strlen(passwordbuffer)); if(not bret){ - error(0, errno, "print_out_password"); - exitstatus = EX_IOERR; + perror("print_out_password"); + exitstatus = EXIT_FAILURE; } } /* Restore old signal handler */ ret = sigaction(SIGCHLD, &old_sigchld_action, NULL); if(ret == -1){ - error(0, errno, "sigaction"); - exitstatus = EX_OSERR; + perror("sigaction"); + exitstatus = EXIT_FAILURE; } - + if(custom_argv != NULL){ for(char **arg = custom_argv+1; *arg != NULL; arg++){ free(*arg); } free(custom_argv); } - - free(direntries); - - if(dir_fd != -1){ - close(dir_fd); + free_plugin_list(plugin_list); + + if(dir != NULL){ + closedir(dir); } - /* Kill the processes */ - for(plugin *p = plugin_list; p != NULL; p = p->next){ - if(p->pid != 0){ - close(p->fd); - ret = kill(p->pid, SIGTERM); - if(ret == -1 and errno != ESRCH){ - /* Set-uid proccesses might not get closed */ - error(0, errno, "kill"); - } + /* Free the process list and kill the processes */ + for(process *next; process_list != NULL; process_list = next){ + next = process_list->next; + close(process_list->fd); + ret = kill(process_list->pid, SIGTERM); + if(ret == -1 and errno != ESRCH){ + /* set-uid proccesses migth not get closed */ + perror("kill"); } + free(process_list->buffer); + free(process_list); } /* Wait for any remaining child processes to terminate */ - do { + do{ 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; === removed file 'plugin-runner.conf' --- plugin-runner.conf 2009-04-17 08:26:17 +0000 +++ plugin-runner.conf 1970-01-01 00:00:00 +0000 @@ -1,10 +0,0 @@ -## This is the configuration file for plugin-runner(8mandos). This -## file should be installed as "/etc/mandos/plugin-runner.conf", and -## will be copied to "/conf/conf.d/mandos/plugin-runner.conf" in the -## initrd.img file. -## -## After editing this file, the initrd image file must be updated for -## the changes to take effect! - -## Example: -#--options-for=mandos-client:--debug === modified file 'plugin-runner.xml' --- plugin-runner.xml 2019-02-09 23:23:26 +0000 +++ plugin-runner.xml 2008-08-16 20:31:21 +0000 @@ -1,53 +1,63 @@ - + + - - -%common; ]> - + - Mandos Manual - - Mandos - &version; - &TIMESTAMP; + &COMMANDNAME; + + &COMMANDNAME; + &VERSION; 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 - Teddy Hogeborn - Björn Påhlsson + Teddy Hogeborn & Björn Påhlsson - + + + 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 this program; If not, see + . + +
- + &COMMANDNAME; 8mandos @@ -56,642 +66,209 @@ &COMMANDNAME; - Run Mandos plugins, pass data from first to succeed. + get password for encrypted rootdisk - + &COMMANDNAME; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - &COMMANDNAME; - - - - - - - &COMMANDNAME; - - - - &COMMANDNAME; - - - - - + --global-optionsOPTIONS + --options-forPLUGIN:OPTIONS + --disablePLUGIN + --groupidID + --useridID + --plugin-dirDIRECTORY + --debug + + + &COMMANDNAME; + --help + + + &COMMANDNAME; + --usage + + + &COMMANDNAME; + --version + - + DESCRIPTION - &COMMANDNAME; is a program which is meant to - be specified as a keyscript for the root disk in + &COMMANDNAME; is a plugin runner that waits + for any of its plugins to return sucessfull with a password, and + passes it to cryptsetup as stdout message. This command is not + meant to be invoked directly, but is instead meant to be run by + cryptsetup by being specified in /etc/crypttab as a keyscript + and subsequlently started in the initrd environment. See crypttab - 5. The aim of this - program is therefore to output a password, which then - cryptsetup - 8 will use to unlock the - root disk. - - - This program is not meant to be invoked directly, but can be in - order to test it. Note that any password obtained will simply - be output on standard output. - - - - - 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. - - - + 5 for more information on + keyscripts. + + + + plugins is looked for in the plugins directory which by default will be + /conf/conf.d/mandos/plugins.d if not changed by option --plugin-dir. + + OPTIONS - - - - - This option will add an environment variable setting to - all plugins. This will override any inherited environment - variable. - - - - - - - - - - This option will add an environment variable setting to - the PLUGIN plugin. This will - override any inherited environment variables or - environment variables specified using - . - - - - - - - - - - Pass some options to all plugins. - OPTIONS is a comma separated - list of options. This is not a very useful option, except - for specifying the - option to all plugins. - - - - - - - - - - Pass some options to a specific plugin. PLUGIN is the name (file basename) of a - plugin, and OPTIONS is a comma - separated list of options. - - - Note that since options are not split on whitespace, the - way to pass, to the plugin - foo, the option - with the option argument - baz is either - --options-for=foo:--bar=baz or - --options-for=foo:--bar,baz. Using - --options-for="foo:--bar baz". will - not work. - - - - - - - - - - Disable the plugin named - PLUGIN. The plugin will not be - started. - - - - - - - - - - Re-enable the plugin named - PLUGIN. This is only useful to - undo a previous option, maybe - from the configuration file. - - - - - - - - - Change to group ID ID on - startup. The default is 65534. All plugins will be - started using this group ID. Note: - This must be a number, not a name. - - - - - - - - - Change to user ID ID on - startup. The default is 65534. All plugins will be - started using this user ID. Note: - This must be a number, not a name. - - - - - - - - - Specify a different plugin directory. The default is - /lib/mandos/plugins.d, which will - exist in the initial RAM disk - environment. - - - - - - - - - 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 ) - - - - - - - - - Specify a different file to read additional options from. - See . Other command line options - will override options specified in the file. - - - - - - - - - Enable debug mode. This will enable a lot of output to - standard error about what the program is doing. The - program will still perform all other functions normally. - The default is to not run in debug - mode. - - - The plugins will not be affected by - this option. Use - - if complete debugging eruption is desired. - - - - - - - - - - Gives a help message about options and their meanings. - - - - - - - - - Gives a short usage message. - - - - - - - - - - Prints the program version. - - - + -g,--global-options + OPTIONS + + + Global options given to all plugins as additional start + arguments. Options are specified with a -o flag followed + by a comma separated string of options. + + + + + + -o, --options-for + PLUGIN:OPTION + + + + Plugin specific options given to the plugin as additional + start arguments. Options are specified with a -o flag + followed by a comma separated string of options. + + + + + + -d, --disable + PLUGIN + + + + Disable a specific plugin + + + + + + --groupid ID + + + + Group ID the plugins will run as + + + + + + --userid ID + + + + User ID the plugins will run as + + + + + + --plugin-dir DIRECTORY + + + + Specify a different plugin directory + + + + + + --debug + + + Debug mode + + + + + + -?, --help + + + Gives a help message + + + + + + --usage + + + Gives a short usage message + + + + + + -V, --version + + + Prints the program version + + + - - - OVERVIEW - - - This program will run on the client side in the initial - RAM disk environment, and is responsible for - getting a password. It does this by running plugins, one of - which will normally be the actual client program communicating - with the server. - - - - PLUGINS - - This program will get a password by running a number of - plugins, which are simply executable - programs in a directory in the initial RAM - disk environment. The default directory is - /lib/mandos/plugins.d, but this can be - changed with the option. The - plugins are started in parallel, and the first plugin to output - a password and exit with a successful exit - code will make this plugin-runner output the password from that - plugin, stop any other plugins, and exit. - - - - WRITING PLUGINS - - A plugin is simply a program which prints a password to its - standard output and then exits with a successful (zero) exit - status. If the exit status is not zero, any output on - standard output will be ignored by the plugin runner. Any - output on its standard error channel will simply be passed to - the standard error of the plugin runner, usually the system - console. - - - If the password is a single-line, manually entered passprase, - a final trailing newline character should - not be printed. - - - 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. - - - The plugin must exit cleanly and free all allocated resources - upon getting the TERM signal, since this is what the plugin - runner uses to stop all other plugins when one plugin has - output a password and exited cleanly. - - - The plugin must not use resources, like for instance reading - from the standard input, without knowing that no other plugin - is also using it. - - - It is useful, but not required, for the plugin to take the - option. - - - - - - FALLBACK - - If no plugins succeed, this program will, as a fallback, ask for - a password on the console using getpass3, - and output it. This is not meant to be the normal mode of - operation, as there is a separate plugin for getting a password - from the console. - - - + EXIT STATUS - Exit status of this program is zero if no errors were - encountered, and otherwise not. The fallback (see ) may or may not have succeeded in either - case. - - - - - ENVIRONMENT - - This program does not use any environment variables itself, it - 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. - - - - + + + + FILES - - - /conf/conf.d/mandos/plugin-runner.conf - - - Since this program will be run as a keyscript, there is - little to no opportunity to pass command line arguments - to it. Therefore, it will also - read this file and use its contents as - whitespace-separated command line options. Also, - everything from a # character to the end - of a line is ignored. - - - This program is meant to run in the initial RAM disk - environment, so that is where this file is assumed to - exist. The file does not need to exist in the normal - file system. - - - This file will be processed before - the normal command line options, so the latter can - override the former, if need be. - - - This file name is the default; the file to read for - arguments can be changed using the - option. - - - - - /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. - - - - + + + + + NOTES + BUGS - The option is ignored when - specified from within a configuration file. - - - + + - EXAMPLE - - - Normal invocation needs no options: - - - &COMMANDNAME; - - - - - Run the program, but not the plugins, in debug mode: - - - - - &COMMANDNAME; --debug - - - - - - Run all plugins, but run the foo plugin in - debug mode: - - - - - &COMMANDNAME; --options-for=foo:--debug - - - - - - Run all plugins, but not the program, in debug mode: - - - - - &COMMANDNAME; --global-options=--debug - - - - - - Read a different configuration file, run plugins from a - different directory, specify an alternate plugin helper - directory and add two options to the - mandos-client - 8mandos plugin: - - - - -cd /etc/keys/mandos; &COMMANDNAME; --config-file=/etc/mandos/plugin-runner.conf --plugin-dir /usr/lib/x86_64-linux-gnu/mandos/plugins.d --plugin-helper-dir /usr/lib/x86_64-linux-gnu/mandos/plugin-helpers --options-for=mandos-client:--pubkey=pubkey.txt,--seckey=seckey.txt,--tls-pubkey=tls-pubkey.pem,--tls-privkey=tls-privkey.pem - - - + EXAMPLES + + + SECURITY - This program will, when starting, try to switch to another user. - If it is started as root, it will succeed, and will by default - switch to user and group 65534, which are assumed to be - non-privileged. This user and group is then what all plugins - will be started as. Therefore, the only way to run a plugin as - a privileged user is to have the set-user-ID or set-group-ID bit - set on the plugin executable file (see - execve2 - ). - - - If this program is used as a keyscript in crypttab5 - , there is a slight risk that if this program - fails to work, there might be no way to boot the system except - for booting from another media and editing the initial RAM disk - image to not run this program. This is, however, unlikely, - since the password-prompt8mandos - plugin will read a password from the console in - case of failure of the other plugins, and this plugin runner - will also, in case of catastrophic failure, itself fall back to - asking and outputting a password on the console (see ). - + SEE ALSO - intro - 8mandos, - cryptsetup - 8, - crypttab - 5, - execve - 2, mandos - 8, - password-prompt - 8mandos, - mandos-client - 8mandos + 8, + password-request + 8mandos, + password-prompt + 8mandos, and + cryptsetup + 8 - +
- - - - - === removed file 'plugins.d/askpass-fifo.c' --- plugins.d/askpass-fifo.c 2018-02-08 10:23:55 +0000 +++ plugins.d/askpass-fifo.c 1970-01-01 00:00:00 +0000 @@ -1,218 +0,0 @@ -/* -*- coding: utf-8 -*- */ -/* - * Askpass-FIFO - Read a password from a FIFO 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 - * WITHOUT 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 /* TEMP_FAILURE_RETRY() */ -#include /* uid_t, gid_t, ssize_t */ -#include /* mkfifo(), S_IRUSR, S_IWUSR */ -#include /* and */ -#include /* errno, EACCES, ENOTDIR, ELOOP, - ENAMETOOLONG, ENOSPC, EROFS, - ENOENT, EEXIST, EFAULT, EMFILE, - ENFILE, ENOMEM, EBADF, EINVAL, EIO, - EISDIR, EFBIG */ -#include /* error() */ -#include /* fprintf(), vfprintf(), - vasprintf() */ -#include /* EXIT_FAILURE, NULL, size_t, free(), - realloc(), EXIT_SUCCESS */ -#include /* open(), O_RDONLY */ -#include /* read(), close(), write(), - STDOUT_FILENO */ -#include /* EX_OSERR, EX_OSFILE, - EX_UNAVAILABLE, EX_IOERR */ -#include /* 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"); - return; - } - fprintf(stderr, "Mandos plugin "); - error(status, errnum, "%s", text); - 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; - switch(e){ - case EACCES: - case ENOTDIR: - case ELOOP: - error_plus(EX_OSFILE, errno, "mkfifo"); - case ENAMETOOLONG: - case ENOSPC: - case EROFS: - default: - error_plus(EX_OSERR, errno, "mkfifo"); - case ENOENT: - /* no "/lib/cryptsetup"? */ - error_plus(EX_UNAVAILABLE, errno, "mkfifo"); - case EEXIST: - break; /* not an error */ - } - } - - /* Open FIFO */ - int fifo_fd = open(passfifo, O_RDONLY); - if(fifo_fd == -1){ - int e = errno; - error_plus(0, errno, "open"); - switch(e){ - case EACCES: - case ENOENT: - case EFAULT: - return EX_UNAVAILABLE; - case ENAMETOOLONG: - case EMFILE: - case ENFILE: - case ENOMEM: - default: - return EX_OSERR; - case ENOTDIR: - case ELOOP: - return EX_OSFILE; - } - } - - /* 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; - { - size_t buf_allocated = 0; - const size_t blocksize = 1024; - do { - if(buf_len + blocksize > buf_allocated){ - char *tmp = realloc(buf, buf_allocated + blocksize); - if(tmp == NULL){ - error_plus(0, errno, "realloc"); - free(buf); - return EX_OSERR; - } - buf = tmp; - buf_allocated += blocksize; - } - sret = read(fifo_fd, buf + buf_len, buf_allocated - buf_len); - if(sret == -1){ - int e = errno; - free(buf); - errno = e; - error_plus(0, errno, "read"); - switch(e){ - case EBADF: - case EFAULT: - case EINVAL: - default: - return EX_OSERR; - case EIO: - return EX_IOERR; - case EISDIR: - return EX_UNAVAILABLE; - } - } - buf_len += (size_t)sret; - } while(sret != 0); - } - - /* Close FIFO */ - close(fifo_fd); - - /* Print password to stdout */ - size_t written = 0; - while(written < buf_len){ - sret = write(STDOUT_FILENO, buf + written, buf_len - written); - if(sret == -1){ - int e = errno; - free(buf); - errno = e; - error_plus(0, errno, "write"); - switch(e){ - case EBADF: - case EFAULT: - case EINVAL: - return EX_OSFILE; - case EFBIG: - case EIO: - case ENOSPC: - default: - return EX_IOERR; - } - } - written += (size_t)sret; - } - free(buf); - - ret = close(STDOUT_FILENO); - if(ret == -1){ - int e = errno; - error_plus(0, errno, "close"); - switch(e){ - case EBADF: - return EX_OSFILE; - case EIO: - default: - return EX_IOERR; - } - } - return EXIT_SUCCESS; -} === removed file 'plugins.d/askpass-fifo.xml' --- plugins.d/askpass-fifo.xml 2018-02-08 10:23:55 +0000 +++ plugins.d/askpass-fifo.xml 1970-01-01 00:00:00 +0000 @@ -1,177 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2008 - 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - Teddy Hogeborn - Björn Påhlsson - -
- - - &COMMANDNAME; - 8mandos - - - - &COMMANDNAME; - Mandos plugin to get a password from a - FIFO. - - - - - &COMMANDNAME; - - - - - DESCRIPTION - - This program reads a password from a FIFO and - outputs it to standard output. - - - 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. - - - This program is meant to be imitate a feature of the - askpass program, so that programs written to - interface with it can keep working under the - Mandos system. - - - - - 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. - - - - - FILES - - - /lib/cryptsetup/passfifo - - - This is the FIFO where this program - will read the password. If it does not exist, it will be - created. - - - - - - - - BUGS - - - - - 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 - - The only 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, - fifo - 7, - plugin-runner - 8mandos - - -
- - - - - === modified file 'plugins.d/password-prompt.c' --- plugins.d/password-prompt.c 2018-02-08 10:23:55 +0000 +++ plugins.d/password-prompt.c 2008-08-16 16:58:31 +0000 @@ -1,235 +1,66 @@ -/* -*- coding: utf-8; mode: c; mode: orgtbl -*- */ +/* -*- coding: utf-8 -*- */ /* - * Password-prompt - Read a password from the terminal and print 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 + * Passprompt - Read a password from the terminal and print it + * + * Copyright © 2007-2008 Teddy Hogeborn & 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 and + * . */ -#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() */ + SIGQUIT, SIGHUP, SIGTERM */ #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() */ + getopt_long, getenv() */ #include /* fprintf(), stderr, getline(), - stdin, feof(), fputc(), vfprintf(), - vasprintf() */ -#include /* errno, EBADF, ENOTTY, EINVAL, - EFAULT, EFBIG, EIO, ENOSPC, EINTR - */ -#include /* error() */ + stdin, feof(), perror(), fputc(), + stdout, getopt_long */ +#include /* errno, EINVAL */ #include /* or, not */ #include /* bool, false, true */ -#include /* strtoumax() */ -#include /* struct stat, lstat(), open() */ -#include /* strlen, rindex, memcmp, strerror() - */ +#include /* strlen, rindex, strncmp, strcmp */ #include /* struct argp_option, struct argp_state, struct argp, argp_parse(), error_t, ARGP_KEY_ARG, ARGP_KEY_END, ARGP_ERR_UNKNOWN */ -#include /* EX_SOFTWARE, EX_OSERR, - EX_UNAVAILABLE, EX_IOERR, EX_OK */ -#include /* open() */ -#include /* va_list, va_start(), ... */ -volatile sig_atomic_t quit_now = 0; -int signal_received; +volatile bool quit_now = false; 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); -} - -static void termination_handler(int signum){ - if(quit_now){ - return; - } - quit_now = 1; - 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; -} - +const char *argp_program_version = "password-prompt 1.0"; +const char *argp_program_bug_address = ""; + +static void termination_handler(__attribute__((unused))int signum){ + quit_now = true; +} 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; @@ -242,182 +73,112 @@ struct argp_option options[] = { { .name = "prefix", .key = 'p', .arg = "PREFIX", .flags = 0, - .doc = "Prefix shown before the prompt", .group = 2 }, + .doc = "Prefix used before the passprompt", .group = 2 }, { .name = "debug", .key = 128, .doc = "Debug mode", .group = 3 }, - /* - * These reproduce what we would get without ARGP_NO_HELP - */ - { .name = "help", .key = '?', - .doc = "Give this help list", .group = -1 }, - { .name = "usage", .key = -3, - .doc = "Give a short usage message", .group = -1 }, - { .name = "version", .key = 'V', - .doc = "Print program version", .group = -1 }, { .name = NULL } }; - - __attribute__((nonnull(3))) - error_t parse_opt (int key, char *arg, struct argp_state *state){ - errno = 0; - switch (key){ + + error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the INPUT argument from `argp_parse', which we know is a + pointer to our plugin list pointer. */ + switch (key) { case 'p': prefix = arg; break; case 128: debug = true; break; - /* - * These reproduce what we would get without ARGP_NO_HELP - */ - case '?': /* --help */ - argp_state_help(state, state->out_stream, - (ARGP_HELP_STD_HELP | ARGP_HELP_EXIT_ERR) - & ~(unsigned int)ARGP_HELP_EXIT_OK); - case -3: /* --usage */ - argp_state_help(state, state->out_stream, - ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR); - case 'V': /* --version */ - fprintf(state->out_stream, "%s\n", argp_program_version); - exit(argp_err_exit_status); + case ARGP_KEY_ARG: + argp_usage (state); + break; + case ARGP_KEY_END: break; default: return ARGP_ERR_UNKNOWN; } - return errno; + return 0; } - + struct argp argp = { .options = options, .parser = parse_opt, .args_doc = "", - .doc = "Mandos password-prompt -- Read and" - " output a password" }; - ret = argp_parse(&argp, argc, argv, - ARGP_IN_ORDER | ARGP_NO_HELP, NULL, NULL); - switch(ret){ - case 0: - break; - case ENOMEM: - default: - errno = ret; - error_plus(0, errno, "argp_parse"); - return EX_OSERR; - case EINVAL: - return EX_USAGE; + .doc = "Mandos Passprompt -- Provides a passprompt" }; + ret = argp_parse (&argp, argc, argv, 0, 0, NULL); + if (ret == ARGP_ERR_UNKNOWN){ + fprintf(stderr, "Unknown error while parsing arguments\n"); + return EXIT_FAILURE; } } - - if(debug){ + + 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){ + if (debug){ fprintf(stderr, "Storing current terminal attributes\n"); } - if(tcgetattr(STDIN_FILENO, &t_old) != 0){ - int e = errno; - error_plus(0, errno, "tcgetattr"); - switch(e){ - case EBADF: - case ENOTTY: - return EX_UNAVAILABLE; - default: - return EX_OSERR; - } + if (tcgetattr(STDIN_FILENO, &t_old) != 0){ + return EXIT_FAILURE; } sigemptyset(&new_action.sa_mask); - ret = sigaddset(&new_action.sa_mask, SIGINT); - if(ret == -1){ - error_plus(0, errno, "sigaddset"); - return EX_OSERR; - } - ret = sigaddset(&new_action.sa_mask, SIGHUP); - if(ret == -1){ - error_plus(0, errno, "sigaddset"); - return EX_OSERR; - } - ret = sigaddset(&new_action.sa_mask, SIGTERM); - if(ret == -1){ - error_plus(0, errno, "sigaddset"); - return EX_OSERR; - } - /* Need to check if the handler is SIG_IGN before handling: - | [[info:libc:Initial Signal Actions]] | - | [[info:libc:Basic Signal Handling]] | - */ + sigaddset(&new_action.sa_mask, SIGINT); + sigaddset(&new_action.sa_mask, SIGHUP); + sigaddset(&new_action.sa_mask, SIGTERM); ret = sigaction(SIGINT, NULL, &old_action); if(ret == -1){ - error_plus(0, errno, "sigaction"); - return EX_OSERR; + perror("sigaction"); + return EXIT_FAILURE; } - if(old_action.sa_handler != SIG_IGN){ + if (old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGINT, &new_action, NULL); if(ret == -1){ - error_plus(0, errno, "sigaction"); - return EX_OSERR; + perror("sigaction"); + return EXIT_FAILURE; } } ret = sigaction(SIGHUP, NULL, &old_action); if(ret == -1){ - error_plus(0, errno, "sigaction"); - return EX_OSERR; + perror("sigaction"); + return EXIT_FAILURE; } - if(old_action.sa_handler != SIG_IGN){ + if (old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGHUP, &new_action, NULL); if(ret == -1){ - error_plus(0, errno, "sigaction"); - return EX_OSERR; + perror("sigaction"); + return EXIT_FAILURE; } } ret = sigaction(SIGTERM, NULL, &old_action); if(ret == -1){ - error_plus(0, errno, "sigaction"); - return EX_OSERR; + perror("sigaction"); + return EXIT_FAILURE; } - if(old_action.sa_handler != SIG_IGN){ + if (old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGTERM, &new_action, NULL); if(ret == -1){ - error_plus(0, errno, "sigaction"); - return EX_OSERR; + perror("sigaction"); + return EXIT_FAILURE; } } - if(debug){ + if (debug){ fprintf(stderr, "Removing echo flag from terminal attributes\n"); } t_new = t_old; - t_new.c_lflag &= ~(tcflag_t)ECHO; - if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_new) != 0){ - int e = errno; - error_plus(0, errno, "tcsetattr-echo"); - switch(e){ - case EBADF: - case ENOTTY: - return EX_UNAVAILABLE; - case EINVAL: - default: - return EX_OSERR; - } + t_new.c_lflag &= ~ECHO; + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_new) != 0){ + perror("tcsetattr-echo"); + return EXIT_FAILURE; } - - if(debug){ + + if (debug){ fprintf(stderr, "Waiting for input from stdin \n"); } while(true){ - if(quit_now){ - if(debug){ - fprintf(stderr, "Interrupted by signal, exiting.\n"); - } + if (quit_now){ status = EXIT_FAILURE; break; } @@ -426,136 +187,52 @@ fprintf(stderr, "%s ", prefix); } { - const char *cryptsource = getenv("CRYPTTAB_SOURCE"); - const char *crypttarget = getenv("CRYPTTAB_NAME"); - /* Before cryptsetup 1.1.0~rc2 */ - if(cryptsource == NULL){ - cryptsource = getenv("cryptsource"); - } - if(crypttarget == NULL){ - crypttarget = getenv("crypttarget"); - } - const char *const prompt1 = "Unlocking the disk"; - const char *const prompt2 = "Enter passphrase"; + const char *cryptsource = getenv("cryptsource"); + const char *crypttarget = getenv("crypttarget"); + const char *const prompt + = "Enter passphrase to unlock the disk"; if(cryptsource == NULL){ if(crypttarget == NULL){ - fprintf(stderr, "%s to unlock the disk: ", prompt2); + fprintf(stderr, "%s: ", prompt); } else { - fprintf(stderr, "%s (%s)\n%s: ", prompt1, crypttarget, - prompt2); + fprintf(stderr, "%s (%s): ", prompt, crypttarget); } } else { if(crypttarget == NULL){ - fprintf(stderr, "%s %s\n%s: ", prompt1, cryptsource, - prompt2); + fprintf(stderr, "%s %s: ", prompt, cryptsource); } else { - fprintf(stderr, "%s %s (%s)\n%s: ", prompt1, cryptsource, - crypttarget, prompt2); + fprintf(stderr, "%s %s (%s): ", prompt, cryptsource, + crypttarget); } } } - sret = getline(&buffer, &n, stdin); - if(sret > 0){ + ret = getline(&buffer, &n, stdin); + if (ret > 0){ + fprintf(stdout, "%s", buffer); status = EXIT_SUCCESS; - /* Make n = data size instead of allocated buffer size */ - n = (size_t)sret; - /* Strip final newline */ - if(n > 0 and buffer[n-1] == '\n'){ - buffer[n-1] = '\0'; /* not strictly necessary */ - n--; - } - size_t written = 0; - while(written < n){ - sret = write(STDOUT_FILENO, buffer + written, n - written); - if(sret < 0){ - int e = errno; - error_plus(0, errno, "write"); - switch(e){ - case EBADF: - case EFAULT: - case EINVAL: - case EFBIG: - case EIO: - case ENOSPC: - default: - status = EX_IOERR; - break; - case EINTR: - status = EXIT_FAILURE; - break; - } - break; - } - written += (size_t)sret; - } - sret = close(STDOUT_FILENO); - if(sret == -1){ - int e = errno; - error_plus(0, errno, "close"); - switch(e){ - case EBADF: - status = EX_OSFILE; - break; - case EIO: - default: - status = EX_IOERR; - break; - } - } break; } - if(sret < 0){ - int e = errno; - if(errno != EINTR and not feof(stdin)){ - error_plus(0, errno, "getline"); - switch(e){ - case EBADF: - status = EX_UNAVAILABLE; - break; - case EIO: - case EINVAL: - default: - status = EX_IOERR; - break; - } + if (ret < 0){ + if (errno != EINTR and not feof(stdin)){ + perror("getline"); + status = EXIT_FAILURE; 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 - will print that later, so no need to show this too. */ - fprintf(stderr, "getline() returned 0, retrying.\n"); - } } - free(buffer); - - if(debug){ + if (debug){ fprintf(stderr, "Restoring terminal attributes\n"); } - if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_old) != 0){ - error_plus(0, errno, "tcsetattr+echo"); - } - - if(quit_now){ - sigemptyset(&old_action.sa_mask); - old_action.sa_handler = SIG_DFL; - ret = sigaction(signal_received, &old_action, NULL); - if(ret == -1){ - error_plus(0, errno, "sigaction"); - } - raise(signal_received); - } - - if(debug){ - fprintf(stderr, "%s is exiting with status %d\n", argv[0], - status); - } - if(status == EXIT_SUCCESS or status == EX_OK){ - fputc('\n', stderr); + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_old) != 0){ + perror("tcsetattr+echo"); + } + + if (debug){ + fprintf(stderr, "%s is exiting\n", argv[0]); } return status; === modified file 'plugins.d/password-prompt.xml' --- plugins.d/password-prompt.xml 2018-02-08 10:23:55 +0000 +++ plugins.d/password-prompt.xml 2008-08-18 05:24:20 +0000 @@ -1,53 +1,63 @@ - + + - - -%common; ]> - + - Mandos Manual - - Mandos - &version; - &TIMESTAMP; + &COMMANDNAME; + + &COMMANDNAME; + &VERSION; 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 - Teddy Hogeborn - Björn Påhlsson + Teddy Hogeborn & Björn Påhlsson - + + + 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 this program; If not, see + . + +
- + &COMMANDNAME; 8mandos @@ -55,263 +65,146 @@ &COMMANDNAME; - Prompt for a password and output it. + + Passprompt for luks during boot sequence + &COMMANDNAME; - - - PREFIX - - - - - - &COMMANDNAME; - - - - - - - &COMMANDNAME; - - - - &COMMANDNAME; - - - - - + --prefixPREFIX + --debug + + + &COMMANDNAME; + --help + + + &COMMANDNAME; + --usage + + + &COMMANDNAME; + --version + - + DESCRIPTION - All &COMMANDNAME; does is prompt for a - password and output any given password to standard output. - - - 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. - - - This program is little more than a getpass3 - wrapper, although actual use of that function is not guaranteed - or implied. + &COMMANDNAME; is a terminal program that ask for + passwords during boot sequence. It is a plugin to + mandos, and is used as a fallback and + alternative to retriving passwords from a mandos server. During + boot sequence the user is prompted for the disk password, and + when a password is given it then gets forwarded to + LUKS. OPTIONS - This program is commonly not invoked from the command line; it - is normally started by the Mandos - plugin runner, see plugin-runner8mandos - . Any command line options this program accepts - are therefore normally provided by the plugin runner, and not - directly. + Commonly not invoked as command lines but from configuration + file of plugin runner. - + - - - - - Prefix string shown before the password prompt. - - - - - - - - - Enable debug mode. This will enable a lot of output to - standard error about what the program is doing. The - program will still perform all other functions normally. - - - - - - - - - - Gives a help message about options and their meanings. - - - - - - - - - Gives a short usage message. - - - - - - - - - - Prints the program version. - - - + -p, --prefix=PREFIX + + + + Prefix used before the passprompt + + + + + + --debug + + + Debug mode + + + + + + -?, --help + + + Gives a help message + + + + + + --usage + + + Gives a short usage message + + + + + + -V, --version + + + Prints the program version + + + - + 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 has encountered an error, and any output so far could be - corrupt and/or truncated, and should therefore be ignored. - + ENVIRONMENT - - - CRYPTTAB_SOURCE - CRYPTTAB_NAME - - - 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 + + BUGS - - - + + + + EXAMPLE - Note that normally, command line options will not be given - directly, but via options for the Mandos plugin-runner - 8mandos. - - - Normal invocation needs no options: - - - &COMMANDNAME; - - - - - Show a prefix before the prompt; in this case, a host name. - It might be useful to be reminded of which host needs a - password, in case of KVM switches, etc. - - - - -&COMMANDNAME; --prefix=host.example.org: - - - - - - Run in debug mode. - - - - &COMMANDNAME; --debug - - - + SECURITY - On its own, this program is very simple, and does not exactly - present any security risks. The one 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. - - - To further alleviate any risk of being locked out of a system, - the plugin-runner - 8mandos has a fallback - mode which does the same thing as this program, only with less - features. - + SEE ALSO - intro - 8mandos - crypttab - 5 - mandos-client - 8mandos - plugin-runner - 8mandos, + mandos + 8, + plugin-runner + 8mandos and + password-request + 8mandos - + +
- - - - - === renamed file 'plugins.d/mandos-client.c' => 'plugins.d/password-request.c' --- plugins.d/mandos-client.c 2019-02-09 23:23:26 +0000 +++ plugins.d/password-request.c 2008-08-24 10:49:09 +0000 @@ -1,6 +1,6 @@ /* -*- coding: utf-8 -*- */ /* - * Mandos-client - get and decrypt data from a Mandos server + * Mandos client - get and decrypt data from a Mandos server * * This program is partly derived from an example program for an Avahi * service browser, downloaded from @@ -9,102 +9,65 @@ * "browse_callback", and parts of "main". * * Everything else is - * 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 © 2007-2008 Teddy Hogeborn & 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 */ -#ifndef _FILE_OFFSET_BITS #define _FILE_OFFSET_BITS 64 -#endif /* not _FILE_OFFSET_BITS */ #define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), asprintf() */ #include /* fprintf(), stderr, fwrite(), stdout, ferror() */ -#include /* uint16_t, uint32_t, intptr_t */ +#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 /* ioctl */ +#include /* free(), EXIT_SUCCESS, EXIT_FAILURE, + srand() */ +#include /* bool, true */ +#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 */ + SOCK_STREAM, INET6_ADDRSTRLEN, + uid_t, gid_t */ +#include /* PRIu16 */ #include /* socket(), struct sockaddr_in6, - inet_pton(), connect(), - getnameinfo() */ -#include /* open(), unlinkat(), AT_REMOVEDIR */ -#include /* opendir(), struct dirent, readdir() - */ -#include /* PRIu16, PRIdMAX, intmax_t, - strtoimax() */ -#include /* perror(), errno, EINTR, EINVAL, - EAI_SYSTEM, ENETUNREACH, - EHOSTUNREACH, ECONNREFUSED, EPROTO, - EIO, ENOENT, ENXIO, ENOMEM, EISDIR, - ENOTEMPTY, - program_invocation_short_name */ -#include /* nanosleep(), time(), sleep() */ + struct in6_addr, inet_pton(), + connect() */ +#include /* assert() */ +#include /* perror(), errno */ +#include /* time() */ #include /* ioctl, ifreq, SIOCGIFFLAGS, IFF_UP, SIOCSIFFLAGS, if_indextoname(), if_nametoindex(), IF_NAMESIZE */ -#include /* IN6_IS_ADDR_LINKLOCAL, - INET_ADDRSTRLEN, INET6_ADDRSTRLEN - */ #include /* close(), SEEK_SET, off_t, write(), - getuid(), getgid(), seteuid(), - setgid(), pause(), _exit(), - unlinkat() */ -#include /* inet_pton(), htons() */ -#include /* not, or, and */ + getuid(), getgid(), setuid(), + setgid() */ +#include +#include /* inet_pton(), htons */ +#include /* not, and */ #include /* struct argp_option, error_t, struct argp_state, struct argp, argp_parse(), ARGP_KEY_ARG, ARGP_KEY_END, ARGP_ERR_UNKNOWN */ -#include /* sigemptyset(), sigaddset(), - sigaction(), SIGTERM, sig_atomic_t, - raise() */ -#include /* EX_OSERR, EX_USAGE, EX_UNAVAILABLE, - EX_NOHOST, EX_IOERR, EX_PROTOCOL */ -#include /* waitpid(), WIFEXITED(), - WEXITSTATUS(), WTERMSIG() */ -#include /* setgroups() */ -#include /* 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() */ -#endif /* __linux__ */ /* Avahi */ /* All Avahi types, constants and functions @@ -123,15 +86,8 @@ 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 +#include /* gnutls_certificate_set_openpgp_key_file(), + GNUTLS_OPENPGP_FMT_BASE64 */ /* GPGME */ #include /* All GPGME types, constants and @@ -142,484 +98,147 @@ #define BUFFER_SIZE 256 -#define PATHDIR "/conf/conf.d/mandos" -#define SECKEY "seckey.txt" -#define PUBKEY "pubkey.txt" -#define TLS_PRIVKEY "tls-privkey.pem" -#define TLS_PUBKEY "tls-pubkey.pem" -#define HOOKDIR "/lib/mandos/network-hooks.d" - bool debug = false; +static const char *keydir = "/conf/conf.d/mandos"; 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_version = "password-request 1.0"; +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; - -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, + * Make room in "buffer" for at least BUFFER_SIZE additional 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){ - 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; +size_t adjustbuffer(char **buffer, size_t buffer_length, + size_t buffer_capacity){ + if (buffer_length + BUFFER_SIZE > buffer_capacity){ + *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. + * Decrypt OpenPGP data using keyrings in HOMEDIR. + * Returns -1 on error */ -__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 ssize_t pgp_packet_decrypt (const char *cryptotext, + size_t crypto_size, + char **plaintext, + const char *homedir){ + gpgme_data_t dh_crypto, dh_plain; + gpgme_ctx_t ctx; gpgme_error_t rc; + ssize_t ret; + size_t plaintext_capacity = 0; + ssize_t plaintext_length = 0; gpgme_engine_info_t engine_info; - /* - * Helper function to insert pub and seckey to the engine keyring. - */ - bool import_key(const char * const 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"); - 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)); - return false; - } - - 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)); - return false; - } - { - gpgme_import_result_t import_result - = gpgme_op_import_result(mc->ctx); - if((import_result->imported < 1 - or import_result->not_imported > 0) - and import_result->unchanged == 0){ - fprintf_plus(stderr, "bad gpgme_op_import_results:\n"); - fprintf_plus(stderr, - "The total number of considered keys: %d\n", - import_result->considered); - fprintf_plus(stderr, - "The number of keys without user ID: %d\n", - import_result->no_user_id); - fprintf_plus(stderr, - "The total number of imported keys: %d\n", - import_result->imported); - fprintf_plus(stderr, "The number of imported RSA keys: %d\n", - import_result->imported_rsa); - fprintf_plus(stderr, "The number of unchanged keys: %d\n", - import_result->unchanged); - fprintf_plus(stderr, "The number of new user IDs: %d\n", - import_result->new_user_ids); - fprintf_plus(stderr, "The number of new sub keys: %d\n", - import_result->new_sub_keys); - fprintf_plus(stderr, "The number of new signatures: %d\n", - import_result->new_signatures); - fprintf_plus(stderr, "The number of new revocations: %d\n", - import_result->new_revocations); - fprintf_plus(stderr, - "The total number of secret keys read: %d\n", - import_result->secret_read); - fprintf_plus(stderr, - "The number of imported secret keys: %d\n", - import_result->secret_imported); - fprintf_plus(stderr, - "The number of unchanged secret keys: %d\n", - import_result->secret_unchanged); - fprintf_plus(stderr, "The number of keys not imported: %d\n", - import_result->not_imported); - for(gpgme_import_status_t import_status - = import_result->imports; - import_status != NULL; - import_status = import_status->next){ - fprintf_plus(stderr, "Import status for key: %s\n", - import_status->fpr); - if(import_status->result != GPG_ERR_NO_ERROR){ - fprintf_plus(stderr, "Import result: %s: %s\n", - gpgme_strsource(import_status->result), - gpgme_strerror(import_status->result)); - } - fprintf_plus(stderr, "Key status:\n"); - fprintf_plus(stderr, - import_status->status & GPGME_IMPORT_NEW - ? "The key was new.\n" - : "The key was not new.\n"); - fprintf_plus(stderr, - import_status->status & GPGME_IMPORT_UID - ? "The key contained new user IDs.\n" - : "The key did not contain new user IDs.\n"); - fprintf_plus(stderr, - import_status->status & GPGME_IMPORT_SIG - ? "The key contained new signatures.\n" - : "The key did not contain new signatures.\n"); - fprintf_plus(stderr, - import_status->status & GPGME_IMPORT_SUBKEY - ? "The key contained new sub keys.\n" - : "The key did not contain new sub keys.\n"); - fprintf_plus(stderr, - import_status->status & GPGME_IMPORT_SECRET - ? "The key contained a secret key.\n" - : "The key did not contain a secret key.\n"); - } - return false; - } - } - - ret = close(fd); - if(ret == -1){ - perror_plus("close"); - } - gpgme_data_release(pgp_data); - return true; - } - - if(debug){ - fprintf_plus(stderr, "Initializing GPGME\n"); + if (debug){ + fprintf(stderr, "Trying to decrypt OpenPGP data\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)); - return false; + if (rc != GPG_ERR_NO_ERROR){ + fprintf(stderr, "bad gpgme_engine_check_version: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); + return -1; } /* 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)); - return false; + rc = gpgme_get_engine_info (&engine_info); + if (rc != GPG_ERR_NO_ERROR){ + fprintf(stderr, "bad gpgme_get_engine_info: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); + return -1; } while(engine_info != NULL){ if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){ gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP, - engine_info->file_name, tempdir); + engine_info->file_name, homedir); break; } engine_info = engine_info->next; } if(engine_info == NULL){ - fprintf_plus(stderr, "Could not set GPGME home dir to %s\n", - tempdir); - return false; - } - - /* Create new GPGME "context" */ - rc = gpgme_new(&(mc->ctx)); - if(rc != GPG_ERR_NO_ERROR){ - fprintf_plus(stderr, "bad gpgme_new: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); - return false; - } - - if(not import_key(pubkey) or not import_key(seckey)){ - return false; - } - - return true; -} - -/* - * 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){ - gpgme_data_t dh_crypto, dh_plain; - gpgme_error_t rc; - ssize_t ret; - size_t plaintext_capacity = 0; - ssize_t plaintext_length = 0; - - if(debug){ - fprintf_plus(stderr, "Trying to decrypt OpenPGP data\n"); + fprintf(stderr, "Could not set GPGME home dir to %s\n", homedir); + return -1; } /* 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)); + if (rc != GPG_ERR_NO_ERROR){ + 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)); + if (rc != GPG_ERR_NO_ERROR){ + fprintf(stderr, "bad gpgme_data_new: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); gpgme_data_release(dh_crypto); return -1; } + /* Create new GPGME "context" */ + rc = gpgme_new(&ctx); + if (rc != GPG_ERR_NO_ERROR){ + fprintf(stderr, "bad gpgme_new: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); + plaintext_length = -1; + goto decrypt_end; + } + /* Decrypt data from the cryptotext data buffer to the plaintext data buffer */ - 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)); + rc = gpgme_op_decrypt(ctx, dh_crypto, dh_plain); + if (rc != GPG_ERR_NO_ERROR){ + fprintf(stderr, "bad gpgme_op_decrypt: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); plaintext_length = -1; - if(debug){ + if (debug){ gpgme_decrypt_result_t result; - result = gpgme_op_decrypt_result(mc->ctx); - if(result == NULL){ - fprintf_plus(stderr, "gpgme_op_decrypt_result failed\n"); + result = gpgme_op_decrypt_result(ctx); + if (result == NULL){ + 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; + if(recipient){ + 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,44 +246,44 @@ } 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"); + if (gpgme_data_seek(dh_plain, (off_t) 0, SEEK_SET) == -1){ + perror("pgpme_data_seek"); plaintext_length = -1; goto decrypt_end; } *plaintext = NULL; while(true){ - plaintext_capacity = incbuffer(plaintext, - (size_t)plaintext_length, - plaintext_capacity); - if(plaintext_capacity == 0){ - perror_plus("incbuffer"); - plaintext_length = -1; - goto decrypt_end; + plaintext_capacity = adjustbuffer(plaintext, + (size_t)plaintext_length, + plaintext_capacity); + if (plaintext_capacity == 0){ + perror("adjustbuffer"); + plaintext_length = -1; + goto decrypt_end; } ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length, BUFFER_SIZE); /* Print the data, if any */ - if(ret == 0){ + if (ret == 0){ /* EOF */ break; } if(ret < 0){ - perror_plus("gpgme_data_read"); + perror("gpgme_data_read"); plaintext_length = -1; goto decrypt_end; } plaintext_length += ret; } - + 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,38 +300,36 @@ 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 */ + 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){ +static int init_gnutls_global(mandos_context *mc, + const char *pubkeyfilename, + const char *seckeyfilename){ int ret; if(debug){ - fprintf_plus(stderr, "Initializing GnuTLS\n"); - } - - if(debug){ + 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){ /* "Use a log level over 10 to enable all debugging options." * - GnuTLS manual */ @@ -721,307 +338,95 @@ } /* OpenPGP credentials */ - ret = gnutls_certificate_allocate_credentials(&mc->cred); - if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, "GnuTLS memory error: %s\n", - safer_gnutls_strerror(ret)); + gnutls_certificate_allocate_credentials(&mc->cred); + if (ret != GNUTLS_E_SUCCESS){ + fprintf (stderr, "GnuTLS memory error: %s\n", /* Spurious + warning */ + 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 certificate %s" + " and keyfile %s as GnuTLS credentials\n", pubkeyfilename, + seckeyfilename); } -#if GNUTLS_VERSION_NUMBER >= 0x030606 - ret = gnutls_certificate_set_rawpk_key_file - (mc->cred, pubkeyfilename, seckeyfilename, - GNUTLS_X509_FMT_PEM, /* format */ - NULL, /* pass */ - /* key_usage */ - GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT, - NULL, /* names */ - 0, /* names_length */ - /* privkey_flags */ - GNUTLS_PKCS_PLAIN | GNUTLS_PKCS_NULL_PASSWORD, - 0); /* pkcs11_flags */ -#elif GNUTLS_VERSION_NUMBER < 0x030600 ret = gnutls_certificate_set_openpgp_key_file (mc->cred, pubkeyfilename, seckeyfilename, GNUTLS_OPENPGP_FMT_BASE64); -#else -#error "Needs GnuTLS 3.6.6 or later, or before 3.6.0" -#endif - if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, - "Error[%d] while reading the key pair ('%s'," - " '%s')\n", ret, pubkeyfilename, seckeyfilename); - fprintf_plus(stderr, "The GnuTLS error is: %s\n", - safer_gnutls_strerror(ret)); + if (ret != GNUTLS_E_SUCCESS) { + fprintf(stderr, + "Error[%d] while reading the OpenPGP key pair ('%s'," + " '%s')\n", ret, pubkeyfilename, seckeyfilename); + fprintf(stdout, "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); - } + 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_global_deinit(); return -1; + } -__attribute__((nonnull, warn_unused_result)) -static int init_gnutls_session(gnutls_session_t *session, - mandos_context *mc){ +static int init_gnutls_session(mandos_context *mc, + 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 - )); - 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)); + ret = gnutls_init(session, GNUTLS_SERVER); + if (ret != GNUTLS_E_SUCCESS){ + 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); - 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)); - gnutls_deinit(*session); + ret = gnutls_priority_set_direct(*session, mc->priority, &err); + if (ret != GNUTLS_E_SUCCESS) { + fprintf(stderr, "Syntax error at: %s\n", err); + fprintf(stderr, "GnuTLS error: %s\n", + safer_gnutls_strerror(ret)); + gnutls_deinit (*session); return -1; } } - do { - ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE, - 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)); - gnutls_deinit(*session); + ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE, + mc->cred); + if (ret != GNUTLS_E_SUCCESS) { + fprintf(stderr, "Error setting GnuTLS credentials: %s\n", + safer_gnutls_strerror(ret)); + gnutls_deinit (*session); return -1; } /* ignore client certificate if any. */ - gnutls_certificate_server_set_request(*session, GNUTLS_CERT_IGNORE); + gnutls_certificate_server_set_request (*session, + GNUTLS_CERT_IGNORE); + + gnutls_dh_set_prime_bits (*session, mc->dh_bits); return 0; } @@ -1030,634 +435,222 @@ 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 ret, tcp_sd = -1; - ssize_t sret; - struct sockaddr_storage to; + mandos_context *mc){ + int ret, tcp_sd; + union { struct sockaddr in; struct sockaddr_in6 in6; } to; char *buffer = NULL; - char *decrypted_buffer = NULL; + char *decrypted_buffer; size_t buffer_length = 0; size_t buffer_capacity = 0; + ssize_t decrypted_buffer_size; size_t written; - int retval = -1; + int retval = 0; + char interface[IF_NAMESIZE]; gnutls_session_t session; - int pf; /* Protocol family */ - bool route_added = false; - - errno = 0; - - if(quit_now){ - errno = EINTR; - return -1; - } - - switch(af){ - case AF_INET6: - pf = PF_INET6; - break; - case AF_INET: - pf = PF_INET; - break; - default: - fprintf_plus(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)); - } - } + + ret = init_gnutls_session (mc, &session); + if (ret != 0){ + return -1; + } + + if(debug){ + fprintf(stderr, "Setting up a tcp connection to %s, port %" PRIu16 + "\n", ip, port); + } + + tcp_sd = socket(PF_INET6, SOCK_STREAM, 0); + if(tcp_sd < 0) { + perror("socket"); + return -1; + } + + if(debug){ + if(if_indextoname((unsigned int)if_index, interface) == NULL){ + perror("if_indextoname"); return -1; } + fprintf(stderr, "Binding to interface %s\n", interface); } - ret = init_gnutls_session(&session, mc); - if(ret != 0){ + memset(&to, 0, sizeof(to)); + to.in6.sin6_family = AF_INET6; + /* It would be nice to have a way to detect if we were passed an + IPv4 address here. Now we assume an IPv6 address. */ + ret = inet_pton(AF_INET6, ip, &to.in6.sin6_addr); + if (ret < 0 ){ + perror("inet_pton"); return -1; } - - if(debug){ - fprintf_plus(stderr, "Setting up a TCP connection to %s, port %" - PRIuMAX "\n", ip, (uintmax_t)port); - } - - tcp_sd = socket(pf, SOCK_STREAM | SOCK_CLOEXEC, 0); - if(tcp_sd < 0){ - int e = errno; - perror_plus("socket"); - errno = e; - goto mandos_end; - } - - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - - 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); - } 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); - } - if(ret < 0 ){ - int e = errno; - perror_plus("inet_pton"); - errno = e; - goto mandos_end; - } if(ret == 0){ - int e = errno; - fprintf_plus(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)){ - if(if_index == AVAHI_IF_UNSPEC){ - fprintf_plus(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; - } - } else { - ((struct sockaddr_in *)&to)->sin_port = htons(port); - } + fprintf(stderr, "Bad address: %s\n", ip); + return -1; + } + to.in6.sin6_port = htons(port); /* Spurious warning */ - if(quit_now){ - errno = EINTR; - goto mandos_end; - } + to.in6.sin6_scope_id = (uint32_t)if_index; if(debug){ - 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"); - } else { - fprintf_plus(stderr, "Connection to: %s%%%s, port %" PRIuMAX - "\n", ip, interface, (uintmax_t)port); - } - } else { - fprintf_plus(stderr, "Connection to: %s, port %" PRIuMAX "\n", - ip, (uintmax_t)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; - } - + fprintf(stderr, "Connection to: %s, port %" PRIu16 "\n", ip, + port); + char addrstr[INET6_ADDRSTRLEN] = ""; + if(inet_ntop(to.in6.sin6_family, &(to.in6.sin6_addr), addrstr, + sizeof(addrstr)) == NULL){ + perror("inet_ntop"); + } else { + if(strcmp(addrstr, ip) != 0){ + fprintf(stderr, "Canonical address form: %s\n", addrstr); + } + } + } + + ret = connect(tcp_sd, &to.in, sizeof(to)); + if (ret < 0){ + perror("connect"); + return -1; + } + const char *out = mandos_protocol_version; written = 0; - while(true){ + while (true){ size_t out_size = strlen(out); - ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written, - out_size - written)); - if(ret == -1){ - int e = errno; - perror_plus("write"); - errno = e; + ret = TEMP_FAILURE_RETRY(write(tcp_sd, out + written, + out_size - written)); + if (ret == -1){ + perror("write"); + retval = -1; goto mandos_end; } written += (size_t)ret; if(written < out_size){ continue; } else { - if(out == mandos_protocol_version){ + if (out == mandos_protocol_version){ written = 0; out = "\r\n"; } else { break; } } - - if(quit_now){ - errno = EINTR; - goto mandos_end; - } } - + if(debug){ - fprintf_plus(stderr, "Establishing TLS session with %s\n", ip); - } - - if(quit_now){ - errno = EINTR; - 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); - - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - - do { - ret = gnutls_handshake(session); - if(quit_now){ - errno = EINTR; - goto mandos_end; - } + fprintf(stderr, "Establishing TLS session with %s\n", ip); + } + + gnutls_transport_set_ptr (session, (gnutls_transport_ptr_t) tcp_sd); + + do{ + ret = gnutls_handshake (session); } while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED); - if(ret != GNUTLS_E_SUCCESS){ + if (ret != GNUTLS_E_SUCCESS){ if(debug){ - fprintf_plus(stderr, "*** GnuTLS Handshake failed ***\n"); - gnutls_perror(ret); + fprintf(stderr, "*** GnuTLS Handshake failed ***\n"); + gnutls_perror (ret); } - errno = EPROTO; + retval = -1; goto mandos_end; } /* Read OpenPGP packet that contains the wanted password */ if(debug){ - fprintf_plus(stderr, "Retrieving OpenPGP encrypted password from" - " %s\n", ip); + fprintf(stderr, "Retrieving pgp encrypted password from %s\n", + ip); } - + while(true){ - - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - - buffer_capacity = incbuffer(&buffer, buffer_length, - buffer_capacity); - if(buffer_capacity == 0){ - int e = errno; - perror_plus("incbuffer"); - errno = e; - goto mandos_end; - } - - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - - sret = gnutls_record_recv(session, buffer+buffer_length, - BUFFER_SIZE); - if(sret == 0){ + buffer_capacity = adjustbuffer(&buffer, buffer_length, + buffer_capacity); + if (buffer_capacity == 0){ + perror("adjustbuffer"); + retval = -1; + goto mandos_end; + } + + ret = gnutls_record_recv(session, buffer+buffer_length, + BUFFER_SIZE); + if (ret == 0){ break; } - if(sret < 0){ - switch(sret){ + if (ret < 0){ + switch(ret){ case GNUTLS_E_INTERRUPTED: case GNUTLS_E_AGAIN: break; case GNUTLS_E_REHANDSHAKE: - do { - ret = gnutls_handshake(session); - - if(quit_now){ - errno = EINTR; - goto mandos_end; - } + do{ + ret = gnutls_handshake (session); } while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED); - if(ret < 0){ - fprintf_plus(stderr, "*** GnuTLS Re-handshake failed " - "***\n"); - gnutls_perror(ret); - errno = EPROTO; + if (ret < 0){ + fprintf(stderr, "*** GnuTLS Re-handshake failed ***\n"); + gnutls_perror (ret); + retval = -1; goto mandos_end; } break; default: - fprintf_plus(stderr, "Unknown error while reading data from" - " encrypted session with Mandos server\n"); - gnutls_bye(session, GNUTLS_SHUT_RDWR); - errno = EIO; + fprintf(stderr, "Unknown error while reading data from" + " encrypted session with Mandos server\n"); + retval = -1; + gnutls_bye (session, GNUTLS_SHUT_RDWR); goto mandos_end; } } else { - buffer_length += (size_t) sret; + buffer_length += (size_t) ret; } } if(debug){ - fprintf_plus(stderr, "Closing TLS session\n"); - } - - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - - do { - ret = gnutls_bye(session, GNUTLS_SHUT_RDWR); - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - } while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED); - - if(buffer_length > 0){ - ssize_t decrypted_buffer_size; - decrypted_buffer_size = pgp_packet_decrypt(buffer, buffer_length, - &decrypted_buffer, mc); - if(decrypted_buffer_size >= 0){ - - clearerr(stdout); + fprintf(stderr, "Closing TLS session\n"); + } + + gnutls_bye (session, GNUTLS_SHUT_RDWR); + + if (buffer_length > 0){ + decrypted_buffer_size = pgp_packet_decrypt(buffer, + buffer_length, + &decrypted_buffer, + keydir); + if (decrypted_buffer_size >= 0){ written = 0; while(written < (size_t) decrypted_buffer_size){ - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - - ret = (int)fwrite(decrypted_buffer + written, 1, - (size_t)decrypted_buffer_size - written, - stdout); + ret = (int)fwrite (decrypted_buffer + written, 1, + (size_t)decrypted_buffer_size - written, + stdout); 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; + retval = -1; + break; } 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; + free(decrypted_buffer); + } else { + retval = -1; } + } else { + retval = -1; } /* Shutdown procedure */ 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); - } - if(ret == -1){ - if(e == 0){ - e = errno; - } - perror_plus("close"); - } - gnutls_deinit(session); - errno = e; - if(quit_now){ - errno = EINTR; - retval = -1; - } - } + free(buffer); + close(tcp_sd); + gnutls_deinit (session); return retval; } static void resolve_callback(AvahiSServiceResolver *r, AvahiIfIndex interface, - AvahiProtocol proto, + AVAHI_GCC_UNUSED AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, @@ -1668,27 +661,19 @@ AVAHI_GCC_UNUSED AvahiStringList *txt, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, - void *mc){ - if(r == NULL){ - return; - } + void* userdata) { + mandos_context *mc = 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){ + 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,58 +681,42 @@ 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, %" + PRIu16 ") on port %d\n", name, host_name, ip, + interface, port); } - int ret = start_mandos_communication(ip, (in_port_t)port, - interface, - avahi_proto_to_af(proto), - mc); - 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); - } + int ret = start_mandos_communication(ip, port, interface, mc); + if (ret == 0){ + avahi_simple_poll_quit(mc->simple_poll); } } } avahi_s_service_resolver_free(r); } -static void browse_callback(AvahiSServiceBrowser *b, - AvahiIfIndex interface, - AvahiProtocol protocol, - AvahiBrowserEvent event, - const char *name, - const char *type, - const char *domain, - AVAHI_GCC_UNUSED AvahiLookupResultFlags - flags, - void *mc){ - if(b == NULL){ - return; - } +static void browse_callback( AvahiSServiceBrowser *b, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *name, + const char *type, + const char *domain, + AVAHI_GCC_UNUSED AvahiLookupResultFlags + flags, + void* userdata) { + mandos_context *mc = userdata; + assert(b); /* Called whenever a new services becomes available on the LAN or is removed from the LAN */ - if(quit_now){ - return; - } - - switch(event){ + switch (event) { 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 +725,12 @@ 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, + AVAHI_PROTO_INET6, 0, + resolve_callback, mc))) + fprintf(stderr, "Avahi: Failed to resolve service '%s': %s\n", + name, avahi_strerror(avahi_server_errno(mc->server))); break; case AVAHI_BROWSER_REMOVE: @@ -1772,1576 +739,315 @@ 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 */ -static void handle_sigterm(int sig){ - if(quit_now){ - return; - } - 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; -} +/* Combines file name and path and returns the malloced new + string. some sane checks could/should be added */ +static char *combinepath(const char *first, const char *second){ + char *tmp; + int ret = asprintf(&tmp, "%s/%s", first, second); + if(ret < 0){ + return NULL; + } + return tmp; +} + 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 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; - 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 }; - - uid = getuid(); - gid = getgid(); - - /* Lower any group privileges we might have, just to be safe */ - errno = 0; - ret = setgid(gid); - if(ret == -1){ - perror_plus("setgid"); - } - - /* Lower user privileges (temporarily) */ - errno = 0; - ret = seteuid(uid); - if(ret == -1){ - perror_plus("seteuid"); - } - - if(quit_now){ - goto end; - } - - { - struct argp_option options[] = { - { .name = "debug", .key = 128, - .doc = "Debug mode", .group = 3 }, - { .name = "connect", .key = 'c', - .arg = "ADDRESS:PORT", - .doc = "Connect directly to a specific Mandos server", - .group = 1 }, - { .name = "interface", .key = 'i', - .arg = "NAME", - .doc = "Network interface that will be used to search for" - " Mandos servers", - .group = 1 }, - { .name = "seckey", .key = 's', - .arg = "FILE", - .doc = "OpenPGP secret key file base name", - .group = 1 }, - { .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 }, - { .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", - .group = 1 }, - { .name = "delay", .key = 131, - .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 - */ - { .name = "help", .key = '?', - .doc = "Give this help list", .group = -1 }, - { .name = "usage", .key = -3, - .doc = "Give a short usage message", .group = -1 }, - { .name = "version", .key = 'V', - .doc = "Print program version", .group = -1 }, - { .name = NULL } - }; - - error_t parse_opt(int key, char *arg, - struct argp_state *state){ + AvahiSServiceBrowser *sb = NULL; + int error; + int ret; + int exitcode = EXIT_SUCCESS; + const char *interface = "eth0"; + struct ifreq network; + int sd; + uid_t uid; + gid_t gid; + char *connect_to = NULL; + AvahiIfIndex if_index = AVAHI_IF_UNSPEC; + char *pubkeyfilename = NULL; + char *seckeyfilename = NULL; + const char *pubkeyname = "pubkey.txt"; + const char *seckeyname = "seckey.txt"; + mandos_context mc = { .simple_poll = NULL, .server = NULL, + .dh_bits = 1024, .priority = "SECURE256"}; + bool gnutls_initalized = false; + + { + struct argp_option options[] = { + { .name = "debug", .key = 128, + .doc = "Debug mode", .group = 3 }, + { .name = "connect", .key = 'c', + .arg = "IP", + .doc = "Connect directly to a sepcified mandos server", + .group = 1 }, + { .name = "interface", .key = 'i', + .arg = "INTERFACE", + .doc = "Interface that Avahi will conntect through", + .group = 1 }, + { .name = "keydir", .key = 'd', + .arg = "KEYDIR", + .doc = "Directory where the openpgp keyring is", + .group = 1 }, + { .name = "seckey", .key = 's', + .arg = "SECKEY", + .doc = "Secret openpgp key for gnutls authentication", + .group = 1 }, + { .name = "pubkey", .key = 'p', + .arg = "PUBKEY", + .doc = "Public openpgp key for gnutls authentication", + .group = 2 }, + { .name = "dh-bits", .key = 129, + .arg = "BITS", + .doc = "dh-bits to use in gnutls communication", + .group = 2 }, + { .name = "priority", .key = 130, + .arg = "PRIORITY", + .doc = "GNUTLS priority", .group = 1 }, + { .name = NULL } + }; + + + error_t parse_opt (int key, char *arg, + struct argp_state *state) { + /* Get the INPUT argument from `argp_parse', which we know is + a pointer to our plugin list pointer. */ + switch (key) { + case 128: + debug = true; + break; + case 'c': + connect_to = arg; + break; + case 'i': + interface = arg; + break; + case 'd': + keydir = arg; + break; + case 's': + seckeyname = arg; + break; + case 'p': + pubkeyname = arg; + break; + case 129: + errno = 0; + mc.dh_bits = (unsigned int) strtol(arg, NULL, 10); + if (errno){ + perror("strtol"); + exit(EXIT_FAILURE); + } + break; + case 130: + mc.priority = arg; + break; + case ARGP_KEY_ARG: + argp_usage (state); + case ARGP_KEY_END: + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; + } + + struct argp argp = { .options = options, .parser = parse_opt, + .args_doc = "", + .doc = "Mandos client -- Get and decrypt" + " passwords from mandos server" }; + ret = argp_parse (&argp, argc, argv, 0, 0, NULL); + if (ret == ARGP_ERR_UNKNOWN){ + fprintf(stderr, "Unknown error while parsing arguments\n"); + exitcode = EXIT_FAILURE; + goto end; + } + } + + pubkeyfilename = combinepath(keydir, pubkeyname); + if (pubkeyfilename == NULL){ + perror("combinepath"); + exitcode = EXIT_FAILURE; + goto end; + } + + seckeyfilename = combinepath(keydir, seckeyname); + if (seckeyfilename == NULL){ + perror("combinepath"); + exitcode = EXIT_FAILURE; + goto end; + } + + ret = init_gnutls_global(&mc, pubkeyfilename, seckeyfilename); + if (ret == -1){ + fprintf(stderr, "init_gnutls_global failed\n"); + exitcode = EXIT_FAILURE; + goto end; + } else { + gnutls_initalized = true; + } + + /* If the interface is down, bring it up */ + { + sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); + if(sd < 0) { + perror("socket"); + exitcode = EXIT_FAILURE; + goto end; + } + strcpy(network.ifr_name, interface); + ret = ioctl(sd, SIOCGIFFLAGS, &network); + if(ret == -1){ + perror("ioctl SIOCGIFFLAGS"); + exitcode = EXIT_FAILURE; + goto end; + } + if((network.ifr_flags & IFF_UP) == 0){ + network.ifr_flags |= IFF_UP; + ret = ioctl(sd, SIOCSIFFLAGS, &network); + if(ret == -1){ + perror("ioctl SIOCSIFFLAGS"); + exitcode = EXIT_FAILURE; + goto end; + } + } + close(sd); + } + + uid = getuid(); + gid = getgid(); + + ret = setuid(uid); + if (ret == -1){ + perror("setuid"); + } + + setgid(gid); + if (ret == -1){ + perror("setgid"); + } + + if_index = (AvahiIfIndex) if_nametoindex(interface); + if(if_index == 0){ + fprintf(stderr, "No such interface: \"%s\"\n", interface); + exit(EXIT_FAILURE); + } + + if(connect_to != NULL){ + /* Connect directly, do not use Zeroconf */ + /* (Mainly meant for debugging) */ + char *address = strrchr(connect_to, ':'); + if(address == NULL){ + fprintf(stderr, "No colon in address\n"); + exitcode = EXIT_FAILURE; + goto end; + } errno = 0; - switch(key){ - case 128: /* --debug */ - debug = true; - break; - case 'c': /* --connect */ - 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)); - } - break; - case 's': /* --seckey */ - seckey = arg; - break; - 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); - if(errno != 0 or tmp == arg or *tmp != '\0' - or tmpmax != (typeof(mc.dh_bits))tmpmax){ - argp_error(state, "Bad number of DH bits"); - } - 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; - case 131: /* --delay */ - errno = 0; - delay = strtof(arg, &tmp); - 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 - */ - case '?': /* --help */ - argp_state_help(state, state->out_stream, - (ARGP_HELP_STD_HELP | ARGP_HELP_EXIT_ERR) - & ~(unsigned int)ARGP_HELP_EXIT_OK); - case -3: /* --usage */ - argp_state_help(state, state->out_stream, - ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR); - case 'V': /* --version */ - fprintf_plus(state->out_stream, "%s\n", argp_program_version); - exit(argp_err_exit_status); - break; - default: - return ARGP_ERR_UNKNOWN; - } - return errno; - } - - struct argp argp = { .options = options, .parser = parse_opt, - .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){ - case 0: - break; - case ENOMEM: - default: - errno = ret_errno; - perror_plus("argp_parse"); - exitcode = EX_OSERR; - goto end; - case EINVAL: - exitcode = EX_USAGE; - goto end; - } - } - - { - /* 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"); + uint16_t port = (uint16_t) strtol(address+1, NULL, 10); + if(errno){ + perror("Bad port number"); + exitcode = EXIT_FAILURE; 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); - } - - /* Initialize Avahi early so avahi_simple_poll_quit() can be called - 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"); - exitcode = EX_UNAVAILABLE; - goto end; - } - - sigemptyset(&sigterm_action.sa_mask); - ret = sigaddset(&sigterm_action.sa_mask, SIGINT); - if(ret == -1){ - perror_plus("sigaddset"); - exitcode = EX_OSERR; - goto end; - } - ret = sigaddset(&sigterm_action.sa_mask, SIGHUP); - if(ret == -1){ - perror_plus("sigaddset"); - exitcode = EX_OSERR; - goto end; - } - ret = sigaddset(&sigterm_action.sa_mask, SIGTERM); - if(ret == -1){ - perror_plus("sigaddset"); - exitcode = EX_OSERR; - goto end; - } - /* Need to check if the handler is SIG_IGN before handling: - | [[info:libc:Initial Signal Actions]] | - | [[info:libc:Basic Signal Handling]] | - */ - ret = sigaction(SIGINT, NULL, &old_sigterm_action); - if(ret == -1){ - perror_plus("sigaction"); - return EX_OSERR; - } - if(old_sigterm_action.sa_handler != SIG_IGN){ - ret = sigaction(SIGINT, &sigterm_action, NULL); - if(ret == -1){ - perror_plus("sigaction"); - exitcode = EX_OSERR; - goto end; - } - } - ret = sigaction(SIGHUP, NULL, &old_sigterm_action); - if(ret == -1){ - perror_plus("sigaction"); - return EX_OSERR; - } - if(old_sigterm_action.sa_handler != SIG_IGN){ - ret = sigaction(SIGHUP, &sigterm_action, NULL); - if(ret == -1){ - perror_plus("sigaction"); - exitcode = EX_OSERR; - goto end; - } - } - ret = sigaction(SIGTERM, NULL, &old_sigterm_action); - if(ret == -1){ - perror_plus("sigaction"); - return EX_OSERR; - } - if(old_sigterm_action.sa_handler != SIG_IGN){ - ret = sigaction(SIGTERM, &sigterm_action, NULL); - if(ret == -1){ - perror_plus("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); - } 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); - } - - 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 - if(ret == -1){ - fprintf_plus(stderr, "init_gnutls_global failed\n"); - exitcode = EX_UNAVAILABLE; - goto end; - } else { - gnutls_initialized = true; - } - - if(quit_now){ - 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"); - goto end; - } - - if(quit_now){ - goto end; - } - - if(not init_gpgme(pubkey, seckey, tempdir, &mc)){ - fprintf_plus(stderr, "init_gpgme failed\n"); - exitcode = EX_UNAVAILABLE; - goto end; - } else { - gpgme_initialized = true; - } - - if(quit_now){ - goto end; - } - - if(connect_to != NULL){ - /* 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"); - exitcode = EX_USAGE; - goto end; - } - - if(quit_now){ - goto end; - } - - in_port_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"); - exitcode = EX_USAGE; - goto end; - } - - if(quit_now){ - goto end; - } - - port = (in_port_t)tmpmax; - *address = '\0'; - /* Colon in address indicates IPv6 */ - int af; - if(strchr(connect_to, ':') != 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){ - exitcode = EXIT_SUCCESS; - } - - goto end; - } - - if(quit_now){ - goto end; - } - - { - AvahiServerConfig config; - /* Do not publish any local Zeroconf records */ - avahi_server_config_init(&config); - config.publish_hinfo = 0; - config.publish_addresses = 0; - config.publish_workstation = 0; - config.publish_domain = 0; - - /* Allocate a new server */ - mc.server = avahi_server_new(avahi_simple_poll_get(simple_poll), - &config, NULL, NULL, &ret); - - /* Free the Avahi configuration data */ - avahi_server_config_free(&config); - } - - /* 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)); - exitcode = EX_UNAVAILABLE; - goto end; - } - - if(quit_now){ - goto end; - } - - /* 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); - if(sb == NULL){ - fprintf_plus(stderr, "Failed to create service browser: %s\n", - avahi_strerror(avahi_server_errno(mc.server))); - exitcode = EX_UNAVAILABLE; - goto end; - } - - if(quit_now){ - goto end; - } - - /* Run the main loop */ - - if(debug){ - fprintf_plus(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"); - } - + *address = '\0'; + address = connect_to; + ret = start_mandos_communication(address, port, if_index, &mc); + if(ret < 0){ + exitcode = EXIT_FAILURE; + } else { + exitcode = EXIT_SUCCESS; + } + goto end; + } + + if (not debug){ + avahi_set_log_function(empty_log); + } + + /* Initialize the pseudo-RNG for Avahi */ + srand((unsigned int) time(NULL)); + + /* Allocate main Avahi loop object */ + mc.simple_poll = avahi_simple_poll_new(); + if (mc.simple_poll == NULL) { + fprintf(stderr, "Avahi: Failed to create simple poll" + " object.\n"); + exitcode = EXIT_FAILURE; + goto end; + } + + { + AvahiServerConfig config; + /* Do not publish any local Zeroconf records */ + avahi_server_config_init(&config); + config.publish_hinfo = 0; + config.publish_addresses = 0; + config.publish_workstation = 0; + config.publish_domain = 0; + + /* Allocate a new server */ + 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); + } + + /* Check if creating the Avahi server object succeeded */ + if (mc.server == NULL) { + fprintf(stderr, "Failed to create Avahi server: %s\n", + avahi_strerror(error)); + exitcode = EXIT_FAILURE; + goto end; + } + + /* Create the Avahi service browser */ + sb = avahi_s_service_browser_new(mc.server, if_index, + AVAHI_PROTO_INET6, + "_mandos._tcp", NULL, 0, + browse_callback, &mc); + if (sb == NULL) { + fprintf(stderr, "Failed to create service browser: %s\n", + avahi_strerror(avahi_server_errno(mc.server))); + exitcode = EXIT_FAILURE; + goto end; + } + + /* Run the main loop */ + + if (debug){ + fprintf(stderr, "Starting Avahi loop search\n"); + } + + 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]); - } - } - - /* 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(gnutls_initialized){ - gnutls_certificate_free_credentials(mc.cred); - gnutls_dh_params_deinit(mc.dh_params); - } - - if(gpgme_initialized){ - 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); - } - - if(quit_now){ - sigemptyset(&old_sigterm_action.sa_mask); - old_sigterm_action.sa_handler = SIG_DFL; - ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received, - &old_sigterm_action, - NULL)); - if(ret == -1){ - perror_plus("sigaction"); - } - do { - ret = raise(signal_received); - } while(ret != 0 and errno == EINTR); - if(ret != 0){ - perror_plus("raise"); - abort(); - } - TEMP_FAILURE_RETRY(pause()); - } - - return exitcode; + + if (debug){ + fprintf(stderr, "%s exiting\n", argv[0]); + } + + /* Cleanup things */ + if (sb != NULL) + avahi_s_service_browser_free(sb); + + if (mc.server != NULL) + avahi_server_free(mc.server); + + if (mc.simple_poll != NULL) + avahi_simple_poll_free(mc.simple_poll); + free(pubkeyfilename); + free(seckeyfilename); + + if (gnutls_initalized){ + gnutls_certificate_free_credentials(mc.cred); + gnutls_global_deinit (); + } + + return exitcode; } === renamed file 'plugins.d/mandos-client.xml' => 'plugins.d/password-request.xml' --- plugins.d/mandos-client.xml 2019-02-09 23:23:26 +0000 +++ plugins.d/password-request.xml 2008-08-18 05:24:20 +0000 @@ -1,53 +1,63 @@ - + + - - -%common; + + ]> - + - Mandos Manual - - Mandos - &version; - &TIMESTAMP; + &COMMANDNAME; + + &COMMANDNAME; + &VERSION; 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 - Teddy Hogeborn - Björn Påhlsson + Teddy Hogeborn & Björn Påhlsson - + + + 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 this program; If not, see + . + +
- + &COMMANDNAME; 8mandos @@ -56,960 +66,252 @@ &COMMANDNAME; - Client for Mandos + Client for mandos - + &COMMANDNAME; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - &COMMANDNAME; - - - - - - - &COMMANDNAME; - - - - &COMMANDNAME; - - - - - + --connectIP + --keydirKEYDIR + --interfaceINTERFACE + --pubkeyPUBKEY + --seckeySECKEY + --priorityPRIORITY + --dh-bitsBITS + --debug + + + &COMMANDNAME; + --help + + + &COMMANDNAME; + --usage + + + &COMMANDNAME; + --version + - + DESCRIPTION - &COMMANDNAME; is a client program that - 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 . - - - This program is not meant to be run directly; it is really meant - to run as a plugin of the Mandos - plugin-runner - 8mandos, which runs in the - initial RAM disk environment because it is - specified as a keyscript in the - crypttab5 - file. - - - - - 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. - - + &COMMANDNAME; is a mandos plugin that works + like a client program that through avahi detects mandos servers, + sets up a gnutls connect and request a encrypted password. Any + passwords given is automaticly decrypted and passed to + cryptsetup. + + OPTIONS - This program is commonly not invoked from the command line; it - is normally started by the Mandos - plugin runner, see plugin-runner8mandos - . Any command line options this program accepts - are therefore normally provided by the plugin runner, and not - directly. + Commonly not invoked as command lines but from configuration + file of plugin runner. - + - - - - - Do not use Zeroconf to locate servers. Connect directly - to only one specified Mandos - server. Note that an IPv6 address has colon characters in - it, so the last colon character is - 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. - - - - - - - - - - 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. - - - 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. - - - 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 . - - - 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. - - - - - - - - - - OpenPGP public key file name. The default name is - /conf/conf.d/mandos/pubkey.txt. - - - - - - - - - - OpenPGP secret key file name. The default name is - /conf/conf.d/mandos/seckey.txt. - - - - - - - - - - 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. - - - - - - - - - - - - - - - - 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. - - - - - - - - - After bringing a 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 - console, alleviating any other plugins which might be - using the system console. This option sets the upper - limit of seconds to wait. The default is 2.5 seconds. - - - - - - - - - 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. - - - - - - - - - Enable debug mode. This will enable a lot of output to - standard error about what the program is doing. The - program will still perform all other functions normally. - - - It will also enable debug mode in the Avahi and GnuTLS - libraries, making them print large amounts of debugging - output. - - - - - - - - - - Gives a help message about options and their meanings. - - - - - - - - - Gives a short usage message. - - - - - - - - - - Prints the program version. - - - + -c, --connect= + IP + + + Connect directly to a specified mandos server + + + + + + -d, --keydir= + KEYDIR + + + Directory where the openpgp keyring is + + + + + + -i, --interface= + INTERFACE + + + Interface that Avahi will conntect through + + + + + + -p, --pubkey= + PUBKEY + + + Public openpgp key for gnutls authentication + + + + + + -s, --seckey= + SECKEY + + + Secret openpgp key for gnutls authentication + + + + + + --priority=PRIORITY + + + + GNUTLS priority + + + + + + --dh-bits=BITS + + + + dh-bits to use in gnutls communication + + + + + + --debug + + + Debug mode + + + + + + -?, --help + + + Gives a help message + + + + + + --usage + + + Gives a short usage message + + + + + + -V, --version + + + Prints the program version + + + - - - OVERVIEW - - - This program is the client part. It is a plugin started by - plugin-runner - 8mandos which will run in - an initial RAM disk environment. - - - This program could, theoretically, be used as a keyscript in - /etc/crypttab, but it would then be - impossible to enter a password for the encrypted root disk at - the console, since this program does not read from the console - at all. This is why a separate plugin runner ( - 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. - - - + EXIT STATUS - This program will exit with a successful (zero) exit status if a - 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. - + 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 - purposefully 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 - - - /conf/conf.d/mandos/pubkey.txt - /conf/conf.d/mandos/seckey.txt - - - OpenPGP public and private key files, in ASCII - Armor format. These are the default file names, - they can be changed with the and - options. - - - - - /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 - Note that normally, command line options will not be given - directly, but via options for the Mandos plugin-runner - 8mandos. - - - Normal invocation needs no options, if the network interfaces - can be automatically determined: - - - &COMMANDNAME; - - - - - Search for Mandos servers (and connect to them) using one - specific interface: - - - - &COMMANDNAME; --interface eth1 - - - - - Run in debug mode, and use custom keys: - - - - -&COMMANDNAME; --debug --pubkey keydir/pubkey.txt --seckey keydir/seckey.txt --tls-pubkey keydir/tls-pubkey.pem --tls-privkey keydir/tls-privkey.pem - - - - - - Run in debug mode, with custom keys, and do not use Zeroconf - to locate a server; connect directly to the IPv6 link-local - address fe80::aede:48ff:fe71:f6f2, port 4711, - using interface eth2: - - - - -&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 - - - - + SECURITY - This program is set-uid to root, but will switch back to the - original (and presumably non-privileged) user and group after - bringing up the network interface. - - - To use this program for its intended purpose (see ), the password for the root file system will - have to be given out to be stored in a server computer, after - having been encrypted using an OpenPGP key. This encrypted data - which will be stored in a server can only be decrypted by the - OpenPGP key, and the data will only be given out to those - clients who can prove they actually have that key. This key, - however, is stored unencrypted on the client side in its initial - RAM disk image file system. This is normally - readable by all, but this is normally fixed during installation - of this program; file permissions are set so that no-one is able - to read that file. - - - 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. - - - Note: This makes it completely insecure to - have Mandos clients which dual-boot - to another operating system which is not - trusted to keep the initial RAM disk image - confidential. - + SEE ALSO - - intro - 8mandos, - cryptsetup - 8, - crypttab - 5, - mandos - 8, - password-prompt - 8mandos, - plugin-runner - 8mandos - - - - - Zeroconf - - - - Zeroconf is the network protocol standard used for finding - Mandos servers on the local network. - - - - - - Avahi - - - - Avahi is the library this program calls to find Zeroconf - services. - - - - - - 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. - - - - - - GPGME - - - - GPGME is the library used to decrypt the OpenPGP data sent - by the server. - - - - - - RFC 4291: IP Version 6 Addressing - Architecture - - - - - Section 2.2: Text Representation of - Addresses - - - - Section 2.5.5.2: IPv4-Mapped IPv6 - Address - - - - Section 2.5.6, Link-Local IPv6 Unicast - Addresses - - - 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 - is brought up. - - - - - - - - - RFC 5246: The Transport Layer Security (TLS) - Protocol Version 1.2 - - - - TLS 1.2 is the protocol implemented by GnuTLS. - - - - - - RFC 4880: OpenPGP Message Format - - - - The data received from the server is binary encrypted - OpenPGP data. - - - - - - RFC 7250: Using Raw Public Keys in Transport - Layer Security (TLS) and Datagram Transport Layer Security - (DTLS) - - - - This is implemented by GnuTLS in version 3.6.6 and is, if - present, used by this program so that raw public keys can be - used. - - - - - - RFC 6091: Using OpenPGP Keys for Transport Layer - Security - - - - This is implemented by GnuTLS before version 3.6.0 and is, - if present, used by this program so that OpenPGP keys can be - used. - - - - + + + mandos + 8 + + + + plugin-runner + 8mandos + + + + password-prompt + 8mandos + + + + Zeroconf + + + + Avahi + + + + GnuTLS + + + + + GPGME + + + + RFC 4880: OpenPGP Message + Format + + + + RFC 5081: Using OpenPGP Keys for + Transport Layer Security + + + + RFC 4291: IP Version 6 Addressing + Architecture, section 2.5.6, Link-Local IPv6 + Unicast Addresses + + +
- - - - - - === 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 2018-02-08 10:23:55 +0000 +++ plugins.d/plymouth.xml 1970-01-01 00:00:00 +0000 @@ -1,289 +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 - 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 - - -
- - - - - === removed file 'plugins.d/splashy.c' --- plugins.d/splashy.c 2018-02-08 10:23:55 +0000 +++ plugins.d/splashy.c 1970-01-01 00:00:00 +0000 @@ -1,472 +0,0 @@ -/* -*- coding: utf-8 -*- */ -/* - * 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 - * WITHOUT 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 /* TEMP_FAILURE_RETRY(), asprintf() */ -#include /* sig_atomic_t, struct sigaction, - sigemptyset(), sigaddset(), SIGINT, - SIGHUP, SIGTERM, sigaction, - SIG_IGN, kill(), SIGKILL */ -#include /* NULL */ -#include /* getenv() */ -#include /* asprintf(), vasprintf(), vprintf(), - fprintf() */ -#include /* EXIT_FAILURE, free(), - EXIT_SUCCESS */ -#include /* pid_t, DIR, struct dirent, - ssize_t */ -#include /* opendir(), readdir(), closedir() */ -#include /* intmax_t, strtoimax() */ -#include /* struct stat, lstat(), S_ISLNK */ -#include /* not, or, and */ -#include /* readlink(), fork(), execl(), - sleep(), dup2() STDERR_FILENO, - STDOUT_FILENO, _exit(), - pause() */ -#include /* memcmp(), strerror() */ -#include /* errno, EACCES, ENOTDIR, ELOOP, - ENOENT, ENAMETOOLONG, EMFILE, - ENFILE, ENOMEM, ENOEXEC, EINVAL, - E2BIG, EFAULT, EIO, ETXTBSY, - EISDIR, ELIBBAD, EPERM, EINTR, - ECHILD */ -#include /* 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; - } - interrupted_by_signal = 1; - signal_received = signum; -} - -int main(__attribute__((unused))int argc, - __attribute__((unused))char **argv){ - int ret = 0; - char *prompt = NULL; - DIR *proc_dir = NULL; - pid_t splashy_pid = 0; - pid_t splashy_command_pid = 0; - int exitstatus = EXIT_FAILURE; - - /* Create prompt string */ - { - const char *const cryptsource = getenv("cryptsource"); - const char *const crypttarget = getenv("crypttarget"); - const char *const prompt_start = "getpass " - "Enter passphrase to unlock the disk"; - - if(cryptsource == NULL){ - if(crypttarget == NULL){ - ret = asprintf(&prompt, "%s: ", prompt_start); - } else { - ret = asprintf(&prompt, "%s (%s): ", prompt_start, - crypttarget); - } - } else { - if(crypttarget == NULL){ - ret = asprintf(&prompt, "%s %s: ", prompt_start, cryptsource); - } else { - ret = asprintf(&prompt, "%s %s (%s): ", prompt_start, - cryptsource, crypttarget); - } - } - if(ret == -1){ - prompt = NULL; - exitstatus = EX_OSERR; - goto failure; - } - } - - /* Find splashy process */ - { - const char splashy_name[] = "/sbin/splashy"; - proc_dir = opendir("/proc"); - if(proc_dir == NULL){ - int e = errno; - error_plus(0, errno, "opendir"); - switch(e){ - case EACCES: - case ENOTDIR: - case ELOOP: - case ENOENT: - default: - exitstatus = EX_OSFILE; - break; - case ENAMETOOLONG: - case EMFILE: - case ENFILE: - case ENOMEM: - exitstatus = EX_OSERR; - break; - } - goto failure; - } - for(struct dirent *proc_ent = readdir(proc_dir); - proc_ent != NULL; - proc_ent = readdir(proc_dir)){ - pid_t pid; - { - intmax_t tmpmax; - char *tmp; - errno = 0; - tmpmax = strtoimax(proc_ent->d_name, &tmp, 10); - if(errno != 0 or tmp == proc_ent->d_name or *tmp != '\0' - or tmpmax != (pid_t)tmpmax){ - /* Not a process */ - continue; - } - pid = (pid_t)tmpmax; - } - /* Find the executable name by doing readlink() on the - /proc//exe link */ - char exe_target[sizeof(splashy_name)]; - ssize_t sret; - { - char *exe_link; - ret = asprintf(&exe_link, "/proc/%s/exe", proc_ent->d_name); - if(ret == -1){ - error_plus(0, errno, "asprintf"); - exitstatus = EX_OSERR; - goto failure; - } - - /* Check that it refers to a symlink owned by root:root */ - struct stat exe_stat; - ret = lstat(exe_link, &exe_stat); - if(ret == -1){ - if(errno == ENOENT){ - free(exe_link); - continue; - } - int e = errno; - error_plus(0, errno, "lstat"); - free(exe_link); - switch(e){ - case EACCES: - case ENOTDIR: - case ELOOP: - default: - exitstatus = EX_OSFILE; - break; - case ENAMETOOLONG: - exitstatus = EX_OSERR; - break; - } - goto failure; - } - if(not S_ISLNK(exe_stat.st_mode) - or exe_stat.st_uid != 0 - or exe_stat.st_gid != 0){ - free(exe_link); - continue; - } - - sret = readlink(exe_link, exe_target, sizeof(exe_target)); - free(exe_link); - } - if((sret == ((ssize_t)sizeof(exe_target)-1)) - and (memcmp(splashy_name, exe_target, - sizeof(exe_target)-1) == 0)){ - splashy_pid = pid; - break; - } - } - closedir(proc_dir); - proc_dir = NULL; - } - if(splashy_pid == 0){ - exitstatus = EX_UNAVAILABLE; - goto failure; - } - - /* Set up the signal handler */ - { - struct sigaction old_action, - new_action = { .sa_handler = termination_handler, - .sa_flags = 0 }; - sigemptyset(&new_action.sa_mask); - ret = sigaddset(&new_action.sa_mask, SIGINT); - if(ret == -1){ - error_plus(0, errno, "sigaddset"); - exitstatus = EX_OSERR; - goto failure; - } - ret = sigaddset(&new_action.sa_mask, SIGHUP); - if(ret == -1){ - error_plus(0, errno, "sigaddset"); - exitstatus = EX_OSERR; - goto failure; - } - ret = sigaddset(&new_action.sa_mask, SIGTERM); - if(ret == -1){ - error_plus(0, errno, "sigaddset"); - exitstatus = EX_OSERR; - goto failure; - } - ret = sigaction(SIGINT, NULL, &old_action); - if(ret == -1){ - error_plus(0, errno, "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"); - exitstatus = EX_OSERR; - goto failure; - } - } - ret = sigaction(SIGHUP, NULL, &old_action); - if(ret == -1){ - error_plus(0, errno, "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"); - exitstatus = EX_OSERR; - goto failure; - } - } - ret = sigaction(SIGTERM, NULL, &old_action); - if(ret == -1){ - error_plus(0, errno, "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"); - exitstatus = EX_OSERR; - goto failure; - } - } - } - - if(interrupted_by_signal){ - goto failure; - } - - /* Fork off the splashy command to prompt for password */ - splashy_command_pid = fork(); - if(splashy_command_pid != 0 and interrupted_by_signal){ - goto failure; - } - if(splashy_command_pid == -1){ - error_plus(0, errno, "fork"); - exitstatus = EX_OSERR; - goto failure; - } - /* Child */ - if(splashy_command_pid == 0){ - if(not interrupted_by_signal){ - const char splashy_command[] = "/sbin/splashy_update"; - execl(splashy_command, splashy_command, prompt, (char *)NULL); - int e = errno; - error_plus(0, errno, "execl"); - switch(e){ - case EACCES: - case ENOENT: - case ENOEXEC: - case EINVAL: - _exit(EX_UNAVAILABLE); - case ENAMETOOLONG: - case E2BIG: - case ENOMEM: - case EFAULT: - case EIO: - case EMFILE: - case ENFILE: - case ETXTBSY: - default: - _exit(EX_OSERR); - case ENOTDIR: - case ELOOP: - case EISDIR: -#ifdef ELIBBAD - case ELIBBAD: /* Linux only */ -#endif - case EPERM: - _exit(EX_OSFILE); - } - } - free(prompt); - _exit(EXIT_FAILURE); - } - - /* Parent */ - free(prompt); - prompt = NULL; - - if(interrupted_by_signal){ - goto failure; - } - - /* Wait for command to complete */ - { - int status; - do { - ret = waitpid(splashy_command_pid, &status, 0); - } while(ret == -1 and errno == EINTR - and not interrupted_by_signal); - if(interrupted_by_signal){ - goto failure; - } - if(ret == -1){ - error_plus(0, errno, "waitpid"); - if(errno == ECHILD){ - splashy_command_pid = 0; - } - } else { - /* The child process has exited */ - splashy_command_pid = 0; - if(WIFEXITED(status) and WEXITSTATUS(status) == 0){ - return EXIT_SUCCESS; - } - } - } - - failure: - - free(prompt); - - if(proc_dir != NULL){ - TEMP_FAILURE_RETRY(closedir(proc_dir)); - } - - if(splashy_command_pid != 0){ - TEMP_FAILURE_RETRY(kill(splashy_command_pid, SIGTERM)); - - TEMP_FAILURE_RETRY(kill(splashy_pid, SIGTERM)); - sleep(2); - while(TEMP_FAILURE_RETRY(kill(splashy_pid, 0)) == 0){ - TEMP_FAILURE_RETRY(kill(splashy_pid, SIGKILL)); - sleep(1); - } - pid_t new_splashy_pid = (pid_t)TEMP_FAILURE_RETRY(fork()); - if(new_splashy_pid == 0){ - /* Child; will become new splashy process */ - - /* Make the effective user ID (root) the only user ID instead of - the real user ID (_mandos) */ - ret = setuid(geteuid()); - if(ret == -1){ - error_plus(0, errno, "setuid"); - } - - setsid(); - ret = chdir("/"); - if(ret == -1){ - error_plus(0, errno, "chdir"); - } -/* if(fork() != 0){ */ -/* _exit(EXIT_SUCCESS); */ -/* } */ - ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace stdout */ - if(ret == -1){ - error_plus(0, errno, "dup2"); - _exit(EX_OSERR); - } - - execl("/sbin/splashy", "/sbin/splashy", "boot", (char *)NULL); - { - int e = errno; - error_plus(0, errno, "execl"); - switch(e){ - case EACCES: - case ENOENT: - case ENOEXEC: - default: - _exit(EX_UNAVAILABLE); - case ENAMETOOLONG: - case E2BIG: - case ENOMEM: - _exit(EX_OSERR); - case ENOTDIR: - case ELOOP: - _exit(EX_OSFILE); - } - } - } - } - - if(interrupted_by_signal){ - struct sigaction signal_action; - sigemptyset(&signal_action.sa_mask); - signal_action.sa_handler = SIG_DFL; - ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received, - &signal_action, NULL)); - if(ret == -1){ - error_plus(0, errno, "sigaction"); - } - do { - ret = raise(signal_received); - } while(ret != 0 and errno == EINTR); - if(ret != 0){ - error_plus(0, errno, "raise"); - abort(); - } - TEMP_FAILURE_RETRY(pause()); - } - - return exitstatus; -} === removed file 'plugins.d/splashy.xml' --- plugins.d/splashy.xml 2018-02-08 10:23:55 +0000 +++ plugins.d/splashy.xml 1970-01-01 00:00:00 +0000 @@ -1,295 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2008 - 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - Teddy Hogeborn - Björn Påhlsson - - -
- - - &COMMANDNAME; - 8mandos - - - - &COMMANDNAME; - Mandos plugin to use splashy to get a - password. - - - - - &COMMANDNAME; - - - - - DESCRIPTION - - This program prompts for a password using - splashy_update - 8 and outputs any given - password to standard output. If no splashy8 - 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 - splashy8 - to abort requesting a password, because - splashy - 8 does not support this. - Therefore, this program will then kill the - running splashy - 8 process and start a - new one, using boot as the only argument. - - - - - 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 - - - /sbin/splashy_update - - - This is the command run to retrieve a password from - splashy - 8. See - splashy_update8 - . - - - - - /proc - - - To find the running splashy8 - , this directory will be searched for - numeric entries which will be assumed to be directories. - In all those directories, the exe - entry will be used to determine the name of the running - binary and the effective user and group - ID of the process. See - proc5. - - - - - /sbin/splashy - - - This is the name of the binary which will be searched for - in the process list. See splashy8 - . - - - - - - - - BUGS - - Killing splashy - 8 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 splashy8 - 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 splashy - 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, - splashy - 8, - splashy_update - 8 - - -
- - - - - === added file 'plugins.d/usplash' --- plugins.d/usplash 1970-01-01 00:00:00 +0000 +++ plugins.d/usplash 2008-08-14 02:24:59 +0000 @@ -0,0 +1,42 @@ +#!/bin/sh -e + +# If not on a tty, then get rid of possibly disrupting stderr output +if ! tty -s; then + exec 2>/dev/null +fi + +test -x /sbin/usplash + +usplash="`pidof usplash -o $$`" +test -n "$usplash" + +# We get some variables from cryptsetup: +# $cryptsource the device node, like "/dev/sda3" +# $crypttarget the device mapper name, like "sda3_crypt". + +prompt="Enter passphrase to unlock" +if [ -n "$crypttarget" ]; then + prompt="$prompt the disk $crypttarget" +fi +if [ -n "$cryptsource" ]; then + prompt="$prompt ($cryptsource)" +fi + +splash_input_password(){ + test -p /dev/.initramfs/usplash_outfifo || return 1 + /sbin/usplash_write "INPUTQUIET $1" || return 1 + cat /dev/.initramfs/usplash_outfifo 2> /dev/null || return 1 +} + +# Usplash keeps waiting for input even if some other plugin provided +# the password, so we must kill it +trap "kill -TERM $usplash; sleep 2; kill -KILL $usplash; + kill -TERM $$" TERM HUP + +password="`splash_input_password \"$prompt: \" password`" + +trap - TERM + +/sbin/usplash_write "TIMEOUT 15" + +echo -n "$password" === removed file 'plugins.d/usplash.c' --- plugins.d/usplash.c 2018-02-08 10:23:55 +0000 +++ plugins.d/usplash.c 1970-01-01 00:00:00 +0000 @@ -1,685 +0,0 @@ -/* -*- coding: utf-8 -*- */ -/* - * 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 - * WITHOUT 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(), - SIG_IGN, kill(), SIGKILL */ -#include /* bool, false, true */ -#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 /* close(), write(), readlink(), - read(), STDOUT_FILENO, sleep(), - fork(), setuid(), geteuid(), - setsid(), chdir(), dup2(), - STDERR_FILENO, execv() */ -#include /* free(), EXIT_FAILURE, realloc(), - EXIT_SUCCESS, malloc(), _exit(), - getenv() */ -#include /* opendir(), readdir(), closedir() */ -#include /* intmax_t, strtoimax() */ -#include /* 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; - } - interrupted_by_signal = 1; - signal_received = signum; -} - -static bool usplash_write(int *fifo_fd_r, - const char *cmd, const char *arg){ - /* - * usplash_write(&fd, "TIMEOUT", "15") will write "TIMEOUT 15\0" - * usplash_write(&fd, "PULSATE", NULL) will write "PULSATE\0" - * SEE ALSO - * usplash_write(8) - */ - int ret; - if(*fifo_fd_r == -1){ - ret = open("/dev/.initramfs/usplash_fifo", O_WRONLY); - if(ret == -1){ - return false; - } - *fifo_fd_r = ret; - } - - const char *cmd_line; - size_t cmd_line_len; - char *cmd_line_alloc = NULL; - if(arg == NULL){ - cmd_line = cmd; - cmd_line_len = strlen(cmd) + 1; - } else { - do { - ret = asprintf(&cmd_line_alloc, "%s %s", cmd, arg); - if(ret == -1){ - int e = errno; - close(*fifo_fd_r); - errno = e; - return false; - } - } while(ret == -1); - cmd_line = cmd_line_alloc; - cmd_line_len = (size_t)ret + 1; - } - - size_t written = 0; - ssize_t sret = 0; - while(written < cmd_line_len){ - sret = write(*fifo_fd_r, cmd_line + written, - cmd_line_len - written); - if(sret == -1){ - int e = errno; - close(*fifo_fd_r); - free(cmd_line_alloc); - errno = e; - return false; - } - written += (size_t)sret; - } - free(cmd_line_alloc); - - return true; -} - -/* 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[] = "Enter passphrase to unlock the disk"; - - if(cryptsource == NULL){ - if(crypttarget == NULL){ - ret = asprintf(&prompt, "%s: ", prompt_start); - } else { - ret = asprintf(&prompt, "%s (%s): ", prompt_start, - crypttarget); - } - } else { - if(crypttarget == NULL){ - ret = asprintf(&prompt, "%s %s: ", prompt_start, cryptsource); - } else { - ret = asprintf(&prompt, "%s %s (%s): ", prompt_start, - cryptsource, crypttarget); - } - } - if(ret == -1){ - return NULL; - } - return prompt; -} - -pid_t find_usplash(char **cmdline_r, size_t *cmdline_len_r){ - int ret = 0; - ssize_t sret = 0; - char *cmdline = NULL; - size_t cmdline_len = 0; - DIR *proc_dir = opendir("/proc"); - if(proc_dir == NULL){ - error_plus(0, errno, "opendir"); - return -1; - } - errno = 0; - for(struct dirent *proc_ent = readdir(proc_dir); - proc_ent != NULL; - proc_ent = readdir(proc_dir)){ - pid_t pid; - { - intmax_t tmpmax; - char *tmp; - tmpmax = strtoimax(proc_ent->d_name, &tmp, 10); - if(errno != 0 or tmp == proc_ent->d_name or *tmp != '\0' - or tmpmax != (pid_t)tmpmax){ - /* Not a process */ - errno = 0; - continue; - } - pid = (pid_t)tmpmax; - } - /* Find the executable name by doing readlink() on the - /proc//exe link */ - char exe_target[sizeof(usplash_name)]; - { - /* create file name string */ - char *exe_link; - ret = asprintf(&exe_link, "/proc/%s/exe", proc_ent->d_name); - if(ret == -1){ - error_plus(0, errno, "asprintf"); - goto fail_find_usplash; - } - - /* Check that it refers to a symlink owned by root:root */ - struct stat exe_stat; - ret = lstat(exe_link, &exe_stat); - if(ret == -1){ - if(errno == ENOENT){ - free(exe_link); - continue; - } - error_plus(0, errno, "lstat"); - free(exe_link); - goto fail_find_usplash; - } - if(not S_ISLNK(exe_stat.st_mode) - or exe_stat.st_uid != 0 - or exe_stat.st_gid != 0){ - free(exe_link); - continue; - } - - sret = readlink(exe_link, exe_target, sizeof(exe_target)); - free(exe_link); - } - /* Compare executable name */ - if((sret != ((ssize_t)sizeof(exe_target)-1)) - or (memcmp(usplash_name, exe_target, - sizeof(exe_target)-1) != 0)){ - /* Not it */ - continue; - } - /* Found usplash */ - /* Read and save the command line of usplash in "cmdline" */ - { - /* Open /proc//cmdline */ - int cl_fd; - { - char *cmdline_filename; - ret = asprintf(&cmdline_filename, "/proc/%s/cmdline", - proc_ent->d_name); - if(ret == -1){ - error_plus(0, errno, "asprintf"); - goto fail_find_usplash; - } - cl_fd = open(cmdline_filename, O_RDONLY); - free(cmdline_filename); - if(cl_fd == -1){ - error_plus(0, errno, "open"); - goto fail_find_usplash; - } - } - size_t cmdline_allocated = 0; - 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"); - close(cl_fd); - goto fail_find_usplash; - } - 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"); - close(cl_fd); - goto fail_find_usplash; - } - cmdline_len += (size_t)sret; - } while(sret != 0); - ret = close(cl_fd); - if(ret == -1){ - error_plus(0, errno, "close"); - goto fail_find_usplash; - } - } - /* Close directory */ - ret = closedir(proc_dir); - if(ret == -1){ - error_plus(0, errno, "closedir"); - goto fail_find_usplash; - } - /* Success */ - *cmdline_r = cmdline; - *cmdline_len_r = cmdline_len; - return pid; - } - - fail_find_usplash: - - free(cmdline); - if(proc_dir != NULL){ - int e = errno; - closedir(proc_dir); - errno = e; - } - return 0; -} - -int main(__attribute__((unused))int argc, - __attribute__((unused))char **argv){ - int ret = 0; - ssize_t sret; - int fifo_fd = -1; - int outfifo_fd = -1; - char *buf = NULL; - size_t buf_len = 0; - pid_t usplash_pid = -1; - bool usplash_accessed = false; - int status = EXIT_FAILURE; /* Default failure exit status */ - - char *prompt = makeprompt(); - if(prompt == NULL){ - status = EX_OSERR; - goto failure; - } - - /* Find usplash process */ - char *cmdline = NULL; - size_t cmdline_len = 0; - usplash_pid = find_usplash(&cmdline, &cmdline_len); - if(usplash_pid == 0){ - status = EX_UNAVAILABLE; - goto failure; - } - - /* Set up the signal handler */ - { - struct sigaction old_action, - new_action = { .sa_handler = termination_handler, - .sa_flags = 0 }; - sigemptyset(&new_action.sa_mask); - ret = sigaddset(&new_action.sa_mask, SIGINT); - if(ret == -1){ - error_plus(0, errno, "sigaddset"); - status = EX_OSERR; - goto failure; - } - ret = sigaddset(&new_action.sa_mask, SIGHUP); - if(ret == -1){ - error_plus(0, errno, "sigaddset"); - status = EX_OSERR; - goto failure; - } - ret = sigaddset(&new_action.sa_mask, SIGTERM); - if(ret == -1){ - error_plus(0, errno, "sigaddset"); - status = EX_OSERR; - goto failure; - } - ret = sigaction(SIGINT, NULL, &old_action); - if(ret == -1){ - if(errno != EINTR){ - error_plus(0, errno, "sigaction"); - status = EX_OSERR; - } - goto failure; - } - if(old_action.sa_handler != SIG_IGN){ - ret = sigaction(SIGINT, &new_action, NULL); - if(ret == -1){ - if(errno != EINTR){ - error_plus(0, errno, "sigaction"); - status = EX_OSERR; - } - goto failure; - } - } - ret = sigaction(SIGHUP, NULL, &old_action); - if(ret == -1){ - if(errno != EINTR){ - error_plus(0, errno, "sigaction"); - status = EX_OSERR; - } - goto failure; - } - if(old_action.sa_handler != SIG_IGN){ - ret = sigaction(SIGHUP, &new_action, NULL); - if(ret == -1){ - if(errno != EINTR){ - error_plus(0, errno, "sigaction"); - status = EX_OSERR; - } - goto failure; - } - } - ret = sigaction(SIGTERM, NULL, &old_action); - if(ret == -1){ - if(errno != EINTR){ - error_plus(0, errno, "sigaction"); - status = EX_OSERR; - } - goto failure; - } - if(old_action.sa_handler != SIG_IGN){ - ret = sigaction(SIGTERM, &new_action, NULL); - if(ret == -1){ - if(errno != EINTR){ - error_plus(0, errno, "sigaction"); - status = EX_OSERR; - } - goto failure; - } - } - } - - usplash_accessed = true; - /* Write command to FIFO */ - if(not usplash_write(&fifo_fd, "TIMEOUT", "0")){ - if(errno != EINTR){ - error_plus(0, errno, "usplash_write"); - status = EX_OSERR; - } - goto failure; - } - - if(interrupted_by_signal){ - goto failure; - } - - if(not usplash_write(&fifo_fd, "INPUTQUIET", prompt)){ - if(errno != EINTR){ - error_plus(0, errno, "usplash_write"); - status = EX_OSERR; - } - goto failure; - } - - if(interrupted_by_signal){ - goto failure; - } - - free(prompt); - prompt = NULL; - - /* Read reply from usplash */ - /* Open FIFO */ - outfifo_fd = open("/dev/.initramfs/usplash_outfifo", O_RDONLY); - if(outfifo_fd == -1){ - if(errno != EINTR){ - error_plus(0, errno, "open"); - status = EX_OSERR; - } - goto failure; - } - - if(interrupted_by_signal){ - goto failure; - } - - /* Read from FIFO */ - size_t buf_allocated = 0; - const size_t blocksize = 1024; - do { - /* Allocate more space */ - if(buf_len + blocksize > buf_allocated){ - char *tmp = realloc(buf, buf_allocated + blocksize); - if(tmp == NULL){ - if(errno != EINTR){ - error_plus(0, errno, "realloc"); - status = EX_OSERR; - } - goto failure; - } - buf = tmp; - buf_allocated += blocksize; - } - sret = read(outfifo_fd, buf + buf_len, - buf_allocated - buf_len); - if(sret == -1){ - if(errno != EINTR){ - error_plus(0, errno, "read"); - status = EX_OSERR; - } - close(outfifo_fd); - goto failure; - } - if(interrupted_by_signal){ - break; - } - - buf_len += (size_t)sret; - } while(sret != 0); - ret = close(outfifo_fd); - if(ret == -1){ - if(errno != EINTR){ - error_plus(0, errno, "close"); - status = EX_OSERR; - } - goto failure; - } - outfifo_fd = -1; - - if(interrupted_by_signal){ - goto failure; - } - - if(not usplash_write(&fifo_fd, "TIMEOUT", "15")){ - if(errno != EINTR){ - error_plus(0, errno, "usplash_write"); - status = EX_OSERR; - } - goto failure; - } - - if(interrupted_by_signal){ - goto failure; - } - - ret = close(fifo_fd); - if(ret == -1){ - if(errno != EINTR){ - error_plus(0, errno, "close"); - status = EX_OSERR; - } - goto failure; - } - fifo_fd = -1; - - /* Print password to stdout */ - size_t written = 0; - while(written < buf_len){ - do { - sret = write(STDOUT_FILENO, buf + written, buf_len - written); - if(sret == -1){ - if(errno != EINTR){ - error_plus(0, errno, "write"); - status = EX_OSERR; - } - goto failure; - } - } while(sret == -1); - - if(interrupted_by_signal){ - goto failure; - } - written += (size_t)sret; - } - free(buf); - buf = NULL; - - if(interrupted_by_signal){ - goto failure; - } - - free(cmdline); - return EXIT_SUCCESS; - - failure: - - free(buf); - - free(prompt); - - /* If usplash was never accessed, we can stop now */ - if(not usplash_accessed){ - return status; - } - - /* Close FIFO */ - if(fifo_fd != -1){ - ret = close(fifo_fd); - if(ret == -1 and errno != EINTR){ - error_plus(0, errno, "close"); - } - fifo_fd = -1; - } - - /* Close output FIFO */ - if(outfifo_fd != -1){ - ret = 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 */ - - /* Kill old usplash */ - kill(usplash_pid, SIGTERM); - sleep(2); - while(kill(usplash_pid, 0) == 0){ - kill(usplash_pid, SIGKILL); - sleep(1); - } - - pid_t new_usplash_pid = fork(); - if(new_usplash_pid == 0){ - /* Child; will become new usplash process */ - - /* Make the effective user ID (root) the only user ID instead of - the real user ID (_mandos) */ - ret = setuid(geteuid()); - if(ret == -1){ - error_plus(0, errno, "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"); - _exit(EX_OSERR); - } - - execv(usplash_name, cmdline_argv); - if(not interrupted_by_signal){ - error_plus(0, errno, "execv"); - } - free(cmdline); - free(cmdline_argv); - _exit(EX_OSERR); - } - free(cmdline); - free(cmdline_argv); - sleep(2); - if(not usplash_write(&fifo_fd, "PULSATE", NULL)){ - if(errno != EINTR){ - error_plus(0, errno, "usplash_write"); - } - } - - /* Close FIFO (again) */ - if(fifo_fd != -1){ - ret = close(fifo_fd); - if(ret == -1 and errno != EINTR){ - error_plus(0, errno, "close"); - } - fifo_fd = -1; - } - - if(interrupted_by_signal){ - struct sigaction signal_action = { .sa_handler = SIG_DFL }; - sigemptyset(&signal_action.sa_mask); - ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received, - &signal_action, NULL)); - if(ret == -1){ - error_plus(0, errno, "sigaction"); - } - do { - ret = raise(signal_received); - } while(ret != 0 and errno == EINTR); - if(ret != 0){ - error_plus(0, errno, "raise"); - abort(); - } - TEMP_FAILURE_RETRY(pause()); - } - - return status; -} === removed file 'plugins.d/usplash.xml' --- plugins.d/usplash.xml 2018-02-08 10:23:55 +0000 +++ plugins.d/usplash.xml 1970-01-01 00:00:00 +0000 @@ -1,309 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2008 - 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - Teddy Hogeborn - Björn Påhlsson - - -
- - - &COMMANDNAME; - 8mandos - - - - &COMMANDNAME; - Mandos plugin to use usplash to get a - password. - - - - - &COMMANDNAME; - - - - - DESCRIPTION - - This program prompts for a password using - usplash8 - and outputs any given password to standard - output. If no usplash8 - 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 - usplash8 - to abort requesting a password, because - usplash - 8 does not support this. - Therefore, this program will then kill the - running usplash - 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 - - - /dev/.initramfs/usplash_fifo - - - This is the FIFO to where this program - will write the commands for usplash8 - . See fifo7 - . - - - - - /dev/.initramfs/usplash_outfifo - - - This is the FIFO where this program - will read the password from usplash8 - . See fifo7 - . - - - - - /proc - - - To find the running usplash8 - , 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/usplash - - - This is the name of the binary which will be searched for - in the process list. See usplash8 - . - - - - - - - - BUGS - - Killing usplash - 8 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 usplash8 - 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 usplash - 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, - fifo - 7, - plugin-runner - 8mandos, - proc - 5, - usplash - 8 - - -
- - - - - === 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