=== 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 === removed file '.bzrignore' --- .bzrignore 2012-05-17 01:55:58 +0000 +++ .bzrignore 1970-01-01 00:00:00 +0000 @@ -1,14 +0,0 @@ -*.5 -*.8 -*.8mandos -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 2012-01-15 20:27:28 +0000 +++ DBUS-API 1970-01-01 00:00:00 +0000 @@ -1,181 +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 | - | "/clients/CLIENTNAME" | Mandos Client | - - -* Mandos Server Interface: - Interface name: "se.recompile.Mandos" - -** Methods: -*** GetAllClients() → (ao: Clients) - Returns an array of all client D-Bus object paths - -*** GetAllClientsWithProperties() → (a{oa{sv}}: ClientProperties) - Returns an array of all clients and all their properties - -*** RemoveClient(o: ObjectPath) → nothing - Removes a client - -** Signals: -*** ClientAdded(o: ObjectPath) - A new client was added. - -*** ClientNotFound(s: Fingerprint, s: Address) - A client connected from Address using Fingerprint, but was - rejected because it was not found in the server. The fingerprint - is represented as a string of hexadecimal digits. The address is - an IPv4 or IPv6 address in its normal string format. - -*** ClientRemoved(o: ObjectPath, s: Name) - A client named Name on ObjectPath was removed. - - -* 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. - -*** Disable() → nothing - Disable this client. See also the "Enabled" property. - -*** Enable() → nothing - Enable this client. See also the "Enabled" property. - -*** StartChecker() → nothing - Start a new checker for this client, if none is currently - running. See also the "CheckerRunning" property. - -*** StopChecker() → nothing - Abort a running checker process for this client, if any. See also - the "CheckerRunning" 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 | - | 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) | - | ObjectPath | o | Read | N/A | - | Secret (k) | ay | Write | secret (or secfile) | - | Timeout (a) | t | Read/Write | timeout | - - a) Represented as milliseconds. - - b) An approval is currently pending. - - c) Setting this property is equivalent to calling StartChecker() or - StopChecker(). - - d) The creation time of this client object, as an RFC 3339 string. - - e) Setting this property is equivalent to calling Enable() or - Disable(). - - 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. - -*** PropertyChanged(s: Property, v: Value) - The Property on this client has changed to Value. - -*** Rejected(s: Reason) - This client was not given its secret for a specified Reason. - -* Copyright - - Copyright © 2010-2012 Teddy Hogeborn - Copyright © 2010-2012 Björn Påhlsson - -** License: - - This program is free software: you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see - . - - -#+STARTUP: showall === removed file 'INSTALL' --- INSTALL 2013-06-23 15:13:06 +0000 +++ INSTALL 1970-01-01 00:00:00 +0000 @@ -1,137 +0,0 @@ --*- org -*- - -* Prerequisites - -** Operating System - - Debian 6.0 "squeeze" or Ubuntu 10.10 "Maverick Meerkat". - - 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 initrd.img file is automatically made unreadable. The - server and client programs themselves *could* be run in other - distributions, but they *are* specific to GNU/Linux systems, and - are not 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/topic/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 2.4 http://www.gnu.org/software/gnutls/ - + Avahi 0.6.16 http://www.avahi.org/ - + Python 2.6 http://www.python.org/ - + Python-GnuTLS 1.1.5 http://pypi.python.org/pypi/python-gnutls/ - + dbus-python 0.82.4 http://dbus.freedesktop.org/doc/dbus-python/ - + PyGObject 2.14.2 http://library.gnome.org/devel/pygobject/ - + Urwid 0.9.8.3 http://excess.org/urwid/ - - Strongly recommended: - + fping 2.4b2-to-ipv6 http://www.fping.com/ - - Package names: - python-gnutls avahi-daemon python python-avahi python-dbus - python-gobject python-urwid - -*** Mandos Client - + initramfs-tools 0.85i - http://packages.qa.debian.org/i/initramfs-tools.html - + GnuTLS 2.4 http://www.gnu.org/software/gnutls/ - + Avahi 0.6.16 http://www.avahi.org/ - + GnuPG 1.4.9 http://www.gnupg.org/ - + GPGME 1.1.6 http://www.gnupg.org/related_software/gpgme/ - - Package names: - initramfs-tools libgnutls-dev libavahi-core-dev gnupg - libgpgme11-dev - -* 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 the correct network interface. The - interface to use is automatically chosen at boot, and if this - needs to be adjusted, it will be necessary to edit - /etc/initramfs-tools/initramfs.conf to change the DEVICE setting - there. Alternatively, the file /etc/mandos/plugin-runner.conf - can be edited to add a "--device" parameter for the - mandos-client(8) plugin. Please note: If any of those files are - changed, the initrd.img file must be updated, possibly using the - following command: - - # update-initramfs -k all -u - - 5. On the server computer, start the server by running the command - For Debian: su -c 'invoke-rc.d mandos start' - For Ubuntu: sudo service mandos start - - 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; 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 an hour to reboot. - -* Further customizations - - You may want to tighten or loosen the timeouts in the server - configuration files; see mandos.conf(5) and mandos-clients.conf(5). - If IPsec is not used, it is suggested that a more cryptographically - secure checker program is used and configured, since without IPsec - ping packets can be faked. === modified file 'Makefile' --- Makefile 2013-10-30 21:05:47 +0000 +++ Makefile 2008-08-23 07:17:28 +0000 @@ -1,467 +1,188 @@ WARN=-O -Wall -Wformat=2 -Winit-self -Wmissing-include-dirs \ -Wswitch-default -Wswitch-enum -Wunused-parameter \ - -Wstrict-aliasing=1 -Wextra -Wfloat-equal -Wundef -Wshadow \ + -Wstrict-aliasing=2 -Wextra -Wfloat-equal -Wundef -Wshadow \ -Wunsafe-loop-optimizations -Wpointer-arith \ -Wbad-function-cast -Wcast-qual -Wcast-align -Wwrite-strings \ -Wconversion -Wstrict-prototypes -Wold-style-definition \ - -Wpacked -Wnested-externs -Winline -Wvolatile-register-var -# -Wunreachable-code -#DEBUG=-ggdb3 + -Wpacked -Wnested-externs -Wunreachable-code -Winline \ + -Wvolatile-register-var +DEBUG=-ggdb3 # For info about _FORTIFY_SOURCE, see -# -# and . -FORTIFY=-D_FORTIFY_SOURCE=2 -fstack-protector-all -fPIC -LINK_FORTIFY_LD=-z relro -z now -LINK_FORTIFY= - -# If BROKEN_PIE is set, do not build with -pie -ifndef BROKEN_PIE -FORTIFY += -fPIE -LINK_FORTIFY += -pie -endif +# +FORTIFY=-D_FORTIFY_SOURCE=2 # -fstack-protector-all #COVERAGE=--coverage OPTIMIZE=-Os LANGUAGE=-std=gnu99 -htmldir=man -version=1.6.2 -SED=sed - -USER=$(firstword $(subst :, ,$(shell getent passwd _mandos || getent passwd nobody || echo 65534))) -GROUP=$(firstword $(subst :, ,$(shell getent group _mandos || getent group nobody || 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) - -GNUTLS_CFLAGS=$(shell pkg-config --cflags-only-I gnutls) -GNUTLS_LIBS=$(shell pkg-config --libs gnutls) +# PREFIX=/usr/local +PREFIX=/usr +# CONFDIR=/usr/local/lib/mandos +CONFDIR=/etc/mandos +# MANDIR=/usr/local/man +MANDIR=/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; getconf LFS_CFLAGS) -GPGME_LIBS=$(shell gpgme-config --libs; getconf LFS_LIBS; \ - getconf LFS_LDFLAGS) +GPGME_CFLAGS=$(shell gpgme-config --cflags) +GPGME_LIBS=$(shell gpgme-config --libs) # Do not change these two -CFLAGS+=$(WARN) $(DEBUG) $(FORTIFY) $(COVERAGE) $(OPTIMIZE) \ - $(LANGUAGE) $(GNUTLS_CFLAGS) $(AVAHI_CFLAGS) $(GPGME_CFLAGS) \ - -DVERSION='"$(version)"' -LDFLAGS+=-Xlinker --as-needed $(COVERAGE) $(LINK_FORTIFY) $(foreach flag,$(LINK_FORTIFY_LD),-Xlinker $(flag)) +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 $<); \ - $(MANPOST) $(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) -# DocBook-to-man post-processing to fix a '\n' escape bug -MANPOST=$(SED) --in-place --expression='s,\\\\en,\\en,g;s,\\n,\\en,g' - -DOCBOOKTOHTML=$(strip xsltproc --nonet --xinclude \ - --param make.year.ranges 1 \ - --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 -CPROGS=plugin-runner $(PLUGINS) -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/' \ - $@) - -plugins.d/mandos-client: plugins.d/mandos-client.c - $(LINK.c) $^ -lrt $(GNUTLS_LIBS) $(AVAHI_LIBS) $(strip\ - ) $(GPGME_LIBS) $(LOADLIBES) $(LDLIBS) -o $@ - -.PHONY : all doc html clean distclean run-client run-server install \ +%.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 -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 - @echo "###################################################################" - @echo "# The following error messages are harmless and can be safely #" - @echo "# ignored. The messages are caused by not running as root, but #" - @echo "# you should NOT run \"make run-client\" as root unless you also #" - @echo "# unpacked and compiled Mandos as root, which is NOT recommended. #" - @echo "# From plugin-runner: setuid: Operation not permitted #" - @echo "# From askpass-fifo: mkfifo: Permission denied #" - @echo "# From mandos-client: setuid: Operation not permitted #" - @echo "# seteuid: Operation not permitted #" - @echo "# klogctl: Operation not permitted #" - @echo "###################################################################" +run-client: all + -mkdir keydir + -./mandos-keygen --dir keydir ./plugin-runner --plugin-dir=plugins.d \ - --config-file=plugin-runner.conf \ - --options-for=mandos-client:--seckey=keydir/seckey.txt,--pubkey=keydir/pubkey.txt,--network-hook-dir=network-hooks.d \ - $(CLIENTARGS) - -# Used by run-client -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) - -# 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 - install --directory confdir - install --mode=u=rw $< $@ -# Add a client password - ./mandos-keygen --dir keydir --password >> $@ -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) + --options-for=password-request:--keydir=keydir + +run-server: + ./mandos --debug --configdir=. + +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 - 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) \ + mkdir --mode=0755 --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 - if [ "$(CONFDIR)" != "$(LIBDIR)/mandos" ]; then \ - install --mode=u=rwx \ - --directory "$(CONFDIR)/plugins.d"; \ - fi - install --mode=u=rwx,go=rx --directory \ - "$(CONFDIR)/network-hooks.d" - install --mode=u=rwx,go=rx \ - --target-directory=$(LIBDIR)/mandos plugin-runner - install --mode=u=rwx,go=rx --target-directory=$(PREFIX)/sbin \ +install-client: all doc /usr/share/initramfs-tools/hooks/. + mkdir --mode=0755 --parents $(PREFIX)/lib/mandos $(CONFDIR) \ + $(MANDIR)/man8 + -mkdir --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=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-hook-conf \ - $(INITRAMFSTOOLS)/conf-hooks.d/mandos + /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 --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 - -rm --force $(CONFDIR)/plugin-runner.conf \ - $(KEYDIR)/pubkey.txt $(KEYDIR)/seckey.txt - -rmdir $(KEYDIR) $(CONFDIR)/plugins.d $(CONFDIR) + -rm --force $(CONFDIR)/seckey.txt $(CONFDIR)/pubkey.txt + -rmdir $(CONFDIR) $(CONFDIR)/plugins.d === removed file 'NEWS' --- NEWS 2013-10-24 20:38:44 +0000 +++ NEWS 1970-01-01 00:00:00 +0000 @@ -1,293 +0,0 @@ -This NEWS file records noteworthy changes, very tersely. -See the manual for detailed information. - -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 2012-01-01 20:45:53 +0000 +++ README 1970-01-01 00:00:00 +0000 @@ -1,11 +0,0 @@ -Please see: http://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 2013-10-30 21:05:47 +0000 +++ TODO 2008-08-23 07:17:28 +0000 @@ -1,132 +1,143 @@ -*- org -*- -* GIT -** General: [[https://www.atlassian.com/git/workflows][Git Workflows]], [[http://gitimmersion.com/][Git Immersion]] -** Intro: [[http://www.eyrie.org/~eagle/notes/debian/git.html][Using Git for Debian Packaging]] -** Use: [[https://honk.sigxcpu.org/piki/projects/git-buildpackage/][git-buildpackage]] -** Migration - tailor? - Using bzr-fastimport: [[http://www.fusonic.net/en/blog/2013/03/26/migrating-from-bazaar-to-git/][Migrating from Bazaar to Git]] -** Unresolved: [[http://jameswestby.net/bzr/builddeb/user_manual/split.html][bzr builddeb split mode]] - Maybe: [[http://honk.sigxcpu.org/projects/git-buildpackage/manual-html/gbp.import.html#GBP.IMPORT.UPSTREAM.GIT.NOTARBALL>][git-buildpackage - No upstream tarballs]] - -* [[http://www.undeadly.org/cgi?action=article&sid=20110530221728][OpenBSD]] - -* Testing -** python-nemu - -* mandos-applet - -* mandos-client -** TODO [#B] Use capabilities instead of seteuid(). -** TODO [#B] Use struct sockaddr_storage instead of a union -** TODO [#B] Use getaddrinfo(hints=AI_NUMERICHOST) instead of inet_pton() -** TODO [#B] Use getnameinfo(serv=NULL, NI_NUMERICHOST) instead of inet_ntop() -** TODO [#B] Prefer /run/tmp over /tmp, if it exists -** TODO [#C] Make start_mandos_communication() take "struct server". - -* splashy -** TODO [#B] use scandir(3) instead of readdir(3) - -* usplash (Deprecated) -** TODO [#A] Make it work again -** TODO [#B] use scandir(3) instead of readdir(3) - -* askpass-fifo -** TODO [#B] Drop privileges after opening FIFO. +* [#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()?) - -* plymouth - -* TODO [#B] passdev - -* plugin-runner -** TODO handle printing for errors for plugins -*** Hook up stderr of plugins, buffer them, and prepend "Mandos Plugin [plugin name]" -** TODO [#B] use scandir(3) instead of readdir(3) -** TODO [#C] use same file name rules as run-parts(8) -** kernel command line option for debug info -** TODO [#B] Use openat() +** [#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] 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 [#A] 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 Use python-tlslite? -** TODO D-Bus AddClient() method on server object -** TODO Use org.freedesktop.DBus.Method.NoReply annotation on async methods. :2: -** TODO Emit [[http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties][org.freedesktop.DBus.Properties.PropertiesChanged]] signal :2: - TODO Deprecate se.recompile.Mandos.Client.PropertyChanged - annotate! - TODO Can use "invalidates" annotation to also emit on changed secret. -** TODO Support [[http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager][org.freedesktop.DBus.ObjectManager]] interface on server object :2: - Deprecate methods GetAllClients(), GetAllClientsWithProperties() - and signals ClientAdded and ClientRemoved. -** TODO Save state periodically to recover better from hard shutdowns -** TODO CheckerCompleted method, deprecate CheckedOK -** TODO Secret Service API? - http://standards.freedesktop.org/secret-service/ -** TODO Remove D-Bus interfaces with old domain name :2: -** TODO Remove old string_to_delta format :2: -** TODO --no-zeroconf (only valid if port or socket is set) - -* mandos.xml -** Add mandos contact info in manual pages - -* mandos-ctl -*** Handle "no D-Bus server" and/or "no Mandos server found" better -*** [#B] --dump option -** TODO Remove old string_to_delta format :2: - -* TODO mandos-dispatch - Listens for specified D-Bus signals and spawns shell commands with - arguments. - -* mandos-monitor -** 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. -** Rename module "gobject" to "GObject". - -* mandos-keygen -** TODO "--secfile" option - Using the "secfile" option instead of "secret" -** TODO [#B] "--test" option - For testing decryption before rebooting. - -* Makefile -** TODO [#C] Implement DEB_BUILD_OPTIONS - http://www.debian.org/doc/debian-policy/ch-source.html#s-debianrules-options - -* Package +** [#A] Config file man page: man5/mandos-clients.conf (clients.conf) +*** For option "secret", document which program can help generate it. +** [#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 + +* Installer +** Client-side +*** Update initrd.img after installation + This seems to use some kind of "trigger" system +*** Keydir move: /etc/mandos -> /etc/keys/mandos + Must create in preinst if not pre-depending on cryptsetup +*** mandos-keygen +**** [#A] Output cut-and-paste ready snippet for clients.conf. +** 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] /etc/bash_completion.d/mandos +*** 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 +** unperish +** bzr-builddeb + +* INSTALL file + +* Web site + +* Mailing list + +* Announce project on news + [[news:comp.os.linux.announce]] #+STARTUP: showall === modified file 'clients.conf' --- clients.conf 2012-06-23 00:58:49 +0000 +++ clients.conf 2008-08-22 00:16:20 +0000 @@ -2,36 +2,19 @@ # 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 ;#### @@ -60,6 +43,7 @@ ; 5MHdW9AYsNJZAQSOpirE4Xi31CSlWAi9KV+cUCmWF5zOFy1x23P6PjdaRm ; 4T2zw4dxS5NswXWU0sVEXxjs6PYxuIiCTL7vdpx8QjBkrPWDrAbcMyBr2O ; QlnHIvPzEArRQLo= +; =iHhv ; ;# Host name; used only by the checker, not used by the server itself. ;host = foo.example.org @@ -72,16 +56,11 @@ ;fingerprint = 3e393aeaefb84c7e89e2f547b3a107558fca3a27 ; ;# If "secret" is not specified, a file can be read for the data. -;secfile = /etc/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 2013-10-24 20:38:44 +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 2013-10-24 20:38:44 +0000 +++ debian/changelog 1970-01-01 00:00:00 +0000 @@ -1,381 +0,0 @@ -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 2013-10-20 15:25:09 +0000 +++ debian/compat 1970-01-01 00:00:00 +0000 @@ -1,1 +0,0 @@ -9 === removed file 'debian/control' --- debian/control 2013-12-15 15:38:51 +0000 +++ debian/control 1970-01-01 00:00:00 +0000 @@ -1,57 +0,0 @@ -Source: mandos -Section: admin -Priority: extra -Maintainer: Mandos Maintainers -Uploaders: Teddy Hogeborn , - Björn Påhlsson -Build-Depends: debhelper (>= 9), docbook-xml, docbook-xsl, - libavahi-core-dev, libgpgme11-dev, libgnutls-dev, xsltproc, - pkg-config, systemd -Standards-Version: 3.9.4 -Vcs-Bzr: http://ftp.recompile.se/pub/mandos/trunk -Vcs-Browser: http://bzr.recompile.se/loggerhead/mandos/trunk/files -Homepage: http://www.recompile.se/mandos - -Package: mandos -Architecture: all -Depends: ${misc:Depends}, python (>=2.6), python-gnutls, python-dbus, - python-avahi, python-gobject, avahi-daemon, adduser, - python-urwid, python (>=2.7) | python-argparse, gnupg (<< 2), - initscripts (>= 2.88dsf-13.3), avahi-daemon (>= 0.6.31-3) - | systemd-sysv -Recommends: 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 an OpenPGP - key; each client has one unique to it. The server sends the - clients an encrypted password. The encrypted password is - decrypted by the clients using the same OpenPGP key, and the - password is then used to unlock the root file system, - whereupon the computers can continue booting normally. - -Package: mandos-client -Architecture: linux-any -Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, cryptsetup, - gnupg (<< 2), initramfs-tools -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 an OpenPGP - key; each client has one unique to it. The server sends the - clients an encrypted password. The encrypted password is - decrypted by the clients using the same OpenPGP key, and the - password is then used to unlock the root file system, - whereupon the computers can continue booting normally. === removed file 'debian/copyright' --- debian/copyright 2013-10-20 15:25:09 +0000 +++ debian/copyright 1970-01-01 00:00:00 +0000 @@ -1,25 +0,0 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: Mandos -Upstream-Contact: Mandos -Source: - -Files: * -Copyright: Copyright © 2008-2013 Teddy Hogeborn - Copyright © 2008-2013 Björn Påhlsson -License: GPL-3+ - This program is free software: you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - . - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - . - You should have received a copy of the GNU General Public License - along with this program. If not, see - . - . - On Debian systems, the complete text of the GNU General Public - License can be found in "/usr/share/common-licenses/GPL". === removed file 'debian/mandos-client.README.Debian' --- debian/mandos-client.README.Debian 2013-10-28 10:04:05 +0000 +++ debian/mandos-client.README.Debian 1970-01-01 00:00:00 +0000 @@ -1,94 +0,0 @@ -* 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: - - /usr/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH \ - )/mandos/plugins.d/mandos-client \ - --pubkey=/etc/keys/mandos/pubkey.txt \ - --seckey=/etc/keys/mandos/seckey.txt; echo - - This command should retrieve the password from the server, decrypt - it, and output it to standard output. There it can be verified to - be the correct password, before rebooting. - -* Emergency Escape - - If it ever should be necessary, the Mandos client can be temporarily - prevented from running at startup by passing the parameter - "mandos=off" to the kernel. - -* Specifying a Client Network Interface - - At boot time the network interfaces to use will by default be - automatically detected. If this should result in incorrect - interfaces, edit the DEVICE setting in the - "/etc/initramfs-tools/initramfs.conf" file. (The default setting is - empty, meaning it will autodetect the interface.) *If* the DEVICE - setting is changed, it will be necessary to update the initrd image - by running the 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 it possible to specify simply - "mandos=connect" on the kernel command line to make the system only - set up the network (using the data in the "ip=" option) and not pass - any extra "--connect" options to mandos-client at boot. For this to - work, "--options-for=mandos-client:--connect=
:" needs - to be manually added to the file "/etc/mandos/plugin-runner.conf". - - -- Teddy Hogeborn , Mon, 28 Oct 2013 11:02:26 +0100 === removed file 'debian/mandos-client.dirs' --- debian/mandos-client.dirs 2009-02-07 04:50:39 +0000 +++ debian/mandos-client.dirs 1970-01-01 00:00:00 +0000 @@ -1,5 +0,0 @@ -usr/share/man/man8 -usr/sbin -usr/share/initramfs-tools/hooks -usr/share/initramfs-tools/conf-hooks.d -usr/share/initramfs-tools/scripts/init-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 2010-10-13 06:12:52 +0000 +++ debian/mandos-client.lintian-overrides 1970-01-01 00:00:00 +0000 @@ -1,28 +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 - -# 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 === removed file 'debian/mandos-client.postinst' --- debian/mandos-client.postinst 2011-10-10 20:29:58 +0000 +++ debian/mandos-client.postinst 1970-01-01 00:00:00 +0000 @@ -1,83 +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() -{ - if [ -x /usr/sbin/update-initramfs ]; then - update-initramfs -u -k all - fi - - if dpkg --compare-versions "$2" lt-nl "1.0.10-1"; then - # Make old initrd.img files unreadable too, in case they were - # 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 pair -create_key(){ - if [ -r /etc/keys/mandos/pubkey.txt \ - -a -r /etc/keys/mandos/seckey.txt ]; then - return 0 - fi - if [ -x /usr/sbin/mandos-keygen ]; then - mandos-keygen - fi -} - -case "$1" in - configure) - add_mandos_user "$@" - create_key "$@" - update_initramfs "$@" - ;; - 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 2011-10-10 20:29:58 +0000 +++ debian/mandos-client.postrm 1970-01-01 00:00:00 +0000 @@ -1,62 +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() -{ - if [ -x /usr/sbin/update-initramfs ]; then - update-initramfs -u -k all - fi -} - -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 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 2013-10-27 17:42:23 +0000 +++ debian/mandos.dirs 1970-01-01 00:00:00 +0000 @@ -1,8 +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 === 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 2008-10-01 15:29:01 +0000 +++ debian/mandos.lintian-overrides 1970-01-01 00:00:00 +0000 @@ -1,4 +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 === removed file 'debian/mandos.postinst' --- debian/mandos.postinst 2014-01-06 17:22:30 +0000 +++ debian/mandos.postinst 1970-01-01 00:00:00 +0000 @@ -1,62 +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 - fi - chown _mandos:_mandos /var/lib/mandos - ;; - - 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.rd-c, 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 2011-10-10 20:29:58 +0000 +++ debian/mandos.prerm 1970-01-01 00:00:00 +0000 @@ -1,38 +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) - if [ -x /etc/init.d/mandos ]; then - if [ -x /usr/sbin/invoke-rc.d ]; then - invoke-rc.d mandos stop - else - /etc/init.d/mandos stop - fi - fi - ;; - upgrade|failed-upgrade) - ;; - *) - echo "prerm called with unknown argument '$1'" >&2 - exit 0 - ;; -esac - -#DEBHELPER# - -exit 0 === removed directory 'debian/po' === removed file 'debian/rules' --- debian/rules 2013-10-05 19:34:40 +0000 +++ debian/rules 1970-01-01 00:00:00 +0000 @@ -1,29 +0,0 @@ -#!/usr/bin/make -f -%: - 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: - dh_fixperms --exclude etc/keys/mandos \ - --exclude etc/mandos/clients.conf \ - --exclude etc/mandos/plugins.d \ - --exclude usr/lib/mandos/plugins.d \ - --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" === 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 file 'debian/watch' --- debian/watch 2011-10-05 16:00:56 +0000 +++ debian/watch 1970-01-01 00:00:00 +0000 @@ -1,2 +0,0 @@ -version=3 -ftp://ftp.recompile.se/pub/mandos/mandos[-_]([^\s]+?)(?:\.orig)?\.tar\.(?:gz|bz2|7z|xz) === 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 2014-01-06 15:56:54 +0000 +++ init.d-mandos 1970-01-01 00:00:00 +0000 @@ -1,167 +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 -# -# Please remove the "Author" lines above and replace them -# with your own name if you copy and modify this script. - -# 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 - -: === modified file 'initramfs-tools-hook' --- initramfs-tools-hook 2013-10-28 08:38:47 +0000 +++ initramfs-tools-hook 2008-08-14 02:24:59 +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,31 +47,26 @@ CONFDIR="/conf/conf.d/mandos" MANDOSDIR="/lib/mandos" PLUGINDIR="${MANDOSDIR}/plugins.d" -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}" +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}" ;; + *~|.*|\#*\#|*.dpkg-old|*.dpkg-new|*.dpkg-divert) : ;; + *) copy_exec "$file" "${PLUGINDIR}";; esac done @@ -102,93 +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 - -# 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 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 module; do - if [ -z "${target}" ]; then - force_load "$module" - fi - done - fi -done - -# GPGME needs GnuPG -libgpgme11_version="`dpkg-query --showformat='${Version}' --show libgpgme11`" -if dpkg --compare-versions "$libgpgme11_version" ge 1.4.1-0.1; then - gpg=/usr/bin/gpg2 -else - gpg=/usr/bin/gpg -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 - 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" + *~|.*|*.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 "$keydir"/*; do +for file in /etc/mandos/*; do if [ -d "$file" ]; then continue fi cp --archive --sparse=always "$file" "${DESTDIR}${CONFDIR}" - chown ${mandos_user}:${mandos_group} \ - "${DESTDIR}${CONFDIR}/`basename \"$file\"`" done +# Create key ring files +gpg --no-random-seed-file --quiet --batch --no-tty \ + --no-default-keyring --no-options \ + --homedir "${DESTDIR}${CONFDIR}" --no-permission-warning \ + --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 @@ -202,7 +112,7 @@ # 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\"`" @@ -212,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 -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 === modified file 'initramfs-tools-hook-conf' --- initramfs-tools-hook-conf 2009-05-17 00:50:09 +0000 +++ initramfs-tools-hook-conf 2008-08-12 19:22:34 +0000 @@ -1,13 +1,1 @@ -# -*- shell-script -*- - -# if mkinitramfs is started by mkinitramfs-kpkg, mkinitramfs-kpkg has -# already touched the initrd file with umask 022 before we had a -# chance to affect it. We cannot allow a readable initrd file, -# therefore we must fix this now. -if [ -e "${outfile}" ] \ - && [ `stat --format=%s "${outfile}"` -eq 0 ]; then - rm "${outfile}" - (umask 027; touch "${outfile}") -fi - UMASK=027 === modified file 'initramfs-tools-script' --- initramfs-tools-script 2011-07-16 00:29:19 +0000 +++ initramfs-tools-script 2008-08-14 02:24:59 +0000 @@ -6,109 +6,31 @@ # # 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 - -test -r /conf/conf.d/cryptroot -test -w /conf/conf.d - -# Get DEVICE from /conf/initramfs.conf and other files -. /conf/initramfs.conf -for conf in /conf/conf.d/*; do - [ -f ${conf} ] && . ${conf} -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 - configure_networking - if [ -n "$connect" ]; then - cat <<-EOF >>/conf/conf.d/mandos/plugin-runner.conf - - --options-for=mandos-client:--connect=${connect} -EOF - fi -fi + 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 -test -x "$mandos" # parse /conf/conf.d/cryptroot. Format: # target=sda2_crypt,source=/dev/sda2,key=none,keyscript=/foo/bar/baz === removed file 'initramfs-unpack' --- initramfs-unpack 2013-10-13 15:43:42 +0000 +++ initramfs-unpack 1970-01-01 00:00:00 +0000 @@ -1,67 +0,0 @@ -#!/bin/bash -# -# Initramfs unpacker - unpacks initramfs images into /tmp -# -# Copyright © 2013 Teddy Hogeborn -# Copyright © 2013 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 this program. 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 2011-12-31 23:05:34 +0000 +++ intro.xml 1970-01-01 00:00:00 +0000 @@ -1,411 +0,0 @@ - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2011 - 2012 - 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 an OpenPGP key; each client - has one unique to it. The server sends the clients an encrypted - password. The encrypted password is decrypted by the clients - using the same OpenPGP key, and the password is then used to - unlock the root file system, whereupon the computers can - continue booting normally. - - - - - 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 OpenPGP private key corresponding to - that client. - - - - - Physically grabbing the Mandos server computer? - - You could protect that computer the - old-fashioned way, with a must-type-in-the-password-at-boot - method. Or you could have two computers be the Mandos server - for each other. - - - Multiple Mandos servers can coexist on a network without any - trouble. They do not clash, and clients will try all - available servers. This means that if just one reboots then - the other can bring it back up, but if both reboot at the same - time they will stay down until someone types in the password - on one of them. - - - - - Faking ping replies? - - The default for the server is to use - fping, the replies to which - could be faked to eliminate the timeout. But this could - easily be changed to any shell command, with any security - measures you like. It could, for instance, be changed to an - SSH command with strict keychecking, which could not be faked. - Or IPsec could be used for the ping packets, making them - secure. - - - - - - SECURITY - - 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. - - - - - 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 2008-09-06 17:24:58 +0000 +++ legalnotice.xml 1970-01-01 00:00:00 +0000 @@ -1,27 +0,0 @@ - - - - - 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 - http://www.gnu.org/licenses/. - - === modified file 'mandos' --- mandos 2014-01-06 16:01:46 +0000 +++ mandos 2008-08-22 00:16:20 +0000 @@ -6,13 +6,12 @@ # 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-2014 Teddy Hogeborn -# Copyright © 2008-2014 Björn Påhlsson +# 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 @@ -25,20 +24,17 @@ # 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 -# . -# -# Contact the authors at . -# - -from __future__ import (division, absolute_import, print_function, - unicode_literals) - -from future_builtins import * - -import SocketServer as 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 import gnutls.crypto @@ -47,183 +43,44 @@ import gnutls.library.functions import gnutls.library.constants import gnutls.library.types -import ConfigParser as configparser +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 -import cPickle as pickle -import multiprocessing -import types -import binascii -import tempfile -import itertools -import collections import dbus -import dbus.service import gobject import avahi from dbus.mainloop.glib import DBusGMainLoop import ctypes -import ctypes.util -import xml.dom.minidom -import inspect - -try: - SO_BINDTODEVICE = socket.SO_BINDTODEVICE -except AttributeError: - try: - from IN import SO_BINDTODEVICE - except ImportError: - SO_BINDTODEVICE = None - -version = "1.6.2" -stored_state_file = "clients.pickle" - -logger = logging.getLogger() -syslogger = (logging.handlers.SysLogHandler - (facility = logging.handlers.SysLogHandler.LOG_DAEMON, - address = str("/dev/log"))) - -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(str("16s16x"), - interface)) - interface_index = struct.unpack(str("I"), - ifreq[16:20])[0] - return interface_index - - -def initlogger(debug, level=logging.WARNING): - """init logger and add loglevel""" - - 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.gnupgargs = ['--batch', - '--home', self.tempdir, - '--force-mdc', - '--quiet', - '--no-use-agent'] - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - self._cleanup() - return False - - def __del__(self): - self._cleanup() - - def _cleanup(self): - if self.tempdir is not None: - # Delete contents of tempdir - for root, dirs, files in os.walk(self.tempdir, - topdown = False): - 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(['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(['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 - + +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 - super(AvahiError, self).__init__(value, *args, **kwargs) - def __unicode__(self): - return unicode(repr(self.value)) + def __str__(self): + return repr(self.value) class AvahiServiceError(AvahiError): pass @@ -234,13 +91,12 @@ 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. @@ -248,517 +104,254 @@ 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): + 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): """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 service name found after %i" + u" retries, exiting.", rename_count) raise AvahiServiceError("Too many renames") - self.name = unicode(self.server - .GetAlternativeServiceName(self.name)) - logger.info("Changing Zeroconf service name to %r ...", - self.name) + self.name = server.GetAlternativeServiceName(self.name) + logger.info(u"Changing name to %r ...", str(self.name)) + syslogger.setFormatter(logging.Formatter\ + ('Mandos (%s): %%(levelname)s:' + ' %%(message)s' % self.name)) self.remove() - try: - self.add() - except dbus.exceptions.DBusException as error: - logger.critical("D-Bus Exception", exc_info=error) - self.cleanup() - os._exit(1) + self.add() self.rename_count += 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", - unicode(error)) - raise AvahiGroupError("State changed: {0!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: - self.add() - 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): - """Add the new name to the syslog messages""" - ret = AvahiService.rename(self) - syslogger.setFormatter(logging.Formatter - ('Mandos ({0}) [%(process)d]:' - ' %(levelname)s: %(message)s' - .format(self.name))) - return ret - - -def timedelta_to_milliseconds(td): - "Convert a datetime.timedelta() to milliseconds" - return ((td.days * 24 * 60 * 60 * 1000) - + (td.seconds * 1000) - + (td.microseconds // 1000)) + 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 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 gobject 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 gobject 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 gobject event source tag, or None - enabled: bool() - fingerprint: string (40 or 32 hexadecimal digits); used to - uniquely identify the client - host: string; available for use by the checker command - interval: datetime.timedelta(); How often to start a new checker - last_approval_request: datetime.datetime(); (UTC) or None - 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_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", - "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", - } - - def timeout_milliseconds(self): - "Return the 'timeout' attribute in milliseconds" - return timedelta_to_milliseconds(self.timeout) - - def extended_timeout_milliseconds(self): - "Return the 'extended_timeout' attribute in milliseconds" - return timedelta_to_milliseconds(self.extended_timeout) - - def interval_milliseconds(self): - "Return the 'interval' attribute in milliseconds" - return timedelta_to_milliseconds(self.interval) - - def approval_delay_milliseconds(self): - return timedelta_to_milliseconds(self.approval_delay) - - @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") - - client["fingerprint"] = (section["fingerprint"].upper() - .replace(" ", "")) - if "secret" in section: - client["secret"] = section["secret"].decode("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 {0}" - .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.iteritems(): - 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) - else: - self.last_enabled = None - self.expires = None - - logger.debug("Creating client %r", self.name) + 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 - logger.debug(" Fingerprint: %s", self.fingerprint) - self.created = settings.get("created", - datetime.datetime.utcnow()) - - # attributes specific for this server instance + 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: + 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__.iterkeys() - 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: - gobject.source_remove(self.disable_initiator_tag) - self.disable_initiator_tag = None - self.expires = None - if getattr(self, "checker_initiator_tag", None) is not None: - gobject.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 gobject.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: - gobject.source_remove(self.checker_initiator_tag) - self.checker_initiator_tag = (gobject.timeout_add - (self.interval_milliseconds(), - self.start_checker)) - # Schedule a disable() when 'timeout' has passed - if self.disable_initiator_tag is not None: - gobject.source_remove(self.disable_initiator_tag) - self.disable_initiator_tag = (gobject.timeout_add - (self.timeout_milliseconds(), - 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, pid, condition, 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 - if os.WIFEXITED(condition): - self.last_checker_status = os.WEXITSTATUS(condition) - 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 - 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)) - - 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.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: - gobject.source_remove(self.disable_initiator_tag) - self.disable_initiator_tag = None - if getattr(self, "enabled", False): - self.disable_initiator_tag = (gobject.timeout_add - (timedelta_to_milliseconds - (timeout), 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 a checker exists, make sure it is not a zombie - try: - pid, status = os.waitpid(self.checker.pid, os.WNOHANG) - except AttributeError: - pass - except OSError as error: - if error.errno != errno.ECHILD: - raise - else: - if pid: - logger.warning("Checker was a zombie") - gobject.source_remove(self.checker_callback_tag) - self.checker_callback(pid, status, - self.current_checker_command) - # Start a new checker if needed + # 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 = dict( - (attr, re.escape(unicode(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 - try: - logger.info("Starting checker %r for %s", + 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.) - # The exception is when not debugging but nevertheless - # running in the foreground; use the previously - # created wnull. - popen_args = {} - if (not self.server_settings["debug"] - and self.server_settings["foreground"]): - popen_args.update({"stdout": wnull, - "stderr": wnull }) self.checker = subprocess.Popen(command, close_fds=True, - shell=True, cwd="/", - **popen_args) - except OSError as error: - logger.error("Failed to start subprocess", - exc_info=error) - return True - self.checker_callback_tag = (gobject.child_watch_add - (self.checker.pid, - self.checker_callback, - data=command)) - # The checker may have completed before the gobject - # watch was added. Check for this. - try: - pid, status = os.waitpid(self.checker.pid, os.WNOHANG) - except OSError as error: - if error.errno == errno.ECHILD: - # This should never happen - logger.error("Child process vanished", - exc_info=error) - return True - raise - if pid: - gobject.source_remove(self.checker_callback_tag) - self.checker_callback(pid, status, command) + 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: @@ -766,1464 +359,204 @@ self.checker_callback_tag = None if getattr(self, "checker", None) is None: return - logger.debug("Stopping checker for %(name)s", vars(self)) + logger.debug(u"Stopping checker for %(name)s", vars(self)) try: - self.checker.terminate() - #time.sleep(0.5) + os.kill(self.checker.pid, signal.SIGTERM) + #os.sleep(0.5) #if self.checker.poll() is None: - # self.checker.kill() - except OSError as error: + # 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 {0!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_service_property("org.example.Interface", signature="b", - access="r") - @dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true", - "org.freedesktop.DBus.Property." - "EmitsChangedSignal": "false"}) - def Property_dbus_property(self): - return dbus.Boolean(False) - """ - 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 - """ - def __unicode__(self): - return unicode(str(self)) - - -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 DBusObjectWithProperties(dbus.service.Object): - """A D-Bus object with properties. - - Classes inheriting from this can use the dbus_service_property - decorator to expose methods as D-Bus properties. It exposes the - standard Get(), Set(), and GetAll() methods on the D-Bus. - """ - - @staticmethod - def _is_dbus_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_{0}".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))) - - 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(self.dbus_object_path + ":" - + interface_name + "." - + property_name) - - @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 {0!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.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 = dbus.service.Object.Introspect(self, object_path, - connection) - try: - document = xml.dom.minidom.parseString(xmlstring) - def make_tag(document, name, prop): - e = document.createElement("property") - e.setAttribute("name", name) - e.setAttribute("type", prop._dbus_signature) - e.setAttribute("access", prop._dbus_access) - 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 typ in ("method", "signal", "property"): - 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.iteritems(): - 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().iteritems() - for name, annotations in - self._get_all_dbus_things("interface") - if name == if_tag.getAttribute("name") - )).iteritems(): - ann_tag = document.createElement("annotation") - ann_tag.setAttribute("name", annotation) - ann_tag.setAttribute("value", value) - if_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 - - -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.iteritems()): - 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 - nonmethod_func = (dict( - zip(attribute.func_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 = (dbus.service.signal - (alt_interface, - attribute._dbus_signature) - (types.FunctionType( - nonmethod_func.func_code, - nonmethod_func.func_globals, - nonmethod_func.func_name, - nonmethod_func.func_defaults, - nonmethod_func.func_closure))) - # 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""" - def call_both(*args, **kwargs): - """This function will emit two D-Bus - signals by calling func1 and func2""" - func1(*args, **kwargs) - func2(*args, **kwargs) - 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) - (types.FunctionType - (attribute.func_code, - attribute.func_globals, - attribute.func_name, - attribute.func_defaults, - attribute.func_closure))) - # 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"]) - (types.FunctionType - (attribute.func_code, - attribute.func_globals, - attribute.func_name, - attribute.func_defaults, - attribute.func_closure))) - # 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) - (types.FunctionType - (attribute.func_code, - attribute.func_globals, - attribute.func_name, - attribute.func_defaults, - attribute.func_closure))) - if deprecate: - # Deprecate all alternate interfaces - iname="_AlternateDBusNames_interface_annotation{0}" - 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. - cls = type(b"{0}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",)) - - # 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 = unicode(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): - """ 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 = "_{0}".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)): - dbus_value = transform_func(type_func(value), - variant_level - =variant_level) - self.PropertyChanged(dbus.String(dbus_name), - dbus_value) - 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 = - timedelta_to_milliseconds) - approval_duration = notifychangeproperty( - dbus.UInt64, "ApprovalDuration", - type_func = timedelta_to_milliseconds) - host = notifychangeproperty(dbus.String, "Host") - timeout = notifychangeproperty(dbus.UInt64, "Timeout", - type_func = - timedelta_to_milliseconds) - extended_timeout = notifychangeproperty( - dbus.UInt64, "ExtendedTimeout", - type_func = timedelta_to_milliseconds) - interval = notifychangeproperty(dbus.UInt64, - "Interval", - type_func = - timedelta_to_milliseconds) - checker_command = notifychangeproperty(dbus.String, "Checker") - - 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, pid, condition, command, - *args, **kwargs): - self.checker_callback_tag = None - self.checker = None - if os.WIFEXITED(condition): - exitstatus = os.WEXITSTATUS(condition) - # Emit D-Bus signal - self.CheckerCompleted(dbus.Int16(exitstatus), - dbus.Int64(condition), - dbus.String(command)) - else: - # Emit D-Bus signal - self.CheckerCompleted(dbus.Int16(-1), - dbus.Int64(condition), - dbus.String(command)) - - return Client.checker_callback(self, pid, condition, command, - *args, **kwargs) - - def start_checker(self, *args, **kwargs): - old_checker = self.checker - if self.checker is not None: - old_checker_pid = self.checker.pid - else: - old_checker_pid = None - r = Client.start_checker(self, *args, **kwargs) - # Only if new checker process was started - if (self.checker is not None - and old_checker_pid != self.checker.pid): - # Emit D-Bus signal - self.CheckerStarted(self.current_checker_command) - return r - - def _reset_approved(self): - self.approved = None - return False - - def approve(self, value=True): - self.approved = value - gobject.timeout_add(timedelta_to_milliseconds - (self.approval_duration), - self._reset_approved) - self.send_changedstate() - - ## D-Bus methods, signals & properties - _interface = "se.recompile.Mandos.Client" - - ## Interfaces - - @dbus_interface_annotations(_interface) - def _foo(self): - return { "org.freedesktop.DBus.Property.EmitsChangedSignal": - "false"} - - ## 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.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.service.method(_interface) - def Enable(self): - "D-Bus method" - self.enable() - - # StartChecker - method - @dbus.service.method(_interface) - def StartChecker(self): - "D-Bus method" - self.start_checker() - - # Disable - method - @dbus.service.method(_interface) - def Disable(self): - "D-Bus method" - self.disable() - - # StopChecker - method - @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_milliseconds()) - 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(timedelta_to_milliseconds( - self.approval_duration)) - self.approval_duration = datetime.timedelta(0, 0, 0, value) - - # Name - property - @dbus_service_property(_interface, signature="s", access="read") - def Name_dbus_property(self): - return dbus.String(self.name) - - # Fingerprint - property - @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 = unicode(value) - - # Created - property - @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_milliseconds()) - 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 - gobject.source_remove(self.disable_initiator_tag) - self.disable_initiator_tag = ( - gobject.timeout_add( - timedelta_to_milliseconds(self.expires - now), - 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_milliseconds()) - 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_milliseconds()) - self.interval = datetime.timedelta(0, 0, 0, value) - if getattr(self, "checker_initiator_tag", None) is None: - return - if self.enabled: - # Reschedule checker run - gobject.source_remove(self.checker_initiator_tag) - self.checker_initiator_tag = (gobject.timeout_add - (value, self.start_checker)) - self.start_checker() # Start one now, too - - # Checker - property - @dbus_service_property(_interface, signature="s", - access="readwrite") - def Checker_dbus_property(self, value=None): - if value is None: # get - return dbus.String(self.checker_command) - self.checker_command = unicode(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_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_service_property(_interface, signature="ay", - access="write", byte_arrays=True) - def Secret_dbus_property(self, value): - self.secret = str(value) - - del _interface - - -class ProxyClient(object): - def __init__(self, child_pipe, fpr, address): - self._pipe = child_pipe - self._pipe.send(('init', fpr, address)) - if not self._pipe.recv(): - raise KeyError() - - 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) + # 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", - unicode(self.client_address)) - logger.debug("Pipe FD: %d", - self.server.child_pipe.fileno()) - - session = (gnutls.connection - .ClientSession(self.request, - gnutls.connection - .X509Credentials())) - - # Note: gnutls.connection.X509Credentials is really a - # generic GnuTLS certificate credentials object so long as - # no X.509 keys are added to it. Therefore, we can use it - # here despite using OpenPGP certificates. - - #priority = ':'.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.library.functions - .gnutls_priority_set_direct(session._c_object, - priority, None)) - - # Start communication using the Mandos protocol - # Get protocol number - line = self.request.makefile().readline() - logger.debug("Protocol version: %r", line) - 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.errors.GNUTLSError 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: - try: - fpr = self.fingerprint(self.peer_certificate - (session)) - except (TypeError, - gnutls.errors.GNUTLSError) as error: - logger.warning("Bad certificate: %s", error) - return - logger.debug("Fingerprint: %s", fpr) - - try: - client = ProxyClient(child_pipe, fpr, - self.client_address) - except KeyError: - return - - if client.approval_delay: - delay = client.approval_delay - client.approvals_pending += 1 - approval_required = True - - while True: - if not client.enabled: - 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_milliseconds(), - 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( - float(timedelta_to_milliseconds(delay) - / 1000)) - 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 - - sent_size = 0 - while sent_size < len(client.secret): - try: - sent = session.send(client.secret[sent_size:]) - except gnutls.errors.GNUTLSError as error: - logger.warning("gnutls send failed", - exc_info=error) - return - logger.debug("Sent: %d, remaining: %d", - sent, len(client.secret) - - (sent_size + sent)) - sent_size += sent - - 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.errors.GNUTLSError as error: - logger.warning("GnuTLS bye failed", - exc_info=error) - - @staticmethod - 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(1) - cert_list = (gnutls.library.functions - .gnutls_certificate_get_peers - (session._c_object, ctypes.byref(list_size))) - if not bool(cert_list) and list_size.value != 0: - raise gnutls.errors.GNUTLSError("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 fingerprint(openpgp): - "Convert an OpenPGP bytestring to a hexdigit fingerprint" - # 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 - buf = ctypes.create_string_buffer(20) - buf_len = ctypes.c_size_t() - # Get the fingerprint from the certificate into the buffer - (gnutls.library.functions - .gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf), - ctypes.byref(buf_len))) - # Deinit the certificate - gnutls.library.functions.gnutls_openpgp_crt_deinit(crt) - # Convert the buffer to a Python bytestring - fpr = ctypes.string_at(buf, buf_len.value) - # Convert the bytestring to hexadecimal notation - hex_fpr = binascii.hexlify(fpr).upper() - 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.""" - if self.interface is not None: - if SO_BINDTODEVICE is None: - logger.error("SO_BINDTODEVICE does not exist;" - " cannot bind to interface %s", - self.interface) - else: - try: - self.socket.setsockopt(socket.SOL_SOCKET, - SO_BINDTODEVICE, - str(self.interface + '\0')) - except socket.error as error: - if error.errno == errno.EPERM: - logger.error("No permission to bind to" - " interface %s", self.interface) - elif error.errno == errno.ENOPROTOOPT: - logger.error("SO_BINDTODEVICE not available;" - " cannot bind to interface %s", - self.interface) - elif error.errno == errno.ENODEV: - logger.error("Interface %s does not exist," - " cannot bind", self.interface) - else: - raise + 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.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 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: +# 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 gobject.MainLoop event loop. - """ - def __init__(self, server_address, RequestHandlerClass, - interface=None, use_ipv6=True, clients=None, - gnutls_priority=None, use_dbus=True, socketfd=None): - 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 - gobject.io_add_watch(parent_pipe.fileno(), - gobject.IO_IN | gobject.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 & (gobject.IO_ERR | gobject.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': - fpr = request[1] - address = request[2] - - for c in self.clients.itervalues(): - if c.fingerprint == fpr: - client = c - break - else: - logger.info("Client not found for fingerprint: %s, ad" - "dress: %s", fpr, address) - if self.use_dbus: - # Emit D-Bus signal - mandos_dbus_service.ClientNotFound(fpr, - address[0]) - parent_pipe.send(False) - return False - - gobject.io_add_watch(parent_pipe.fileno(), - gobject.IO_IN | gobject.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 callable(client_object.__getattribute__(attrname)): - 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 = datetime.timedelta() # Value so far - found_token = None - followers = frozenset(token_duration,) # Following valid tokens - s = duration # String left to parse - # Loop until end token is found - while found_token is not token_end: - # Search for any currently valid tokens - 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") - # End token found - return value +# (self.settings +# ["interface"])) + return super(type(self), self).server_bind() def string_to_delta(interval): """Parse a string and return a datetime.timedelta - + >>> string_to_delta('7d') datetime.timedelta(7) >>> string_to_delta('60s') @@ -2232,44 +565,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 = unicode(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 {0!r}" - .format(suffix)) - except IndexError as e: - raise ValueError(*(e.args)) + raise ValueError + except (ValueError, IndexError): + raise ValueError timevalue += delta return timevalue +def server_state_changed(state): + """Derived from the Avahi example code""" + if state == avahi.SERVER_COLLISION: + logger.error(u"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"state change: %i", state) + + if state == avahi.ENTRY_GROUP_ESTABLISHED: + logger.debug(u"Service established.") + elif state == avahi.ENTRY_GROUP_COLLISION: + logger.warning(u"Service name collision.") + service.rename() + elif state == avahi.ENTRY_GROUP_FAILURE: + logger.critical(u"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() @@ -2280,11 +653,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, - "{0} 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()) @@ -2293,53 +665,30 @@ def main(): - - ################################################################## - # Parsing of options, both command line and config file - - parser = argparse.ArgumentParser() - parser.add_argument("-v", "--version", action="version", - version = "%(prog)s {0}".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) - - 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 @@ -2352,518 +701,170 @@ "port": "", "debug": "False", "priority": - "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:+SIGN-RSA-SHA224:+SIGN-RSA-RMD160", + "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP", "servicename": "Mandos", - "use_dbus": "True", - "use_ipv6": "True", - "debuglevel": "", - "restore": "True", - "socket": "", - "statedir": "/var/lib/mandos", - "foreground": "False", } # 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")) + 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", "foreground"): - 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"): + "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 type(server_settings[option]) is str: - server_settings[option] = unicode(server_settings[option]) - # Force all boolean options to be boolean - for option in ("debug", "use_dbus", "use_ipv6", "restore", - "foreground"): - 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" - ################################################################## - - # 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"] - 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 ({0}) [%(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 - - 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=(server_settings["socket"] - or None)) - if not foreground: - pidfilename = "/run/mandos.pid" - if not os.path.isdir("/run/."): - pidfilename = "/var/run/mandos.pid" - pidfile = None - try: - pidfile = open(pidfilename, "w") - except IOError as e: - logger.error("Could not open file %r", pidfilename, - exc_info=e) - - for name in ("_mandos", "mandos", "nobody"): - try: - uid = pwd.getpwnam(name).pw_uid - gid = pwd.getpwnam(name).pw_gid - break - except KeyError: - continue - else: - uid = 65534 - gid = 65534 - try: - os.setgid(gid) - os.setuid(uid) - except OSError as error: - 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.library.functions.gnutls_global_set_log_level(11) - - @gnutls.library.types.gnutls_log_func - def debug_gnutls(level, string): - logger.debug("GnuTLS: %s", string[:-1]) - - (gnutls.library.functions - .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 gobject we - # need to inform gobject that threads will be used. - gobject.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) + 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.NameExistsException as e: - logger.error("Disabling D-Bus:", exc_info=e) - use_dbus = False - server_settings["use_dbus"] = False - tcp_server.use_dbus = False - 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 - (str(server_settings["interface"]))) - - 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: - clients_data, old_client_settings = (pickle.load - (stored_state)) - os.remove(stored_state_path) - except IOError as e: - if e.errno == errno.ENOENT: - logger.warning("Could not load persistent state: {0}" - .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.iteritems(): - # 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. Clients - # 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 {0} - Client never " - "performed a successful checker" - .format(client_name)) - client["enabled"] = False - elif client["last_checker_status"] != 0: - logger.warning( - "disabling client {0} - Client " - "last checker failed with error code {1}" - .format(client_name, - client["last_checker_status"])) - client["enabled"] = False - else: - client["expires"] = (datetime.datetime - .utcnow() - + client["timeout"]) - logger.debug("Last checker succeeded," - " keeping {0} 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 {0} 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.iteritems(): - 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: - try: - with pidfile: - pid = os.getpid() - pidfile.write(str(pid) + "\n".encode("utf-8")) - 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 - - signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit()) - signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit()) - - if use_dbus: - @alternate_dbus_interfaces({"se.recompile.Mandos": - "se.bsnet.fukt.Mandos"}) - class MandosDBusService(DBusObjectWithProperties): - """A D-Bus proxy object""" - def __init__(self): - dbus.service.Object.__init__(self, bus, "/") - _interface = "se.recompile.Mandos" - - @dbus_interface_annotations(_interface) - def _foo(self): - return { "org.freedesktop.DBus.Property" - ".EmitsChangedSignal": - "false"} - - @dbus.service.signal(_interface, signature="o") - def ClientAdded(self, objpath): - "D-Bus signal" - pass - - @dbus.service.signal(_interface, signature="ss") - def ClientNotFound(self, fingerprint, address): - "D-Bus signal" - pass - - @dbus.service.signal(_interface, signature="os") - def ClientRemoved(self, objpath, name): - "D-Bus signal" - pass - - @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.itervalues()) - - @dbus.service.method(_interface, - out_signature="a{oa{sv}}") - def GetAllClientsWithProperties(self): - "D-Bus method" - return dbus.Dictionary( - ((c.dbus_object_path, c.GetAll("")) - for c in tcp_server.clients.itervalues()), - signature="oa{sv}") - - @dbus.service.method(_interface, in_signature="o") - def RemoveClient(self, object_path): - "D-Bus method" - for c in tcp_server.clients.itervalues(): - if c.dbus_object_path == object_path: - del tcp_server.clients[c.name] - c.remove_from_connection() - # Don't signal anything except ClientRemoved - c.disable(quiet=True) - # Emit D-Bus signal - self.ClientRemoved(object_path, c.name) - return - raise KeyError(object_path) - - del _interface - - mandos_dbus_service = MandosDBusService() + except IOError, err: + logger.error(u"Could not write %s file with PID %d", + pidfilename, os.getpid()) def cleanup(): "Cleanup function; run on exit" - service.cleanup() - - multiprocessing.active_children() - wnull.close() - if not (tcp_server.clients or client_settings): - return - - # Store client before exiting. Secrets are encrypted with key - # based on what config file has. If config file is - # removed/edited, old secret will thus be unrecovable. - clients = {} - with PGPEngine() as pgp: - for client in tcp_server.clients.itervalues(): - 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 = set(("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) - 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: {0}" - .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 anything except ClientRemoved - client.disable(quiet=True) - if use_dbus: - # Emit D-Bus signal - mandos_dbus_service.ClientRemoved(client - .dbus_object_path, - client.name) - 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.itervalues(): - if use_dbus: - # Emit D-Bus signal - mandos_dbus_service.ClientAdded(client.dbus_object_path) - # 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 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()) + 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: # From the Avahi example code + server.connect_to_signal("StateChanged", server_state_changed) try: - service.activate() - except dbus.exceptions.DBusException as error: - logger.critical("D-Bus Exception", exc_info=error) - cleanup() + 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)) + tcp_server.handle_request\ + (*args[2:], **kwargs) or True) - logger.debug("Starting main loop") + 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 2013-10-20 15:25:09 +0000 +++ mandos-clients.conf.xml 2008-08-22 00:16:20 +0000 @@ -1,48 +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 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 @@ -54,25 +69,23 @@ Configuration file for the Mandos server - + - &CONFPATH; + + &CONFPATH; + - + DESCRIPTION - The file &CONFPATH; is a configuration file for 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. + 8, read by it at startup, + where each client that will be able to use the service needs to + be listed. 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 @@ -98,86 +111,77 @@ OPTIONS - Note: all option values are subject to - start time expansion, see . - - - Unknown options are ignored. The used options are as follows: - - + The possible options are: + + - - - - - - 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 + + + 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. The output of the command will be ignored, only + the exit code is checked. The command will be run using /bin/sh - , so - PATH will be searched. The default - value for the checker command is fping %%(host)s. + . The default + command is fping + + %(host)s. In addition to normal start time expansion, this option @@ -188,35 +192,11 @@ - - - - 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 @@ -227,140 +207,72 @@ - - - - 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. - + 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 . This should, of course, be OpenPGP encrypted data, decryptable only by the client. - The program mandos-keygen8 can, using its - option, be used to generate - this, if desired. - - - Note: this value of this option will probably be very - long. A useful feature to avoid having unreadably-long - 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. - - - + + + + + + + Note: this value of this option will probably run over + many lines, and will then have to use the fact that a line + beginning with white space adds to the value of the + previous line, RFC 822-style. + + + + + + secfile + + + Base 64 encoded OpenPGP encrypted password encrypted by + the clients openpgp certificate as a binary file. + + + + + + host + + + Host name that can be used in for checking that the client is up. + + + + + + checker + + + Shell command that the server will use to check up if a + client is still up. + + + + + + timeout + + + Duration that a client can be down whitout be removed from + the client list. + + + - + EXPANSION @@ -397,42 +309,23 @@ %%(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, - 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 percent character (%) in a - checker option, four + checker options, four 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 @@ -460,9 +353,9 @@ [DEFAULT] -timeout = PT5M -interval = PT2M -checker = fping -q -- %%(host)s +timeout = 1h +interval = 5m +checker = fping -q -- %(host)s # Client "foo" [foo] @@ -483,52 +376,17 @@ 5MHdW9AYsNJZAQSOpirE4Xi31CSlWAi9KV+cUCmWF5zOFy1x23P6PjdaRm 4T2zw4dxS5NswXWU0sVEXxjs6PYxuIiCTL7vdpx8QjBkrPWDrAbcMyBr2O QlnHIvPzEArRQLo= + =iHhv host = foo.example.org -interval = PT1M +interval = 5m # Client "bar" [bar] fingerprint = 3e393aeaefb84c7e89e2f547b3a107558fca3a27 -secfile = /etc/mandos/bar-secret -timeout = PT15M -approved_by_default = False -approval_delay = PT30S +secfile = /etc/mandos/bar-secret.txt.asc + - - - - SEE ALSO - - intro - 8mandos, - mandos-keygen - 8, - mandos.conf - 5, - mandos - 8, - fping - 8 - - - - - RFC 3339: Date and Time on the Internet: - Timestamps - - - - The time intervals are in the "duration" format, as - specified in ABNF in Appendix A of RFC 3339. - - - - - + +
- - - - - === removed file 'mandos-ctl' --- mandos-ctl 2013-10-24 20:38:44 +0000 +++ mandos-ctl 1970-01-01 00:00:00 +0000 @@ -1,460 +0,0 @@ -#!/usr/bin/python -# -*- mode: python; coding: utf-8 -*- -# -# Mandos Monitor - Control and monitor the Mandos server -# -# Copyright © 2008-2012 Teddy Hogeborn -# Copyright © 2008-2012 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 this program. If not, see -# . -# -# Contact the authors at . -# - -from __future__ import (division, absolute_import, print_function, - unicode_literals) - -from future_builtins import * - -import sys -import argparse -import locale -import datetime -import re -import os -import collections -import doctest - -import dbus - -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", - "CheckerRunning": "Check Is Running", - "LastEnabled": "Last Enabled", - "ApprovalPending": "Approval Is Pending", - "ApprovedByDefault": "Approved By Default", - "ApprovalDelay": "Approval Delay", - "ApprovalDuration": "Approval Duration", - "Checker": "Checker", - "ExtendedTimeout" : "Extended Timeout" - } -defaultkeywords = ("Name", "Enabled", "Timeout", "LastCheckedOK") -domain = "se.recompile" -busname = domain + ".Mandos" -server_path = "/" -server_interface = domain + ".Mandos" -client_interface = domain + ".Mandos.Client" -version = "1.6.2" - -def timedelta_to_milliseconds(td): - """Convert a datetime.timedelta object to milliseconds""" - return ((td.days * 24 * 60 * 60 * 1000) - + (td.seconds * 1000) - + (td.microseconds // 1000)) - -def milliseconds_to_string(ms): - td = datetime.timedelta(0, 0, 0, ms) - return ("{days}{hours:02}:{minutes:02}:{seconds:02}" - .format(days = "{0}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 = datetime.timedelta() # Value so far - found_token = None - followers = frozenset(token_duration,) # Following valid tokens - s = duration # String left to parse - # Loop until end token is found - while found_token is not token_end: - # Search for any currently valid tokens - 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") - # 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 unicode(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(**dict((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 {0}".format(version), - help="show version number and exit") - parser.add_argument("-a", "--all", action="store_true", - help="Select all clients") - parser.add_argument("-v", "--verbose", action="store_true", - help="Print all fields") - parser.add_argument("-e", "--enable", action="store_true", - help="Enable client") - parser.add_argument("-d", "--disable", action="store_true", - help="disable client") - parser.add_argument("-b", "--bump-timeout", action="store_true", - help="Bump timeout for client") - parser.add_argument("--start-checker", action="store_true", - help="Start checker for client") - parser.add_argument("--stop-checker", action="store_true", - help="Stop checker for client") - parser.add_argument("-V", "--is-enabled", action="store_true", - help="Check if client is enabled") - parser.add_argument("-r", "--remove", action="store_true", - help="Remove client") - parser.add_argument("-c", "--checker", - help="Set checker command for client") - parser.add_argument("-t", "--timeout", - help="Set timeout for client") - parser.add_argument("--extended-timeout", - help="Set extended timeout for client") - parser.add_argument("-i", "--interval", - help="Set checker interval for client") - parser.add_argument("--approve-by-default", action="store_true", - default=None, dest="approved_by_default", - help="Set client to be approved by default") - parser.add_argument("--deny-by-default", action="store_false", - dest="approved_by_default", - help="Set client to be denied by default") - parser.add_argument("--approval-delay", - help="Set delay before client approve/deny") - parser.add_argument("--approval-duration", - help="Set duration of one client approval") - parser.add_argument("-H", "--host", help="Set host for client") - parser.add_argument("-s", "--secret", type=file, - 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 or with" - " --all.") - if options.all and not has_actions(options): - parser.error("--all requires an action.") - - if options.check: - fail_count, test_count = doctest.testmod() - sys.exit(0 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) - - #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 = mandos_serv.GetAllClientsWithProperties() - finally: - #restore stderr - os.dup2(stderrcopy, sys.stderr.fileno()) - os.close(stderrcopy) - except dbus.exceptions.DBusException: - print("Access denied: Accessing mandos server through dbus.", - file=sys.stderr) - sys.exit(1) - - # Compile dict of (clients: properties) to process - clients={} - - if options.all or not options.client: - clients = dict((bus.get_object(busname, path), properties) - for path, properties in - mandos_clients.iteritems()) - else: - for name in options.client: - for path, client in mandos_clients.iteritems(): - if client["Name"] == name: - client_objc = bus.get_object(busname, path) - clients[client_objc] = client - break - else: - print("Client not found on server: {0!r}" - .format(name), file=sys.stderr) - sys.exit(1) - - if not has_actions(options) and clients: - if options.verbose: - keywords = ("Name", "Enabled", "Timeout", - "LastCheckedOK", "Created", "Interval", - "Host", "Fingerprint", "CheckerRunning", - "LastEnabled", "ApprovalPending", - "ApprovedByDefault", - "LastApprovalRequest", "ApprovalDelay", - "ApprovalDuration", "Checker", - "ExtendedTimeout") - else: - keywords = defaultkeywords - - print_clients(clients.values(), keywords) - else: - # Process each client in the list by all selected options - for client in clients: - def set_client_prop(prop, value): - """Set a Client D-Bus property""" - client.Set(client_interface, prop, value, - dbus_interface=dbus.PROPERTIES_IFACE) - def set_client_prop_ms(prop, value): - """Set a Client D-Bus property, converted - from a string to milliseconds.""" - set_client_prop(prop, - timedelta_to_milliseconds - (string_to_delta(value))) - if options.remove: - mandos_serv.RemoveClient(client.__dbus_object_path__) - if options.enable: - set_client_prop("Enabled", dbus.Boolean(True)) - if options.disable: - set_client_prop("Enabled", dbus.Boolean(False)) - if options.bump_timeout: - set_client_prop("LastCheckedOK", "") - if options.start_checker: - set_client_prop("CheckerRunning", dbus.Boolean(True)) - if options.stop_checker: - set_client_prop("CheckerRunning", dbus.Boolean(False)) - if options.is_enabled: - sys.exit(0 if client.Get(client_interface, - "Enabled", - dbus_interface= - dbus.PROPERTIES_IFACE) - else 1) - if options.checker is not None: - set_client_prop("Checker", options.checker) - if options.host is not None: - set_client_prop("Host", options.host) - if options.interval is not None: - set_client_prop_ms("Interval", options.interval) - if options.approval_delay is not None: - set_client_prop_ms("ApprovalDelay", - options.approval_delay) - if options.approval_duration is not None: - set_client_prop_ms("ApprovalDuration", - options.approval_duration) - if options.timeout is not None: - set_client_prop_ms("Timeout", options.timeout) - if options.extended_timeout is not None: - set_client_prop_ms("ExtendedTimeout", - options.extended_timeout) - if options.secret is not None: - set_client_prop("Secret", - dbus.ByteArray(options.secret.read())) - if options.approved_by_default is not None: - set_client_prop("ApprovedByDefault", - dbus.Boolean(options - .approved_by_default)) - if options.approve: - client.Approve(dbus.Boolean(True), - dbus_interface=client_interface) - elif options.deny: - client.Approve(dbus.Boolean(False), - dbus_interface=client_interface) - -if __name__ == "__main__": - main() === removed file 'mandos-ctl.xml' --- mandos-ctl.xml 2012-06-22 23:33:56 +0000 +++ mandos-ctl.xml 1970-01-01 00:00:00 +0000 @@ -1,605 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2010 - 2011 - 2012 - Teddy Hogeborn - Björn Påhlsson - - -
- - - &COMMANDNAME; - 8 - - - - &COMMANDNAME; - - Control the operation of the Mandos server - - - - - - &COMMANDNAME; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CLIENT - - - - - &COMMANDNAME; - - - - - - - CLIENT - - - - - &COMMANDNAME; - - - - - CLIENT - - - &COMMANDNAME; - - - - - - - &COMMANDNAME; - - - - - - - &COMMANDNAME; - - - - - - DESCRIPTION - - &COMMANDNAME; is a program to control 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. - - - - - - - - - - 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. - - - - - - - - - - - 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 2013-10-24 20:38:44 +0000 +++ mandos-keygen 2008-08-22 00:16:20 +0000 @@ -2,8 +2,7 @@ # # Mandos key generator - create a new OpenPGP key for a Mandos client # -# Copyright © 2008-2013 Teddy Hogeborn -# Copyright © 2008-2013 Björn Påhlsson +# 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 @@ -18,86 +17,56 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -# Contact the authors at . +# Contact the authors at . # -VERSION="1.6.2" +VERSION="1.0" -KEYDIR="/etc/keys/mandos" -KEYTYPE=RSA -KEYLENGTH=4096 -SUBKEYTYPE=RSA -SUBKEYLENGTH=4096 -KEYNAME="`hostname --fqdn 2>/dev/null || hostname`" +KEYDIR="/etc/mandos" +KEYTYPE=DSA +KEYLENGTH=1024 +KEYNAME="`hostname --fqdn`" KEYEMAIL="" -KEYCOMMENT="" +KEYCOMMENT="Mandos client key" KEYEXPIRE=0 FORCE=no 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:f \ - --longoptions version,help,password,passfile:,dir:,type:,length:,subtype:,sublength:,name:,email:,comment:,expire:,force \ +TEMP=`getopt --options vhd:t:l:n:e:c:x:f \ + --longoptions version,help,dir:,type:,length:,name:,email:,comment:,expire:,force \ --name "$0" -- "$@"` help(){ -basename="`basename $0`" cat <&2 exit 1 fi -if [ ! -r "$KEYDIR" ]; then - echo "Directory $KEYDIR not readable" >&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 - fi - - if [ -z "$KEYNAME" ]; then - echo "Empty key name" >&2 - exit 1 - fi - - if [ -z "$KEYLENGTH" ] || [ "$KEYLENGTH" -lt 512 ]; then - echo "Invalid key length" >&2 - exit 1 - fi - - if [ -z "$KEYEXPIRE" ]; then - echo "Empty key expiration" >&2 - exit 1 - fi - - # Make FORCE be 0 or 1 - case "$FORCE" in - [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]) FORCE=1;; - [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|*) FORCE=0;; - esac - - if [ \( -e "$SECKEYFILE" -o -e "$PUBKEYFILE" \) \ - -a "$FORCE" -eq 0 ]; then - echo "Refusing to overwrite old key files; use --force" >&2 - exit 1 - fi - - # Set lines for GnuPG batch file - if [ -n "$KEYCOMMENT" ]; then - KEYCOMMENTLINE="Name-Comment: $KEYCOMMENT" - fi - if [ -n "$KEYEMAIL" ]; then - KEYEMAILLINE="Name-Email: $KEYEMAIL" - fi - - # Create temporary gpg batch file - BATCHFILE="`mktemp -t mandos-keygen-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`" +if [ -w "$KEYDIR" ]; then :; else + echo "Directory $KEYDIR not writeable" >&2 + exit 1 +fi + +if [ -z "$KEYTYPE" ]; then + echo "Empty key type" >&2 + exit 1 +fi + +if [ -z "$KEYNAME" ]; then + echo "Empty key name" >&2 + exit 1 +fi + +if [ -z "$KEYLENGTH" ] || [ "$KEYLENGTH" -lt 512 ]; then + echo "Invalid key length" >&2 + exit 1 +fi + +if [ -z "$KEYEXPIRE" ]; then + echo "Empty key expiration" >&2 + exit 1 +fi + +# Make FORCE be 0 or 1 +case "$FORCE" in + [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]) FORCE=1;; + [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|*) FORCE=0;; +esac + +if { [ -e "$SECKEYFILE" ] || [ -e "$PUBKEYFILE" ]; } \ + && [ "$FORCE" -eq 0 ]; then + echo "Refusing to overwrite old key files; use --force" >&2 + exit 1 +fi + +# Set lines for GnuPG batch file +if [ -n "$KEYCOMMENT" ]; then + KEYCOMMENTLINE="Name-Comment: $KEYCOMMENT" +fi +if [ -n "$KEYEMAIL" ]; then + KEYEMAILLINE="Name-Email: $KEYEMAIL" +fi + +# Create temp files +BATCHFILE="`mktemp -t mandos-gpg-batch.XXXXXXXXXX`" +SECRING="`mktemp -t mandos-gpg-secring.XXXXXXXXXX`" +PUBRING="`mktemp -t mandos-gpg-pubring.XXXXXXXXXX`" # Remove temporary files on exit trap " -set +e; \ -test -n \"$SECFILE\" && shred --remove \"$SECFILE\"; \ -shred --remove \"$RINGDIR\"/sec*; -test -n \"$BATCHFILE\" && rm --force \"$BATCHFILE\"; \ -rm --recursive --force \"$RINGDIR\"; -tty --quiet && stty echo; \ +set +e +rm --force $PUBRING $BATCHFILE; +shred --remove $SECRING; +stty echo " EXIT -set -e - -umask 077 - -if [ "$mode" = keygen ]; then - # Create batch file for GnuPG - cat >"$BATCHFILE" <<-EOF - Key-Type: $KEYTYPE - Key-Length: $KEYLENGTH - Key-Usage: sign,auth - Subkey-Type: $SUBKEYTYPE - Subkey-Length: $SUBKEYLENGTH - Subkey-Usage: encrypt - Name-Real: $KEYNAME - $KEYCOMMENTLINE - $KEYEMAILLINE - Expire-Date: $KEYEXPIRE - #Preferences: - #Handle: - #%pubring pubring.gpg - #%secring secring.gpg - %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 - - # Generate a new key in the key rings - gpg --quiet --batch --no-tty --no-options --enable-dsa2 \ - --homedir "$RINGDIR" --trust-model always \ - --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 - shred --remove "$SECKEYFILE" - fi - if cp --backup=numbered --force "$PUBKEYFILE" "$PUBKEYFILE" \ - 2>/dev/null; then - rm --force "$PUBKEYFILE" - fi - - FILECOMMENT="Mandos client key for $KEYNAME" - if [ "$KEYCOMMENT" != "$KEYCOMMENT_ORIG" ]; then - FILECOMMENT="$FILECOMMENT ($KEYCOMMENT)" - fi - - if [ -n "$KEYEMAIL" ]; then - 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 -fi - -if [ "$mode" = password ]; then - # 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" - - # 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}'`" - - test -n "$FINGERPRINT" - - 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: " >&2 - read first - tty --quiet && echo >&2 - echo -n "Repeat passphrase: " >&2 - read 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 - - cat <<-EOF - [$KEYNAME] - host = $KEYNAME - fingerprint = $FINGERPRINT - secret = - EOF - sed --quiet --expression=' - /^-----BEGIN PGP MESSAGE-----$/,/^-----END PGP MESSAGE-----$/{ - /^$/,${ - # Remove 24-bit Radix-64 checksum - s/=....$// - # Indent four spaces - /^[^-]/s/^/ /p - } - }' < "$SECFILE" -fi +# Create batch file for GnuPG +cat >"$BATCHFILE" </dev/null; then + shred --remove "$SECKEYFILE" +fi +if cp --backup=numbered --force "$PUBKEYFILE" "$PUBKEYFILE" \ + 2>/dev/null; then + rm --force "$PUBKEYFILE" +fi + +FILECOMMENT="Mandos client key for $KEYNAME" +if [ "$KEYCOMMENT" != "$KEYCOMMENT_ORIG" ]; then + FILECOMMENT="$FILECOMMENT ($KEYCOMMENT)" +fi + +if [ -n "$KEYEMAIL" ]; then + FILECOMMENT="$FILECOMMENT <$KEYEMAIL>" +fi + +# Export keys from key rings to key files +gpg --no-random-seed-file --quiet --batch --no-tty --armor \ + --no-default-keyring --no-options --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 --secret-keyring "$SECRING" \ + --keyring "$PUBRING" --export-options export-minimal \ + --comment "$FILECOMMENT" --output "$PUBKEYFILE" \ + --export trap - EXIT -set +e -# Remove the password file, if any -if [ -n "$SECFILE" ]; then - shred --remove "$SECFILE" -fi # Remove the key rings -shred --remove "$RINGDIR"/sec* -rm --recursive --force "$RINGDIR" +shred --remove "$SECRING" +rm --force "$PUBRING" === modified file 'mandos-keygen.xml' --- mandos-keygen.xml 2013-10-22 19:24:01 +0000 +++ mandos-keygen.xml 2008-08-20 00:35:41 +0000 @@ -1,46 +1,61 @@ - + - - -%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 - 2011 - 2012 - 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; 8 @@ -49,255 +64,188 @@ &COMMANDNAME; - Generate key and password for Mandos client and server. + Generate keys for password-request + 8mandos - + &COMMANDNAME; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - &COMMANDNAME; - - - - - - FILE - - - - - - - - - - - - - - &COMMANDNAME; - - - - - - - &COMMANDNAME; - - - + + + directory + + + + type + + + + bits + + + + NAME + + + + EMAIL + + + + COMMENT + + + + TIME + + + + + + + &COMMANDNAME; + + + directory + + + + type + + + + bits + + + + NAME + + + + EMAIL + + + + COMMENT + + + + TIME + + + + + + + &COMMANDNAME; + + + + + + + &COMMANDNAME; + + + - + DESCRIPTION &COMMANDNAME; is a program to generate the - OpenPGP key used by - mandos-client - 8mandos. The key is + OpenPGP keys used by + password-request + 8mandos. The keys are normally written to /etc/mandos for later installation into the - initrd image, but this, and most other things, can be changed - with command line options. - - - This program can also be used with the - or - options to generate a ready-made section for - clients.conf (see - mandos-clients.conf - 5). + initrd image, but this, like most things, can be changed with + command line options. 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/mandos. - - - - - - - - - - Key type. Default is RSA. - - - - - - - - - - Key length in bits. Default is 4096. - - - - - - - - - - Subkey type. Default is RSA (Elgamal - encryption-only). - - - - - - - - - - Subkey length in bits. Default is 4096. - - - - - - - + + + -d, --dir + directory + + + Target directory for key files. + + + + + + -t, --type + type + + + Key type. Default is DSA. + + + + + + -l, --length + bits + + + Key length in bits. Default is 1024. + + + + + + -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 @@ -306,65 +254,32 @@ - - - - - - - Force overwriting old key. - - - - - - - - - Prompt for a password and encrypt it with the key already - 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. + + + -f, --force + + + Force overwriting old keys. - + 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. + This program is a small program 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. @@ -372,7 +287,7 @@ ENVIRONMENT - TMPDIR + TMPDIR If set, temporary files will be created here. See @@ -384,7 +299,7 @@ - + FILES Use the option to change where @@ -411,7 +326,7 @@ - /tmp + /tmp Temporary files will be written here if @@ -421,13 +336,14 @@ - - - - - - - + + + BUGS + + None are known at this time. + + + EXAMPLE @@ -435,80 +351,47 @@ 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 key in /etc/mandos and output a section - suitable for clients.conf. - - - &COMMANDNAME; --password - - - - - Prompt for a password, encrypt it with the key 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. + The and + 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 + 1
- - - - - === removed file 'mandos-monitor' --- mandos-monitor 2013-10-24 20:38:44 +0000 +++ mandos-monitor 1970-01-01 00:00:00 +0000 @@ -1,724 +0,0 @@ -#!/usr/bin/python -# -*- mode: python; coding: utf-8 -*- -# -# Mandos Monitor - Control and monitor the Mandos server -# -# Copyright © 2009-2013 Teddy Hogeborn -# Copyright © 2009-2013 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 this program. If not, see -# . -# -# Contact the authors at . -# - -from __future__ import (division, absolute_import, print_function, - unicode_literals) -try: - from future_builtins import * -except ImportError: - pass - -import sys -import os -import signal - -import datetime - -import urwid.curses_display -import urwid - -from dbus.mainloop.glib import DBusGMainLoop -try: - import gobject -except ImportError: - from gi.repository import GObject as gobject - -import dbus - -import locale - -if sys.version_info[0] == 2: - str = unicode - -locale.setlocale(locale.LC_ALL, '') - -import logging -logging.getLogger('dbus.proxies').setLevel(logging.CRITICAL) - -# Some useful constants -domain = 'se.recompile' -server_interface = domain + '.Mandos' -client_interface = domain + '.Mandos.Client' -version = "1.6.2" - -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("PropertyChanged", - self._property_changed, - client_interface, - 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 _property_changed(self, property, value): - """Helper which takes positional arguments""" - return self.property_changed(property=property, value=value) - - def property_changed(self, property=None, value=None): - """This is called whenever we get a PropertyChanged signal - It updates the changed property in the "properties" dict. - """ - # Update properties dict with new value - self.properties[property] = value - - 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 {0}' - # .format(self.properties["Name"])) - - 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 = (gobject.timeout_add - (1000, - self.update_timer)) - elif not (flag or self._update_timer_callback_tag is None): - gobject.source_remove(self._update_timer_callback_tag) - self._update_timer_callback_tag = None - - def checker_completed(self, exitstatus, condition, command): - if exitstatus == 0: - self.update() - return - # Checker failed - if os.WIFEXITED(condition): - self.logger('Checker for client {0} (command "{1}")' - ' failed with exit code {2}' - .format(self.properties["Name"], command, - os.WEXITSTATUS(condition))) - elif os.WIFSIGNALED(condition): - self.logger('Checker for client {0} (command "{1}") was' - ' killed by signal {2}' - .format(self.properties["Name"], command, - os.WTERMSIG(condition))) - elif os.WCOREDUMP(condition): - self.logger('Checker for client {0} (command "{1}")' - ' dumped core' - .format(self.properties["Name"], command)) - else: - self.logger('Checker for client {0} completed' - ' mysteriously' - .format(self.properties["Name"])) - self.update() - - def checker_started(self, command): - """Server signals that a checker started. This could be useful - to log in the future. """ - #self.logger('Client {0} started checker "{1}"' - # .format(self.properties["Name"], - # str(command))) - pass - - def got_secret(self): - self.logger('Client {0} received its secret' - .format(self.properties["Name"])) - - def need_approval(self, timeout, default): - if not default: - message = 'Client {0} needs approval within {1} seconds' - else: - message = 'Client {0} will get its secret in {1} seconds' - self.logger(message.format(self.properties["Name"], - timeout/1000)) - - def rejected(self, reason): - self.logger('Client {0} was rejected; reason: {1}' - .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 {0}. (d)eny?" - else: - message = "Denial in {0}. (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: {0}' - .format(str(timer).rsplit(".", 1)[0])) - self.using_timer(True) - else: - message = "enabled" - self.using_timer(False) - self._text = "{0}{1}".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 gobject. Will indefinitely loop until - gobject.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: - gobject.source_remove(self._update_timer_callback_tag) - self._update_timer_callback_tag = None - for match in self.match_objects: - match.remove() - self.match_objects = () - if self.delete_hook is not None: - self.delete_hook(self) - return super(MandosClientWidget, self).delete(**kwargs) - - def render(self, maxcolrow, focus=False): - """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.Enable(dbus_interface = client_interface, - ignore_reply=True) - elif key == "-": - self.proxy.Disable(dbus_interface = client_interface, - ignore_reply=True) - 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.StartChecker(dbus_interface = client_interface, - ignore_reply=True) - elif key == "S": - self.proxy.StopChecker(dbus_interface = client_interface, - ignore_reply=True) - 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 property_changed(self, property=None, **kwargs): - """Call self.update() if old value is not new value. - This overrides the method from MandosClientPropertyCache""" - property_name = str(property) - old_value = self.properties.get(property_name) - super(MandosClientWidget, self).property_changed( - property=property, **kwargs) - if self.properties.get(property_name) != old_value: - 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): - 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 - #self.divider = "━" # \u2501 - else: - #self.divider = "-" # \u002d - self.divider = "_" # \u005f - - self.screen.start() - - self.size = self.screen.get_cols_rows() - - self.clients = urwid.SimpleListWalker([]) - self.clients_dict = {} - - # We will add Text widgets to this list - self.log = [] - self.max_log_length = max_log_length - - # 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 = gobject.MainLoop() - - def client_not_found(self, fingerprint, address): - self.log_message("Client with address {0} and fingerprint" - " {1} 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): - """Log message formatted with timestamp""" - timestamp = datetime.datetime.now().isoformat() - self.log_message_raw(timestamp + ": " + message) - - def log_message_raw(self, markup): - """Add a log message to the log buffer.""" - self.log.append(urwid.Text(markup, wrap=self.log_wrap)) - if (self.max_log_length - and len(self.log) > self.max_log_length): - del self.log[0:len(self.log)-self.max_log_length-1] - self.logbox.set_focus(len(self.logbox.body.contents), - coming_from="above") - self.refresh() - - def toggle_log_display(self): - """Toggle visibility of the log buffer.""" - self.log_visible = not self.log_visible - self.rebuild() - #self.log_message("Log visibility changed to: " - # + str(self.log_visible)) - - def change_log_display(self): - """Change type of log display. - Currently, this toggles wrapping of text lines.""" - if self.log_wrap == "clip": - self.log_wrap = "any" - else: - self.log_wrap = "clip" - for textwidget in self.log: - textwidget.set_wrap_mode(self.log_wrap) - #self.log_message("Wrap mode: " + self.log_wrap) - - def find_and_remove_client(self, path, name): - """Find a client by its object path and remove it. - - This is connected to the ClientRemoved signal from the - Mandos server object.""" - try: - client = self.clients_dict[path] - except KeyError: - # not found? - self.log_message("Unknown client {0!r} ({1!r}) removed" - .format(name, path)) - return - client.delete() - - def add_new_client(self, path): - client_proxy_object = self.bus.get_object(self.busname, path) - self.add_client(MandosClientWidget(server_proxy_object - =self.mandos_serv, - proxy_object - =client_proxy_object, - update_hook - =self.refresh, - delete_hook - =self.remove_client, - logger - =self.log_message), - 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("ClientRemoved", - self.find_and_remove_client, - dbus_interface=server_interface, - byte_arrays=True)) - (self.mandos_serv - .connect_to_signal("ClientAdded", - self.add_new_client, - dbus_interface=server_interface, - 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 = (gobject.io_add_watch - (sys.stdin.fileno(), - gobject.IO_IN, - self.process_input)) - self.main_loop.run() - # Main loop has finished, we should close everything now - gobject.source_remove(self._input_callback_tag) - self.screen.stop() - - def stop(self): - self.main_loop.quit() - - def process_input(self, source, condition): - keys = self.screen.get_input() - translations = { "ctrl n": "down", # Emacs - "ctrl p": "up", # Emacs - "ctrl v": "page down", # Emacs - "meta v": "page up", # Emacs - " ": "page down", # less - "f": "page down", # less - "b": "page up", # less - "j": "down", # vi - "k": "up", # vi - } - 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 == "\f": # Ctrl-L - 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)")))) - 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 == "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 2011-12-31 23:05:34 +0000 +++ mandos-monitor.xml 1970-01-01 00:00:00 +0000 @@ -1,237 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2010 - 2011 - 2012 - 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 - - - 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 service name of - 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 2013-10-24 20:21:45 +0000 +++ mandos-options.xml 2008-08-23 07:17:28 +0000 @@ -5,8 +5,6 @@
@@ -14,11 +12,11 @@ If this is specified, the server will only announce the service - 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. + and listen to requests on network interface + IF. Default is to use all available + interfaces. Note: a failure to bind to the + specified interface is not considered critical, and the server + does not exit, but will instead continue starting normally. @@ -26,12 +24,9 @@ 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 the ::FFFF:192.0.2.3 format. @@ -42,85 +37,29 @@ If the server is run in debug mode, it will run in the foreground - and print a lot of debugging information. The default is to - not run in debug mode. - - - - GnuTLS priority string for the TLS handshake. - The default is SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:+SIGN-RSA-SHA224: - +SIGN-RSA-RMD160. - See gnutls_priority_init - 3 for the syntax. - Warning: changing this may make the - TLS handshake fail, making server-client - communication impossible. + and print a lot of debugging information. The default is + not to run in debug mode. - GnuTLS priority string for the TLS handshake. - The default is SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP. See - gnutls_priority_init - 3 for the syntax. - Warning: changing this may make the - TLS handshake fail, making server-client - communication impossible. + GnuTLS priority string for the TLS handshake with the clients. + The default is + SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP. + See gnutls_priority_init + 3 for the + syntax. Warning: changing this may make the + TLS handshake fail, making 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. - -
=== modified file 'mandos.conf' --- mandos.conf 2013-10-22 19:24:01 +0000 +++ mandos.conf 2008-08-18 23:55:28 +0000 @@ -4,44 +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:+SIGN-RSA-SHA224:+SIGN-RSA-RMD160 +;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 === modified file 'mandos.conf.xml' --- mandos.conf.xml 2013-10-24 20:21:45 +0000 +++ mandos.conf.xml 2008-08-20 03:22:45 +0000 @@ -1,48 +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 - 2011 - 2012 - 2013 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 @@ -54,11 +69,13 @@ Configuration file for the Mandos server - + - &CONFPATH; + + &CONFPATH; + - + DESCRIPTION @@ -76,114 +93,76 @@ # or ; are ignored and may be used to provide comments. - + OPTIONS - + interface + interface = IF + - + - + address + address = ADDRESS + - + - + port + port = PORT + - + - + + debug = { 1 | yes | true | on | 0 | no | false | off } - + >false | off } + - + - + priority - + priority = PRIORITY + + - + - + servicename + servicename = NAME + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -199,7 +178,7 @@ The [DEFAULT] is necessary because the Python built-in module ConfigParser - requires it. + requres it. @@ -221,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 servicename = Daena -use_dbus = False -use_ipv6 = True -restore = True -statedir = /var/lib/mandos @@ -237,65 +212,11 @@ SEE ALSO - intro - 8mandos, - gnutls_priority_init3, - mandos - 8, - mandos-clients.conf - 5 + + mandos + 8, + mandos-clients.conf + 5 - - - - - 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 - - - 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 - is brought up. - - - - - - - - - Zeroconf - - - - Zeroconf is the network protocol standard used by clients - for finding the Mandos server on the local network. - - - -
- - - - - === removed file 'mandos.lsm' --- mandos.lsm 2013-10-24 20:38:44 +0000 +++ mandos.lsm 1970-01-01 00:00:00 +0000 @@ -1,22 +0,0 @@ -Begin4 -Title: Mandos -Version: 1.6.2 -Entered-date: 2013-10-24 -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: http://www.recompile.se/mandos - 156K mandos_1.6.2.orig.tar.gz -Alternate-site: ftp://ftp.recompile.se/pub/mandos - 156K mandos_1.6.2.orig.tar.gz -Platforms: Requires GCC, GNU libC, Avahi, GnuPG, Python 2.6, 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 2013-12-31 16:02:18 +0000 +++ mandos.service 1970-01-01 00:00:00 +0000 @@ -1,21 +0,0 @@ -[Unit] -Description=Server of encrypted passwords to Mandos clients - -[Service] -Type=simple -## Type=dbus is not appropriate, because Mandos also needs to announce -## its ZeroConf service and be reachable on the network. -#Type=dbus -BusName=se.recompile.Mandos -# If you add --no-dbus, also comment out BusName above, and vice versa -ExecStart=/usr/sbin/mandos --foreground -Restart=always -KillMode=process -## Using socket activation won't work either, 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 - -[Install] -WantedBy=multi-user.target === modified file 'mandos.xml' --- mandos.xml 2013-10-26 19:05:21 +0000 +++ mandos.xml 2008-08-20 00:35:41 +0000 @@ -1,48 +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 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 @@ -51,152 +65,116 @@ &COMMANDNAME; - Gives encrypted passwords to authenticated Mandos clients + Sends encrypted passwords to authenticated Mandos clients - + &COMMANDNAME; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + --interfaceIF + --addressADDRESS + --portPORT + --priorityPRIORITY + --servicenameNAME + --configdirDIRECTORY + --debug + + + &COMMANDNAME; + -iIF + -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 + IF - + - - + -a, --address + ADDRESS - + - - + -p, --port + PORT - + - + --check Run the server’s self-tests. This includes any unit @@ -204,53 +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 @@ -262,80 +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 @@ -393,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 @@ -468,12 +332,12 @@ critical error is encountered. - + ENVIRONMENT - PATH + PATH To start the configured checker (see - - + + FILES Use the option to change where @@ -518,29 +382,11 @@ - /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. - - - - - /dev/log - - - /var/lib/mandos - - - Directory where persistent state will be saved. Change - this with the option. See - also the option. + /var/run/mandos/mandos.pid + + + The file containing the process id of + &COMMANDNAME;. @@ -574,11 +420,24 @@ 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. - This server does not check the expire time of clients’ OpenPGP - keys. + Debug mode is conflated with running in the foreground. + + + The console log messages does not show a timestamp. @@ -589,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 @@ -614,24 +473,24 @@ -&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 @@ -644,7 +503,7 @@ 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 @@ -652,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 + @@ -698,8 +572,8 @@ - GnuTLS + GnuTLS @@ -711,40 +585,23 @@ - RFC 4291: IP Version 6 Addressing - Architecture + RFC 4291: IP Version 6 Addressing + Architecture, section 2.5.6, Link-Local IPv6 + Unicast Addresses - - - 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 - - - The clients use IPv6 link-local addresses, which are - immediately usable since a link-local addresses is - automatically assigned to a network interfaces when it - is brought up. - - - - + + The clients use IPv6 link-local addresses, which are + immediately usable since a link-local addresses is + automatically assigned to a network interfaces when it is + brought up. + - RFC 4346: The Transport Layer Security (TLS) - Protocol Version 1.1 + RFC 4346: The Transport Layer Security + (TLS) Protocol Version 1.1 @@ -754,7 +611,8 @@ - RFC 4880: OpenPGP Message Format + RFC 4880: OpenPGP Message + Format @@ -764,8 +622,8 @@ - RFC 5081: Using OpenPGP Keys for Transport Layer - Security + RFC 5081: Using OpenPGP Keys for + Transport Layer Security @@ -777,8 +635,3 @@
- - - - - === removed directory 'network-hooks.d' === removed file 'network-hooks.d/bridge' --- network-hooks.d/bridge 2012-06-13 22:06:57 +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 Teddy Hogeborn -# Copyright © 2012 Björn Påhlsson -# -# Copying and distribution of this file, with or without modification, -# are permitted in any medium without royalty provided the copyright -# 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" -o -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 2012-06-13 22:06:57 +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 Teddy Hogeborn -# Copyright © 2012 Björn Påhlsson -# -# Copying and distribution of this file, with or without modification, -# are permitted in any medium without royalty provided the copyright -# 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 2012-06-13 22:06:57 +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 Teddy Hogeborn -# Copyright © 2012 Björn Påhlsson -# -# Copying and distribution of this file, with or without modification, -# are permitted in any medium without royalty provided the copyright -# 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 2008-09-13 15:36:18 +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 an OpenPGP key; each - client has one unique to it. The server sends the clients an - encrypted password. The encrypted password is decrypted by the - clients using the same OpenPGP key, and the password is then used to - unlock the root file system, whereupon the computers can continue - booting normally. + 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. === modified file 'plugin-runner.c' --- plugin-runner.c 2013-12-15 22:21:28 +0000 +++ plugin-runner.c 2008-08-16 18:15:07 +0000 @@ -1,9 +1,8 @@ -/* -*- coding: utf-8; mode: c; mode: orgtbl -*- */ +/* -*- coding: utf-8 -*- */ /* * Mandos plugin runner - Run Mandos plugins * - * Copyright © 2008-2013 Teddy Hogeborn - * Copyright © 2008-2013 Björn Påhlsson + * 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 @@ -19,18 +18,18 @@ * along with this program. If not, see * . * - * Contact the authors at . + * Contact the authors at . */ #define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), getline(), - asprintf(), O_CLOEXEC */ + 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 */ -#include /* DIR, fdopendir(), stat(), struct +#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(), @@ -38,48 +37,51 @@ #include /* fd_set, select(), FD_ZERO(), FD_SET(), FD_ISSET(), FD_CLR */ #include /* wait(), waitpid(), WIFEXITED(), - WEXITSTATUS(), WTERMSIG(), - WCOREDUMP() */ + WEXITSTATUS() */ #include /* struct stat, stat(), S_ISREG() */ #include /* and, or, not */ -#include /* DIR, struct dirent, fdopendir(), +#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(), + dup2, STDOUT_FILENO, _exit(), execv(), write(), read(), close() */ #include /* fcntl(), F_GETFD, F_SETFD, FD_CLOEXEC */ -#include /* strsep, strlen(), asprintf(), - strsignal(), strcmp(), strncmp() */ +#include /* strsep, strlen(), asprintf() */ #include /* errno */ #include /* struct argp_option, struct argp_state, struct argp, argp_parse(), ARGP_ERR_UNKNOWN, - ARGP_KEY_END, ARGP_KEY_ARG, - error_t */ + ARGP_KEY_END, ARGP_KEY_ARG, error_t */ #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() */ #define BUFFER_SIZE 256 - -#define PDIR "/lib/mandos/plugins.d" -#define AFILE "/conf/conf.d/mandos/plugin-runner.conf" - -const char *argp_program_version = "plugin-runner " VERSION; -const char *argp_program_bug_address = ""; +#define ARGFILE "/conf/conf.d/mandos/plugin-runner.conf" + +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; + bool completed; + int status; + struct process *next; +} process; typedef struct plugin{ char *name; /* can be NULL or any plugin name */ @@ -88,106 +90,64 @@ 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 */ -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){ - return NULL; - } - char *copy_name = NULL; - if(name != NULL){ - do { - copy_name = strdup(name); - } while(copy_name == NULL and errno == EINTR); - 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 }; - - do { - new_plugin->argv = malloc(sizeof(char *) * 2); - } while(new_plugin->argv == NULL and errno == EINTR); - if(new_plugin->argv == NULL){ - int e = errno; - free(copy_name); + plugin *new_plugin = malloc(sizeof(plugin)); + if (new_plugin == NULL){ + return NULL; + } + char *copy_name = strdup(name); + if(copy_name == NULL){ + return NULL; + } + + *new_plugin = (plugin) { .name = copy_name, + .argc = 1, + .envc = 0, + .disabled = false, + .next = *plugin_list }; + + new_plugin->argv = malloc(sizeof(char *) * 2); + if (new_plugin->argv == NULL){ 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)) static bool add_to_char_array(const char *new, char ***array, int *len){ /* Resize the pointed-to array to hold one more pointer */ - do { - *array = realloc(*array, sizeof(char *) - * (size_t) ((*len) + 2)); - } while(*array == NULL and errno == EINTR); + *array = realloc(*array, sizeof(char *) + * (size_t) ((*len) + 2)); /* Malloc check */ if(*array == NULL){ return false; } /* 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; } @@ -200,7 +160,6 @@ } /* Add to a plugin's argument vector */ -__attribute__((nonnull(2))) static bool add_argument(plugin *p, const char *arg){ if(p == NULL){ return false; @@ -209,89 +168,58 @@ } /* Add to a plugin's environment */ -__attribute__((nonnull(2))) -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 **e = p->environ; *e != NULL; e++){ - if(strncmp(*e, def, namelen + 1) == 0){ - /* It already exists */ - if(replace){ - char *new; - do { - new = realloc(*e, strlen(def) + 1); - } while(new == NULL and errno == EINTR); - if(new == NULL){ - return false; - } - *e = new; - strcpy(*e, def); - } - return true; - } - } return add_to_char_array(def, &(p->environ), &(p->envc)); } + /* * 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. */ -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); } +process *process_list = NULL; -/* Mark processes as completed when they exit, and save their exit +/* Mark a process as completed when it exits, and save its exit status. */ -static void handle_sigchld(__attribute__((unused)) int sig){ - int old_errno = errno; - while(true){ - plugin *proc = plugin_list; - int status; - pid_t pid = waitpid(-1, &status, WNOHANG); - if(pid == 0){ - /* Only still running child processes */ - break; - } - if(pid == -1){ - if(errno == ECHILD){ - /* No child processes */ - break; - } - error(0, errno, "waitpid"); - } - - /* A child exited, find it in process_list */ - while(proc != NULL and proc->pid != pid){ - proc = proc->next; - } - if(proc == NULL){ - /* Process not found in process list */ - continue; - } - proc->status = status; - proc->completed = 1; - } - errno = old_errno; +void handle_sigchld(__attribute__((unused)) int sig){ + process *proc = process_list; + int status; + pid_t pid = wait(&status); + if(pid == -1){ + perror("wait"); + return; + } + while(proc != NULL and proc->pid != pid){ + proc = proc->next; + } + if(proc == NULL){ + /* Process not found in process list */ + return; + } + proc->status = status; + proc->completed = true; } -/* Prints out a password to stdout */ -__attribute__((nonnull)) -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)); @@ -302,46 +230,46 @@ 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; - } +char **add_to_argv(char **argv, int *argc, char *arg){ + if (argv == NULL){ + *argc = 1; + argv = malloc(sizeof(char*) * 2); + if(argv == NULL){ + return NULL; } - } - - free(plugin_node); + argv[0] = NULL; /* Will be set to argv[0] in main before parsing */ + argv[1] = NULL; + } + *argc += 1; + argv = realloc(argv, sizeof(char *) + * ((unsigned int) *argc + 1)); + if(argv == NULL){ + return NULL; + } + argv[*argc-1] = arg; + argv[*argc] = NULL; + return argv; } -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; plugin_list != NULL; plugin_list = next){ + next = plugin_list->next; + free(plugin_list->name); + 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 *argfile = NULL; + const char *plugindir = "/lib/mandos/plugins.d"; + const char *argfile = ARGFILE; FILE *conffp; size_t d_name_len; DIR *dir = NULL; @@ -349,7 +277,6 @@ 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; @@ -364,14 +291,14 @@ 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; } @@ -380,275 +307,168 @@ { .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 }, - { .name = "config-file", .key = 129, - .arg = "FILE", - .doc = "Specify a different configuration file", .group = 2 }, - { .name = "userid", .key = 130, - .arg = "ID", .flags = 0, - .doc = "User ID the plugins will run as", .group = 3 }, - { .name = "groupid", .key = 131, - .arg = "ID", .flags = 0, - .doc = "Group ID the plugins will run as", .group = 3 }, - { .name = "debug", .key = 132, - .doc = "Debug mode", .group = 4 }, - /* - * 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 = "userid", .key = 129, + .arg = "ID", .flags = 0, + .doc = "User ID the plugins will run as", .group = 2 }, + { .name = "groupid", .key = 130, + .arg = "ID", .flags = 0, + .doc = "Group ID the plugins will run as", .group = 2 }, + { .name = "debug", .key = 131, + .doc = "Debug mode", .group = 3 }, { .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; - } - } - } - break; - case 'G': /* --global-env */ - add_environment(getplugin(NULL), arg, true); - 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; - } - } - } - 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; - } - add_environment(getplugin(arg), envdef, true); - } - break; - case 'd': /* --disable */ - { - plugin *p = getplugin(arg); - if(p != NULL){ - p->disabled = true; - } - } - break; - case 'e': /* --enable */ - { - plugin *p = getplugin(arg); - if(p != NULL){ - p->disabled = false; - } - } - break; - case 128: /* --plugin-dir */ - free(plugindir); - plugindir = strdup(arg); - 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; - 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; - break; - case 132: /* --debug */ + 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 = arg; + break; + case 129: + uid = (uid_t)strtol(arg, NULL, 10); + break; + case 130: + gid = (gid_t)strtol(arg, NULL, 10); + break; + case 131: debug = true; 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; - } - 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); - break; - case 130: /* --userid */ - case 131: /* --groupid */ - case 132: /* --debug */ - 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; - goto fallback; - } - - /* Reset to the normal argument parser */ - argp.parser = parse_opt; - - /* Open the configfile if available */ - if(argfile == NULL){ - conffp = fopen(AFILE, "r"); - } else { - conffp = fopen(argfile, "r"); - } + 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; + } + + 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; - 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){ @@ -656,84 +476,35 @@ continue; } new_arg = strdup(p); - if(new_arg == NULL){ - error(0, errno, "strdup"); - exitstatus = EX_OSERR; - free(org_line); - goto fallback; - } - - custom_argc += 1; - custom_argv = realloc(custom_argv, sizeof(char *) - * ((unsigned int) custom_argc + 1)); - if(custom_argv == NULL){ - error(0, errno, "realloc"); - exitstatus = EX_OSERR; - free(org_line); - goto fallback; - } - custom_argv[custom_argc-1] = new_arg; - custom_argv[custom_argc] = NULL; + custom_argv = add_to_argv(custom_argv, &custom_argc, new_arg); + if (custom_argv == NULL){ + perror("add_to_argv"); + exitstatus = EXIT_FAILURE; + goto fallback; + } } } - 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; + custom_argv[0] = argv[0]; + 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; } } - /* 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; - } - if(debug){ for(plugin *p = plugin_list; p != NULL; p=p->next){ fprintf(stderr, "Plugin: %s has %d arguments\n", @@ -741,104 +512,53 @@ 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){ - error(0, errno, "open"); - } 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"); - } - } - } - TEMP_FAILURE_RETRY(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"); + } + + 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 */ { - int dir_fd = -1; - if(plugindir == NULL){ - dir_fd = open(PDIR, O_RDONLY | -#ifdef O_CLOEXEC - O_CLOEXEC -#else /* not O_CLOEXEC */ - 0 -#endif /* not O_CLOEXEC */ - ); - } else { - dir_fd = open(plugindir, O_RDONLY | -#ifdef O_CLOEXEC - O_CLOEXEC -#else /* not O_CLOEXEC */ - 0 -#endif /* not O_CLOEXEC */ - ); - } - if(dir_fd == -1){ - error(0, errno, "Could not open plugin dir"); - 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"); - TEMP_FAILURE_RETRY(close(dir_fd)); - exitstatus = EX_OSERR; - goto fallback; - } -#endif /* O_CLOEXEC */ - - dir = fdopendir(dir_fd); - if(dir == NULL){ - error(0, errno, "Could not open plugin dir"); - TEMP_FAILURE_RETRY(close(dir_fd)); - exitstatus = EX_OSERR; - goto fallback; + 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; + } } } FD_ZERO(&rfds_all); - /* Read and execute any executable in the plugin directory*/ while(true){ - do { - dirst = readdir(dir); - } while(dirst == NULL and errno == EINTR); + dirst = readdir(dir); - /* All directory entries have been processed */ + // All directory entries have been processed if(dirst == NULL){ - if(errno == EBADF){ - error(0, errno, "readdir"); - exitstatus = EX_IOERR; + if (errno == EBADF){ + perror("readdir"); + exitstatus = EXIT_FAILURE; goto fallback; } break; @@ -846,7 +566,7 @@ d_name_len = strlen(dirst->d_name); - /* Ignore dotfiles, backup files and other junk */ + // Ignore dotfiles, backup files and other junk { bool bad_name = false; @@ -854,7 +574,6 @@ const char const *bad_suffixes[] = { "~", "#", ".dpkg-new", ".dpkg-old", - ".dpkg-bak", ".dpkg-divert", NULL }; for(const char **pre = bad_prefixes; *pre != NULL; pre++){ size_t pre_len = strlen(*pre); @@ -868,13 +587,15 @@ 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) + and (strcmp((dirst->d_name)+d_name_len-suf_len, *suf) == 0)){ if(debug){ fprintf(stderr, "Ignoring plugin dir entry \"%s\"" @@ -889,31 +610,22 @@ continue; } } - + char *filename; - if(plugindir == NULL){ - ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, PDIR "/%s", - dirst->d_name)); - } else { - ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, "%s/%s", - plugindir, - dirst->d_name)); - } + ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name); if(ret < 0){ - error(0, errno, "asprintf"); + perror("asprintf"); continue; } - ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st)); - if(ret == -1){ - error(0, errno, "stat"); + ret = stat(filename, &st); + if (ret == -1){ + perror("stat"); free(filename); continue; } - /* Ignore non-executable files */ - if(not S_ISREG(st.st_mode) - or (TEMP_FAILURE_RETRY(access(filename, X_OK)) != 0)){ + if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){ if(debug){ fprintf(stderr, "Ignoring plugin dir entry \"%s\"" " with bad type or mode\n", filename); @@ -921,10 +633,9 @@ free(filename); continue; } - - plugin *p = getplugin(dirst->d_name); + plugin *p = getplugin(dirst->d_name, &plugin_list); if(p == NULL){ - error(0, errno, "getplugin"); + perror("getplugin"); free(filename); continue; } @@ -938,17 +649,17 @@ } { /* 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"); } } } @@ -958,68 +669,67 @@ 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]; - ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd)); - if(ret == -1){ - error(0, errno, "pipe"); - exitstatus = EX_OSERR; + int pipefd[2]; + ret = pipe(pipefd); + if (ret == -1){ + perror("pipe"); + exitstatus = EXIT_FAILURE; goto fallback; } - /* Ask OS to automatic close the pipe on exec */ ret = set_cloexec_flag(pipefd[0]); if(ret < 0){ - error(0, errno, "set_cloexec_flag"); - exitstatus = EX_OSERR; + perror("set_cloexec_flag"); + exitstatus = EXIT_FAILURE; goto fallback; } ret = set_cloexec_flag(pipefd[1]); if(ret < 0){ - error(0, errno, "set_cloexec_flag"); - exitstatus = EX_OSERR; + perror("set_cloexec_flag"); + exitstatus = EXIT_FAILURE; goto fallback; } /* 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; + 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"); - exitstatus = EX_OSERR; + 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(dirfd(dir) < 0){ @@ -1029,296 +739,224 @@ } if(p->environ[0] == NULL){ if(execv(filename, p->argv) < 0){ - error(0, errno, "execv for %s", filename); - _exit(EX_OSERR); + perror("execv"); + _exit(EXIT_FAILURE); } } else { if(execve(filename, p->argv, p->environ) < 0){ - error(0, errno, "execve for %s", filename); - _exit(EX_OSERR); + perror("execve"); + _exit(EXIT_FAILURE); } } /* no return */ } - /* Parent process */ - TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of - pipe */ + /* parent process */ free(filename); - plugin *new_plugin = getplugin(dirst->d_name); - if(new_plugin == NULL){ - error(0, errno, "getplugin"); - ret = (int)(TEMP_FAILURE_RETRY - (sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, - NULL))); + 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; + exitstatus = EXIT_FAILURE; goto fallback; } - new_plugin->pid = pid; - new_plugin->fd = pipefd[0]; - + *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; } -#if defined (__GNUC__) and defined (__GLIBC__) -#if not __GLIBC_PREREQ(2, 16) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wsign-conversion" -#endif -#endif - FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from - -Wconversion in GNU libc - before 2.16 */ -#if defined (__GNUC__) and defined (__GLIBC__) -#if not __GLIBC_PREREQ(2, 16) -#pragma GCC diagnostic pop -#endif -#endif + FD_SET(new_process->fd, &rfds_all); - if(maxfd < new_plugin->fd){ - maxfd = new_plugin->fd; + if (maxfd < new_process->fd){ + maxfd = new_process->fd; } + } - TEMP_FAILURE_RETRY(closedir(dir)); + free_plugin_list(plugin_list); + + closedir(dir); dir = NULL; - 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(); - } + + if (process_list == NULL){ + fprintf(stderr, "No plugin processes started. Incorrect plugin" + " directory?\n"); + process_list = NULL; } - - /* Main loop while running plugins exist */ - while(plugin_list){ + 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 %s [%" PRIdMAX "] dumped" - " core\n", proc->name, (intmax_t) (proc->pid)); + fprintf(stderr, "Plugin %d dumped core\n", + (unsigned int) (proc->pid)); } } - /* Remove the plugin */ -#if defined (__GNUC__) and defined (__GLIBC__) -#if not __GLIBC_PREREQ(2, 16) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wsign-conversion" -#endif -#endif - FD_CLR(proc->fd, &rfds_all); /* Spurious warning from - -Wconversion in GNU libc - before 2.16 */ -#if defined (__GNUC__) and defined (__GLIBC__) -#if not __GLIBC_PREREQ(2, 16) -#pragma GCC diagnostic pop -#endif -#endif - + 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); + + 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 defined (__GNUC__) and defined (__GLIBC__) -#if not __GLIBC_PREREQ(2, 16) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wsign-conversion" -#endif -#endif - if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ /* Spurious - warning from - -Wconversion - in GNU libc - before - 2.16 */ -#if defined (__GNUC__) and defined (__GLIBC__) -#if not __GLIBC_PREREQ(2, 16) -#pragma GCC diagnostic pop -#endif -#endif + 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){ proc->buffer = realloc(proc->buffer, proc->buffer_size + (size_t) BUFFER_SIZE); - if(proc->buffer == NULL){ - error(0, errno, "malloc"); - exitstatus = EX_OSERR; + if (proc->buffer == NULL){ + perror("malloc"); + exitstatus = EXIT_FAILURE; goto fallback; } 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)){ - /* Fallback if all plugins failed, none are found or an error - occured */ + 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++){ + for(char **arg = custom_argv; *arg != NULL; arg++){ free(*arg); } free(custom_argv); } + 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(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 2011-12-31 23:05:34 +0000 +++ plugin-runner.xml 2008-08-16 20:31:21 +0000 @@ -1,45 +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 - 2012 - 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 @@ -48,596 +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 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. - - - 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. - - - - + + + + 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. - - - - + + + + + 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 - - - - - - Run plugins from a different directory, read a different - configuration file, and add two options to the - mandos-client - 8mandos plugin: - - - - -cd /etc/keys/mandos; &COMMANDNAME; --config-file=/etc/mandos/plugin-runner.conf --plugin-dir /usr/lib/mandos/plugins.d --options-for=mandos-client:--pubkey=pubkey.txt,--seckey=seckey.txt - - - + 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 2011-12-31 23:05:34 +0000 +++ plugins.d/askpass-fifo.c 1970-01-01 00:00:00 +0000 @@ -1,202 +0,0 @@ -/* -*- coding: utf-8 -*- */ -/* - * Askpass-FIFO - Read a password from a FIFO and output it - * - * Copyright © 2008-2012 Teddy Hogeborn - * Copyright © 2008-2012 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 this program. If not, see - * . - * - * Contact the authors at . - */ - -#define _GNU_SOURCE /* TEMP_FAILURE_RETRY() */ -#include /* 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(), ... */ - - -/* 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; - - /* 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; - } - } - - /* 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 2011-12-31 23:05:34 +0000 +++ plugins.d/askpass-fifo.xml 1970-01-01 00:00:00 +0000 @@ -1,166 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2008 - 2009 - 2011 - 2012 - 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. - - - - - - - - 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 2013-10-20 15:25:09 +0000 +++ plugins.d/password-prompt.c 2008-08-16 16:58:31 +0000 @@ -1,9 +1,8 @@ -/* -*- coding: utf-8; mode: c; mode: orgtbl -*- */ +/* -*- coding: utf-8 -*- */ /* - * Password-prompt - Read a password from the terminal and print it - * - * Copyright © 2008-2013 Teddy Hogeborn - * Copyright © 2008-2013 Björn Påhlsson + * 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 @@ -19,220 +18,49 @@ * 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"; - -__attribute__((format (gnu_printf, 2, 3), nonnull(1))) -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)); -} - -/* 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"); - } - 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; @@ -245,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; } @@ -429,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 2011-12-31 23:05:34 +0000 +++ plugins.d/password-prompt.xml 2008-08-18 05:24:20 +0000 @@ -1,46 +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 - 2011 - 2012 - 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 @@ -48,265 +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 - None are known at this time. - - + + 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 2013-12-15 22:21:28 +0000 +++ plugins.d/password-request.c 2008-08-17 22:42:28 +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,8 +9,7 @@ * "browse_callback", and parts of "main". * * Everything else is - * Copyright © 2008-2013 Teddy Hogeborn - * Copyright © 2008-2013 Björn Påhlsson + * 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 @@ -26,75 +25,49 @@ * 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 -#ifndef _FILE_OFFSET_BITS #define _FILE_OFFSET_BITS 64 -#endif #define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), asprintf() */ #include /* fprintf(), stderr, fwrite(), - stdout, ferror(), remove() */ -#include /* uint16_t, uint32_t, intptr_t */ + stdout, ferror() */ +#include /* uint16_t, uint32_t */ #include /* NULL, size_t, ssize_t */ -#include /* free(), EXIT_SUCCESS, srand(), - strtof(), abort() */ -#include /* bool, false, true */ +#include /* free(), EXIT_SUCCESS, EXIT_FAILURE, + srand() */ +#include /* bool, true */ #include /* memset(), strcmp(), strlen(), strerror(), asprintf(), strcpy() */ -#include /* ioctl */ +#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() */ -#include /* open() */ -#include /* opendir(), struct dirent, readdir() - */ -#include /* PRIu16, PRIdMAX, intmax_t, - strtoimax() */ -#include /* perror(), errno, - 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() */ -#include /* inet_pton(), htons, inet_ntop() */ -#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() */ - -#ifdef __linux__ -#include /* klogctl() */ -#endif /* __linux__ */ /* Avahi */ /* All Avahi types, constants and functions @@ -113,9 +86,8 @@ gnutls_* init_gnutls_session(), GNUTLS_* */ -#include - /* gnutls_certificate_set_openpgp_key_file(), - GNUTLS_OPENPGP_FMT_BASE64 */ +#include /* gnutls_certificate_set_openpgp_key_file(), + GNUTLS_OPENPGP_FMT_BASE64 */ /* GPGME */ #include /* All GPGME types, constants and @@ -126,80 +98,32 @@ #define BUFFER_SIZE 256 -#define PATHDIR "/conf/conf.d/mandos" -#define SECKEY "seckey.txt" -#define PUBKEY "pubkey.txt" -#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; -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))) -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. */ -size_t incbuffer(char **buffer, size_t buffer_length, - size_t buffer_capacity){ - if(buffer_length + BUFFER_SIZE > buffer_capacity){ +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){ + if (buffer == NULL){ return 0; } buffer_capacity += BUFFER_SIZE; @@ -207,250 +131,160 @@ return buffer_capacity; } -/* Add server to set of servers to retry periodically */ -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"); - 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; - /* Place the new server last in the list */ - } else { - new_server->next = *current_server; - new_server->prev = (*current_server)->prev; - new_server->prev->next = new_server; - (*current_server)->prev = new_server; - } - ret = clock_gettime(CLOCK_MONOTONIC, &(*current_server)->last_seen); - if(ret == -1){ - perror_plus("clock_gettime"); - return false; - } - return true; -} - /* - * Initialize GPGME. + * Decrypt OpenPGP data using keyrings in HOMEDIR. + * Returns -1 on error */ -static bool init_gpgme(const char *seckey, const char *pubkey, - const char *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 *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; - } - - 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; - } - - ret = (int)TEMP_FAILURE_RETRY(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, "Mandos plugin mandos-client: " - "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 - */ -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, "Mandos plugin mandos-client: " - "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){ - gpgme_decrypt_result_t result; - result = gpgme_op_decrypt_result(mc->ctx); - if(result == NULL){ - fprintf_plus(stderr, "gpgme_op_decrypt_result failed\n"); - } else { - fprintf_plus(stderr, "Unsupported algorithm: %s\n", - result->unsupported_algorithm); - fprintf_plus(stderr, "Wrong key usage: %u\n", - result->wrong_key_usage); - if(result->file_name != NULL){ - fprintf_plus(stderr, "File name: %s\n", result->file_name); - } - gpgme_recipient_t recipient; - recipient = result->recipients; + goto decrypt_end; + } + + if(debug){ + fprintf(stderr, "Decryption of OpenPGP data succeeded\n"); + } + + if (debug){ + gpgme_decrypt_result_t result; + result = gpgme_op_decrypt_result(ctx); + if (result == NULL){ + fprintf(stderr, "gpgme_op_decrypt_result failed\n"); + } else { + 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(stderr, "File name: %s\n", result->file_name); + } + gpgme_recipient_t recipient; + recipient = result->recipients; + if(recipient){ while(recipient != NULL){ - fprintf_plus(stderr, "Public key algorithm: %s\n", - gpgme_pubkey_algo_name - (recipient->pubkey_algo)); - fprintf_plus(stderr, "Key ID: %s\n", recipient->keyid); - fprintf_plus(stderr, "Secret key available: %s\n", - recipient->status == GPG_ERR_NO_SECKEY - ? "No" : "Yes"); + 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; } } } - goto decrypt_end; - } - - if(debug){ - fprintf_plus(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]); } @@ -467,9 +301,9 @@ return plaintext_length; } -static const char * safer_gnutls_strerror(int value){ - const char *ret = gnutls_strerror(value); - if(ret == NULL) +static const char * safer_gnutls_strerror (int value) { + const char *ret = gnutls_strerror (value); /* Spurious warning */ + if (ret == NULL) ret = "(unknown)"; return ret; } @@ -477,26 +311,26 @@ /* GnuTLS log function callback */ static void debuggnutls(__attribute__((unused)) int level, const char* string){ - fprintf_plus(stderr, "GnuTLS: %s", string); + fprintf(stderr, "GnuTLS: %s", string); } -static int init_gnutls_global(const char *pubkeyfilename, - const char *seckeyfilename, - 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"); + fprintf(stderr, "Initializing GnuTLS\n"); } ret = gnutls_global_init(); - if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, "GnuTLS global_init: %s\n", - safer_gnutls_strerror(ret)); + if (ret != GNUTLS_E_SUCCESS) { + fprintf (stderr, "GnuTLS global_init: %s\n", + safer_gnutls_strerror(ret)); return -1; } - if(debug){ + if (debug){ /* "Use a log level over 10 to enable all debugging options." * - GnuTLS manual */ @@ -505,113 +339,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_global_deinit(); + 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 OpenPGP public key %s and" - " secret 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); } ret = gnutls_certificate_set_openpgp_key_file (mc->cred, pubkeyfilename, seckeyfilename, GNUTLS_OPENPGP_FMT_BASE64); - if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, - "Error[%d] while reading the OpenPGP key pair ('%s'," - " '%s')\n", ret, pubkeyfilename, seckeyfilename); - fprintf_plus(stderr, "The GnuTLS error is: %s\n", - safer_gnutls_strerror(ret)); + 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)); + 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_plus(stderr, "Error in GnuTLS prime generation: %s\n", - safer_gnutls_strerror(ret)); + 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_global_deinit(); - gnutls_dh_params_deinit(mc->dh_params); return -1; + } -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(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); + gnutls_dh_set_prime_bits (*session, mc->dh_bits); return 0; } @@ -621,415 +437,219 @@ __attribute__((unused)) const char *txt){} /* Called when a Mandos server is found */ -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; - union { - struct sockaddr_in in; - struct sockaddr_in6 in6; - } 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 */ - - 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; } - } - - ret = init_gnutls_session(&session, mc); - if(ret != 0){ - 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, 0); - if(tcp_sd < 0){ - int e = errno; - perror_plus("socket"); - errno = e; - goto mandos_end; - } - - if(quit_now){ - errno = EINTR; - goto mandos_end; + fprintf(stderr, "Binding to interface %s\n", interface); } memset(&to, 0, sizeof(to)); - if(af == AF_INET6){ - to.in6.sin6_family = (sa_family_t)af; - ret = inet_pton(af, ip, &to.in6.sin6_addr); - } else { /* IPv4 */ - to.in.sin_family = (sa_family_t)af; - ret = inet_pton(af, ip, &to.in.sin_addr); - } - if(ret < 0 ){ - int e = errno; - perror_plus("inet_pton"); - errno = e; - goto mandos_end; + 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(ret == 0){ - int e = errno; - fprintf_plus(stderr, "Bad address: %s\n", ip); - errno = e; - goto mandos_end; - } - if(af == AF_INET6){ - to.in6.sin6_port = htons(port); -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wstrict-aliasing" -#endif - if(IN6_IS_ADDR_LINKLOCAL /* Spurious warnings from */ - (&to.in6.sin6_addr)){ /* -Wstrict-aliasing=2 or lower */ -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - 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 */ - to.in6.sin6_scope_id = (uint32_t)if_index; - } - } else { - to.in.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] = ""; - const char *pcret; - if(af == AF_INET6){ - pcret = inet_ntop(af, &(to.in6.sin6_addr), addrstr, - sizeof(addrstr)); - } else { - pcret = inet_ntop(af, &(to.in.sin_addr), addrstr, - sizeof(addrstr)); - } - if(pcret == NULL){ - perror_plus("inet_ntop"); + 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_plus(stderr, "Canonical address form: %s\n", addrstr); + fprintf(stderr, "Canonical address form: %s\n", addrstr); } } } - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - - if(af == AF_INET6){ - ret = connect(tcp_sd, &to.in6, sizeof(to)); - } else { - ret = connect(tcp_sd, &to.in, sizeof(to)); /* IPv4 */ - } - if(ret < 0){ - if ((errno != ECONNREFUSED and errno != ENETUNREACH) or debug){ - int e = errno; - perror_plus("connect"); - errno = e; - } - goto mandos_end; - } - - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - + 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){ - + 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; } - retval = 0; + free(decrypted_buffer); + } else { + retval = -1; } } /* Shutdown procedure */ mandos_end: - { - int e = errno; - free(decrypted_buffer); - free(buffer); - if(tcp_sd >= 0){ - ret = (int)TEMP_FAILURE_RETRY(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, @@ -1040,26 +660,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){ - 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: @@ -1067,58 +680,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: @@ -1127,14 +724,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: @@ -1143,1395 +738,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; -} - -bool get_flags(const char *ifname, struct ifreq *ifr){ - int ret; - error_t ret_errno; - - int s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); - if(s < 0){ - ret_errno = errno; - perror_plus("socket"); - errno = ret_errno; - return false; - } - strcpy(ifr->ifr_name, ifname); - ret = ioctl(s, SIOCGIFFLAGS, ifr); - if(ret == -1){ - if(debug){ - ret_errno = errno; - perror_plus("ioctl SIOCGIFFLAGS"); - errno = ret_errno; - } - return false; - } - return true; -} - -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.) - */ -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. - */ -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 - */ -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); -} - -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? */ -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; - } - - char *fullname = NULL; - ret = asprintf(&fullname, "%s/%s", hookdir, direntry->d_name); +/* 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){ - perror_plus("asprintf"); - return 0; - } - - ret = stat(fullname, &st); - 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; + return NULL; + } + return tmp; } -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); + +int main(int argc, char *argv[]){ + 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 { - if (debug){ - fprintf_plus(stderr, "Check current_server if we should run" - " it, or wait\n"); + 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; } - /* the current time */ - ret = clock_gettime(CLOCK_MONOTONIC, &now); + strcpy(network.ifr_name, interface); + ret = ioctl(sd, SIOCGIFFLAGS, &network); 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; - } - } - } -} - -/* Set effective uid to 0, return errno */ -error_t raise_privileges(void){ - error_t old_errno = errno; - error_t ret_errno = 0; - if(seteuid(0) == -1){ - ret_errno = errno; - perror_plus("seteuid"); - } - errno = old_errno; - return ret_errno; -} - -/* Set effective and real user ID to 0. Return errno. */ -error_t raise_privileges_permanently(void){ - error_t old_errno = errno; - error_t ret_errno = raise_privileges(); - if(ret_errno != 0){ - errno = old_errno; - return ret_errno; - } - if(setuid(0) == -1){ - ret_errno = errno; - perror_plus("seteuid"); - } - errno = old_errno; - return ret_errno; -} - -/* Set effective user ID to unprivileged saved user ID */ -error_t lower_privileges(void){ - error_t old_errno = errno; - error_t ret_errno = 0; - if(seteuid(uid) == -1){ - ret_errno = errno; - perror_plus("seteuid"); - } - errno = old_errno; - return ret_errno; -} - -/* Lower privileges permanently */ -error_t lower_privileges_permanently(void){ - error_t old_errno = errno; - error_t ret_errno = 0; - if(setuid(uid) == -1){ - ret_errno = errno; - perror_plus("setuid"); - } - errno = old_errno; - return ret_errno; -} - -bool run_network_hooks(const char *mode, const char *interface, - const float delay){ - struct dirent **direntries; - struct dirent *direntry; - int ret; - int numhooks = scandir(hookdir, &direntries, runnable_hook, - alphasort); - if(numhooks == -1){ - if(errno == ENOENT){ - if(debug){ - fprintf_plus(stderr, "Network hook directory \"%s\" not" - " found\n", hookdir); - } - } else { - perror_plus("scandir"); - } - } else { - int devnull = open("/dev/null", O_RDONLY); - for(int i = 0; i < numhooks; i++){ - direntry = direntries[i]; - char *fullname = NULL; - ret = asprintf(&fullname, "%s/%s", hookdir, direntry->d_name); - if(ret < 0){ - perror_plus("asprintf"); - continue; - } - 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 */ - raise_privileges_permanently(); - /* Set group */ - errno = 0; - ret = setgid(0); - if(ret == -1){ - perror_plus("setgid"); - } - /* Reset supplementary groups */ - errno = 0; - ret = setgroups(0, NULL); - if(ret == -1){ - perror_plus("setgroups"); - } - dup2(devnull, STDIN_FILENO); - close(devnull); - dup2(STDERR_FILENO, STDOUT_FILENO); - 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", 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); - } - } - if(execl(fullname, direntry->d_name, mode, NULL) == -1){ - perror_plus("execl"); - _exit(EXIT_FAILURE); - } - } else { - int status; - if(TEMP_FAILURE_RETRY(waitpid(hook_pid, &status, 0)) == -1){ - perror_plus("waitpid"); - free(fullname); - 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(fullname); - continue; - } - } else if(WIFSIGNALED(status)){ - fprintf_plus(stderr, "Warning: network hook \"%s\" died by" - " signal %d\n", direntry->d_name, - WTERMSIG(status)); - free(fullname); - continue; - } else { - fprintf_plus(stderr, "Warning: network hook \"%s\"" - " crashed\n", direntry->d_name); - free(fullname); - continue; - } - } - free(fullname); - if(debug){ - fprintf_plus(stderr, "Network hook \"%s\" ran successfully\n", - direntry->d_name); - } - } - close(devnull); - } - return true; -} - -error_t bring_up_interface(const char *const interface, - const float delay){ - int sd = -1; - error_t old_errno = errno; - error_t ret_errno = 0; - int ret, ret_setflags; - 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)){ - if(not get_flags(interface, &network) and debug){ - ret_errno = errno; - fprintf_plus(stderr, "Failed to get flags for interface " - "\"%s\"\n", interface); - return ret_errno; - } - network.ifr_flags |= IFF_UP; - - sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); - if(sd < 0){ - ret_errno = errno; - perror_plus("socket"); - errno = old_errno; - return ret_errno; - } - - if(quit_now){ + 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); - errno = old_errno; - return EINTR; - } - - if(debug){ - fprintf_plus(stderr, "Bringing up interface \"%s\"\n", - interface); - } - - /* Raise priviliges */ - raise_privileges(); - -#ifdef __linux__ - /* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO - messages about the network interface to mess up the prompt */ - int ret_linux = klogctl(8, NULL, 5); - bool restore_loglevel = true; - if(ret_linux == -1){ - restore_loglevel = false; - perror_plus("klogctl"); - } -#endif /* __linux__ */ - ret_setflags = ioctl(sd, SIOCSIFFLAGS, &network); - ret_errno = errno; -#ifdef __linux__ - if(restore_loglevel){ - ret_linux = klogctl(7, NULL, 0); - if(ret_linux == -1){ - perror_plus("klogctl"); + } + + 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; } - } -#endif /* __linux__ */ - - /* Lower privileges */ - lower_privileges(); - - /* Close the socket */ - ret = (int)TEMP_FAILURE_RETRY(close(sd)); - if(ret == -1){ - perror_plus("close"); - } - - if(ret_setflags == -1){ - errno = ret_errno; - perror_plus("ioctl SIOCSIFFLAGS +IFF_UP"); - errno = old_errno; - return ret_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; -} - -error_t take_down_interface(const char *const interface){ - int sd = -1; - error_t old_errno = errno; - error_t ret_errno = 0; - int ret, ret_setflags; - 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)){ - if(not get_flags(interface, &network) and debug){ - ret_errno = errno; - fprintf_plus(stderr, "Failed to get flags for interface " - "\"%s\"\n", interface); - return ret_errno; - } - network.ifr_flags &= ~(short)IFF_UP; /* clear flag */ - - sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); - if(sd < 0){ - ret_errno = errno; - perror_plus("socket"); - errno = old_errno; - return ret_errno; - } - - if(debug){ - fprintf_plus(stderr, "Taking down interface \"%s\"\n", - interface); - } - - /* Raise priviliges */ - raise_privileges(); - - ret_setflags = ioctl(sd, SIOCSIFFLAGS, &network); - ret_errno = errno; - - /* Lower privileges */ - lower_privileges(); - - /* Close the socket */ - ret = (int)TEMP_FAILURE_RETRY(close(sd)); - if(ret == -1){ - perror_plus("close"); - } - - if(ret_setflags == -1){ - errno = ret_errno; - perror_plus("ioctl SIOCSIFFLAGS -IFF_UP"); - errno = old_errno; - return ret_errno; - } - } else if(debug){ - fprintf_plus(stderr, "Interface \"%s\" is already down; odd\n", - interface); - } - - errno = old_errno; - return 0; -} - -int main(int argc, char *argv[]){ - mandos_context mc = { .server = NULL, .dh_bits = 1024, - .priority = "SECURE256:!CTYPE-X.509:" - "+CTYPE-OPENPGP", .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 tempdir[] = "/tmp/mandosXXXXXX"; - bool tempdir_created = false; - AvahiIfIndex if_index = AVAHI_IF_UNSPEC; - const char *seckey = PATHDIR "/" SECKEY; - const char *pubkey = PATHDIR "/" PUBKEY; - char *interfaces_hooks = NULL; - size_t interfaces_hooks_size = 0; - - 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 = 2 }, - { .name = "dh-bits", .key = 129, - .arg = "BITS", - .doc = "Bit length of the prime number used in the" - " Diffie-Hellman key exchange", - .group = 2 }, - { .name = "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){ 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 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 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 = argp_parse(&argp, argc, argv, - ARGP_IN_ORDER | ARGP_NO_HELP, 0, NULL); - switch(ret){ - case 0: - break; - case ENOMEM: - default: - errno = ret; - perror_plus("argp_parse"); - exitcode = EX_OSERR; - goto end; - case EINVAL: - exitcode = EX_USAGE; - goto end; - } - } - - { - /* Work around Debian bug #633582: - */ - - /* Re-raise priviliges */ - if(raise_privileges() == 0){ - 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"); - } - } - } - TEMP_FAILURE_RETRY(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"); - } - } - } - TEMP_FAILURE_RETRY(close(pubkey_fd)); - } - } - - /* Lower privileges */ - 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); - interfaces_hooks_size = mc.interfaces_size; - argz_stringify(interfaces_hooks, interfaces_hooks_size, - (int)','); - } - if(not run_network_hooks("start", interfaces_hooks != NULL ? - interfaces_hooks : "", delay)){ - goto end; - } - } - - 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; - /* 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){ - perror_plus("argz_add"); - continue; - } - if(debug){ - fprintf_plus(stderr, "Will use interface \"%s\"\n", - direntries[i]->d_name); - } - } - free(direntries); - } else { - 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); - ret = bring_up_interface(interface, delay); - if(not interface_was_up){ - if(ret != 0){ - errno = ret; - perror_plus("Failed to bring up interface"); - } else { - ret_errno = argz_add(&interfaces_to_take_down, - &interfaces_to_take_down_size, - interface); - } - } - } - 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; - } - - ret = init_gnutls_global(pubkey, seckey, &mc); - 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; - } - - if(mkdtemp(tempdir) == NULL){ - perror_plus("mkdtemp"); - goto end; - } - tempdir_created = true; - - 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_errno); - - /* 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_errno)); - 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){ - 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_global_deinit(); - 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; - free(mc.current_server); - mc.current_server = next; - } - } - - /* Re-raise priviliges */ - { - raise_privileges(); - - /* 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_errno = take_down_interface(interface); - if(ret_errno != 0){ - errno = ret_errno; - 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"); - } - } - - lower_privileges_permanently(); - } - - free(interfaces_to_take_down); - free(interfaces_hooks); - - /* Removes the GPGME temp directory and all files inside */ - if(tempdir_created){ - struct dirent **direntries = NULL; - struct dirent *direntry = NULL; - int numentries = scandir(tempdir, &direntries, notdotentries, - alphasort); - if (numentries > 0){ - for(int i = 0; i < numentries; i++){ - direntry = direntries[i]; - char *fullname = NULL; - ret = asprintf(&fullname, "%s/%s", tempdir, - direntry->d_name); - if(ret < 0){ - perror_plus("asprintf"); - continue; - } - ret = remove(fullname); - if(ret == -1){ - fprintf_plus(stderr, "remove(\"%s\"): %s\n", fullname, - strerror(errno)); - } - free(fullname); - } - } - - /* need to clean even if 0 because man page doesn't specify */ - free(direntries); - if (numentries == -1){ - perror_plus("scandir"); - } - ret = rmdir(tempdir); - if(ret == -1 and errno != ENOENT){ - perror_plus("rmdir"); - } - } - - 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 2013-10-20 15:25:09 +0000 +++ plugins.d/password-request.xml 2008-08-18 05:24:20 +0000 @@ -1,46 +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 - 2012 - 2013 - 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 @@ -49,852 +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 an OpenPGP key to ensure authenticity and - confidentiality. This client program keeps running, trying all - servers on the network, until it receives a satisfactory reply - or a TERM signal. After all servers have been tried, all - servers are periodically retried. If no servers are found it - will wait indefinitely for new servers to appear. - - - 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. - - - This option is normally only useful for 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; not bring up - any interfaces specified - after 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. - - - - - - - - - - - - - - - - Sets the number of bits to use for the prime number in the - TLS Diffie-Hellman key exchange. Default is 1024. - - - - - - - - - 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 - This program does not use any 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. - - - - - /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 a custom key: - - - - -&COMMANDNAME; --debug --pubkey keydir/pubkey.txt --seckey keydir/seckey.txt - - - - - - Run in debug mode, with a custom key, and do not use Zeroconf - to locate a server; connect directly to the IPv6 link-local - address fe80::aede:48ff:fe71:f6f2, port 4711, - using interface eth2: - - - - -&COMMANDNAME; --debug --pubkey keydir/pubkey.txt --seckey keydir/seckey.txt --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 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, 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 OpenPGP 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 4346: The Transport Layer Security (TLS) - Protocol Version 1.1 - - - - TLS 1.1 is the protocol implemented by GnuTLS. - - - - - - RFC 4880: OpenPGP Message Format - - - - The data received from the server is binary encrypted - OpenPGP data. - - - - - - RFC 5081: Using OpenPGP Keys for Transport Layer - Security - - - - This is implemented by GnuTLS and 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 2013-10-20 15:25:09 +0000 +++ plugins.d/plymouth.c 1970-01-01 00:00:00 +0000 @@ -1,487 +0,0 @@ -/* -*- coding: utf-8 -*- */ -/* - * Plymouth - Read a password from Plymouth and output it - * - * Copyright © 2010-2013 Teddy Hogeborn - * Copyright © 2010-2013 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 this program. 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_pid[] = "/dev/.initramfs/plymouth.pid"; -/* Used by Ubuntu 11.10 (Oneiric Ocelot) */ -const char plymouth_pid[] = "/run/initramfs/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 = NULL; - char **tmp; - int i = 0; - for (; argv[i]!=NULL; i++){ - tmp = realloc(new_argv, sizeof(const char *) * ((size_t)i + 1)); - 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_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){ - ret = sscanf(direntries[0]->d_name, "%" SCNuMAX, &proc_id); - if (ret < 0){ - error_plus(0, errno, "sscanf"); - } - } - /* scandir might preallocate for this variable (man page unclear). - 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; -} - -const char * const * 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 (const char * const *)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); - - const char * const *plymouthd_argv; - pid_t pid = get_pid(); - if(pid == 0){ - error_plus(0, 0, "plymouthd pid not found"); - plymouthd_argv = plymouthd_default_argv; - } else { - plymouthd_argv = getargv(pid); - } - - bret = exec_and_wait(NULL, plymouth_path, (const char *[]) - { plymouth_path, "quit", NULL }, - false, false); - if(not bret){ - exit(EXIT_FAILURE); - } - bret = exec_and_wait(NULL, plymouthd_path, plymouthd_argv, - false, true); - 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 2011-12-31 23:05:34 +0000 +++ plugins.d/plymouth.xml 1970-01-01 00:00:00 +0000 @@ -1,282 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2010 - 2011 - 2012 - 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 2011-12-31 23:05:34 +0000 +++ plugins.d/splashy.c 1970-01-01 00:00:00 +0000 @@ -1,471 +0,0 @@ -/* -*- coding: utf-8 -*- */ -/* - * Splashy - Read a password from splashy and output it - * - * Copyright © 2008-2012 Teddy Hogeborn - * Copyright © 2008-2012 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 this program. 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 2011-12-31 23:05:34 +0000 +++ plugins.d/splashy.xml 1970-01-01 00:00:00 +0000 @@ -1,286 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2008 - 2009 - 2012 - 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 2011-12-31 23:05:34 +0000 +++ plugins.d/usplash.c 1970-01-01 00:00:00 +0000 @@ -1,684 +0,0 @@ -/* -*- coding: utf-8 -*- */ -/* - * Usplash - Read a password from usplash and output it - * - * Copyright © 2008-2012 Teddy Hogeborn - * Copyright © 2008-2012 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 this program. 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; - TEMP_FAILURE_RETRY(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; - TEMP_FAILURE_RETRY(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; - } - TEMP_FAILURE_RETRY(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 = (int)TEMP_FAILURE_RETRY(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 = (int)TEMP_FAILURE_RETRY(close(outfifo_fd)); - if(ret == -1){ - error_plus(0, errno, "close"); - } - } - - /* Create argv for new usplash*/ - char **cmdline_argv = malloc((argz_count(cmdline, cmdline_len) + 1) - * sizeof(char *)); /* Count args */ - if(cmdline_argv == NULL){ - error_plus(0, errno, "malloc"); - return status; - } - argz_extract(cmdline, cmdline_len, cmdline_argv); /* Create argv */ - - /* 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 = (int)TEMP_FAILURE_RETRY(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 2011-12-31 23:05:34 +0000 +++ plugins.d/usplash.xml 1970-01-01 00:00:00 +0000 @@ -1,301 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2008 - 2009 - 2011 - 2012 - 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 - - -
- - - - -