=== modified file '.bzrignore'
--- .bzrignore 2008-09-26 05:04:15 +0000
+++ .bzrignore 2012-05-17 01:55:58 +0000
@@ -2,11 +2,13 @@
*.8
*.8mandos
confdir
-debian/po/messages.mo
-debian/po/templates.pot
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
=== added file 'DBUS-API'
--- DBUS-API 1970-01-01 00:00:00 +0000
+++ DBUS-API 2012-01-15 20:27:28 +0000
@@ -0,0 +1,181 @@
+ -*- 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
=== modified file 'INSTALL'
--- INSTALL 2008-09-08 18:54:47 +0000
+++ INSTALL 2013-06-23 15:13:06 +0000
@@ -4,15 +4,15 @@
** Operating System
- Debian 5.0 "lenny" or Ubuntu 8.04 "Hardy Heron".
+ 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 intended to be portable to other Unixes.
-
+ are not written with portabillity to other Unixes in mind.
+
** Libraries
The following libraries and packages are needed. (It is possible
@@ -35,22 +35,23 @@
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.4 http://www.python.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/
- + python-ctypes 1.0.0 http://pypi.python.org/pypi/ctypes
+ + 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-ctypes
-
+ python-gobject python-urwid
+
*** Mandos Client
+ initramfs-tools 0.85i
http://packages.qa.debian.org/i/initramfs-tools.html
@@ -97,9 +98,31 @@
and append this to the file "/etc/mandos/clients.conf" *on the
server computer*.
- 4. On the server computer, start the server by running the command
+ 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 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
@@ -109,6 +132,6 @@
You may want to tighten or loosen the timeouts in the server
configuration files; see mandos.conf(5) and mandos-clients.conf(5).
- Is IPsec is not used, it is suggested that a more cryptographically
+ 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 2008-09-26 19:47:21 +0000
+++ Makefile 2014-02-16 13:12:20 +0000
@@ -1,18 +1,33 @@
WARN=-O -Wall -Wformat=2 -Winit-self -Wmissing-include-dirs \
-Wswitch-default -Wswitch-enum -Wunused-parameter \
- -Wstrict-aliasing=2 -Wextra -Wfloat-equal -Wundef -Wshadow \
+ -Wstrict-aliasing=1 -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
+# -Wunreachable-code
#DEBUG=-ggdb3
# For info about _FORTIFY_SOURCE, see
-#
-FORTIFY=-D_FORTIFY_SOURCE=2 # -fstack-protector-all
+#
+# 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
#COVERAGE=--coverage
OPTIMIZE=-Os
LANGUAGE=-std=gnu99
+htmldir=man
+version=1.6.4
+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
@@ -20,6 +35,8 @@
# 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
@@ -28,97 +45,233 @@
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)
##
-GNUTLS_CFLAGS=$(shell libgnutls-config --cflags)
-GNUTLS_LIBS=$(shell libgnutls-config --libs)
+SYSTEMD=$(DESTDIR)$(shell pkg-config systemd --variable=systemdsystemunitdir)
+
+GNUTLS_CFLAGS=$(shell pkg-config --cflags-only-I gnutls)
+GNUTLS_LIBS=$(shell pkg-config --libs gnutls)
AVAHI_CFLAGS=$(shell pkg-config --cflags-only-I avahi-core)
AVAHI_LIBS=$(shell pkg-config --libs avahi-core)
-GPGME_CFLAGS=$(shell gpgme-config --cflags)
-GPGME_LIBS=$(shell gpgme-config --libs)
+GPGME_CFLAGS=$(shell gpgme-config --cflags; getconf LFS_CFLAGS)
+GPGME_LIBS=$(shell gpgme-config --libs; getconf LFS_LIBS; \
+ getconf LFS_LDFLAGS)
# Do not change these two
-CFLAGS=$(WARN) $(DEBUG) $(FORTIFY) $(COVERAGE) $(OPTIMIZE) \
- $(LANGUAGE) $(GNUTLS_CFLAGS) $(AVAHI_CFLAGS) $(GPGME_CFLAGS)
-LDFLAGS=$(COVERAGE)
+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))
# Commands to format a DocBook document into a manual page
-DOCBOOKTOMAN=cd $(dir $<); xsltproc --nonet --xinclude \
+DOCBOOKTOMAN=$(strip 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 $@)
+ $(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'
+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
-PROGS=plugin-runner $(PLUGINS)
-DOCS=mandos.8 plugin-runner.8mandos mandos-keygen.8 \
+ 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 mandos.conf.5 \
- mandos-clients.conf.5
-
-objects=$(addsuffix .o,$(PROGS))
-
-all: $(PROGS)
+ 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
doc: $(DOCS)
-%.5: %.xml legalnotice.xml
- $(DOCBOOKTOMAN)
-
-%.8: %.xml legalnotice.xml
- $(DOCBOOKTOMAN)
-
-%.8mandos: %.xml legalnotice.xml
- $(DOCBOOKTOMAN)
-
-mandos.8: mandos.xml mandos-options.xml overview.xml legalnotice.xml
- $(DOCBOOKTOMAN)
-
-mandos-keygen.8: mandos-keygen.xml overview.xml legalnotice.xml
- $(DOCBOOKTOMAN)
-
-mandos.conf.5: mandos.conf.xml mandos-options.xml legalnotice.xml
- $(DOCBOOKTOMAN)
-
-plugin-runner.8mandos: plugin-runner.xml overview.xml legalnotice.xml
- $(DOCBOOKTOMAN)
+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: plugins.d/mandos-client.o
- $(LINK.o) $(GNUTLS_LIBS) $(AVAHI_LIBS) $(GPGME_LIBS) \
- $(COMMON) $^ $(LOADLIBES) $(LDLIBS) -o $@
-
-.PHONY : all doc clean distclean run-client run-server install \
+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 \
install-server install-client uninstall uninstall-server \
uninstall-client purge purge-server purge-client
clean:
- -rm --force $(PROGS) $(objects) $(DOCS) core
+ -rm --force $(CPROGS) $(objects) $(htmldocs) $(DOCS) core
distclean: clean
mostlyclean: clean
maintainer-clean: clean
- -rm --force --recursive keydir confdir
+ -rm --force --recursive keydir confdir statedir
-check:
+check: all
./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 "###################################################################"
./plugin-runner --plugin-dir=plugins.d \
--config-file=plugin-runner.conf \
- --options-for=mandos-client:--seckey=keydir/seckey.txt,--pubkey=keydir/pubkey.txt
+ --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
@@ -126,8 +279,9 @@
./mandos-keygen --dir keydir --force
# Run the server with a local config
-run-server: confdir/mandos.conf confdir/clients.conf
- ./mandos --debug --configdir=confdir
+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
@@ -138,18 +292,40 @@
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)
+
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) \
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 \
@@ -157,53 +333,72 @@
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 $(PREFIX)/lib/mandos $(CONFDIR)
+ install --directory $(LIBDIR)/mandos $(CONFDIR)
install --directory --mode=u=rwx $(KEYDIR) \
- $(PREFIX)/lib/mandos/plugins.d
- if [ "$(CONFDIR)" != "$(PREFIX)/lib/mandos" ]; then \
+ $(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=$(PREFIX)/lib/mandos plugin-runner
+ --target-directory=$(LIBDIR)/mandos plugin-runner
install --mode=u=rwx,go=rx --target-directory=$(PREFIX)/sbin \
mandos-keygen
install --mode=u=rwx,go=rx \
- --target-directory=$(PREFIX)/lib/mandos/plugins.d \
+ --target-directory=$(LIBDIR)/mandos/plugins.d \
plugins.d/password-prompt
install --mode=u=rwxs,go=rx \
- --target-directory=$(PREFIX)/lib/mandos/plugins.d \
+ --target-directory=$(LIBDIR)/mandos/plugins.d \
plugins.d/mandos-client
install --mode=u=rwxs,go=rx \
- --target-directory=$(PREFIX)/lib/mandos/plugins.d \
+ --target-directory=$(LIBDIR)/mandos/plugins.d \
plugins.d/usplash
install --mode=u=rwxs,go=rx \
- --target-directory=$(PREFIX)/lib/mandos/plugins.d \
+ --target-directory=$(LIBDIR)/mandos/plugins.d \
plugins.d/splashy
install --mode=u=rwxs,go=rx \
- --target-directory=$(PREFIX)/lib/mandos/plugins.d \
+ --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 initramfs-tools-hook \
$(INITRAMFSTOOLS)/hooks/mandos
install --mode=u=rw,go=r initramfs-tools-hook-conf \
$(INITRAMFSTOOLS)/conf-hooks.d/mandos
install initramfs-tools-script \
- $(INITRAMFSTOOLS)/scripts/local-top/mandos
+ $(INITRAMFSTOOLS)/scripts/init-premount/mandos
install --mode=u=rw,go=r plugin-runner.conf $(CONFDIR)
gzip --best --to-stdout mandos-keygen.8 \
> $(MANDIR)/man8/mandos-keygen.8.gz
gzip --best --to-stdout plugin-runner.8mandos \
> $(MANDIR)/man8/plugin-runner.8mandos.gz
+ gzip --best --to-stdout plugins.d/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/mandos-client.8mandos \
- > $(MANDIR)/man8/mandos-client.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
@@ -215,7 +410,11 @@
uninstall-server:
-rm --force $(PREFIX)/sbin/mandos \
+ $(PREFIX)/sbin/mandos-ctl \
+ $(PREFIX)/sbin/mandos-monitor \
$(MANDIR)/man8/mandos.8.gz \
+ $(MANDIR)/man8/mandos-monitor.8.gz \
+ $(MANDIR)/man8/mandos-ctl.8.gz \
$(MANDIR)/man5/mandos.conf.5.gz \
$(MANDIR)/man5/mandos-clients.conf.5.gz
update-rc.d -f mandos remove
@@ -227,31 +426,37 @@
! grep --regexp='^ *[^ #].*keyscript=[^,=]*/mandos/' \
$(DESTDIR)/etc/crypttab
-rm --force $(PREFIX)/sbin/mandos-keygen \
- $(PREFIX)/lib/mandos/plugin-runner \
- $(PREFIX)/lib/mandos/plugins.d/password-prompt \
- $(PREFIX)/lib/mandos/plugins.d/mandos-client \
- $(PREFIX)/lib/mandos/plugins.d/usplash \
- $(PREFIX)/lib/mandos/plugins.d/splashy \
+ $(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/local-top/mandos \
+ $(INITRAMFSTOOLS)/scripts/init-premount/mandos \
+ $(MANDIR)/man8/mandos-keygen.8.gz \
$(MANDIR)/man8/plugin-runner.8mandos.gz \
- $(MANDIR)/man8/mandos-keygen.8.gz \
+ $(MANDIR)/man8/mandos-client.8mandos.gz
$(MANDIR)/man8/password-prompt.8mandos.gz \
- $(MANDIR)/man8/mandos-client.8mandos.gz
- if [ "$(CONFDIR)" != "$(PREFIX)/lib/mandos" ]; then \
- rm --force $(CONFDIR)/plugins.d/README; \
- fi
- -rmdir $(PREFIX)/lib/mandos/plugins.d $(CONFDIR)/plugins.d \
- $(PREFIX)/lib/mandos $(CONFDIR) $(KEYDIR)
+ $(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)
update-initramfs -k all -u
purge: purge-server purge-client
purge-server: uninstall-server
-rm --force $(CONFDIR)/mandos.conf $(CONFDIR)/clients.conf \
+ $(DESTDIR)/etc/dbus-1/system.d/mandos.conf
$(DESTDIR)/etc/default/mandos \
$(DESTDIR)/etc/init.d/mandos \
+ $(SYSTEMD)/mandos.service \
+ $(DESTDIR)/run/mandos.pid \
$(DESTDIR)/var/run/mandos.pid
-rmdir $(CONFDIR)
=== added file 'NEWS'
--- NEWS 1970-01-01 00:00:00 +0000
+++ NEWS 2014-02-16 13:12:20 +0000
@@ -0,0 +1,306 @@
+This NEWS file records noteworthy changes, very tersely.
+See the manual for detailed information.
+
+Version 1.6.4 (2014-02-16)
+* Server
+** Very minor fix to self-test code.
+
+Version 1.6.3 (2014-01-21)
+* Server
+** Add systemd support.
+** For PID file, fall back to /var/run if /run does not exist.
+* Client
+** Moved files from /usr/lib/mandos to whatever the architecture
+ specifies, like /usr/lib/x86_64-linux-gnu/mandos or
+ /usr/lib64/mandos.
+
+Version 1.6.2 (2013-10-24)
+* Server
+** PID file moved from /var/run to /run.
+** Bug fix: Handle long secrets when saving client state.
+** Bug fix: Use more magic in the GnuTLS priority string to handle
+ both old DSA/ELG 2048-bit keys and new RSA/RSA 4096-bit keys.
+* Client
+** mandos-keygen: Bug fix: now generate RSA keys which GnuTLS can use.
+ Bug fix: Output passphrase prompts even when
+ redirecting standard output.
+
+Version 1.6.1 (2013-10-13)
+* Server
+** All client options for time intervals now also take an RFC 3339
+ duration. The same for all options to mandos-ctl.
+** Bug fix: Handle fast checkers (like ":") correctly.
+** Bug fix: Don't print output from checkers when running in
+ foreground.
+** Bug fix: Do not fail when client is removed from clients.conf but
+ saved settings remain.
+** Bug fix: mandos-monitor now displays standout (reverse video) again
+ using new version of Urwid.
+** Bug fix: Make boolean options work from the config file again.
+** Bug fix: Make --no-ipv6 work again.
+** New default priority string to be slightly more compatible with
+ older versions of GnuTLS.
+* Client
+** Bug fix: Fix bashism in mandos-keygen.
+** Default key and subkey types are now RSA and RSA, respectively.
+ Also, new default key size is 4096 bits.
+
+Version 1.6.0 (2012-06-18)
+* Server
+** Takes new --foreground option
+** Init script supports new "status" action.
+* Client
+** Now uses all interfaces by default; the --interface option can
+ still be used to restrict it, and the argument to --interface (as
+ well as the $DEVICE environment variable for the network hooks) is
+ now a comma-separated list of interfaces to use.
+
+Version 1.5.5 (2012-06-01)
+* Server
+** Server takes new --socket option
+
+Version 1.5.4 (2012-05-20)
+* Server
+** Bug fix: Regression fix: Make non-zero approval timeout values work.
+** Bug fix: Regression fix: Allow changing the Timeout D-Bus property.
+** Fall back to not bind to an interface if an invalid interface name
+ is given.
+** Removed support for undocumented feature of using plain "%%s" in
+ "checker" client option.
+** Old D-Bus interface are now marked as deprecated.
+** mandos-monitor: Bug fix: show approval timers correctly.
+** mandos-ctl: Show "Extended Timeout" correctly, not as milliseconds.
+
+Version 1.5.3 (2012-01-15)
+* Server
+** Add D-Bus property se.recompile.Client.LastCheckerStatus and use it
+ in mandos-monitor.
+* Client
+** Fix bugs in the example "bridge" network hook.
+
+Version 1.5.2 (2012-01-08)
+* Server
+** Removed D-Bus signal se.recompile.Mandos.NewRequest() added in
+ 1.5.0. It was buggy and was of questionable utility.
+
+Version 1.5.1 (2012-01-01)
+* Server
+** Include intro(8mandos) manual page, missing since migration from
+ README file in version 1.4.0.
+
+Version 1.5.0 (2012-01-01)
+* Client
+** Network hooks. The Mandos client can now run custom scripts to take
+ up a network interface before the client is run. Three example
+ scripts are provided: "wireless", "openvpn", and "bridge".
+ To facilitate this, the client now prefers network interfaces which
+ are up (if any) over all other interfaces.
+* Server
+** Persistent state. Client state is now saved between server
+ restarts.
+** clients.conf file can now contain "enabled" setting for clients.
+** Bug fix: Fix rare crash bug.
+** Bug fix: Send corrent D-Bus type in PropertyChanged for
+ "ApprovalDelay", "ApprovalDuration", "Timeout", and
+ "ExtendedTimeout".
+** mandos-ctl: Bare numbers as arguments are taken to be milliseconds.
+** Bug fix: mandos-ctl --secret option now works.
+** New D-Bus signal: se.recompile.Mandos.NewRequest(s).
+
+Version 1.4.1 (2011-10-15)
+* Server
+** Make D-Bus properties settable again, and handle checkers
+ for disabled clients correctly.
+* Miscellaneous fixes to "pedantic" Lintian warnings
+
+Version 1.4.0 (2011-10-09)
+* README file migrated to manual page intro(8mandos).
+* Client:
+** Fixed warning about "rmdir: Directory not empty".
+* Server:
+** Default values changed: timeout 5 minutes, interval 2 minutes.
+** Clients gets an expiration extension when receiving a password,
+ controlled by new "extended_timeout" setting.
+** New domain name: "fukt.bsnet.se" changes to "recompile.se". This
+ also affects the D-Bus bus and interface names (old names still
+ work). Users should start using the new names immediately.
+** New D-Bus Client object properties "Expires" and "ExtendedTimeout";
+ see DBUS-API for details.
+
+Version 1.3.1 (2011-07-27)
+* Client:
+** Client now retries all Mandos servers periodically.
+** Work around Debian bug #633582 - fixes "Permission denied" problem.
+
+Version 1.3.0 (2011-03-08)
+* Server:
+** Updated for Python 2.6.
+* Client:
+** Bug fix: Make the password-prompt plugin not conflict with
+ Plymouth.
+** Bug fix: Bug fix: update initramfs also when purging package.
+
+Version 1.2.3 (2010-10-11)
+* Server:
+** Bug fix: Expose D-Bus API also in non-debug mode.
+
+Version 1.2.2 (2010-10-07)
+* Client:
+** splashy: Minor fix to compile with non-Linux kernels.
+
+Version 1.2.1 (2010-10-02)
+* Server:
+** mandos-monitor(8): Documentation bug fix: Key for removing client
+ is "R", not "r".
+
+Version 1.2 (2010-09-28)
+* Client:
+** New "plymouth" plugin to ask for a password using the Plymouth
+ graphical boot system.
+** The Mandos client now automatically chooses a network interface if
+ the DEVICE setting in /etc/initramfs-tools/initramfs.conf is set to
+ the empty string. This is also the new default instead of "eth0".
+** The Mandos client --connect option now loops indefinitely until a
+ password is received from the specified server.
+** Bug fix: Quote directory correctly in mandos-keygen with --password
+** Bug fix: don't use "echo -e" in mandos-keygen; unsupported by dash.
+* Server:
+** Terminology change: clients are now "ENABLED" or "DISABLED", not
+ "valid" or "invalid".
+** New D-Bus API; see the file "DBUS-API".
+** New control utilities using the new D-Bus API:
+ + mandos-ctl A command-line based utility
+ + mandos-monitor A text-based GUI interface
+** New feature: manual interactive approval or denying of clients on a
+ case-by-case basis.
+** New --debuglevel option to control logging
+** Will not write PID file if --debug is passed
+** Bug fix: Avoid race conditions with short "interval" values or
+ fast checkers.
+** Bug fix: Don't try to bind to a network interface when none is
+ specified
+
+Version 1.0.14 (2009-10-25)
+Enable building without -pie and -fPIE if BROKEN_PIE is set.
+
+Version 1.0.13 (2009-10-22)
+* Client
+** Security bug fix: If Mandos server is also installed, do not copy
+ its config files (with encrypted passwords) into the initrd.img-*
+ files.
+
+Version 1.0.12 (2009-09-17)
+* Client
+** Bug fix: Allow network interface renaming by "udev" by taking down
+ the network interface after using it.
+** Bug fix: User-supplied plugins are now installed correctly.
+** Bug fix: If usplash was used but the password was instead provided
+ by the Mandos server, the usplash daemon used to ignore the first
+ command passed to it. This has been fixed.
+** Bug fix: Make the "--userid" and "--groupid" options in
+ "plugin-runner.conf" work.
+* Server
+** Bug fix: Fix the LSB header in the init.d script to make dependency
+ based booting work.
+** A client receiving its password now also counts as if a checker was
+ run successfully (i.e. the timeout timer is reset).
+
+Version 1.0.11 (2009-05-23)
+* Client
+** Bug fix: Use "pkg-config" instead of old "libgnutls-config".
+
+Version 1.0.10 (2009-05-17)
+* Client
+** Security bug fix: Fix permissions on initrd.img-*.bak files when
+ upgrading from older versions.
+
+Version 1.0.9 (2009-05-17)
+* Client
+** Security bug fix: Fix permissions on initrd.img file when
+ installing new linux-image-* packages calling mkinitramfs-kpkg (all
+ version lower than 2.6.28-1-* does this).
+
+Version 1.0.8 (2009-02-25)
+* Client
+** Bug fix: Fix missing quote characters in initramfs-tools-hook.
+
+Version 1.0.7 (2009-02-24)
+* Client
+** Bug fix: Do not depend on GNU awk.
+
+Version 1.0.6 (2009-02-13)
+* Server
+** Fix bug where server would stop responding, with a zombie checker
+** Support for disabling IPv6 (only for advanced users)
+** Fix bug which made server not change group ID
+
+* Client
+** Bug fix: Fix permission for /lib64 (on relevant architechtures).
+** Add support for IPv4 addresses.
+** Add support in mandos-client for not bringing up a network
+ interface by specifying an empty string to "--interface".
+** Make password prompt on boot not be mangled by kernel log messages
+ about network interface.
+** Get network interface from initramfs.conf and/or from kernel
+ command line.
+** If set by "ip=" kernel command line, configure network on boot.
+** Support connecting directly using "mandos=connect" kernel command.
+ line option, provided network is configured using "ip=".
+** Fix bug which made plugin-runner and mandos-client not change group
+ ID.
+** Fix bug where the "--options-for" option of plugin-runner would
+ truncate the value at the first colon character.
+** Fix bug where plugin-runner would not go to fallback if all plugins
+ failed.
+** Fix bug where mandos-client would not clean temporary directory on
+ a signal or on certain file systems.
+** Bug fix: remove bashism in /bin/sh script "mandos-keygen".
+
+Version 1.0.5 (2009-01-17)
+* Client
+** Fix small memory leak in plugin-runner.
+
+Version 1.0.4 (2009-01-15)
+* Server
+** Only find matched user/group pairs when searching for suitable
+ nonprivileged user/group to switch to.
+
+* Client
+** New kernel parameter "mandos=off" makes client not run at boot.
+** Fix linking errors and compilation warnings on AMD64.
+** Parse numbers in command line options better.
+** The splashy and usplash plugins are more robust while traversing
+ /proc, and will not abort if a process suddenly disappears.
+
+Version 1.0.3 (2009-01-06)
+* Server
+** Now tries to change to user and group "_mandos" before falling back
+ to trying the old values "mandos", "nobody:nogroup", and "65534".
+** Now does not abort on startup even if no clients are defined in
+ clients.conf.
+
+* Client
+** Plugins named "*.dpkg-bak" are now ignored.
+** Hopefully fixed compilation failure on some architectures where the
+ C compiler does not recognize the "-z" option as a linker option.
+
+Version 1.0.2 (2008-10-17)
+* mandos-keygen now signs the encrypted key blobs. This signature is
+ not currently verified by mandos-client, but this may change in the
+ future.
+
+Version 1.0.1 (2008-10-07)
+* Server
+** Expand environment variables and ~user in clients.conf's "secfile"
+ The "secfile" option in /etc/mandos/clients.conf now expands
+ "~user/foo" and "$ENVVAR" strings.
+
+* Client (plugin-runner, plugins, etc.)
+** Manual pages for the usplash, splashy, and askpass-fifo plugins.
+ All plugins now have man pages.
+** More secure compilation and linking flags.
+ All programs are now compiled with "-fstack-protector-all -fPIE
+ -pie", and linked using "-z relro -pie" for additional security.
+
+* There is now a "NEWS" file (this one), giving a history of
+ noteworthy changes.
=== modified file 'README'
--- README 2008-09-17 00:34:09 +0000
+++ README 2012-01-01 20:45:53 +0000
@@ -1,152 +1,11 @@
--*- org -*-
-
-* Mandos
- - Have your cake and eat it too!
-
- You know how it is. You’ve heard of it happening. The Man comes
- and takes away your servers, your friends’ servers, the servers of
- everybody in the same hosting facility. The servers of their
- neighbors, and their neighbors’ friends. The servers of people who
- owe them money. And like *that*, they’re gone. And you doubt
- you’ll ever see them again.
-
- That is why your servers have encrypted root file systems. However,
- there’s a downside. There’s no going around it: rebooting is a
- pain. Dragging out that rarely-used keyboard and screen and
- unraveling cables behind your servers to plug them in to type in
- that password is messy, especially if you have many servers. There
- are some people who do clever things like using serial line consoles
- and daisy-chain it to the next server, and keep all the servers
- connected in a ring with serial cables, which will work, if your
- servers are physically close enough. There are also other
- out-of-band management solutions, but with *all* these, you still
- have to be on hand and manually type in the password at boot time.
- Otherwise the server just sits there, waiting for a password.
-
- Wouldn’t it be great if you could have the security of encrypted
- root file systems and still have servers that could boot up
- automatically if there was a short power outage while you were
- asleep? That you could reboot at will, without having someone run
- over to the server to type in the password?
-
- Well, with Mandos, you (almost) can! The gain in convenience will
- only be offset by a small loss in security. The setup is as
- follows:
-
- The server will still have its encrypted root file system. The
- password to this file system will be stored on another computer
- (henceforth known as the Mandos server) on the same local network.
- The password will *not* be stored in plaintext, but encrypted with
- OpenPGP. To decrypt this password, a key is needed. This key (the
- Mandos client key) will not be stored there, but back on the
- original server (henceforth known as the Mandos client) in the
- initial RAM disk image. Oh, and all network Mandos client/server
- communications will be encrypted, using TLS (SSL).
-
- So, at boot time, the Mandos client will ask for its encrypted data
- over the network, decrypt it to get the password, use it to decrypt
- the root file, and continue booting.
-
- Now, of course the initial RAM disk image is not on the encrypted
- root file system, so anyone who had physical access could take the
- Mandos client computer offline and read the disk with their own
- tools to get the authentication keys used by a client. *But*, by
- then the Mandos server should notice that the original server has
- been offline for too long, and will no longer give out the encrypted
- key. The timing here is the only real weak point, and the method,
- frequency and timeout of the server’s checking can be adjusted to
- any desired level of paranoia
-
- (The encrypted keys on the Mandos server is on its normal file
- system, so those are safe, provided the root file system of *that*
- server is encrypted.)
-
-* FAQ - couldn’t the security be defeated by...
-
-** Grabbing the Mandos client key from the initrd *really quickly*?
- This, as mentioned above, is the only real weak point. But if you
- set the timing values tight enough, this will be really difficult
- to do. An attacker would have to physically disassemble the client
- computer, extract the key from the initial RAM disk image, and then
- connect to a *still online* Mandos server to get the encrypted key,
- and do all this *before* the Mandos server timeout kicks in and the
- Mandos server refuses to give out the key to anyone.
-
- Now, as the typical procedure seems to be to barge in and turn off
- and grab *all* computers, to maybe look at them months later, this
- is not likely. If someone does that, the whole system *will* lock
- itself up completely, since Mandos servers are no longer running.
-
- For sophisticated attackers who *could* do the clever thing, *and*
- had physical access to the server for enough time, it would be
- simpler to get a key for an encrypted file system by using hardware
- memory scanners and reading it right off the memory bus.
-
-** Replay attacks?
- Nope, the network stuff is all done over TLS, which provides
- protection against that.
-
-** Man-in-the-middle?
- No. The server only gives out the passwords to clients which have
- *in the TLS handshake* proven that they do indeed hold the OpenPGP
- private key corresponding to that client.
-
-** Physically grabbing the Mandos server computer?
- You could protect *that* computer the old-fashioned way, with a
- must-type-in-the-password-at-boot method. Or you could have two
- computers be the Mandos server for each other.
-
- Multiple Mandos servers can coexist on a network without any
- trouble. They do not clash, and clients will try all available
- servers. This means that if just one reboots then the other can
- bring it back up, but if both reboots at the same time they will
- stay down until someone types in the password on one of them.
-
-** Faking ping replies?
- The default for the server is to use "fping", the replies to which
- could be faked to eliminate the timeout. But this could easily be
- changed to any shell command, with any security measures you like.
- It could, for instance, be changed to an SSH command with strict
- keychecking, which could not be faked. Or IPsec could be used for
- the ping packets, making them secure.
-
-* Security Summary
- So, in summary: The only weakness in the Mandos system is from
- people who have:
- 1. The power to come in and physically take your servers, *and*
- 2. The cunning and patience to do it carefully, one at a time, and
- *quickly*, faking Mandos client/server responses for each one
- before the timeout.
-
- While there are some who may be threatened by people who have *both*
- these attributes, they do not, probably, constitute the majority.
-
- If you *do* face such opponents, you must figure that they could
- just as well open your servers and read the file system keys right
- off the memory by running wires to the memory bus.
-
- What Mandos is designed to protect against is *not* such determined,
- focused, and competent attacks, but against the early morning knock
- on your door and the sudden absence of all the servers in your
- server room. Which it does nicely.
-
-* Copyright
-
- Copyright © 2008 Teddy Hogeborn
- 2008 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
- .
+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 2008-09-21 14:05:44 +0000
+++ TODO 2014-01-20 20:54:47 +0000
@@ -1,71 +1,134 @@
-*- org -*-
-* DONE plugin-runner
+* GIT
+** General: [[https://www.atlassian.com/git/workflows][Git Workflows]], [[http://gitimmersion.com/][Git Immersion]], [[https://news.ycombinator.com/item?id=7036628][Simple git workflow is simple]]
+** Intro: [[http://www.eyrie.org/~eagle/notes/debian/git.html#combine][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.python.org/dev/peps/pep-0374/][PEP 374 - Choosing a distributed VCS for the Python project]]
+ [[http://www.emacswiki.org/emacs/GitForEmacsDevs][Git For Emacs Devs]]
+
+* [[http://www.undeadly.org/cgi?action=article&sid=20110530221728][OpenBSD]]
+
+* Testing
+** python-nemu
+
+* mandos-applet
* mandos-client
-** TODO [#B] Temporarily lower kernel log level
- for less printouts during sucessfull boot.
-** TODO [#C] IPv4 support
-
-* DONE password-prompt
+** 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.
+
+* 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()
* mandos (server)
-** TODO [#B] Log level :bugs:
-** TODO /etc/mandos/clients.d/*.conf
- Watch this directory and add/remove/update clients?
-** TODO config for TXT record
-** TODO [#B] Run-time communication with server :bugs:
- Probably using D-Bus
- See also [[*Mandos-tools]]
-** TODO Implement --foreground :bugs:
- [[info:standards:Option%20Table][Table of Long Options]]
-** TODO Implement --socket
- [[info:standards:Option%20Table][Table of Long Options]]
-** TODO Date+time on console log messages :bugs:
- Is this the default?
-** TODO delete hook when clients fall out by timeout
-
-* Mandos-tools/utilities
- All of this probably using D-Bus
-** TODO List clients
-** TODO Disable client
-** TODO Enable client
-** TODO Reset timer
-
-* Man pages
-** TODO Use xinclude for all common sections
- Like authors, etc.
-
-
-* Installer
-** Client-side
-*** mandos-keygen
-**** TODO "--secfile" option
- Using the "secfile" option instead of "secret"
-**** TODO [#B] "--test" option
- For testing decryption before rebooting.
-
-
-* [#A] Package
+** 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
** /usr/share/initramfs-tools/hooks/mandos
-*** TODO Do not install in initrd.img if configured not to.
- Use "/etc/initramfs-tools/conf.d/mandos"? Definitely a debconf
- question.
-** TODO /etc/bash_completion.d/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
From XML sources directly?
-** TODO unperish
-
-* TODO Web site
-** DONE http://www.fukt.bsnet.se/mandos
- Redirects to the wiki page
-** DONE http://wiki.fukt.bsnet.se/wiki/Mandos
-
-* Mailing list
-** DONE mandos-dev
-*** TODO http://gmane.org/subscribe.php
-
-* TODO Announce project on Usenet
- [[news:comp.os.linux.announce]]
+
+* 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
#+STARTUP: showall
=== modified file 'clients.conf'
--- clients.conf 2008-09-21 13:42:34 +0000
+++ clients.conf 2012-06-23 00:58:49 +0000
@@ -2,19 +2,36 @@
# values, so uncomment and change them if you want different ones.
[DEFAULT]
-# How long until a client is considered invalid - that is, ineligible
-# to get the data this server holds.
-;timeout = 1h
+# How long until a client is disabled and not be allowed to get the
+# data this server holds.
+;timeout = PT5M
# 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.
-;interval = 5m
+# 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
# What command to run as "the checker".
-;checker = fping -q -- %(host)s
+;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
;####
@@ -61,5 +78,10 @@
;host = 192.0.2.3
;
;# Parameters from the [DEFAULT] section can be overridden per client.
-;interval = 5m
+;interval = PT1M
+;
+;# This client requires manual approval before it receives its secret.
+;approved_by_default = False
+;# Require approval within 30 seconds.
+;approval_delay = PT30S
;####
=== added file 'common.ent'
--- common.ent 1970-01-01 00:00:00 +0000
+++ common.ent 2014-02-16 13:12:20 +0000
@@ -0,0 +1,3 @@
+
+
+
=== added file 'dbus-mandos.conf'
--- dbus-mandos.conf 1970-01-01 00:00:00 +0000
+++ dbus-mandos.conf 2011-10-02 19:18:24 +0000
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
=== modified file 'debian/changelog'
--- debian/changelog 2008-09-17 00:34:09 +0000
+++ debian/changelog 2014-02-16 13:12:20 +0000
@@ -1,5 +1,408 @@
-mandos (1.0) unstable; urgency=low
-
- * Initial Release.
-
- -- Mandos Maintainers Sun, 07 Sep 2008 11:55:51 +0200
+mandos (1.6.4-1) unstable; urgency=medium
+
+ * New upstream release.
+ * debian/control (Build-Depends): Add Python dependencies to
+ successfully run self-tests.
+ * debian/copyright: GPLv3 now has its own license file - use it.
+ * debian/watch: Set PGP signature URL.
+
+ -- Teddy Hogeborn Sun, 16 Feb 2014 14:09:25 +0100
+
+mandos (1.6.3-1) unstable; urgency=low
+
+ * New upstream release.
+ * debian/control (Build-Depends): Added "systemd".
+ * debian/mandos.dirs (lib/systemd/system): New.
+ * debian/mandos-client.README.Debian: Refer to architecture libdir.
+ * debian/control (mandos/Depends): Add "avahi-daemon (>= 0.6.31-3) |
+ systemd-sysv".
+ * debian/mandos.postinst: If avahi-daemon is version 0.6.31-2 or older,
+ edit /etc/init.d script headers Required-Start
+ and Required-Stop to have "avahi" instead of
+ "avahi-daemon", before insserv(8) sees it.
+ * debian/mandos-client.lintian-overrides: Libdir changes.
+ * debian/rules (override_dh_fixperms): - '' -
+
+ -- Teddy Hogeborn Tue, 21 Jan 2014 22:01:30 +0100
+
+mandos (1.6.2-1) unstable; urgency=low
+
+ * New upstream release.
+ * debian/compat: Changed to "9".
+ * debian/control (Build-Depends): Changed debhelper version to (>= 9).
+ (Standards-Version): Updated to "3.9.4".
+ (DM-Upload-Allowed): Removed.
+ (mandos/Depends): Add "initscripts (>= 2.88dsf-13.3)" to be able to
+ use the "/run" directory (for mandos.pid).
+ * debian/copyright (Copyright): Update year.
+ * Fix "Mandos/gnutls fails to establish connection, "an algorithm that
+ is not enabled was negotiated"" fixed by upstream. (Closes: #702120)
+
+ -- Teddy Hogeborn Thu, 24 Oct 2013 22:33:40 +0200
+
+mandos (1.6.1-1) unstable; urgency=low
+
+ * New upstream release.
+ * debian/control (mandos/Depends): No longer depends on
+ python-gnupginterface, but does
+ depend on gnupg (<< 2).
+ (Build-Depends): Depend on debhelper 8.9.7 for using "override-*-arch"
+ and "override-*-indep" targets in debian/rules.
+ * debian/mandos-client.README: Update Linux documentation link.
+ * debian/rules: Completely rewritten to use debhelper v7.
+ * initramfs-tools-hook: Bug fix: Make sure the right version of GnuPG is
+ copied into the initramfs image. Always assume that GPGME is used to
+ avoid searching for it since the path might not be /usr/lib. Thanks
+ to Félix Sipma for the initial bug report,
+ and also thanks to Dick Middleton for some more
+ debugging. (Closes: #721903)
+ * Fix "bashism in /bin/sh script" fixed by upstream. (Closes: #690639)
+
+ -- Teddy Hogeborn Sun, 13 Oct 2013 19:03:23 +0200
+
+mandos (1.6.0-1) unstable; urgency=low
+
+ * New upstream release.
+ * debian/copyright (Copyright): Join the two lines to a single line.
+ * debian/mandos-client.README.Debian: Update to refer to the new
+ location of the example network hooks, and the new feature of using
+ all network interfaces.
+ * debian/mandos-client.docs (network-hooks.d): Removed.
+ * debian/mandos-client.examples (network-hooks.d): New.
+ * debian/rules (binary-common): Added "dh_installexamples".
+ (binary-common/dh_fixperms): Exclude new location of
+ "network-hooks.d".
+
+ -- Teddy Hogeborn Mon, 18 Jun 2012 00:15:23 +0200
+
+mandos (1.5.5-1) unstable; urgency=low
+
+ * New upstream release.
+ * debian/copyright (Format): Updated to
+ "http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/".
+ * debian/control (Build-Depends): Removed "man, locales-all".
+
+ -- Teddy Hogeborn Fri, 01 Jun 2012 20:30:41 +0200
+
+mandos (1.5.4-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Teddy Hogeborn Sun, 20 May 2012 15:38:34 +0200
+
+mandos (1.5.3-1.2) unstable; urgency=low
+
+ * Non-maintainer upload.
+ * Set Architecture to linux-any. (Closes: #647670)
+
+ -- Robert Millan Sun, 22 Apr 2012 16:22:01 +0200
+
+mandos (1.5.3-1.1) unstable; urgency=low
+
+ * Non-maintainer upload.
+ * Fix "mandos FTBFS on buildds": add build-dependency on locales-all and
+ pass LC_ALL to dh_auto_build to make sure we have and use the en_US.UTF-8
+ locale for manpage creation.
+ (Closes: #656178)
+
+ -- gregor herrmann Tue, 31 Jan 2012 17:56:05 +0100
+
+mandos (1.5.3-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Teddy Hogeborn Sun, 15 Jan 2012 22:05:54 +0100
+
+mandos (1.5.2-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Teddy Hogeborn Sun, 08 Jan 2012 11:17:20 +0100
+
+mandos (1.5.1-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Teddy Hogeborn Sun, 01 Jan 2012 21:53:31 +0100
+
+mandos (1.5.0-1) unstable; urgency=low
+
+ * New upstream release.
+ * debian/control (mandos-client/Depends): Added "initramfs-tools".
+ * debian/mandos-client.README.Debian: Corrected mail address and adjust
+ wording.
+ * debian/rules (binary-common): Exclude new nework-hooks.d directory
+ from dh_fixperms.
+ * debian/mandos-client.README.Debian: Document network hook facility.
+ * debian/mandos-client.docs (network-hooks.d): Added.
+ * debian/mandos.dirs (var/lib/mandos): Added.
+ * debian/mandos.postinst: Fix ownership of /var/lib/mandos.
+ * debian/control (mandos/Depends): Added "python-gnupginterface".
+
+ -- Teddy Hogeborn Sun, 01 Jan 2012 05:58:11 +0100
+
+mandos (1.4.1-1) unstable; urgency=low
+
+ * New upstream release.
+ * debian/control (Build-Depends): Added "man".
+ * debian/control (Conflicts): Changed to "Breaks:".
+ * debian/copyright: Updated format.
+ * debian/mandos-client.postinst: Use "set -e" instead of "#!/bin/sh -e".
+ * debian/mandos-client.postrm: - '' -
+ * debian/mandos.postinst: - '' -
+ * debian/mandos.prerm: Consistent magic.
+
+ -- Björn Påhlsson Sat, 15 Oct 2011 18:18:52 +0200
+
+mandos (1.4.0-1) unstable; urgency=low
+
+ * New upstream release.
+ * Fix "FTBFS with binutils-gold": Added "-Xlinker --as-needed" to
+ LDFLAGS in Makefile. (Closes: #632145)
+ * Fix "/run transition: uses obsolete /dev/.initramfs": Try both old and
+ new PID file locations. (Closes: #643554)
+ * debian/source/local-options: New; contains "--single-debian-patch".
+ * debian/control (Standards-Version): Upgraded to "3.9.2".
+ (DM-Upload-Allowed): New; set to "yes".
+ * debian/control: Changed domain from "fukt.bsnet.se" to "recompile.se".
+ * debian/copyright: - '' -
+ * debian/mandos-client.README.Debian: - '' -
+ * debian/mandos.README.Debian: - '' -
+ * debian/watch: - '' -
+ * debian/control (mandos/Description): Fix language to placate lintian.
+
+ -- Teddy Hogeborn Sun, 09 Oct 2011 19:15:08 +0200
+
+mandos (1.3.1-1) unstable; urgency=low
+
+ * New upstream release.
+ * Conflict with correct version of dropbear.
+ * New version uses argparse; depend on python (<=2.7) | python-argparse.
+
+ -- Teddy Hogeborn Wed, 27 Jul 2011 19:47:17 +0200
+
+mandos (1.3.0-1) unstable; urgency=low
+
+ * New upstream release.
+ * debian/control (mandos): Depend on Python 2.6, remove dependency on
+ python-multiprocessing.
+ (mandos-client): Conflict with dropbear (<< 0.52-5).
+ * debian/mandos-client.postrm (purge): Bug fix: update initramfs also on
+ purge.
+ * debian/mandos-client.lintian-overrides: Added plugins.d/plymouth.
+
+ -- Teddy Hogeborn Tue, 08 Mar 2011 20:22:57 +0100
+
+mandos (1.2.3-1) experimental; urgency=low
+
+ * New upstream release.
+
+ -- Teddy Hogeborn Mon, 11 Oct 2010 19:37:31 +0200
+
+mandos (1.2.2-1) experimental; urgency=low
+
+ * New upstream release.
+ * plugins.d/splashy.c: Only use ELIBBAD if defined. (Closes: #599256)
+
+ -- Teddy Hogeborn Thu, 07 Oct 2010 20:27:54 +0200
+
+mandos (1.2.1-3) experimental; urgency=low
+
+ * debian/changelog: Include entry for NMU of version 1.0.14-1.1.
+
+ -- Teddy Hogeborn Tue, 05 Oct 2010 20:58:38 +0200
+
+mandos (1.2.1-2) unstable; urgency=low
+
+ * debian/source/format: New; contains "3.0 (quilt)". Really.
+
+ -- Björn Påhlsson Sat, 02 Oct 2010 19:46:59 +0200
+
+mandos (1.2.1-1) unstable; urgency=low
+
+ * New upstream release.
+ * debian/source/format: New; contains "3.0 (quilt)".
+
+ -- Björn Påhlsson Sat, 02 Oct 2010 19:03:58 +0200
+
+mandos (1.2-1) unstable; urgency=low
+
+ * New upstream release.
+ * Makefile (LINK_FORTIFY_LD): Remove "-fPIE". (Closes: #557076)
+ * debian/control: Add gnupg dependency to "mandos-client" and removed it
+ from "mandos". Added dependency on "python-urwid" "mandos" since the
+ new "mandos-monitor" utility needs it, and on "python (>=2.6) |
+ python-multiprocessing" since the Mandos server now uses it.
+ * debian/rules: Set BROKEN_PIE on mips and mipsel if a known buggy
+ version of binutils is used.
+ * debian/mandos.docs: Also install "/usr/share/doc/mandos/DBUS-API".
+ * debian/mandos.dirs: Added "etc/dbus-1/system.d".
+ * debian/mandos-client.README.Debian: Update info about DEVICE setting
+ of initramfs.conf.
+ * debian/mandos-client.README.Debian: Remove warning about --connect not
+ looping, since it now does.
+
+ -- Teddy Hogeborn Tue, 28 Sep 2010 20:46:11 +0200
+
+mandos (1.0.14-1.1) unstable; urgency=low
+
+ * Non-maintainer upload.
+ * Rebuild against libavahi-core-dev (>= 0.6.26-1).
+
+ -- Michael Biebl Mon, 12 Jul 2010 16:34:34 +0200
+
+mandos (1.0.14-1) unstable; urgency=low (HIGH on mips and mipsel)
+
+ * New upstream release.
+ * debian/rules: Build with BROKEN_PIE set on mips and mipsel
+ architectures - fixes FTBFS there.
+
+ -- Teddy Hogeborn Sun, 25 Oct 2009 20:10:09 +0100
+
+mandos (1.0.13-1) unstable; urgency=high
+
+ * New upstream release.
+ * Do not copy unnecessary files to initrd (Closes: #551907)
+
+ -- Teddy Hogeborn Thu, 22 Oct 2009 00:53:21 +0200
+
+mandos (1.0.12-1) unstable; urgency=low
+
+ * New upstream release.
+ * init.d-mandos: Correct dependencies (Closes: #546928)
+ * debian/control (Standards-Version): Changed to "3.8.3".
+ * debian/mandos-client.README.Debian: Improved wording and formatting.
+ Updated location of nfsroot.txt.
+ * debian/mandos.README.Debian: Improved wording and formatting.
+ * debian/mandos-client.postinst (configure): Don't look for user and
+ group with the old name if upgrading from a new enough version.
+ * debian/mandos.postinst (configure): - '' -
+ * debian/mandos-client.README.Debian: Added text about non-usability of
+ pseudo-network interfaces.
+
+ -- Teddy Hogeborn Thu, 17 Sep 2009 15:03:59 +0200
+
+mandos (1.0.11-1) unstable; urgency=low
+
+ * debian/control (Standards-Version): Changed to "3.8.1".
+ * Makefile (GNUTLS_CFLAGS, GNUTLS_CFLAGS): Use "pkg-config" instead of
+ the old "libgnutls-config" script. (Closes: #529836)
+
+ -- Teddy Hogeborn Sat, 23 May 2009 07:12:20 +0200
+
+mandos (1.0.10-1) unstable; urgency=low
+
+ * New upstream release.
+ * debian/mandos-client.postinst (update_initramfs): Fix permissions of
+ old initrd.img-*.bak files.
+
+ -- Teddy Hogeborn Sun, 17 May 2009 04:56:35 +0200
+
+mandos (1.0.9-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Teddy Hogeborn Sun, 17 May 2009 02:59:45 +0200
+
+mandos (1.0.8-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Teddy Hogeborn Wed, 25 Feb 2009 02:26:57 +0100
+
+mandos (1.0.7-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Teddy Hogeborn Tue, 24 Feb 2009 12:58:06 +0100
+
+mandos (1.0.6-1) unstable; urgency=low
+
+ * New upstream release.
+ * debian/mandos-client.postinst: Converted to Bourne shell. Also
+ minor message change.
+ * debian/mandos-client.postrm: Minor message change.
+ * debian/mandos.postinst: Converted to Bourne shell. Also minor
+ message change.
+ * debian/mandos.prerm: Minor message change.
+ * debian/rules (install-indep): Removed "--no-start" from
+ dh_installinit.
+ * debian/mandos-client.lintian-overrides: Remove obsolete override for
+ unbreakable line in plugin-runner manual page.
+ * debian/control (mandos/Depends): Added "python-gobject".
+ * debian/mandos-client.dirs: Change
+ "usr/share/initramfs-tools/scripts/local-top" to
+ "usr/share/initramfs-tools/scripts/init-premount".
+ * debian/mandos-client.README.Debian: Add reference to initramfs.conf
+ and nfsroot.txt. New section about the new non-local connection
+ feature.
+
+ -- Teddy Hogeborn Fri, 13 Feb 2009 09:27:25 +0100
+
+mandos (1.0.5-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Teddy Hogeborn Sat, 17 Jan 2009 02:26:00 +0100
+
+mandos (1.0.4-1) unstable; urgency=low
+
+ * New upstream release.
+ * debian/watch: New file.
+ * debian/mandos-client.README.Debian: Document new "mandos=off" kernel
+ parameter.
+
+ -- Teddy Hogeborn Thu, 15 Jan 2009 05:49:22 +0100
+
+mandos (1.0.3-2) unstable; urgency=low
+
+ * Removed some now-unused debconf files.
+ * Changed postinst scripts to not source debconf/confmodule.
+ * Removed po-debconf from build-depends.
+
+ -- Teddy Hogeborn Tue, 06 Jan 2009 21:28:20 +0100
+
+mandos (1.0.3-1) unstable; urgency=low
+
+ * New upstream release.
+ * Add -Xlinker to linker flags to fix FTBFS for some architectures.
+ Thanks to Thiemo Seufer for the report and
+ fix. (Closes: #509398)
+ * Remove debconf use altogether, thereby stopping debconf abuse. Thanks
+ to Christian Perrier . (Closes: #509653)
+ * Add NEWS file to /usr/share/doc directories.
+ * Use and create "_mandos" user+group. Rename old user+group created by
+ older versions of this package.
+ * Fix manual pages by adding build-depend on "docbook-xml".
+
+ -- Teddy Hogeborn Tue, 06 Jan 2009 01:21:20 +0100
+
+mandos (1.0.2-1) unstable; urgency=low
+
+ * New upstream release.
+ * debian/copyright: Rewritten to conform to
+ .
+
+ -- Teddy Hogeborn Fri, 17 Oct 2008 20:42:12 +0200
+
+mandos (1.0.1-1) unstable; urgency=low
+
+ * New upstream release.
+ * Separate /usr/share/doc/mandos-client/README.Debian into sections with
+ headlines. Add instructions on how to test the server and verify the
+ password.
+
+ -- Teddy Hogeborn Tue, 07 Oct 2008 23:07:23 +0200
+
+mandos (1.0-2) unstable; urgency=low
+
+ * Added comments in debian/*.lintian-overrides files. Added Debian
+ revison number to version number.
+
+ -- Teddy Hogeborn Wed, 01 Oct 2008 17:23:35 +0200
+
+mandos (1.0-1) unstable; urgency=low
+
+ * Initial Release. (Closes: #500727).
+
+ -- Teddy Hogeborn Tue, 30 Sep 2008 21:58:43 +0200
=== modified file 'debian/compat'
--- debian/compat 2008-09-17 00:34:09 +0000
+++ debian/compat 2013-10-20 15:25:09 +0000
@@ -1,1 +1,1 @@
-7
+9
=== modified file 'debian/control'
--- debian/control 2008-09-21 13:42:34 +0000
+++ debian/control 2014-02-16 02:42:42 +0000
@@ -1,20 +1,27 @@
Source: mandos
Section: admin
Priority: extra
-Maintainer: Mandos Maintainers
-Build-Depends: debhelper (>= 7), docbook-xsl, libavahi-core-dev,
- libgpgme11-dev, libgnutls-dev, xsltproc, po-debconf,
- pkg-config
-Standards-Version: 3.8.0
-Vcs-Bzr: ftp://anonymous@ftp.fukt.bsnet.se/pub/mandos/latest
-Homepage: http://www.fukt.bsnet.se/mandos
+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, python (>=2.6), python-gnutls, python-dbus,
+ python-avahi, python-gobject, python (>=2.7) | python-argparse
+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.5), python-gnutls, python-dbus,
- python-avahi, avahi-daemon, gnupg (< 2), adduser
+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: a server giving encrypted passwords to Mandos clients
+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.
@@ -30,8 +37,10 @@
whereupon the computers can continue booting normally.
Package: mandos-client
-Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, cryptsetup
+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
=== modified file 'debian/copyright'
--- debian/copyright 2008-09-17 00:34:09 +0000
+++ debian/copyright 2014-02-16 02:42:42 +0000
@@ -1,27 +1,25 @@
-Authors: Teddy Hogeborn, Björn Påhlsson
-
-Homepage:
-
-Copyright:
-
- Copyright © 2008 Teddy Hogeborn
- 2008 Björn Påhlsson
-
-License:
-
+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".
+ .
+ On Debian systems, the complete text of the GNU General Public
+ License can be found in "/usr/share/common-licenses/GPL-3".
=== modified file 'debian/mandos-client.README.Debian'
--- debian/mandos-client.README.Debian 2008-09-19 20:54:58 +0000
+++ debian/mandos-client.README.Debian 2013-10-28 10:04:05 +0000
@@ -1,23 +1,94 @@
-A client key has been automatically created in /etc/keys/mandos. The
-next step is to run "mandos-keygen --password" to get a config file
-stanza to copy and paste into /etc/mandos/clients.conf on the Mandos
-server.
-
-Also, if some other network interface than "eth0" is used, it will be
-necessary to edit /etc/mandos/plugin-runner.conf to uncomment and
-change the line there. If this file is changed, it will be necessary
-to update the initrd image by doing "update-initramfs -k all -u".
-
-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
-
-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.
-
- -- Teddy Hogeborn , Fri, 19 Sep 2008 22:50:16 +0200
+* 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.config'
--- debian/mandos-client.config 2008-09-21 04:22:50 +0000
+++ debian/mandos-client.config 1970-01-01 00:00:00 +0000
@@ -1,27 +0,0 @@
-#! /bin/sh
-#
-# config Mandos Debconf configuration.
-#
-
-# Source debconf library.
-. /usr/share/debconf/confmodule
-if ! db_version 2.0; then
- echo "mandos.config: need DebConf 2.0 or later"
- exit 1
-fi
-
-set -e
-umask 022
-
-# Now, interaction. Batch it in case any front ends can use this.
-db_beginblock
-
-# If this is a first time install then prompt
-if [ "$1" = "configure" -a "$2" != "" ]; then
- db_input high mandos-client/not-yet-configured || true
-fi
-
-db_endblock
-db_go || true
-
-exit 0
=== modified file 'debian/mandos-client.dirs'
--- debian/mandos-client.dirs 2008-09-17 00:34:09 +0000
+++ debian/mandos-client.dirs 2009-02-07 04:50:39 +0000
@@ -2,4 +2,4 @@
usr/sbin
usr/share/initramfs-tools/hooks
usr/share/initramfs-tools/conf-hooks.d
-usr/share/initramfs-tools/scripts/local-top
+usr/share/initramfs-tools/scripts/init-premount
=== modified file 'debian/mandos-client.docs'
--- debian/mandos-client.docs 2008-09-19 13:50:22 +0000
+++ debian/mandos-client.docs 2012-06-01 21:48:12 +0000
@@ -1,2 +1,3 @@
+NEWS
README
TODO
=== added file 'debian/mandos-client.examples'
--- debian/mandos-client.examples 1970-01-01 00:00:00 +0000
+++ debian/mandos-client.examples 2012-06-01 21:48:12 +0000
@@ -0,0 +1,1 @@
+network-hooks.d
=== modified file 'debian/mandos-client.lintian-overrides'
--- debian/mandos-client.lintian-overrides 2008-09-26 19:47:21 +0000
+++ debian/mandos-client.lintian-overrides 2014-01-20 21:50:11 +0000
@@ -1,7 +1,28 @@
-mandos-client binary: manpage-has-errors-from-man usr/share/man/man8/plugin-runner.8mandos.gz 297: warning [p 4, 5.8i]: can't break line
+# This directory contains secret client key files.
+#
mandos-client binary: non-standard-dir-perm etc/keys/mandos/ 0700 != 0755
-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: non-standard-dir-perm usr/lib/mandos/plugins.d/ 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
=== modified file 'debian/mandos-client.postinst'
--- debian/mandos-client.postinst 2008-09-26 04:54:35 +0000
+++ debian/mandos-client.postinst 2011-10-10 20:29:58 +0000
@@ -1,4 +1,4 @@
-#!/bin/bash -e
+#!/bin/sh
# This script can be called in the following ways:
#
# After the package was installed:
@@ -15,7 +15,7 @@
# If prerm fails during replacement due to conflict:
# abort-remove in-favour
-. /usr/share/debconf/confmodule
+set -e
# Update the initial RAM file system image
update_initramfs()
@@ -23,14 +23,32 @@
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(){
- if ! getent passwd mandos >/dev/null; then
- adduser --disabled-password --quiet --system \
- --home /nonexistent --no-create-home \
- --gecos "Mandos password system" --group mandos
+ # 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
}
@@ -47,15 +65,15 @@
case "$1" in
configure)
- add_mandos_user
- create_key
- update_initramfs
+ add_mandos_user "$@"
+ create_key "$@"
+ update_initramfs "$@"
;;
abort-upgrade|abort-deconfigure|abort-remove)
;;
*)
- echo "$0 called with unknown argument \`$1'" 1>&2
+ echo "$0 called with unknown argument '$1'" 1>&2
exit 1
;;
esac
=== modified file 'debian/mandos-client.postrm'
--- debian/mandos-client.postrm 2008-09-19 20:54:58 +0000
+++ debian/mandos-client.postrm 2011-10-10 20:29:58 +0000
@@ -1,4 +1,4 @@
-#!/bin/sh -e
+#!/bin/sh
# This script can be called in the following ways:
#
# After the package was removed:
@@ -26,6 +26,7 @@
# If preinst fails during upgrade:
# abort-upgrade
+set -e
# Update the initial RAM file system image
update_initramfs()
@@ -45,12 +46,13 @@
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
+ echo "$0 called with unknown argument '$1'" 1>&2
exit 1
;;
esac
=== removed file 'debian/mandos-client.templates'
--- debian/mandos-client.templates 2008-09-21 04:22:50 +0000
+++ debian/mandos-client.templates 1970-01-01 00:00:00 +0000
@@ -1,8 +0,0 @@
-Template: mandos-client/not-yet-configured
-Type: note
-_Description: Your system needs more configuration[ mandos-client]
- Your system can not function as a Mandos client until a
- password for this client has been added to the
- configuration on the Mandos server. Please read
- /usr/share/doc/mandos-client/README.Debian.gz to find out
- how.
=== modified file 'debian/mandos.README.Debian'
--- debian/mandos.README.Debian 2008-09-21 04:22:50 +0000
+++ debian/mandos.README.Debian 2011-10-05 16:00:56 +0000
@@ -1,7 +1,10 @@
-The Mandos server cannot run without at least one configured client in
+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 run "mandos-keygen --password" there
-to get a config file stanza. Append that to /etc/mandos/clients.conf
-on the Mandos server.
-
- -- Teddy Hogeborn , Sat, 20 Sep 2008 21:21:19 +0200
+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.config'
--- debian/mandos.config 2008-09-21 04:22:50 +0000
+++ debian/mandos.config 1970-01-01 00:00:00 +0000
@@ -1,27 +0,0 @@
-#! /bin/sh
-#
-# config Mandos Debconf configuration.
-#
-
-# Source debconf library.
-. /usr/share/debconf/confmodule
-if ! db_version 2.0; then
- echo "mandos.config: need DebConf 2.0 or later"
- exit 1
-fi
-
-set -e
-umask 022
-
-# Now, interaction. Batch it in case any front ends can use this.
-db_beginblock
-
-# If this is a first time install then prompt
-if [ "$1" = "configure" -a "$2" != "" ]; then
- db_input high mandos/not-yet-configured || true
-fi
-
-db_endblock
-db_go || true
-
-exit 0
=== modified file 'debian/mandos.dirs'
--- debian/mandos.dirs 2008-09-17 00:34:09 +0000
+++ debian/mandos.dirs 2013-10-27 17:42:23 +0000
@@ -2,4 +2,7 @@
usr/share/man/man8
etc/init.d
etc/default
+etc/dbus-1/system.d
usr/sbin
+var/lib/mandos
+lib/systemd/system
=== modified file 'debian/mandos.docs'
--- debian/mandos.docs 2008-09-19 13:50:22 +0000
+++ debian/mandos.docs 2010-09-12 03:00:40 +0000
@@ -1,2 +1,4 @@
+NEWS
README
TODO
+DBUS-API
=== modified file 'debian/mandos.lintian-overrides'
--- debian/mandos.lintian-overrides 2008-09-17 00:34:09 +0000
+++ debian/mandos.lintian-overrides 2008-10-01 15:29:01 +0000
@@ -1,1 +1,4 @@
+# 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
=== modified file 'debian/mandos.postinst'
--- debian/mandos.postinst 2008-09-26 04:54:35 +0000
+++ debian/mandos.postinst 2014-01-06 17:22:30 +0000
@@ -1,4 +1,4 @@
-#!/bin/bash -e
+#!/bin/sh
# This script can be called in the following ways:
#
# After the package was installed:
@@ -15,26 +15,48 @@
# If prerm fails during replacement due to conflict:
# abort-remove in-favour
-. /usr/share/debconf/confmodule
+set -e
case "$1" in
configure)
- if ! getent passwd mandos >/dev/null; then
- adduser --disabled-password --quiet --system \
- --home /nonexistent --no-create-home \
- --gecos "Mandos password system" --group mandos
- fi
+ # 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
+ 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
=== modified file 'debian/mandos.prerm'
--- debian/mandos.prerm 2008-09-21 13:42:34 +0000
+++ debian/mandos.prerm 2011-10-10 20:29:58 +0000
@@ -1,4 +1,4 @@
-#! /bin/sh
+#!/bin/sh
# prerm script for mandos
#
# see: dh_installdeb(1)
@@ -6,12 +6,12 @@
set -e
# summary of how this script can be called:
-# * `remove'
-# * `upgrade'
-# * `failed-upgrade'
-# * `remove' `in-favour'
-# * `deconfigure' `in-favour'
-# `removing'
+# * 'remove'
+# * 'upgrade'
+# * 'failed-upgrade'
+# * 'remove' 'in-favour'
+# * 'deconfigure' 'in-favour'
+# 'removing'
#
# for details, see /usr/share/doc/packaging-manual/
@@ -28,7 +28,7 @@
upgrade|failed-upgrade)
;;
*)
- echo "prerm called with unknown argument \`$1'" >&2
+ echo "prerm called with unknown argument '$1'" >&2
exit 0
;;
esac
=== removed file 'debian/mandos.templates'
--- debian/mandos.templates 2008-09-21 04:22:50 +0000
+++ debian/mandos.templates 1970-01-01 00:00:00 +0000
@@ -1,9 +0,0 @@
-Template: mandos/not-yet-configured
-Type: note
-_Description: Your system needs more configuration[ mandos]
- Your system has not yet been completely configured as a
- Mandos server - clients need to be added to to
- /etc/mandos/clients.conf. Please read
- /usr/share/doc/mandos/README.Debian.gz to find out how.
- .
- (The server has not been started.)
=== removed file 'debian/po/POTFILES.in'
--- debian/po/POTFILES.in 2008-09-21 04:22:50 +0000
+++ debian/po/POTFILES.in 1970-01-01 00:00:00 +0000
@@ -1,2 +0,0 @@
-[type: gettext/rfc822deb] mandos.templates
-[type: gettext/rfc822deb] mandos-client.templates
=== removed file 'debian/po/sv.po'
--- debian/po/sv.po 2008-09-21 04:22:50 +0000
+++ debian/po/sv.po 1970-01-01 00:00:00 +0000
@@ -1,66 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the mandos package.
-# FIRST AUTHOR , YEAR.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: 1.0\n"
-"Report-Msgid-Bugs-To: mandos@packages.debian.org\n"
-"POT-Creation-Date: 2008-09-20 23:01+0200\n"
-"PO-Revision-Date: 2008-09-21 06:01+0200\n"
-"Last-Translator: Teddy Hogeborn \n"
-"Language-Team: Swedish \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#. Type: note
-#. Description
-#: ../mandos.templates:1001
-msgid "Your system needs more configuration[ mandos]"
-msgstr "Ditt system behöver ytterligare konfigurering"
-
-#. Type: note
-#. Description
-#: ../mandos.templates:1001
-#| msgid ""
-#| "Your system has not yet been completely configured as a Mandos server - "
-#| "you need to setup /etc/mandos/clients.conf. Please read /usr/share/doc/"
-#| "mandos/README.Debian.gz to find out how."
-msgid ""
-"Your system has not yet been completely configured as a Mandos server - "
-"clients need to be added to to /etc/mandos/clients.conf. Please read /usr/"
-"share/doc/mandos/README.Debian.gz to find out how."
-msgstr ""
-"Ditt system är inte helt inställd som en Mandos-server än -\n"
-"det behövs läggas till klienter i Mandos-serverns\n"
-"inställingar. Var vänlig läs\n"
-"/usr/share/doc/mandos-client/README.Debian.gz för att få\n"
-"veta hur."
-
-#. Type: note
-#. Description
-#: ../mandos.templates:1001
-msgid "(The server has not been started.)"
-msgstr "(Servern har inte startats.)"
-
-#. Type: note
-#. Description
-#: ../mandos-client.templates:1001
-msgid "Your system needs more configuration[ mandos-client]"
-msgstr "Ditt system behöver ytterligare konfigurering"
-
-#. Type: note
-#. Description
-#: ../mandos-client.templates:1001
-msgid ""
-"Your system can not function as a Mandos client until a password for this "
-"client has been added to the configuration on the Mandos server. Please "
-"read /usr/share/doc/mandos-client/README.Debian.gz to find out how."
-msgstr ""
-"Ditt system kan inte fungera som en Mandos-klient förrän\n"
-"ett krypterat lösenord har lagts till i Mandos-serverns\n"
-"inställingar. Var vänlig läs\n"
-"/usr/share/doc/mandos-client/README.Debian.gz för att få\n"
-"veta hur."
=== modified file 'debian/rules'
--- debian/rules 2008-09-21 12:04:02 +0000
+++ debian/rules 2014-01-20 21:50:11 +0000
@@ -1,93 +1,29 @@
#!/usr/bin/make -f
-# Sample debian/rules that uses debhelper.
-#
-# This file was originally written by Joey Hess and Craig Small.
-# As a special exception, when this file is copied by dh-make into a
-# dh-make output file, you may use that output file without restriction.
-# This special exception was added by Craig Small in version 0.37 of dh-make.
-#
-# Modified to make a template file for a multi-binary package with separated
-# build-arch and build-indep targets by Bill Allombert 2001
-
-# Uncomment this to turn on verbose mode.
-#export DH_VERBOSE=1
-
-# This has to be exported to make some magic below work.
-export DH_OPTIONS
-
-configure: configure-stamp
-configure-stamp:
- dh_testdir
- touch configure-stamp
-
-build: build-arch build-indep
-
-build-arch: build-arch-stamp
-build-arch-stamp: configure-stamp
- dh_auto_build -- all doc
- touch $@
-
-build-indep: build-indep-stamp
-build-indep-stamp: configure-stamp
- dh_auto_build -- doc
- touch $@
-
-clean:
- dh_testdir
- dh_testroot
- rm -f build-arch-stamp build-indep-stamp configure-stamp
- dh_auto_clean
- dh_clean
- debconf-updatepo
-
-install: install-indep install-arch
-install-indep:
- dh_testdir
- dh_testroot
- dh_prep
- dh_installdirs --indep
+%:
+ 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
- dh_lintian
- dh_installinit --onlyscripts --no-start \
- --update-rcd-params="defaults 25 15"
- dh_install --indep
-
-install-arch:
- dh_testdir
- dh_testroot
- dh_prep
- dh_installdirs --same-arch
- $(MAKE) DESTDIR=$(CURDIR)/debian/mandos-client install-client-nokey
- dh_lintian
- dh_install --same-arch
-
-binary-common:
- dh_testdir
- dh_testroot
- dh_installchangelogs
- dh_installdocs
- dh_installdebconf
- dh_link
- dh_strip
- dh_compress
+
+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 usr/lib/mandos/plugins.d
- dh_installdeb
- dh_shlibdeps
- dh_gencontrol
- dh_md5sums
- dh_builddeb
-
-# Build architecture independant packages using the common target.
-binary-indep: build-indep install-indep
- $(MAKE) -f debian/rules DH_OPTIONS=--indep binary-common
-
-# Build architecture dependant packages using the common target.
-binary-arch: build-arch install-arch
- $(MAKE) -f debian/rules DH_OPTIONS=--same-arch binary-common
-
-binary: binary-arch binary-indep
-
-.PHONY: build clean binary-indep binary-arch binary install \
- install-indep install-arch configure
+ --exclude etc/mandos/plugins.d \
+ --exclude usr/lib/$(shell dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null)/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"
=== added directory 'debian/source'
=== added file 'debian/source/format'
--- debian/source/format 1970-01-01 00:00:00 +0000
+++ debian/source/format 2010-10-02 17:41:05 +0000
@@ -0,0 +1,1 @@
+3.0 (quilt)
=== added file 'debian/source/local-options'
--- debian/source/local-options 1970-01-01 00:00:00 +0000
+++ debian/source/local-options 2011-10-08 21:13:46 +0000
@@ -0,0 +1,1 @@
+--single-debian-patch
=== added file 'debian/watch'
--- debian/watch 1970-01-01 00:00:00 +0000
+++ debian/watch 2014-02-16 02:42:42 +0000
@@ -0,0 +1,3 @@
+version=3
+opts=pgpsigurlmangle=s/$/.asc/ \
+ ftp://ftp.recompile.se/pub/mandos/mandos[-_]([^\s]+?)(?:\.orig)?\.tar\.(?:gz|bz2|7z|xz)
=== modified file 'init.d-mandos'
--- init.d-mandos 2008-09-21 12:04:02 +0000
+++ init.d-mandos 2014-01-06 15:56:54 +0000
@@ -1,16 +1,16 @@
#! /bin/sh
### BEGIN INIT INFO
# Provides: mandos
-# Required-Start: $remote_fs avahi-daemon
-# Required-Stop: $remote_fs
+# 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: Gives encrypted passwords to Mandos clients
+# Description: Server of encrypted passwords to Mandos clients
### END INIT INFO
-# Author: Teddy Hogeborn
-# Author: Björn Påhlsson
+# Author: Teddy Hogeborn
+# Author: Björn Påhlsson
#
# Please remove the "Author" lines above and replace them
# with your own name if you copy and modify this script.
@@ -23,7 +23,11 @@
NAME=mandos
DAEMON=/usr/sbin/$NAME
DAEMON_ARGS=""
-PIDFILE=/var/run/$NAME.pid
+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
@@ -40,7 +44,8 @@
. /lib/init/vars.sh
# Define LSB log_* functions.
-# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
. /lib/lsb/init-functions
#
@@ -118,6 +123,9 @@
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
@@ -144,14 +152,14 @@
esac
;;
*)
- # Failed to stop
+ # Failed to stop
log_end_msg 1
;;
esac
;;
*)
#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
- echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
+ echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
exit 3
;;
esac
=== modified file 'initramfs-tools-hook'
--- initramfs-tools-hook 2008-09-19 00:54:24 +0000
+++ initramfs-tools-hook 2013-10-28 08:38:47 +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 the
+# happens when a new 'linux-image' package is installed, or when an
# administrator runs 'update-initramfs' by hand to update an initramfs
# image.
@@ -29,13 +29,15 @@
. /usr/share/initramfs-tools/hook-functions
-for d in /usr /usr/local; do
- if [ -d "$d"/lib/mandos ]; then
- prefix="$d"
+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 "$prefix" ]; then
+if [ -z "$libdir" ]; then
# Mandos not found
exit 1
fi
@@ -51,14 +53,13 @@
exit 1
fi
-mandos_user="`{ getent passwd mandos \
- || getent passwd nobody \
- || echo ::65534::::; } \
- | awk --field-separator=: '{ print $3 }'`"
-mandos_group="`{ getent group mandos \
- || getent group nogroup \
- || echo ::65534:; } \
- | awk --field-separator=: '{ print $3 }'`"
+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
@@ -69,29 +70,31 @@
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}${MANDOSDIR}" "${DESTDIR}${HOOKDIR}"
install --owner=${mandos_user} --group=${mandos_group} --directory \
--mode=u=rwx "${DESTDIR}${PLUGINDIR}"
# Copy the Mandos plugin runner
-copy_exec "$prefix"/lib/mandos/plugin-runner "${MANDOSDIR}"
+copy_exec "$libdir"/mandos/plugin-runner "${MANDOSDIR}"
# Copy the plugins
# Copy the packaged plugins
-for file in "$prefix"/lib/mandos/plugins.d/*; do
+for file in "$libdir"/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-new|*.dpkg-divert) : ;;
- "*") :;;
- *) copy_exec "$file" "${PLUGINDIR}";;
+ *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert)
+ : ;;
+ "*") echo "W: Mandos client plugin directory is empty." >&2 ;;
+ *) copy_exec "$file" "${PLUGINDIR}" ;;
esac
done
@@ -99,21 +102,68 @@
for file in /etc/mandos/plugins.d/*; do
base="`basename \"$file\"`"
case "$base" in
- *~|.*|\#*\#|*.dpkg-old|*.dpkg-new|*.dpkg-divert) : ;;
- "*") :;;
- *) copy_exec "$file" "${PLUGINDIR}";;
- esac
-done
-
-# GPGME needs /usr/bin/gpg
-if [ ! -e "${DESTDIR}/usr/bin/gpg" \
- -a -n "`ls \"${DESTDIR}\"/usr/lib/libgpgme.so* \
- 2>/dev/null`" ]; then
- copy_exec /usr/bin/gpg
-fi
+ *~|.*|\#*\#|*.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/*; do
+for file in /etc/mandos/plugin-runner.conf; do
if [ -d "$file" ]; then
continue
fi
@@ -121,17 +171,17 @@
done
if [ ${mandos_user} != 65534 ]; then
- PLUGINRUNNERCONF="${DESTDIR}${CONFDIR}/plugin-runner.conf"
- echo "--userid=${mandos_user}" >> "$PLUGINRUNNERCONF"
+ sed --in-place --expression="1i--userid=${mandos_user}" \
+ "${DESTDIR}${CONFDIR}/plugin-runner.conf"
fi
if [ ${mandos_group} != 65534 ]; then
- PLUGINRUNNERCONF="${DESTDIR}${CONFDIR}/plugin-runner.conf"
- echo "--groupid=${mandos_group}" >> "$PLUGINRUNNERCONF"
+ sed --in-place --expression="1i--groupid=${mandos_group}" \
+ "${DESTDIR}${CONFDIR}/plugin-runner.conf"
fi
-# Key files
-for file in "$keydir"/*; do
+# Key files
+for file in "$keydir"/*; do
if [ -d "$file" ]; then
continue
fi
@@ -166,7 +216,9 @@
chmod a+rX "${DESTDIR}$dir"
fi
done
-for dir in /lib /usr/lib; do
- find "${DESTDIR}$dir" \! -perm -u+rw,g+r -prune -or -print0 \
- | xargs --null --no-run-if-empty chmod a+rX
+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
done
=== modified file 'initramfs-tools-hook-conf'
--- initramfs-tools-hook-conf 2008-08-12 19:22:34 +0000
+++ initramfs-tools-hook-conf 2009-05-17 00:50:09 +0000
@@ -1,1 +1,13 @@
+# -*- shell-script -*-
+
+# if mkinitramfs is started by mkinitramfs-kpkg, mkinitramfs-kpkg has
+# already touched the initrd file with umask 022 before we had a
+# chance to affect it. We cannot allow a readable initrd file,
+# therefore we must fix this now.
+if [ -e "${outfile}" ] \
+ && [ `stat --format=%s "${outfile}"` -eq 0 ]; then
+ rm "${outfile}"
+ (umask 027; touch "${outfile}")
+fi
+
UMASK=027
=== modified file 'initramfs-tools-script'
--- initramfs-tools-script 2008-09-07 15:42:11 +0000
+++ initramfs-tools-script 2011-07-16 00:29:19 +0000
@@ -6,33 +6,109 @@
#
# This script should be installed as
-# "/usr/share/initramfs-tools/scripts/local-top/mandos" which will
-# eventually be "/scripts/local-top/mandos" in the initrd.img file.
+# "/usr/share/initramfs-tools/scripts/init-premount/mandos" which will
+# eventually be "/scripts/init-premount/mandos" in the initrd.img
+# file.
-# No initramfs pre-requirements; we must instead run BEFORE cryptroot.
-# This is not a problem, since cryptroot forces itself to run LAST.
-PREREQ=""
+PREREQ="udev"
prereqs()
{
- echo "$PREREQ"
+ echo "$PREREQ"
}
case $1 in
prereqs)
- prereqs
- exit 0
- ;;
+ 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 -w /conf/conf.d/cryptroot
+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
# 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
=== added file 'initramfs-unpack'
--- initramfs-unpack 1970-01-01 00:00:00 +0000
+++ initramfs-unpack 2013-10-13 15:43:42 +0000
@@ -0,0 +1,67 @@
+#!/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
=== added file 'intro.xml'
--- intro.xml 1970-01-01 00:00:00 +0000
+++ intro.xml 2011-12-31 23:05:34 +0000
@@ -0,0 +1,411 @@
+
+
+
+%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 really quickly?
+
+ This, as mentioned above, is the only real weak point. But if
+ you set the timing values tight enough, this will be really
+ difficult to do. An attacker would have to physically
+ disassemble the client computer, extract the key from the
+ initial RAM disk image, and then connect to a still
+ online Mandos server to get the encrypted key, and do
+ all this before the Mandos server timeout
+ kicks in and the Mandos server refuses to give out the key to
+ anyone.
+
+
+ Now, as the typical procedure seems to be to barge in and turn
+ off and grab all computers, to maybe look
+ at them months later, this is not likely. If someone does that,
+ the whole system will lock itself up
+ completely, since Mandos servers are no longer running.
+
+
+ For sophisticated attackers who could do
+ the clever thing, and had physical access
+ to the server for enough time, it would be simpler to get a key
+ for an encrypted file system by using hardware memory scanners
+ and reading it right off the memory bus.
+
+
+
+
+ Replay attacks?
+
+ Nope, the network stuff is all done over TLS, which provides
+ protection against that.
+
+
+
+
+ Man-in-the-middle?
+
+ No. The server only gives out the passwords to clients which
+ have in the TLS handshake proven that
+ they do indeed hold the OpenPGP private key corresponding to
+ that client.
+
+
+
+
+ Physically grabbing the Mandos server computer?
+
+ You could protect that computer the
+ old-fashioned way, with a must-type-in-the-password-at-boot
+ method. Or you could have two computers be the Mandos server
+ for each other.
+
+
+ Multiple Mandos servers can coexist on a network without any
+ trouble. They do not clash, and clients will try all
+ available servers. This means that if just one reboots then
+ the other can bring it back up, but if both reboot at the same
+ time they will stay down until someone types in the password
+ on one of them.
+
+
+
+
+ Faking ping replies?
+
+ The default for the server is to use
+ fping
, the replies to which
+ could be faked to eliminate the timeout. But this could
+ easily be changed to any shell command, with any security
+ measures you like. It could, for instance, be changed to an
+ SSH command with strict keychecking, which could not be faked.
+ Or IPsec could be used for the ping packets, making them
+ secure.
+
+
+
+
+
+ SECURITY
+
+ 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.
+
+
+
+
+
+
+
+
+
+
+
=== modified file 'mandos'
--- mandos 2008-09-19 20:42:17 +0000
+++ mandos 2014-02-16 13:12:20 +0000
@@ -6,12 +6,13 @@
# This program is partly derived from an example program for an Avahi
# service publisher, downloaded from
# . This includes the
-# methods "add" and "remove" in the "AvahiService" class, the
-# "server_state_changed" and "entry_group_state_changed" functions,
-# and some lines in "main".
+# methods "add", "remove", "server_state_changed",
+# "entry_group_state_changed", "cleanup", and "activate" in the
+# "AvahiService" class, and some lines in "main".
#
# Everything else is
-# Copyright © 2008 Teddy Hogeborn & Björn Påhlsson
+# Copyright © 2008-2014 Teddy Hogeborn
+# Copyright © 2008-2014 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
@@ -27,15 +28,17 @@
# along with this program. If not, see
# .
#
-# Contact the authors at .
+# Contact the authors at .
#
-from __future__ import division
-
-import SocketServer
+from __future__ import (division, absolute_import, print_function,
+ unicode_literals)
+
+from future_builtins import *
+
+import SocketServer as socketserver
import socket
-import select
-from optparse import OptionParser
+import argparse
import datetime
import errno
import gnutls.crypto
@@ -44,45 +47,185 @@
import gnutls.library.functions
import gnutls.library.constants
import gnutls.library.types
-import ConfigParser
+import ConfigParser as 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
-
-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)
+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.4"
+stored_state_file = "clients.pickle"
+
+logger = logging.getLogger()
+syslogger = None
+
+try:
+ if_nametoindex = (ctypes.cdll.LoadLibrary
+ (ctypes.util.find_library("c"))
+ .if_nametoindex)
+except (OSError, AttributeError):
+ def if_nametoindex(interface):
+ "Get an interface index the hard way, i.e. using fcntl()"
+ SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
+ with contextlib.closing(socket.socket()) as s:
+ ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
+ struct.pack(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 = (logging.handlers.SysLogHandler
+ (facility =
+ logging.handlers.SysLogHandler.LOG_DAEMON,
+ address = str("/dev/log")))
+ syslogger.setFormatter(logging.Formatter
+ ('Mandos [%(process)d]: %(levelname)s:'
+ ' %(message)s'))
+ logger.addHandler(syslogger)
+
+ if debug:
+ console = logging.StreamHandler()
+ console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
+ ' [%(process)d]:'
+ ' %(levelname)s:'
+ ' %(message)s'))
+ logger.addHandler(console)
+ logger.setLevel(level)
+
+
+class PGPError(Exception):
+ """Exception if encryption/decryption fails"""
+ pass
+
+
+class PGPEngine(object):
+ """A simple class for OpenPGP symmetric encryption & decryption"""
+ def __init__(self):
+ self.tempdir = tempfile.mkdtemp(prefix="mandos-")
+ self.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
+
class AvahiError(Exception):
- def __init__(self, value):
+ def __init__(self, value, *args, **kwargs):
self.value = value
- def __str__(self):
- return repr(self.value)
+ super(AvahiError, self).__init__(value, *args, **kwargs)
+ def __unicode__(self):
+ return unicode(repr(self.value))
class AvahiServiceError(AvahiError):
pass
@@ -93,12 +236,13 @@
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.
@@ -106,256 +250,517 @@
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,
- type = None, port = None, TXT = None, domain = "",
- host = "", max_renames = 32768):
+ servicetype = None, port = None, TXT = None,
+ domain = "", host = "", max_renames = 32768,
+ protocol = avahi.PROTO_UNSPEC, bus = None):
self.interface = interface
self.name = name
- self.type = type
+ self.type = servicetype
self.port = port
- if TXT is None:
- self.TXT = []
- else:
- self.TXT = TXT
+ self.TXT = TXT if TXT is not None else []
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(u"No suitable Zeroconf service name found"
- u" after %i retries, exiting.",
- rename_count)
+ logger.critical("No suitable Zeroconf service name found"
+ " after %i retries, exiting.",
+ self.rename_count)
raise AvahiServiceError("Too many renames")
- self.name = server.GetAlternativeServiceName(self.name)
- logger.info(u"Changing Zeroconf service name to %r ...",
- str(self.name))
- syslogger.setFormatter(logging.Formatter\
- ('Mandos (%s): %%(levelname)s:'
- ' %%(message)s' % self.name))
+ self.name = unicode(self.server
+ .GetAlternativeServiceName(self.name))
+ logger.info("Changing Zeroconf service name to %r ...",
+ self.name)
self.remove()
- self.add()
+ try:
+ self.add()
+ except dbus.exceptions.DBusException as error:
+ logger.critical("D-Bus Exception", exc_info=error)
+ self.cleanup()
+ os._exit(1)
self.rename_count += 1
+
def remove(self):
"""Derived from the Avahi example code"""
- if group is not None:
- group.Reset()
+ if self.entry_group_state_changed_match is not None:
+ self.entry_group_state_changed_match.remove()
+ self.entry_group_state_changed_match = None
+ if self.group is not None:
+ self.group.Reset()
+
def add(self):
"""Derived from the Avahi example code"""
- global group
- if group is None:
- group = dbus.Interface\
- (bus.get_object(avahi.DBUS_NAME,
- server.EntryGroupNew()),
- avahi.DBUS_INTERFACE_ENTRY_GROUP)
- group.connect_to_signal('StateChanged',
- entry_group_state_changed)
- logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
- service.name, service.type)
- group.AddService(
- self.interface, # interface
- avahi.PROTO_INET6, # protocol
- dbus.UInt32(0), # flags
- self.name, self.type,
- self.domain, self.host,
- dbus.UInt16(self.port),
- avahi.string_array_to_txt_array(self.TXT))
- group.Commit()
-
-# From the Avahi example code:
-group = None # our entry group
-# End of Avahi example code
+ 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))
class Client(object):
"""A representation of a client host served by this server.
+
Attributes:
- name: string; from the config file, used in log messages
+ 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
+ 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
- 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.
- Private attibutes:
- _timeout: Real variable for 'timeout'
- _interval: Real variable for 'interval'
- _timeout_milliseconds: Used when calling gobject.timeout_add()
- _interval_milliseconds: - '' -
+ 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()
"""
- 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."""
+
+ 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):
self.name = name
- logger.debug(u"Creating client %r", self.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)
# Uppercase and remove spaces from fingerprint for later
# comparison purposes with return value from the fingerprint()
# function
- self.fingerprint = config["fingerprint"].upper()\
- .replace(u" ", u"")
- logger.debug(u" Fingerprint: %s", self.fingerprint)
- if "secret" in config:
- self.secret = config["secret"].decode(u"base64")
- elif "secfile" in config:
- sf = open(config["secfile"])
- self.secret = sf.read()
- sf.close()
- else:
- 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
+ logger.debug(" Fingerprint: %s", self.fingerprint)
+ self.created = settings.get("created",
+ datetime.datetime.utcnow())
+
+ # attributes specific for this server instance
self.checker = None
self.checker_initiator_tag = None
- self.stop_initiator_tag = None
+ self.disable_initiator_tag = None
self.checker_callback_tag = None
- self.check_command = config["checker"]
- def start(self):
+ 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):
"""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.
- self.checker_initiator_tag = gobject.timeout_add\
- (self._interval_milliseconds,
- self.start_checker)
+ 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))
# Also start a new checker *right now*.
self.start_checker()
- # 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):
+
+ def checker_callback(self, pid, condition, command):
"""The checker has completed, so take appropriate actions."""
- now = datetime.datetime.now()
self.checker_callback_tag = None
self.checker = None
- 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?",
+ 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?",
vars(self))
- else:
- logger.info(u"Checker for %(name)s failed",
- 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()
+
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, 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
+ # 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
# checkers alone, the checker would have to take more time
- # than 'timeout' for the client to be declared invalid, which
- # is as it should be.
+ # 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
if self.checker is None:
- 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",
+ # 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",
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="/")
- 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)
+ 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)
# 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:
@@ -363,218 +768,1464 @@
self.checker_callback_tag = None
if getattr(self, "checker", None) is None:
return
- logger.debug(u"Stopping checker for %(name)s", vars(self))
+ logger.debug("Stopping checker for %(name)s", vars(self))
try:
- os.kill(self.checker.pid, signal.SIGTERM)
- #os.sleep(0.5)
+ self.checker.terminate()
+ #time.sleep(0.5)
#if self.checker.poll() is None:
- # os.kill(self.checker.pid, signal.SIGKILL)
- except OSError, error:
+ # self.checker.kill()
+ except OSError as error:
if error.errno != errno.ESRCH: # No such process
raise
self.checker = None
- def still_valid(self):
- """Has the timeout not yet passed for this client?"""
- now = datetime.datetime.now()
- if self.last_checked_ok is None:
- return now < (self.created + self.timeout)
- else:
- return now < (self.last_checked_ok + self.timeout)
-
-
-def peer_certificate(session):
- "Return the peer's OpenPGP certificate as a bytestring"
- # If not an OpenPGP certificate...
- if gnutls.library.functions.gnutls_certificate_type_get\
- (session._c_object) \
- != gnutls.library.constants.GNUTLS_CRT_OPENPGP:
- # ...do the normal thing
- return session.peer_certificate
- list_size = ctypes.c_uint()
- cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
- (session._c_object, ctypes.byref(list_size))
- if list_size.value == 0:
- return None
- cert = cert_list[0]
- return ctypes.string_at(cert.data, cert.size)
-
-
-def fingerprint(openpgp):
- "Convert an OpenPGP bytestring to a hexdigit fingerprint string"
- # New GnuTLS "datum" with the OpenPGP public key
- datum = gnutls.library.types.gnutls_datum_t\
- (ctypes.cast(ctypes.c_char_p(openpgp),
- ctypes.POINTER(ctypes.c_ubyte)),
- ctypes.c_uint(len(openpgp)))
- # New empty GnuTLS certificate
- crt = gnutls.library.types.gnutls_openpgp_crt_t()
- gnutls.library.functions.gnutls_openpgp_crt_init\
- (ctypes.byref(crt))
- # Import the OpenPGP public key into the certificate
- gnutls.library.functions.gnutls_openpgp_crt_import\
- (crt, ctypes.byref(datum),
- gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
- # Verify the self signature in the key
- crtverify = ctypes.c_uint();
- gnutls.library.functions.gnutls_openpgp_crt_verify_self\
- (crt, 0, ctypes.byref(crtverify))
- if crtverify.value != 0:
- gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
- raise gnutls.errors.CertificateSecurityError("Verify failed")
- # New buffer for the fingerprint
- buffer = ctypes.create_string_buffer(20)
- buffer_length = ctypes.c_size_t()
- # Get the fingerprint from the certificate into the buffer
- gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
- (crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
- # Deinit the certificate
- gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
- # Convert the buffer to a Python bytestring
- fpr = ctypes.string_at(buffer, buffer_length.value)
- # Convert the bytestring to hexadecimal notation
- hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
- return hex_fpr
-
-
-class tcp_handler(SocketServer.BaseRequestHandler, object):
- """A TCP request handler class.
- Instantiated by IPv6_TCPServer for each request to handle it.
+
+
+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.
Note: This will run in its own forked process."""
def handle(self):
- 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.
+ 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
+
Attributes:
- settings: Server settings
- clients: Set() of Client objects
enabled: Boolean; whether this server is activated yet
+ interface: None or a network interface name (string)
+ use_ipv6: Boolean; to use IPv6 or not
"""
- 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"]
- self.enabled = False
- return super(type(self), self).__init__(*args, **kwargs)
+ 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)
+
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.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
+ 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
# 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]:
- in6addr_any = "::"
- self.server_address = (in6addr_any,
+ 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,
self.server_address[1])
elif not self.server_address[1]:
self.server_address = (self.server_address[0],
0)
-# if self.settings["interface"]:
+# if self.interface:
# self.server_address = (self.server_address[0],
# 0, # port
# 0, # flowinfo
# if_nametoindex
-# (self.settings
-# ["interface"]))
- return super(type(self), self).server_bind()
+# (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 super(type(self), self).server_activate()
+ 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
def string_to_delta(interval):
"""Parse a string and return a datetime.timedelta
-
+
>>> string_to_delta('7d')
datetime.timedelta(7)
>>> string_to_delta('60s')
@@ -583,84 +2234,44 @@
datetime.timedelta(0, 3600)
>>> string_to_delta('24h')
datetime.timedelta(1)
- >>> string_to_delta(u'1w')
+ >>> string_to_delta('1w')
datetime.timedelta(7)
>>> string_to_delta('5m 30s')
datetime.timedelta(0, 330)
"""
+
+ try:
+ return rfc3339_duration_to_delta(interval)
+ except ValueError:
+ pass
+
timevalue = datetime.timedelta(0)
for s in interval.split():
try:
- suffix=unicode(s[-1])
- value=int(s[:-1])
- if suffix == u"d":
+ suffix = unicode(s[-1])
+ value = int(s[:-1])
+ if suffix == "d":
delta = datetime.timedelta(value)
- elif suffix == u"s":
+ elif suffix == "s":
delta = datetime.timedelta(0, value)
- elif suffix == u"m":
+ elif suffix == "m":
delta = datetime.timedelta(0, 0, 0, 0, value)
- elif suffix == u"h":
+ elif suffix == "h":
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
- elif suffix == u"w":
+ elif suffix == "w":
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
else:
- raise ValueError
- except (ValueError, IndexError):
- raise ValueError
+ raise ValueError("Unknown suffix {0!r}"
+ .format(suffix))
+ except IndexError as e:
+ raise ValueError(*(e.args))
timevalue += delta
return timevalue
-def server_state_changed(state):
- """Derived from the Avahi example code"""
- if state == avahi.SERVER_COLLISION:
- logger.error(u"Zeroconf server name collision")
- service.remove()
- elif state == avahi.SERVER_RUNNING:
- service.add()
-
-
-def entry_group_state_changed(state, error):
- """Derived from the Avahi example code"""
- logger.debug(u"Avahi state change: %i", state)
-
- if state == avahi.ENTRY_GROUP_ESTABLISHED:
- logger.debug(u"Zeroconf service established.")
- elif state == avahi.ENTRY_GROUP_COLLISION:
- logger.warning(u"Zeroconf service name collision.")
- service.rename()
- elif state == avahi.ENTRY_GROUP_FAILURE:
- logger.critical(u"Avahi: Error in group state changed %s",
- unicode(error))
- raise AvahiGroupError("State changed: %s", str(error))
-
-def if_nametoindex(interface):
- """Call the C function if_nametoindex(), or equivalent"""
- global if_nametoindex
- try:
- if "ctypes.util" not in sys.modules:
- import ctypes.util
- if_nametoindex = ctypes.cdll.LoadLibrary\
- (ctypes.util.find_library("c")).if_nametoindex
- except (OSError, AttributeError):
- if "struct" not in sys.modules:
- import struct
- if "fcntl" not in sys.modules:
- import fcntl
- def if_nametoindex(interface):
- "Get an interface index the hard way, i.e. using fcntl()"
- SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
- s = socket.socket()
- ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
- struct.pack("16s16x", interface))
- s.close()
- interface_index = struct.unpack("I", ifreq[16:20])[0]
- return interface_index
- return if_nametoindex(interface)
-
-
def daemon(nochdir = False, noclose = False):
"""See daemon(3). Standard BSD Unix function.
+
This should really exist as os.daemon, but it doesn't (yet)."""
if os.fork():
sys.exit()
@@ -671,10 +2282,11 @@
sys.exit()
if not noclose:
# Close all standard open file descriptors
- null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
+ null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
if not stat.S_ISCHR(os.fstat(null).st_mode):
raise OSError(errno.ENODEV,
- "/dev/null not a character device")
+ "{0} not a character device"
+ .format(os.devnull))
os.dup2(null, sys.stdin.fileno())
os.dup2(null, sys.stdout.fileno())
os.dup2(null, sys.stderr.fileno())
@@ -683,35 +2295,58 @@
def main():
- 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()
+
+ ##################################################################
+ # 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()
if options.check:
import doctest
- doctest.testmod()
- sys.exit()
+ fail_count, test_count = doctest.testmod()
+ sys.exit(os.EX_OK if fail_count == 0 else 1)
# Default values for config file for server-global settings
server_defaults = { "interface": "",
@@ -719,205 +2354,518 @@
"port": "",
"debug": "False",
"priority":
- "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
+ "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
"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 getboolean on the boolean config option
- server_settings["debug"] = server_config.getboolean\
- ("DEFAULT", "debug")
+ # 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"])
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"):
+ "priority", "servicename", "configdir",
+ "use_dbus", "use_ipv6", "debuglevel", "restore",
+ "statedir", "socket", "foreground"):
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 not debug:
- syslogger.setLevel(logging.WARNING)
- console.setLevel(logging.WARNING)
+ if debug:
+ initlogger(debug, logging.DEBUG)
+ else:
+ if not debuglevel:
+ initlogger(debug)
+ else:
+ level = getattr(logging, debuglevel.upper())
+ initlogger(debug, level)
if server_settings["servicename"] != "Mandos":
- syslogger.setFormatter(logging.Formatter\
- ('Mandos (%s): %%(levelname)s:'
- ' %%(message)s'
- % server_settings["servicename"]))
+ syslogger.setFormatter(logging.Formatter
+ ('Mandos ({0}) [%(process)d]:'
+ ' %(levelname)s: %(message)s'
+ .format(server_settings
+ ["servicename"])))
# Parse config file with clients
- client_defaults = { "timeout": "1h",
- "interval": "5m",
- "checker": "fping -q -- %(host)s",
- "host": "",
- }
- client_config = ConfigParser.SafeConfigParser(client_defaults)
+ client_config = configparser.SafeConfigParser(Client
+ .client_defaults)
client_config.read(os.path.join(server_settings["configdir"],
"clients.conf"))
- clients = Set()
- tcp_server = IPv6_TCPServer((server_settings["address"],
- server_settings["port"]),
- tcp_handler,
- settings=server_settings,
- clients=clients)
- pidfilename = "/var/run/mandos.pid"
- try:
- pidfile = open(pidfilename, "w")
- except IOError, error:
- logger.error("Could not open file %r", pidfilename)
-
- uid = 65534
- gid = 65534
- try:
- uid = pwd.getpwnam("mandos").pw_uid
- except KeyError:
- try:
- uid = pwd.getpwnam("nobody").pw_uid
- except KeyError:
- pass
- try:
- gid = pwd.getpwnam("mandos").pw_gid
- except KeyError:
- try:
- gid = pwd.getpwnam("nogroup").pw_gid
- except KeyError:
- pass
- try:
+ 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)
- os.setgid(gid)
- except OSError, error:
- if error[0] != errno.EPERM:
- raise error
-
- global service
- service = AvahiService(name = server_settings["servicename"],
- type = "_mandos._tcp", );
- if server_settings["interface"]:
- service.interface = if_nametoindex\
- (server_settings["interface"])
+ 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 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
-
- 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()
-
- try:
- pid = os.getpid()
- pidfile.write(str(pid) + "\n")
- pidfile.close()
+ 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)
del pidfile
- except IOError, err:
- logger.error(u"Could not write to file %r with PID %d",
- pidfilename, pid)
- except NameError:
- # "pidfile" was never created
- pass
- del pidfilename
+ 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()
def cleanup():
"Cleanup function; run on exit"
- 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()
+ 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()
atexit.register(cleanup)
- 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()
+ 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()
# Find out what port we got
service.port = tcp_server.socket.getsockname()[1]
- logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
- u" scope_id %d" % tcp_server.socket.getsockname())
+ if use_ipv6:
+ logger.info("Now listening on address %r, port %d,"
+ " flowinfo %d, scope_id %d",
+ *tcp_server.socket.getsockname())
+ else: # IPv4
+ logger.info("Now listening on address %r, port %d",
+ *tcp_server.socket.getsockname())
#service.interface = tcp_server.socket.getsockname()[3]
try:
# From the Avahi example code
- server.connect_to_signal("StateChanged", server_state_changed)
try:
- server_state_changed(server.GetState())
- except dbus.exceptions.DBusException, error:
- logger.critical(u"DBusException: %s", error)
+ service.activate()
+ except dbus.exceptions.DBusException as error:
+ logger.critical("D-Bus Exception", exc_info=error)
+ cleanup()
sys.exit(1)
# End of Avahi example code
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(u"Starting main loop")
- main_loop_started = True
+ logger.debug("Starting main loop")
main_loop.run()
- except AvahiError, error:
- logger.critical(u"AvahiError: %s" + unicode(error))
+ except AvahiError as error:
+ logger.critical("Avahi Error", exc_info=error)
+ cleanup()
sys.exit(1)
except KeyboardInterrupt:
if debug:
- print
+ print("", file=sys.stderr)
+ logger.debug("Server received KeyboardInterrupt")
+ logger.debug("Server exiting")
+ # Must run before the D-Bus bus name gets deregistered
+ cleanup()
if __name__ == '__main__':
main()
=== modified file 'mandos-clients.conf.xml'
--- mandos-clients.conf.xml 2008-09-12 19:12:40 +0000
+++ mandos-clients.conf.xml 2013-10-20 15:25:09 +0000
@@ -1,10 +1,11 @@
/etc/mandos/clients.conf">
-
+
+
+%common;
]>
@@ -12,26 +13,30 @@
Mandos Manual
Mandos
- &VERSION;
+ &version;
&TIMESTAMP;
Björn
Påhlsson
- belorn@fukt.bsnet.se
+ belorn@recompile.se
Teddy
Hogeborn
- teddy@fukt.bsnet.se
+ teddy@recompile.se
2008
+ 2009
+ 2010
+ 2011
+ 2012
Teddy Hogeborn
Björn Påhlsson
@@ -61,9 +66,13 @@
>mandos
8, read by it at startup.
The file needs to list all clients that should be able to use
- the service. All clients listed will be regarded as valid, even
- if a client was declared invalid in a previous run of the
- server.
+ the service. The settings in this file can be overridden by
+ runtime changes to the server, which it saves across restarts.
+ (See the section called PERSISTENT STATE
in
+ mandos8.) However, any changes to this file (including adding and removing
+ clients) will, at startup, override changes done during runtime.
The format starts with a [section
@@ -99,53 +108,53 @@
-
-
-
- This option is optional.
-
-
- 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.
-
-
-
-
-
-
-
-
- This option is optional.
-
-
- How often to run the checker to confirm that a client is
- still up. Note: a new checker will
- not be started if an old one is still running. The server
- will wait for a checker to complete until the above
- timeout
occurs, at which
- time the client will be marked invalid, and any running
- checker killed. The default interval is 5 minutes.
-
-
- The format of TIME is the same
- as for timeout above.
+
+
+
+ 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
.
@@ -158,17 +167,17 @@
This option is optional.
- This option allows you to override the default shell
- command that the server will use to check if the client is
- still up. Any output of the command will be ignored, only
- the exit code is checked: If the exit code of the command
- is zero, the client is considered up. The command will be
- run using /bin/sh
+ This option overrides the default shell command that the
+ server will use to check if the client is still up. Any
+ output of the command will be ignored, only the exit code
+ is checked: If the exit code of the command is zero, the
+ client is considered up. The command will be run using
+ /bin/sh
, so
PATH will be searched. The default
value for the checker command is fping %(host)s
.
+ >-- %%(host)s
.
In addition to normal start time expansion, this option
@@ -179,6 +188,29 @@
+
+
+
+ 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.
+
+
+
+
+
@@ -195,6 +227,70 @@
+
+
+
+ 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.
+
+
+
+
+
@@ -225,37 +321,40 @@
-
+
- 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.
+ This option is optional.
+
+
+ The timeout is how long the server will wait, after a
+ successful checker run, until a client is disabled and not
+ allowed to get the data this server holds. By default
+ Mandos will use 5 minutes. See also the
+ option.
+
+
+ The TIME is specified as an RFC
+ 3339 duration; for example
+ P1Y2M3DT4H5M6S
meaning
+ one year, two months, three days, four hours, five
+ minutes, and six seconds. Some values can be omitted, see
+ RFC 3339 Appendix A for details.
-
+
- This option is 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.
+ Whether this client should be enabled by default. The
+ default is true
.
@@ -298,10 +397,29 @@
%%(foo)s
will be replaced by the value of the attribute
foo of the internal
- 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.
+ 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.
Note that this means that, in order to include an actual
@@ -342,9 +460,9 @@
[DEFAULT]
-timeout = 1h
-interval = 5m
-checker = fping -q -- %(host)s
+timeout = PT5M
+interval = PT2M
+checker = fping -q -- %%(host)s
# Client "foo"
[foo]
@@ -366,13 +484,15 @@
4T2zw4dxS5NswXWU0sVEXxjs6PYxuIiCTL7vdpx8QjBkrPWDrAbcMyBr2O
QlnHIvPzEArRQLo=
host = foo.example.org
-interval = 1m
+interval = PT1M
# Client "bar"
[bar]
fingerprint = 3e393aeaefb84c7e89e2f547b3a107558fca3a27
secfile = /etc/mandos/bar-secret
-timeout = 15m
+timeout = PT15M
+approved_by_default = False
+approval_delay = PT30S
@@ -380,13 +500,31 @@
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.
+
+
+
+
=== added file 'mandos-ctl'
--- mandos-ctl 1970-01-01 00:00:00 +0000
+++ mandos-ctl 2014-02-16 13:12:20 +0000
@@ -0,0 +1,460 @@
+#!/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.4"
+
+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(os.EX_OK if fail_count == 0 else 1)
+
+ try:
+ bus = dbus.SystemBus()
+ mandos_dbus_objc = bus.get_object(busname, server_path)
+ except dbus.exceptions.DBusException:
+ print("Could not connect to Mandos server",
+ file=sys.stderr)
+ sys.exit(1)
+
+ mandos_serv = dbus.Interface(mandos_dbus_objc,
+ dbus_interface = server_interface)
+
+ #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()
=== added file 'mandos-ctl.xml'
--- mandos-ctl.xml 1970-01-01 00:00:00 +0000
+++ mandos-ctl.xml 2012-06-22 23:33:56 +0000
@@ -0,0 +1,605 @@
+
+
+
+
+%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 2008-09-21 14:05:44 +0000
+++ mandos-keygen 2014-02-16 13:12:20 +0000
@@ -2,7 +2,8 @@
#
# Mandos key generator - create a new OpenPGP key for a Mandos client
#
-# Copyright © 2008 Teddy Hogeborn & Björn Påhlsson
+# Copyright © 2008-2013 Teddy Hogeborn
+# Copyright © 2008-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
@@ -17,19 +18,19 @@
# 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.0"
+VERSION="1.6.4"
KEYDIR="/etc/keys/mandos"
-KEYTYPE=DSA
-KEYLENGTH=2048
-SUBKEYTYPE=ELG-E
-SUBKEYLENGTH=2048
+KEYTYPE=RSA
+KEYLENGTH=4096
+SUBKEYTYPE=RSA
+SUBKEYLENGTH=4096
KEYNAME="`hostname --fqdn 2>/dev/null || hostname`"
KEYEMAIL=""
-KEYCOMMENT="Mandos client key"
+KEYCOMMENT=""
KEYEXPIRE=0
FORCE=no
KEYCOMMENT_ORIG="$KEYCOMMENT"
@@ -59,19 +60,18 @@
-v, --version Show program's version number and exit
-h, --help Show this help message and exit
-d DIR, --dir DIR Target directory for key files
- -t TYPE, --type TYPE Key type. Default is DSA.
+ -t TYPE, --type TYPE Key type. Default is RSA.
-l BITS, --length BITS
- Key length in bits. Default is 2048.
+ Key length in bits. Default is 4096.
-s TYPE, --subtype TYPE
- Subkey type. Default is ELG-E.
+ Subkey type. Default is RSA.
-L BITS, --sublength BITS
- Subkey length in bits. Default is 2048.
+ Subkey length in bits. Default is 4096.
-n NAME, --name NAME Name of key. Default is the FQDN.
-e ADDRESS, --email ADDRESS
Email address of key. Default is empty.
-c TEXT, --comment TEXT
- Comment field for key. The default value is
- "Mandos client key".
+ Comment field for key. The default is empty.
-x TIME, --expire TIME
Key expire time. Default is no expiration.
See gpg(1) for syntax.
@@ -146,7 +146,7 @@
echo "Invalid key length" >&2
exit 1
fi
-
+
if [ -z "$KEYEXPIRE" ]; then
echo "Empty key expiration" >&2
exit 1
@@ -171,7 +171,7 @@
if [ -n "$KEYEMAIL" ]; then
KEYEMAILLINE="Name-Email: $KEYEMAIL"
fi
-
+
# Create temporary gpg batch file
BATCHFILE="`mktemp -t mandos-keygen-batch.XXXXXXXXXX`"
fi
@@ -191,9 +191,11 @@
shred --remove \"$RINGDIR\"/sec*;
test -n \"$BATCHFILE\" && rm --force \"$BATCHFILE\"; \
rm --recursive --force \"$RINGDIR\";
-stty echo; \
+tty --quiet && stty echo; \
" EXIT
+set -e
+
umask 077
if [ "$mode" = keygen ]; then
@@ -201,10 +203,10 @@
cat >"$BATCHFILE" <<-EOF
Key-Type: $KEYTYPE
Key-Length: $KEYLENGTH
- #Key-Usage: encrypt,sign,auth
+ Key-Usage: sign,auth
Subkey-Type: $SUBKEYTYPE
Subkey-Length: $SUBKEYLENGTH
- #Subkey-Usage: encrypt,sign,auth
+ Subkey-Usage: encrypt
Name-Real: $KEYNAME
$KEYCOMMENTLINE
$KEYEMAILLINE
@@ -216,12 +218,27 @@
%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
@@ -271,30 +288,39 @@
FILECOMMENT="Encrypted password for a Mandos client"
- if [ -n "$PASSFILE" ]; then
- cat "$PASSFILE"
- else
- stty -echo
- echo -n "Enter passphrase: " >&2
- first="$(head --lines=1 | tr --delete '\n')"
- echo -n -e "\nRepeat passphrase: " >&2
- second="$(head --lines=1 | tr --delete '\n')"
- echo >&2
- stty echo
- if [ "$first" != "$second" ]; then
- echo -e "Passphrase mismatch" >&2
- false
+ while [ ! -s "$SECFILE" ]; do
+ if [ -n "$PASSFILE" ]; then
+ cat "$PASSFILE"
else
- echo -n "$first"
+ 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
- fi | gpg --quiet --batch --no-tty --no-options --enable-dsa2 \
- --homedir "$RINGDIR" --trust-model always --armor --encrypt \
- --recipient "$FINGERPRINT" --comment "$FILECOMMENT" \
- > "$SECFILE"
- status="${PIPESTATUS[0]}"
- if [ "$status" -ne 0 ]; then
- exit "$status"
- fi
+ done
cat <<-EOF
[$KEYNAME]
=== modified file 'mandos-keygen.xml'
--- mandos-keygen.xml 2008-09-19 23:31:34 +0000
+++ mandos-keygen.xml 2013-10-22 19:24:01 +0000
@@ -1,9 +1,10 @@
-
+
+
+%common;
]>
@@ -11,26 +12,29 @@
Mandos Manual
Mandos
- &VERSION;
+ &version;
&TIMESTAMP;
Björn
Påhlsson
- belorn@fukt.bsnet.se
+ belorn@recompile.se
Teddy
Hogeborn
- teddy@fukt.bsnet.se
+ teddy@recompile.se
2008
+ 2009
+ 2011
+ 2012
Teddy Hogeborn
Björn Påhlsson
@@ -211,7 +215,7 @@
Target directory for key files. Default is
- /etc/mandos.
+ /etc/mandos.
@@ -223,7 +227,7 @@
TYPE
- Key type. Default is DSA
.
+ Key type. Default is RSA
.
@@ -235,7 +239,7 @@
BITS
- Key length in bits. Default is 2048.
+ Key length in bits. Default is 4096.
@@ -247,7 +251,7 @@
KEYTYPE
- Subkey type. Default is ELG-E
(Elgamal
+ Subkey type. Default is RSA
(Elgamal
encryption-only).
@@ -260,7 +264,7 @@
BITS
- Subkey length in bits. Default is 2048.
+ Subkey length in bits. Default is 4096.
@@ -284,8 +288,7 @@
TEXT
- Comment field for key. The default value is
- Mandos client key
.
+ Comment field for key. Default is empty.
@@ -381,7 +384,7 @@
-
+
FILES
Use the option to change where
@@ -408,7 +411,7 @@
- /tmp
+ /tmp
Temporary files will be written here if
@@ -449,9 +452,9 @@
- Prompt for a password, encrypt it with the key in
- /etc/mandos and output a section suitable
- for clients.conf.
+ Prompt for a password, encrypt it with the key in /etc/mandos and output a section
+ suitable for clients.conf.
&COMMANDNAME; --password
@@ -490,6 +493,8 @@
SEE ALSO
+ intro
+ 8mandos,
gpg
1,
mandos-clients.conf
=== added file 'mandos-monitor'
--- mandos-monitor 1970-01-01 00:00:00 +0000
+++ mandos-monitor 2014-02-16 13:12:20 +0000
@@ -0,0 +1,724 @@
+#!/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.4"
+
+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
=== added file 'mandos-monitor.xml'
--- mandos-monitor.xml 1970-01-01 00:00:00 +0000
+++ mandos-monitor.xml 2011-12-31 23:05:34 +0000
@@ -0,0 +1,237 @@
+
+
+
+
+%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 2008-09-06 16:31:49 +0000
+++ mandos-options.xml 2013-10-24 20:21:45 +0000
@@ -26,10 +26,12 @@
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 be an IPv6 address; an
- IPv4 address can only be specified using IPv4-mapped IPv6 address
- syntax: ::FFFF:192.0.2.3
.
+ 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.)
@@ -44,11 +46,24 @@
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.
+
+
GnuTLS priority string for the TLS handshake.
The default is SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP
. See
- gnutls_priority_init
+ gnutls_priority_init
3 for the syntax.
Warning: changing this may make the
TLS handshake fail, making server-client
@@ -58,12 +73,54 @@
Zeroconf service name. The default is
Mandos
. This only needs to be
- changed this if it, for some reason, is necessary to run more than
- one server on the same host, which would not
+ changed if for some reason is would be necessary to run more than
+ one server on the same host. This 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 2008-08-18 23:55:28 +0000
+++ mandos.conf 2013-10-22 19:24:01 +0000
@@ -4,35 +4,44 @@
# 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
-
+;priority = SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:+SIGN-RSA-SHA224:+SIGN-RSA-RMD160
# 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 2008-09-12 19:12:40 +0000
+++ mandos.conf.xml 2013-10-24 20:21:45 +0000
@@ -1,10 +1,11 @@
/etc/mandos/mandos.conf">
-
+
+
+%common;
]>
@@ -12,26 +13,30 @@
Mandos Manual
Mandos
- &VERSION;
+ &version;
&TIMESTAMP;
Björn
Påhlsson
- belorn@fukt.bsnet.se
+ belorn@recompile.se
Teddy
Hogeborn
- teddy@fukt.bsnet.se
+ teddy@recompile.se
2008
+ 2009
+ 2011
+ 2012
+ 2013
Teddy Hogeborn
Björn Påhlsson
@@ -116,7 +121,8 @@
-
+
@@ -129,6 +135,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -166,11 +221,15 @@
[DEFAULT]
# A configuration example
interface = eth0
-address = 2001:db8:f983:bd0b:30de:ae4a:71f2:f672
+address = fe80::aede:48ff:fe71:f6f2
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
@@ -178,6 +237,8 @@
SEE ALSO
+ intro
+ 8mandos,
gnutls_priority_init3,
mandos
@@ -211,7 +272,7 @@
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
+ automatically assigned to a network interface when it
is brought up.
=== added file 'mandos.lsm'
--- mandos.lsm 1970-01-01 00:00:00 +0000
+++ mandos.lsm 2014-02-16 13:12:20 +0000
@@ -0,0 +1,22 @@
+Begin4
+Title: Mandos
+Version: 1.6.4
+Entered-date: 2014-02-16
+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
+ 158K mandos_1.6.4.orig.tar.gz
+Alternate-site: ftp://ftp.recompile.se/pub/mandos
+ 158K mandos_1.6.4.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
=== added file 'mandos.service'
--- mandos.service 1970-01-01 00:00:00 +0000
+++ mandos.service 2013-12-31 16:02:18 +0000
@@ -0,0 +1,21 @@
+[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 2008-09-21 12:20:55 +0000
+++ mandos.xml 2013-10-26 19:05:21 +0000
@@ -1,36 +1,42 @@
-
+
+
+%common;
]>
-
+
Mandos Manual
Mandos
- &VERSION;
+ &version;
&TIMESTAMP;
Björn
Påhlsson
- belorn@fukt.bsnet.se
+ belorn@recompile.se
Teddy
Hogeborn
- teddy@fukt.bsnet.se
+ teddy@recompile.se
2008
+ 2009
+ 2010
+ 2011
+ 2012
+ 2013
Teddy Hogeborn
Björn Påhlsson
@@ -83,6 +89,23 @@
DIRECTORY
+
+
+
+
+
+
+
+
+
+
+
+
+
+
&COMMANDNAME;
@@ -106,14 +129,16 @@
&COMMANDNAME; is a server daemon which
handles incoming request for passwords for a pre-defined list of
- 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.
+ 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.
@@ -188,10 +213,29 @@
+
+
+
+ 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
.
+
+
+
+
+
-
+
@@ -227,6 +271,58 @@
+
+
+
+
+
+
+ See also .
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ See also .
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -304,24 +400,67 @@
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. The timeout,
- checker program, and interval between checks can be configured
- both globally and per client; see
+ longer eligible to receive the encrypted password. (Manual
+ intervention is required to re-enable a client.) The timeout,
+ extended timeout, checker program, and interval between checks
+ can be configured both globally and per client; see
+ mandos-clients.conf
+ 5.
+
+
+
+
+ APPROVAL
+
+ The server can be configured to require manual approval for a
+ client before it is sent its secret. The delay to wait for such
+ approval and the default action (approve or deny) can be
+ configured both globally and per client; see
mandos-clients.conf
- 5.
-
+ 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
@@ -350,7 +489,7 @@
-
+
FILES
Use the option to change where
@@ -379,11 +518,29 @@
- /var/run/mandos.pid
-
-
- The file containing the process id of
- &COMMANDNAME;.
+ /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.
@@ -417,26 +574,9 @@
backtrace. This could be considered a feature.
- Currently, if a client is declared invalid
due to
- having timed out, the server does not record this fact onto
- permanent storage. This has some security implications, see
- .
-
-
- There is currently no way of querying the server of the current
- status of clients, other than analyzing its syslog output.
-
-
There is no fine-grained control over logging and debug output.
- Debug mode is conflated with running in the foreground.
-
-
- The console log messages does not show a time stamp.
-
-
This server does not check the expire time of clients’ OpenPGP
keys.
@@ -455,9 +595,9 @@
Run the server in debug mode, read configuration files from
- the ~/mandos directory, and use the
- Zeroconf service name Test
to not collide with
- any other official Mandos server on this host:
+ the ~/mandos directory,
+ and use the Zeroconf service name Test
to not
+ collide with any other official Mandos server on this host:
@@ -482,7 +622,7 @@
SECURITY
-
+
SERVER
Running this &COMMANDNAME; server program
@@ -491,7 +631,7 @@
soon after startup.
-
+
CLIENTS
The server only gives out its stored data to clients which
@@ -512,22 +652,6 @@
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
8mandos.
@@ -538,15 +662,16 @@
SEE ALSO
-
- mandos-clients.conf
- 5,
- mandos.conf
- 5,
- mandos-client
- 8mandos,
- sh1
-
+ intro
+ 8mandos,
+ mandos-clients.conf
+ 5,
+ mandos.conf
+ 5,
+ mandos-client
+ 8mandos,
+ sh
+ 1
=== added directory 'network-hooks.d'
=== added file 'network-hooks.d/bridge'
--- network-hooks.d/bridge 1970-01-01 00:00:00 +0000
+++ network-hooks.d/bridge 2012-06-13 22:06:57 +0000
@@ -0,0 +1,93 @@
+#!/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
=== added file 'network-hooks.d/bridge.conf'
--- network-hooks.d/bridge.conf 1970-01-01 00:00:00 +0000
+++ network-hooks.d/bridge.conf 2011-12-31 13:25:58 +0000
@@ -0,0 +1,11 @@
+## 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"
=== added file 'network-hooks.d/openvpn'
--- network-hooks.d/openvpn 1970-01-01 00:00:00 +0000
+++ network-hooks.d/openvpn 2012-06-13 22:06:57 +0000
@@ -0,0 +1,66 @@
+#!/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
=== added file 'network-hooks.d/openvpn.conf'
--- network-hooks.d/openvpn.conf 1970-01-01 00:00:00 +0000
+++ network-hooks.d/openvpn.conf 2011-12-02 16:52:50 +0000
@@ -0,0 +1,19 @@
+# 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
=== added file 'network-hooks.d/wireless'
--- network-hooks.d/wireless 1970-01-01 00:00:00 +0000
+++ network-hooks.d/wireless 2012-06-13 22:06:57 +0000
@@ -0,0 +1,165 @@
+#!/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
=== added file 'network-hooks.d/wireless.conf'
--- network-hooks.d/wireless.conf 1970-01-01 00:00:00 +0000
+++ network-hooks.d/wireless.conf 2011-12-31 13:25:58 +0000
@@ -0,0 +1,23 @@
+# 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 'plugin-runner.c'
--- plugin-runner.c 2008-09-26 04:54:35 +0000
+++ plugin-runner.c 2013-12-15 22:21:28 +0000
@@ -1,8 +1,9 @@
-/* -*- coding: utf-8 -*- */
+/* -*- coding: utf-8; mode: c; mode: orgtbl -*- */
/*
* Mandos plugin runner - Run Mandos plugins
*
- * Copyright © 2008 Teddy Hogeborn & Björn Påhlsson
+ * Copyright © 2008-2013 Teddy Hogeborn
+ * Copyright © 2008-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
@@ -18,18 +19,18 @@
* along with this program. If not, see
* .
*
- * Contact the authors at .
+ * Contact the authors at .
*/
#define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), getline(),
- asprintf() */
+ asprintf(), O_CLOEXEC */
#include /* size_t, NULL */
-#include /* malloc(), exit(), EXIT_FAILURE,
- EXIT_SUCCESS, realloc() */
+#include /* malloc(), exit(), EXIT_SUCCESS,
+ realloc() */
#include /* bool, true, false */
-#include /* perror, fileno(), fprintf(),
+#include /* fileno(), fprintf(),
stderr, STDOUT_FILENO */
-#include /* DIR, opendir(), stat(), struct
+#include /* DIR, fdopendir(), stat(), struct
stat, waitpid(), WIFEXITED(),
WEXITSTATUS(), wait(), pid_t,
uid_t, gid_t, getuid(), getgid(),
@@ -37,10 +38,11 @@
#include /* fd_set, select(), FD_ZERO(),
FD_SET(), FD_ISSET(), FD_CLR */
#include /* wait(), waitpid(), WIFEXITED(),
- WEXITSTATUS() */
+ WEXITSTATUS(), WTERMSIG(),
+ WCOREDUMP() */
#include /* struct stat, stat(), S_ISREG() */
#include /* and, or, not */
-#include /* DIR, struct dirent, opendir(),
+#include /* DIR, struct dirent, fdopendir(),
readdir(), closedir(), dirfd() */
#include /* struct stat, stat(), S_ISREG(),
fcntl(), setuid(), setgid(),
@@ -51,7 +53,8 @@
close() */
#include /* fcntl(), F_GETFD, F_SETFD,
FD_CLOEXEC */
-#include /* strsep, strlen(), asprintf() */
+#include /* strsep, strlen(), asprintf(),
+ strsignal(), strcmp(), strncmp() */
#include /* errno */
#include /* struct argp_option, struct
argp_state, struct argp,
@@ -61,16 +64,22 @@
#include /* struct sigaction, sigemptyset(),
sigaddset(), sigaction(),
sigprocmask(), SIG_BLOCK, SIGCHLD,
- SIG_UNBLOCK, kill() */
+ SIG_UNBLOCK, kill(), sig_atomic_t
+ */
#include /* errno, EBADF */
+#include /* intmax_t, PRIdMAX, strtoimax() */
+#include /* EX_OSERR, EX_USAGE, EX_IOERR,
+ EX_CONFIG, EX_UNAVAILABLE, EX_OK */
+#include /* 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 1.0";
-const char *argp_program_bug_address = "";
+const char *argp_program_version = "plugin-runner " VERSION;
+const char *argp_program_bug_address = "";
typedef struct plugin{
char *name; /* can be NULL or any plugin name */
@@ -79,7 +88,7 @@
char **environ;
int envc;
bool disabled;
-
+
/* Variables used for running processes*/
pid_t pid;
int fd;
@@ -87,8 +96,8 @@
size_t buffer_size;
size_t buffer_length;
bool eof;
- volatile bool completed;
- volatile int status;
+ volatile sig_atomic_t completed;
+ int status;
struct plugin *next;
} plugin;
@@ -97,45 +106,61 @@
/* Gets an existing plugin based on name,
or if none is found, creates a new one */
static plugin *getplugin(char *name){
- /* Check for exiting 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))){
+ /* 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))){
return p;
}
}
/* Create a new plugin */
- plugin *new_plugin = malloc(sizeof(plugin));
- if (new_plugin == NULL){
+ 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){
- copy_name = strdup(name);
+ 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 };
+ *new_plugin = (plugin){ .name = copy_name,
+ .argc = 1,
+ .disabled = false,
+ .next = plugin_list };
- new_plugin->argv = malloc(sizeof(char *) * 2);
- if (new_plugin->argv == NULL){
+ 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);
free(new_plugin);
+ errno = e;
return NULL;
}
new_plugin->argv[0] = copy_name;
new_plugin->argv[1] = NULL;
- new_plugin->environ = malloc(sizeof(char *));
+ do {
+ new_plugin->environ = malloc(sizeof(char *));
+ } while(new_plugin->environ == NULL and errno == EINTR);
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;
@@ -146,17 +171,23 @@
}
/* 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 */
- *array = realloc(*array, sizeof(char *)
- * (size_t) ((*len) + 2));
+ do {
+ *array = realloc(*array, sizeof(char *)
+ * (size_t) ((*len) + 2));
+ } while(*array == NULL and errno == EINTR);
/* Malloc check */
if(*array == NULL){
return false;
}
/* Make a copy of the new string */
- char *copy = strdup(new);
+ char *copy;
+ do {
+ copy = strdup(new);
+ } while(copy == NULL and errno == EINTR);
if(copy == NULL){
return false;
}
@@ -169,6 +200,7 @@
}
/* Add to a plugin's argument vector */
+__attribute__((nonnull(2)))
static bool add_argument(plugin *p, const char *arg){
if(p == NULL){
return false;
@@ -177,6 +209,7 @@
}
/* Add to a plugin's environment */
+__attribute__((nonnull(2)))
static bool add_environment(plugin *p, const char *def, bool replace){
if(p == NULL){
return false;
@@ -188,7 +221,10 @@
if(strncmp(*e, def, namelen + 1) == 0){
/* It already exists */
if(replace){
- char *new = realloc(*e, strlen(def) + 1);
+ char *new;
+ do {
+ new = realloc(*e, strlen(def) + 1);
+ } while(new == NULL and errno == EINTR);
if(new == NULL){
return false;
}
@@ -204,22 +240,24 @@
/*
* Based on the example in the GNU LibC manual chapter 13.13 "File
* Descriptor Flags".
- * *Note File Descriptor Flags:(libc)Descriptor Flags.
+ | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] |
*/
static int set_cloexec_flag(int fd){
- int ret = fcntl(fd, F_GETFD, 0);
+ int ret = (int)TEMP_FAILURE_RETRY(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 fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
+ return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD,
+ ret | FD_CLOEXEC));
}
/* Mark processes as completed when they exit, and save their exit
status. */
static void handle_sigchld(__attribute__((unused)) int sig){
+ int old_errno = errno;
while(true){
plugin *proc = plugin_list;
int status;
@@ -229,11 +267,11 @@
break;
}
if(pid == -1){
- if (errno != ECHILD){
- perror("waitpid");
+ if(errno == ECHILD){
+ /* No child processes */
+ break;
}
- /* No child processes */
- break;
+ error(0, errno, "waitpid");
}
/* A child exited, find it in process_list */
@@ -245,11 +283,13 @@
continue;
}
proc->status = status;
- proc->completed = true;
+ proc->completed = 1;
}
+ errno = old_errno;
}
/* Prints out a password to stdout */
+__attribute__((nonnull))
static bool print_out_password(const char *buffer, size_t length){
ssize_t ret;
for(size_t written = 0; written < length; written += (size_t)ret){
@@ -263,6 +303,7 @@
}
/* 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++){
@@ -274,7 +315,7 @@
}
free(plugin_node->environ);
free(plugin_node->buffer);
-
+
/* Removes the plugin from the singly-linked list */
if(plugin_node == plugin_list){
/* First one - simple */
@@ -308,6 +349,7 @@
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;
@@ -322,14 +364,14 @@
sigemptyset(&sigchld_action.sa_mask);
ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD);
if(ret == -1){
- perror("sigaddset");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "sigaddset");
+ exitstatus = EX_OSERR;
goto fallback;
}
ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
if(ret == -1){
- perror("sigaction");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "sigaction");
+ exitstatus = EX_OSERR;
goto fallback;
}
@@ -367,130 +409,162 @@
.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 = NULL }
};
- error_t parse_opt (int key, char *arg, __attribute__((unused))
- struct argp_state *state) {
- switch (key) {
+ __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 */
- if (arg != NULL){
- char *p;
- while((p = strsep(&arg, ",")) != NULL){
- if(p[0] == '\0'){
- continue;
- }
- if(not add_argument(getplugin(NULL), p)){
- perror("add_argument");
- return ARGP_ERR_UNKNOWN;
+ {
+ char *plugin_option;
+ while((plugin_option = strsep(&arg, ",")) != NULL){
+ if(not add_argument(getplugin(NULL), plugin_option)){
+ break;
}
}
}
break;
case 'G': /* --global-env */
- if(arg == NULL){
- break;
- }
- if(not add_environment(getplugin(NULL), arg, true)){
- perror("add_environment");
- }
+ add_environment(getplugin(NULL), arg, true);
break;
case 'o': /* --options-for */
- if (arg != NULL){
- char *p_name = strsep(&arg, ":");
- if(p_name[0] == '\0' or arg == NULL){
- break;
- }
- char *opt = strsep(&arg, ":");
- if(opt[0] == '\0' or opt == NULL){
- break;
- }
- char *p;
- while((p = strsep(&opt, ",")) != NULL){
- if(p[0] == '\0'){
- continue;
- }
- if(not add_argument(getplugin(p_name), p)){
- perror("add_argument");
- return ARGP_ERR_UNKNOWN;
+ {
+ 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 */
- if(arg == NULL){
- break;
- }
{
char *envdef = strchr(arg, ':');
if(envdef == NULL){
+ argp_error(state, "No colon in \"%s\"", arg);
+ errno = EINVAL;
break;
}
*envdef = '\0';
- if(not add_environment(getplugin(arg), envdef+1, true)){
- perror("add_environment");
+ envdef++;
+ if(arg[0] == '\0'){
+ argp_error(state, "Empty plugin name");
+ errno = EINVAL;
+ break;
}
+ add_environment(getplugin(arg), envdef, true);
}
break;
case 'd': /* --disable */
- if (arg != NULL){
+ {
plugin *p = getplugin(arg);
- if(p == NULL){
- return ARGP_ERR_UNKNOWN;
+ if(p != NULL){
+ p->disabled = true;
}
- p->disabled = true;
}
break;
case 'e': /* --enable */
- if (arg != NULL){
+ {
plugin *p = getplugin(arg);
- if(p == NULL){
- return ARGP_ERR_UNKNOWN;
+ if(p != NULL){
+ p->disabled = false;
}
- p->disabled = false;
}
break;
case 128: /* --plugin-dir */
free(plugindir);
plugindir = strdup(arg);
- if(plugindir == NULL){
- perror("strdup");
- }
break;
case 129: /* --config-file */
/* This is already done by parse_opt_config_file() */
break;
case 130: /* --userid */
- uid = (uid_t)strtol(arg, NULL, 10);
+ 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 */
- gid = (gid_t)strtol(arg, NULL, 10);
+ 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 */
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'){
- fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
+ if(arg[0] == '\0'){
+ break;
}
- break;
- case ARGP_KEY_END:
- break;
default:
return ARGP_ERR_UNKNOWN;
}
- return 0;
+ 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) {
- switch (key) {
+ 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 */
@@ -502,20 +576,19 @@
case 129: /* --config-file */
free(argfile);
argfile = strdup(arg);
- if(argfile == NULL){
- perror("strdup");
- }
- break;
+ break;
case 130: /* --userid */
case 131: /* --groupid */
case 132: /* --debug */
+ case '?': /* --help */
+ case -3: /* --usage */
+ case 'V': /* --version */
case ARGP_KEY_ARG:
- case ARGP_KEY_END:
break;
default:
return ARGP_ERR_UNKNOWN;
}
- return 0;
+ return errno;
}
struct argp argp = { .options = options,
@@ -523,12 +596,22 @@
.args_doc = "",
.doc = "Mandos plugin runner -- Run plugins" };
- /* Parse using the parse_opt_config_file in order to get the custom
+ /* 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, 0, NULL);
- if (ret == ARGP_ERR_UNKNOWN){
- fprintf(stderr, "Unknown error while parsing arguments\n");
- exitstatus = EXIT_FAILURE;
+ 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;
}
@@ -536,29 +619,28 @@
argp.parser = parse_opt;
/* Open the configfile if available */
- if (argfile == NULL){
+ if(argfile == NULL){
conffp = fopen(AFILE, "r");
} else {
conffp = fopen(argfile, "r");
- }
+ }
if(conffp != NULL){
char *org_line = NULL;
char *p, *arg, *new_arg, *line;
size_t size = 0;
- ssize_t sret;
const char whitespace_delims[] = " \r\t\f\v\n";
const char comment_delim[] = "#";
-
+
custom_argc = 1;
custom_argv = malloc(sizeof(char*) * 2);
if(custom_argv == NULL){
- perror("malloc");
- exitstatus = EXIT_FAILURE;
+ 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){
@@ -566,7 +648,7 @@
if(sret == -1){
break;
}
-
+
line = org_line;
arg = strsep(&line, comment_delim);
while((p = strsep(&arg, whitespace_delims)) != NULL){
@@ -575,8 +657,8 @@
}
new_arg = strdup(p);
if(new_arg == NULL){
- perror("strdup");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "strdup");
+ exitstatus = EX_OSERR;
free(org_line);
goto fallback;
}
@@ -585,43 +667,70 @@
custom_argv = realloc(custom_argv, sizeof(char *)
* ((unsigned int) custom_argc + 1));
if(custom_argv == NULL){
- perror("realloc");
- exitstatus = EXIT_FAILURE;
+ 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[custom_argc] = NULL;
}
}
+ do {
+ ret = fclose(conffp);
+ } while(ret == EOF and errno == EINTR);
+ if(ret == EOF){
+ error(0, errno, "fclose");
+ exitstatus = EX_IOERR;
+ goto fallback;
+ }
free(org_line);
} else {
/* Check for harmful errors and go to fallback. Other errors might
not affect opening plugins */
- if (errno == EMFILE or errno == ENFILE or errno == ENOMEM){
- perror("fopen");
- exitstatus = EXIT_FAILURE;
+ if(errno == EMFILE or errno == ENFILE or errno == ENOMEM){
+ error(0, errno, "fopen");
+ exitstatus = EX_OSERR;
goto fallback;
}
}
- /* If there was any arguments from configuration file,
- pass them to parser as command arguments */
+ /* 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,
- 0, NULL);
- if (ret == ARGP_ERR_UNKNOWN){
- fprintf(stderr, "Unknown error while parsing arguments\n");
- exitstatus = EXIT_FAILURE;
+ ret = argp_parse(&argp, custom_argc, custom_argv,
+ ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP,
+ NULL, NULL);
+ switch(ret){
+ case 0:
+ break;
+ case ENOMEM:
+ default:
+ errno = ret;
+ error(0, errno, "argp_parse");
+ exitstatus = EX_OSERR;
+ goto fallback;
+ case EINVAL:
+ exitstatus = EX_CONFIG;
goto fallback;
}
}
/* Parse actual command line arguments, to let them override the
config file */
- ret = argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, NULL);
- if (ret == ARGP_ERR_UNKNOWN){
- fprintf(stderr, "Unknown error while parsing arguments\n");
- exitstatus = EXIT_FAILURE;
+ 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;
}
@@ -632,45 +741,88 @@
for(char **a = p->argv; *a != NULL; a++){
fprintf(stderr, "\tArg: %s\n", *a);
}
- fprintf(stderr, "...and %u environment variables\n", p->envc);
+ fprintf(stderr, "...and %d environment variables\n", p->envc);
for(char **a = p->environ; *a != NULL; a++){
fprintf(stderr, "\t%s\n", *a);
}
}
}
- /* Strip permissions down to nobody */
+ 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){
- perror("setuid");
- }
- setgid(gid);
- if (ret == -1){
- perror("setgid");
- }
-
- if (plugindir == NULL){
- dir = opendir(PDIR);
- } else {
- dir = opendir(plugindir);
- }
-
- if(dir == NULL){
- perror("Could not open plugin dir");
- exitstatus = EXIT_FAILURE;
- goto fallback;
- }
-
- /* Set the FD_CLOEXEC flag on the directory, if possible */
+ if(ret == -1){
+ error(0, errno, "setuid");
+ }
+
+ /* Open plugin directory with close_on_exec flag */
{
- 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;
- }
+ 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;
}
}
@@ -678,13 +830,15 @@
/* Read and execute any executable in the plugin directory*/
while(true){
- dirst = readdir(dir);
+ do {
+ dirst = readdir(dir);
+ } while(dirst == NULL and errno == EINTR);
/* All directory entries have been processed */
if(dirst == NULL){
- if (errno == EBADF){
- perror("readdir");
- exitstatus = EXIT_FAILURE;
+ if(errno == EBADF){
+ error(0, errno, "readdir");
+ exitstatus = EX_IOERR;
goto fallback;
}
break;
@@ -700,6 +854,7 @@
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);
@@ -719,7 +874,7 @@
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\""
@@ -734,27 +889,31 @@
continue;
}
}
-
+
char *filename;
if(plugindir == NULL){
- ret = asprintf(&filename, PDIR "/%s", dirst->d_name);
+ ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, PDIR "/%s",
+ dirst->d_name));
} else {
- ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name);
+ ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, "%s/%s",
+ plugindir,
+ dirst->d_name));
}
if(ret < 0){
- perror("asprintf");
+ error(0, errno, "asprintf");
continue;
}
- ret = stat(filename, &st);
- if (ret == -1){
- perror("stat");
+ ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st));
+ if(ret == -1){
+ error(0, errno, "stat");
free(filename);
continue;
}
-
+
/* Ignore non-executable files */
- if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
+ if(not S_ISREG(st.st_mode)
+ or (TEMP_FAILURE_RETRY(access(filename, X_OK)) != 0)){
if(debug){
fprintf(stderr, "Ignoring plugin dir entry \"%s\""
" with bad type or mode\n", filename);
@@ -765,7 +924,7 @@
plugin *p = getplugin(dirst->d_name);
if(p == NULL){
- perror("getplugin");
+ error(0, errno, "getplugin");
free(filename);
continue;
}
@@ -783,13 +942,13 @@
if(g != NULL){
for(char **a = g->argv + 1; *a != NULL; a++){
if(not add_argument(p, *a)){
- perror("add_argument");
+ error(0, errno, "add_argument");
}
}
/* Add global environment variables */
for(char **e = g->environ; *e != NULL; e++){
if(not add_environment(p, *e, false)){
- perror("add_environment");
+ error(0, errno, "add_environment");
}
}
}
@@ -800,62 +959,67 @@
if(p->environ[0] != NULL){
for(char **e = environ; *e != NULL; e++){
if(not add_environment(p, *e, false)){
- perror("add_environment");
+ error(0, errno, "add_environment");
}
}
}
int pipefd[2];
- ret = pipe(pipefd);
- if (ret == -1){
- perror("pipe");
- exitstatus = EXIT_FAILURE;
+ ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd));
+ if(ret == -1){
+ error(0, errno, "pipe");
+ exitstatus = EX_OSERR;
goto fallback;
}
/* Ask OS to automatic close the pipe on exec */
ret = set_cloexec_flag(pipefd[0]);
if(ret < 0){
- perror("set_cloexec_flag");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "set_cloexec_flag");
+ exitstatus = EX_OSERR;
goto fallback;
}
ret = set_cloexec_flag(pipefd[1]);
if(ret < 0){
- perror("set_cloexec_flag");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "set_cloexec_flag");
+ exitstatus = EX_OSERR;
goto fallback;
}
/* Block SIGCHLD until process is safely in process list */
- ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
+ ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
+ &sigchld_action.sa_mask,
+ NULL));
if(ret < 0){
- perror("sigprocmask");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "sigprocmask");
+ exitstatus = EX_OSERR;
goto fallback;
}
/* Starting a new process to be watched */
- pid_t pid = fork();
+ pid_t pid;
+ do {
+ pid = fork();
+ } while(pid == -1 and errno == EINTR);
if(pid == -1){
- perror("fork");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "fork");
+ exitstatus = EX_OSERR;
goto fallback;
}
if(pid == 0){
/* this is the child process */
ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
if(ret < 0){
- perror("sigaction");
- _exit(EXIT_FAILURE);
+ error(0, errno, "sigaction");
+ _exit(EX_OSERR);
}
ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
if(ret < 0){
- perror("sigprocmask");
- _exit(EXIT_FAILURE);
+ error(0, errno, "sigprocmask");
+ _exit(EX_OSERR);
}
ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
if(ret == -1){
- perror("dup2");
- _exit(EXIT_FAILURE);
+ error(0, errno, "dup2");
+ _exit(EX_OSERR);
}
if(dirfd(dir) < 0){
@@ -865,28 +1029,31 @@
}
if(p->environ[0] == NULL){
if(execv(filename, p->argv) < 0){
- perror("execv");
- _exit(EXIT_FAILURE);
+ error(0, errno, "execv for %s", filename);
+ _exit(EX_OSERR);
}
} else {
if(execve(filename, p->argv, p->environ) < 0){
- perror("execve");
- _exit(EXIT_FAILURE);
+ error(0, errno, "execve for %s", filename);
+ _exit(EX_OSERR);
}
}
/* no return */
}
/* Parent process */
- close(pipefd[1]); /* Close unused write end of pipe */
+ TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of
+ pipe */
free(filename);
plugin *new_plugin = getplugin(dirst->d_name);
- if (new_plugin == NULL){
- perror("getplugin");
- ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
+ if(new_plugin == NULL){
+ error(0, errno, "getplugin");
+ ret = (int)(TEMP_FAILURE_RETRY
+ (sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask,
+ NULL)));
if(ret < 0){
- perror("sigprocmask");
+ error(0, errno, "sigprocmask");
}
- exitstatus = EXIT_FAILURE;
+ exitstatus = EX_OSERR;
goto fallback;
}
@@ -895,22 +1062,38 @@
/* Unblock SIGCHLD so signal handler can be run if this process
has already completed */
- ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
+ ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
+ &sigchld_action.sa_mask,
+ NULL));
if(ret < 0){
- perror("sigprocmask");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "sigprocmask");
+ exitstatus = EX_OSERR;
goto fallback;
}
- FD_SET(new_plugin->fd, &rfds_all);
+#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
- if (maxfd < new_plugin->fd){
+ if(maxfd < new_plugin->fd){
maxfd = new_plugin->fd;
}
}
- closedir(dir);
+ TEMP_FAILURE_RETRY(closedir(dir));
dir = NULL;
+ free_plugin(getplugin(NULL));
for(plugin *p = plugin_list; p != NULL; p = p->next){
if(p->pid != 0){
@@ -927,44 +1110,63 @@
while(plugin_list){
fd_set rfds = rfds_all;
int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
- if (select_ret == -1){
- perror("select");
- exitstatus = EXIT_FAILURE;
+ if(select_ret == -1 and errno != EINTR){
+ error(0, errno, "select");
+ exitstatus = EX_OSERR;
goto fallback;
}
/* OK, now either a process completed, or something can be read
from one of them */
for(plugin *proc = plugin_list; proc != NULL;){
/* Is this process completely done? */
- if(proc->eof and proc->completed){
+ if(proc->completed and proc->eof){
/* 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 %u exited with status %d\n",
- (unsigned int) (proc->pid),
+ fprintf(stderr, "Plugin %s [%" PRIdMAX "] exited with"
+ " status %d\n", proc->name,
+ (intmax_t) (proc->pid),
WEXITSTATUS(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(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(WCOREDUMP(proc->status)){
- fprintf(stderr, "Plugin %d dumped core\n",
- (unsigned int) (proc->pid));
+ fprintf(stderr, "Plugin %s [%" PRIdMAX "] dumped"
+ " core\n", proc->name, (intmax_t) (proc->pid));
}
}
/* Remove the plugin */
- FD_CLR(proc->fd, &rfds_all);
-
+#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
+
/* Block signal while modifying process_list */
- ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL);
+ ret = (int)TEMP_FAILURE_RETRY(sigprocmask
+ (SIG_BLOCK,
+ &sigchld_action.sa_mask,
+ NULL));
if(ret < 0){
- perror("sigprocmask");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "sigprocmask");
+ exitstatus = EX_OSERR;
goto fallback;
}
@@ -973,11 +1175,12 @@
proc = next_plugin;
/* We are done modifying process list, so unblock signal */
- ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
- NULL);
+ ret = (int)(TEMP_FAILURE_RETRY
+ (sigprocmask(SIG_UNBLOCK,
+ &sigchld_action.sa_mask, NULL)));
if(ret < 0){
- perror("sigprocmask");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "sigprocmask");
+ exitstatus = EX_OSERR;
goto fallback;
}
@@ -993,14 +1196,30 @@
bool bret = print_out_password(proc->buffer,
proc->buffer_length);
if(not bret){
- perror("print_out_password");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "print_out_password");
+ exitstatus = EX_IOERR;
}
goto fallback;
}
/* This process has not completed. Does it have any output? */
- if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
+#if 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
/* This process had nothing to say at this time */
proc = proc->next;
continue;
@@ -1009,34 +1228,37 @@
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){
- perror("malloc");
- exitstatus = EXIT_FAILURE;
+ if(proc->buffer == NULL){
+ error(0, errno, "malloc");
+ exitstatus = EX_OSERR;
goto fallback;
}
proc->buffer_size += BUFFER_SIZE;
}
/* Read from the process */
- ret = read(proc->fd, proc->buffer + proc->buffer_length,
- BUFFER_SIZE);
- if(ret < 0){
+ sret = TEMP_FAILURE_RETRY(read(proc->fd,
+ proc->buffer
+ + proc->buffer_length,
+ BUFFER_SIZE));
+ if(sret < 0){
/* Read error from this process; ignore the error */
proc = proc->next;
continue;
}
- if(ret == 0){
+ if(sret == 0){
/* got EOF */
proc->eof = true;
} else {
- proc->buffer_length += (size_t) ret;
+ proc->buffer_length += (size_t) sret;
}
}
}
-
-
+
+
fallback:
- if(plugin_list == NULL or exitstatus != EXIT_SUCCESS){
+ if(plugin_list == NULL or (exitstatus != EXIT_SUCCESS
+ and exitstatus != EX_OK)){
/* Fallback if all plugins failed, none are found or an error
occured */
bool bret;
@@ -1050,16 +1272,16 @@
}
bret = print_out_password(passwordbuffer, len);
if(not bret){
- perror("print_out_password");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "print_out_password");
+ exitstatus = EX_IOERR;
}
}
/* Restore old signal handler */
ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
if(ret == -1){
- perror("sigaction");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "sigaction");
+ exitstatus = EX_OSERR;
}
if(custom_argv != NULL){
@@ -1080,17 +1302,17 @@
ret = kill(p->pid, SIGTERM);
if(ret == -1 and errno != ESRCH){
/* Set-uid proccesses might not get closed */
- perror("kill");
+ error(0, errno, "kill");
}
}
}
/* Wait for any remaining child processes to terminate */
- do{
+ do {
ret = wait(NULL);
} while(ret >= 0);
if(errno != ECHILD){
- perror("wait");
+ error(0, errno, "wait");
}
free_plugin_list();
=== modified file 'plugin-runner.conf'
--- plugin-runner.conf 2008-09-06 16:31:49 +0000
+++ plugin-runner.conf 2009-04-17 08:26:17 +0000
@@ -1,9 +1,10 @@
-## This is the configuration file for plugin-runner. It should be
-## installed as "/etc/mandos/plugin-runner.conf", which will be copied
-## to "/conf/conf.d/mandos/plugin-runner.conf" in the initrd.img file.
-##
-## The default network interface for mandos-client(8mandos) is
-## "eth0". Uncomment this line and change it if necessary.
-##
+## 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!
-#--options-for=mandos-client:--interface=eth0
+## Example:
+#--options-for=mandos-client:--debug
=== modified file 'plugin-runner.xml'
--- plugin-runner.xml 2008-09-19 00:00:51 +0000
+++ plugin-runner.xml 2011-12-31 23:05:34 +0000
@@ -1,9 +1,10 @@
-
+
+
+%common;
]>
@@ -11,26 +12,28 @@
Mandos Manual
Mandos
- &VERSION;
+ &version;
&TIMESTAMP;
Björn
Påhlsson
- belorn@fukt.bsnet.se
+ belorn@recompile.se
Teddy
Hogeborn
- teddy@fukt.bsnet.se
+ teddy@recompile.se
2008
+ 2009
+ 2012
Teddy Hogeborn
Björn Påhlsson
@@ -258,7 +261,7 @@
Disable the plugin named
PLUGIN. The plugin will not be
started.
-
+
@@ -577,7 +580,7 @@
-&COMMANDNAME; --config-file=/etc/mandos/plugin-runner.conf --plugin-dir /usr/lib/mandos/plugins.d --options-for=mandos-client:--pubkey=/etc/keys/mandos/pubkey.txt,--seckey=/etc/keys/mandos/seckey.txt
+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
@@ -615,6 +618,8 @@
SEE ALSO
+ intro
+ 8mandos,
cryptsetup
8,
crypttab
=== modified file 'plugins.d/askpass-fifo.c'
--- plugins.d/askpass-fifo.c 2008-09-26 19:47:21 +0000
+++ plugins.d/askpass-fifo.c 2011-12-31 23:05:34 +0000
@@ -1,15 +1,73 @@
+/* -*- 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, EEXIST */
-#include /* perror() */
-#include /* EXIT_FAILURE, NULL, size_t, free(),
+#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){
@@ -18,17 +76,47 @@
/* Create FIFO */
const char passfifo[] = "/lib/cryptsetup/passfifo";
- ret = TEMP_FAILURE_RETRY(mkfifo(passfifo, S_IRUSR | S_IWUSR));
- if(ret == -1 and errno != EEXIST){
- perror("mkfifo");
- return EXIT_FAILURE;
+ 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 = TEMP_FAILURE_RETRY(open(passfifo, O_RDONLY));
+ int fifo_fd = open(passfifo, O_RDONLY);
if(fifo_fd == -1){
- perror("open");
- return EXIT_FAILURE;
+ 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 */
@@ -37,44 +125,78 @@
{
size_t buf_allocated = 0;
const size_t blocksize = 1024;
- do{
+ do {
if(buf_len + blocksize > buf_allocated){
char *tmp = realloc(buf, buf_allocated + blocksize);
if(tmp == NULL){
- perror("realloc");
+ error_plus(0, errno, "realloc");
free(buf);
- return EXIT_FAILURE;
+ return EX_OSERR;
}
buf = tmp;
buf_allocated += blocksize;
}
- sret = TEMP_FAILURE_RETRY(read(fifo_fd, buf + buf_len,
- buf_allocated - buf_len));
+ sret = read(fifo_fd, buf + buf_len, buf_allocated - buf_len);
if(sret == -1){
- perror("read");
+ int e = errno;
free(buf);
- return EXIT_FAILURE;
+ 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);
+ } while(sret != 0);
}
/* Close FIFO */
- TEMP_FAILURE_RETRY(close(fifo_fd));
+ close(fifo_fd);
/* Print password to stdout */
size_t written = 0;
while(written < buf_len){
- sret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buf + written,
- buf_len - written));
+ sret = write(STDOUT_FILENO, buf + written, buf_len - written);
if(sret == -1){
- perror("write");
+ int e = errno;
free(buf);
- return EXIT_FAILURE;
+ 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;
}
=== added file 'plugins.d/askpass-fifo.xml'
--- plugins.d/askpass-fifo.xml 1970-01-01 00:00:00 +0000
+++ plugins.d/askpass-fifo.xml 2011-12-31 23:05:34 +0000
@@ -0,0 +1,166 @@
+
+
+
+
+%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/mandos-client.c'
--- plugins.d/mandos-client.c 2008-09-19 20:42:17 +0000
+++ plugins.d/mandos-client.c 2013-12-15 22:21: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,7 +9,8 @@
* "browse_callback", and parts of "main".
*
* Everything else is
- * Copyright © 2008 Teddy Hogeborn & Björn Påhlsson
+ * Copyright © 2008-2013 Teddy Hogeborn
+ * Copyright © 2008-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
@@ -25,52 +26,75 @@
* 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() */
-#include /* uint16_t, uint32_t */
+ stdout, ferror(), remove() */
+#include /* uint16_t, uint32_t, intptr_t */
#include /* NULL, size_t, ssize_t */
-#include /* free(), EXIT_SUCCESS, EXIT_FAILURE,
- srand() */
-#include /* bool, true */
+#include /* free(), EXIT_SUCCESS, srand(),
+ strtof(), abort() */
+#include /* bool, false, true */
#include /* memset(), strcmp(), strlen(),
strerror(), asprintf(), strcpy() */
-#include /* ioctl */
+#include /* ioctl */
#include /* socket(), inet_pton(), sockaddr,
sockaddr_in6, PF_INET6,
- SOCK_STREAM, INET6_ADDRSTRLEN,
- uid_t, gid_t, open(), opendir(), DIR */
-#include /* open() */
+ SOCK_STREAM, uid_t, gid_t, open(),
+ opendir(), DIR */
+#include /* open(), S_ISREG */
#include /* socket(), struct sockaddr_in6,
- struct in6_addr, inet_pton(),
- connect() */
+ inet_pton(), connect() */
#include /* open() */
-#include /* opendir(), struct dirent, readdir() */
-#include /* PRIu16 */
-#include /* assert() */
-#include /* perror(), errno */
-#include /* time() */
+#include /* opendir(), struct dirent, readdir()
+ */
+#include /* PRIu16, PRIdMAX, intmax_t,
+ strtoimax() */
+#include /* perror(), errno,
+ program_invocation_short_name */
+#include /* nanosleep(), time(), sleep() */
#include /* ioctl, ifreq, SIOCGIFFLAGS, IFF_UP,
SIOCSIFFLAGS, if_indextoname(),
if_nametoindex(), IF_NAMESIZE */
-#include
+#include /* IN6_IS_ADDR_LINKLOCAL,
+ INET_ADDRSTRLEN, INET6_ADDRSTRLEN
+ */
#include /* close(), SEEK_SET, off_t, write(),
- getuid(), getgid(), setuid(),
- setgid() */
-#include /* inet_pton(), htons */
-#include /* not, and */
+ getuid(), getgid(), seteuid(),
+ setgid(), pause(), _exit() */
+#include /* inet_pton(), htons, inet_ntop() */
+#include /* not, or, and */
#include /* struct argp_option, error_t, struct
argp_state, struct argp,
argp_parse(), ARGP_KEY_ARG,
ARGP_KEY_END, ARGP_ERR_UNKNOWN */
+#include /* sigemptyset(), sigaddset(),
+ sigaction(), SIGTERM, sig_atomic_t,
+ raise() */
+#include /* EX_OSERR, EX_USAGE, EX_UNAVAILABLE,
+ EX_NOHOST, EX_IOERR, EX_PROTOCOL */
+#include /* waitpid(), WIFEXITED(),
+ WEXITSTATUS(), WTERMSIG() */
+#include /* setgroups() */
+#include /* 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
@@ -89,8 +113,9 @@
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
@@ -101,40 +126,80 @@
#define BUFFER_SIZE 256
-/*
- #define PATHDIR "/conf/conf.d/mandos"
-*/
-
#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 mandos_protocol_version[] = "1";
-const char *argp_program_version = "mandos-client 1.0";
-const char *argp_program_bug_address = "";
+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;
/* 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 room in "buffer" for at least BUFFER_SIZE additional bytes.
- * "buffer_capacity" is how much is currently allocated,
+ * Make additional room in "buffer" for at least BUFFER_SIZE more
+ * bytes. "buffer_capacity" is how much is currently allocated,
* "buffer_length" is how much is already used.
*/
-size_t adjustbuffer(char **buffer, size_t buffer_length,
- size_t buffer_capacity){
- if (buffer_length + BUFFER_SIZE > buffer_capacity){
+size_t incbuffer(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;
@@ -142,69 +207,105 @@
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.
*/
-static bool init_gpgme(mandos_context *mc, const char *seckey,
- const char *pubkey, const char *tempdir){
- int ret;
+static bool init_gpgme(const char *seckey, const char *pubkey,
+ const char *tempdir, mandos_context *mc){
gpgme_error_t rc;
gpgme_engine_info_t engine_info;
-
/*
- * Helper function to insert pub and seckey to the enigne keyring.
+ * 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 = TEMP_FAILURE_RETRY(open(filename, O_RDONLY));
+ fd = (int)TEMP_FAILURE_RETRY(open(filename, O_RDONLY));
if(fd == -1){
- perror("open");
+ perror_plus("open");
return false;
}
rc = gpgme_data_new_from_fd(&pgp_data, fd);
- if (rc != GPG_ERR_NO_ERROR){
- fprintf(stderr, "bad gpgme_data_new_from_fd: %s: %s\n",
- gpgme_strsource(rc), gpgme_strerror(rc));
+ 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(stderr, "bad gpgme_op_import: %s: %s\n",
- gpgme_strsource(rc), gpgme_strerror(rc));
+ 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 = TEMP_FAILURE_RETRY(close(fd));
+ ret = (int)TEMP_FAILURE_RETRY(close(fd));
if(ret == -1){
- perror("close");
+ perror_plus("close");
}
gpgme_data_release(pgp_data);
return true;
}
- if (debug){
- fprintf(stderr, "Initialize gpgme\n");
+ if(debug){
+ fprintf_plus(stderr, "Initializing GPGME\n");
}
/* Init GPGME */
gpgme_check_version(NULL);
rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
- if (rc != GPG_ERR_NO_ERROR){
- fprintf(stderr, "bad gpgme_engine_check_version: %s: %s\n",
- gpgme_strsource(rc), gpgme_strerror(rc));
+ 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;
}
- /* Set GPGME home directory for the OpenPGP engine only */
- 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));
+ /* 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;
}
while(engine_info != NULL){
@@ -216,57 +317,60 @@
engine_info = engine_info->next;
}
if(engine_info == NULL){
- fprintf(stderr, "Could not set GPGME home dir to %s\n", tempdir);
+ 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(stderr, "bad gpgme_new: %s: %s\n",
- gpgme_strsource(rc), gpgme_strerror(rc));
- return false;
- }
-
- if (not import_key(pubkey) or not import_key(seckey)){
- return false;
- }
-
- return true;
+ 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 mandos_context *mc,
- const char *cryptotext,
- size_t crypto_size,
- char **plaintext){
+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(stderr, "Trying to decrypt OpenPGP data\n");
+ if(debug){
+ fprintf_plus(stderr, "Trying to decrypt OpenPGP data\n");
}
/* Create new GPGME data buffer from memory cryptotext */
rc = gpgme_data_new_from_mem(&dh_crypto, cryptotext, crypto_size,
0);
- if (rc != GPG_ERR_NO_ERROR){
- fprintf(stderr, "bad gpgme_data_new_from_mem: %s: %s\n",
- gpgme_strsource(rc), gpgme_strerror(rc));
+ if(rc != GPG_ERR_NO_ERROR){
+ fprintf_plus(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(stderr, "bad gpgme_data_new: %s: %s\n",
- gpgme_strsource(rc), gpgme_strerror(rc));
+ 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));
gpgme_data_release(dh_crypto);
return -1;
}
@@ -274,35 +378,34 @@
/* 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(stderr, "bad gpgme_op_decrypt: %s: %s\n",
- gpgme_strsource(rc), gpgme_strerror(rc));
+ if(rc != GPG_ERR_NO_ERROR){
+ fprintf_plus(stderr, "bad gpgme_op_decrypt: %s: %s\n",
+ gpgme_strsource(rc), gpgme_strerror(rc));
plaintext_length = -1;
- if (debug){
+ if(debug){
gpgme_decrypt_result_t result;
result = gpgme_op_decrypt_result(mc->ctx);
- if (result == NULL){
- fprintf(stderr, "gpgme_op_decrypt_result failed\n");
+ if(result == NULL){
+ fprintf_plus(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);
+ 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(stderr, "File name: %s\n", result->file_name);
+ fprintf_plus(stderr, "File name: %s\n", result->file_name);
}
gpgme_recipient_t recipient;
recipient = result->recipients;
- if(recipient){
- while(recipient != NULL){
- fprintf(stderr, "Public key algorithm: %s\n",
- gpgme_pubkey_algo_name(recipient->pubkey_algo));
- fprintf(stderr, "Key ID: %s\n", recipient->keyid);
- fprintf(stderr, "Secret key available: %s\n",
- recipient->status == GPG_ERR_NO_SECKEY
- ? "No" : "Yes");
- recipient = recipient->next;
- }
+ 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");
+ recipient = recipient->next;
}
}
}
@@ -310,36 +413,36 @@
}
if(debug){
- fprintf(stderr, "Decryption of OpenPGP data succeeded\n");
+ 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("gpgme_data_seek");
+ if(gpgme_data_seek(dh_plain, (off_t)0, SEEK_SET) == -1){
+ perror_plus("gpgme_data_seek");
plaintext_length = -1;
goto decrypt_end;
}
*plaintext = NULL;
while(true){
- plaintext_capacity = adjustbuffer(plaintext,
- (size_t)plaintext_length,
- plaintext_capacity);
- if (plaintext_capacity == 0){
- perror("adjustbuffer");
- plaintext_length = -1;
- goto decrypt_end;
+ plaintext_capacity = incbuffer(plaintext,
+ (size_t)plaintext_length,
+ plaintext_capacity);
+ if(plaintext_capacity == 0){
+ perror_plus("incbuffer");
+ 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("gpgme_data_read");
+ perror_plus("gpgme_data_read");
plaintext_length = -1;
goto decrypt_end;
}
@@ -347,7 +450,7 @@
}
if(debug){
- fprintf(stderr, "Decrypted password is: ");
+ fprintf_plus(stderr, "Decrypted password is: ");
for(ssize_t i = 0; i < plaintext_length; i++){
fprintf(stderr, "%02hhX ", (*plaintext)[i]);
}
@@ -364,9 +467,9 @@
return plaintext_length;
}
-static const char * safer_gnutls_strerror (int value) {
- const char *ret = gnutls_strerror (value); /* Spurious warning */
- if (ret == NULL)
+static const char * safer_gnutls_strerror(int value){
+ const char *ret = gnutls_strerror(value);
+ if(ret == NULL)
ret = "(unknown)";
return ret;
}
@@ -374,26 +477,26 @@
/* GnuTLS log function callback */
static void debuggnutls(__attribute__((unused)) int level,
const char* string){
- fprintf(stderr, "GnuTLS: %s", string);
+ fprintf_plus(stderr, "GnuTLS: %s", string);
}
-static int init_gnutls_global(mandos_context *mc,
- const char *pubkeyfilename,
- const char *seckeyfilename){
+static int init_gnutls_global(const char *pubkeyfilename,
+ const char *seckeyfilename,
+ mandos_context *mc){
int ret;
if(debug){
- fprintf(stderr, "Initializing GnuTLS\n");
+ fprintf_plus(stderr, "Initializing GnuTLS\n");
}
ret = gnutls_global_init();
- if (ret != GNUTLS_E_SUCCESS) {
- fprintf (stderr, "GnuTLS global_init: %s\n",
- safer_gnutls_strerror(ret));
+ if(ret != GNUTLS_E_SUCCESS){
+ fprintf_plus(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
*/
@@ -402,44 +505,45 @@
}
/* OpenPGP credentials */
- 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 ();
+ 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();
return -1;
}
if(debug){
- fprintf(stderr, "Attempting to use OpenPGP public key %s and"
- " secret key %s as GnuTLS credentials\n", pubkeyfilename,
- seckeyfilename);
+ fprintf_plus(stderr, "Attempting to use OpenPGP public key %s and"
+ " secret key %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(stderr,
- "Error[%d] while reading the OpenPGP key pair ('%s',"
- " '%s')\n", ret, pubkeyfilename, seckeyfilename);
- fprintf(stderr, "The GnuTLS error is: %s\n",
- safer_gnutls_strerror(ret));
+ 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));
goto globalfail;
}
/* GnuTLS server initialization */
ret = gnutls_dh_params_init(&mc->dh_params);
- if (ret != GNUTLS_E_SUCCESS) {
- fprintf (stderr, "Error in GnuTLS DH parameter initialization:"
- " %s\n", safer_gnutls_strerror(ret));
+ if(ret != GNUTLS_E_SUCCESS){
+ fprintf_plus(stderr, "Error in GnuTLS DH parameter"
+ " initialization: %s\n",
+ safer_gnutls_strerror(ret));
goto globalfail;
}
ret = gnutls_dh_params_generate2(mc->dh_params, mc->dh_bits);
- if (ret != GNUTLS_E_SUCCESS) {
- fprintf (stderr, "Error in GnuTLS prime generation: %s\n",
- safer_gnutls_strerror(ret));
+ if(ret != GNUTLS_E_SUCCESS){
+ fprintf_plus(stderr, "Error in GnuTLS prime generation: %s\n",
+ safer_gnutls_strerror(ret));
goto globalfail;
}
@@ -455,42 +559,59 @@
return -1;
}
-static int init_gnutls_session(mandos_context *mc,
- gnutls_session_t *session){
+static int init_gnutls_session(gnutls_session_t *session,
+ mandos_context *mc){
int ret;
/* GnuTLS session creation */
- ret = gnutls_init(session, GNUTLS_SERVER);
- if (ret != GNUTLS_E_SUCCESS){
- fprintf(stderr, "Error in GnuTLS session initialization: %s\n",
- safer_gnutls_strerror(ret));
+ 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));
}
{
const char *err;
- 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);
+ 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);
return -1;
}
}
- 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);
+ 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);
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;
}
@@ -500,223 +621,415 @@
__attribute__((unused)) const char *txt){}
/* Called when a Mandos server is found */
-static int start_mandos_communication(const char *ip, uint16_t port,
+static int start_mandos_communication(const char *ip, in_port_t port,
AvahiIfIndex if_index,
- mandos_context *mc){
- int ret, tcp_sd;
- union { struct sockaddr in; struct sockaddr_in6 in6; } to;
+ int af, mandos_context *mc){
+ int ret, tcp_sd = -1;
+ ssize_t sret;
+ union {
+ struct sockaddr_in in;
+ struct sockaddr_in6 in6;
+ } to;
char *buffer = NULL;
- char *decrypted_buffer;
+ char *decrypted_buffer = NULL;
size_t buffer_length = 0;
size_t buffer_capacity = 0;
- ssize_t decrypted_buffer_size;
size_t written;
- int retval = 0;
- char interface[IF_NAMESIZE];
+ int retval = -1;
gnutls_session_t session;
-
- 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");
+ 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));
+ }
+ }
return -1;
}
- fprintf(stderr, "Binding to interface %s\n", interface);
+ }
+
+ 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;
}
memset(&to, 0, sizeof(to));
- to.in6.sin6_family = AF_INET6;
- /* It would be nice to have a way to detect if we were passed an
- IPv4 address here. Now we assume an IPv6 address. */
- ret = inet_pton(AF_INET6, ip, &to.in6.sin6_addr);
- if (ret < 0 ){
- perror("inet_pton");
- return -1;
+ if(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;
}
if(ret == 0){
- fprintf(stderr, "Bad address: %s\n", ip);
- return -1;
- }
- to.in6.sin6_port = htons(port); /* Spurious warning */
+ 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);
+ }
- to.in6.sin6_scope_id = (uint32_t)if_index;
+ if(quit_now){
+ errno = EINTR;
+ goto mandos_end;
+ }
if(debug){
- 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");
+ 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");
} else {
if(strcmp(addrstr, ip) != 0){
- fprintf(stderr, "Canonical address form: %s\n", addrstr);
+ fprintf_plus(stderr, "Canonical address form: %s\n", addrstr);
}
}
}
- ret = connect(tcp_sd, &to.in, sizeof(to));
- if (ret < 0){
- perror("connect");
- return -1;
+ 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;
}
const char *out = mandos_protocol_version;
written = 0;
- while (true){
+ while(true){
size_t out_size = strlen(out);
- ret = TEMP_FAILURE_RETRY(write(tcp_sd, out + written,
- out_size - written));
- if (ret == -1){
- perror("write");
- retval = -1;
+ ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written,
+ out_size - written));
+ if(ret == -1){
+ int e = errno;
+ perror_plus("write");
+ errno = e;
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(stderr, "Establishing TLS session with %s\n", ip);
- }
-
- gnutls_transport_set_ptr (session, (gnutls_transport_ptr_t) tcp_sd);
-
- do{
- ret = gnutls_handshake (session);
+ 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;
+ }
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
- if (ret != GNUTLS_E_SUCCESS){
+ if(ret != GNUTLS_E_SUCCESS){
if(debug){
- fprintf(stderr, "*** GnuTLS Handshake failed ***\n");
- gnutls_perror (ret);
+ fprintf_plus(stderr, "*** GnuTLS Handshake failed ***\n");
+ gnutls_perror(ret);
}
- retval = -1;
+ errno = EPROTO;
goto mandos_end;
}
/* Read OpenPGP packet that contains the wanted password */
if(debug){
- fprintf(stderr, "Retrieving pgp encrypted password from %s\n",
- ip);
+ fprintf_plus(stderr, "Retrieving OpenPGP encrypted password from"
+ " %s\n", ip);
}
while(true){
- 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){
+
+ 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){
break;
}
- if (ret < 0){
- switch(ret){
+ if(sret < 0){
+ switch(sret){
case GNUTLS_E_INTERRUPTED:
case GNUTLS_E_AGAIN:
break;
case GNUTLS_E_REHANDSHAKE:
- do{
- ret = gnutls_handshake (session);
+ do {
+ ret = gnutls_handshake(session);
+
+ if(quit_now){
+ errno = EINTR;
+ goto mandos_end;
+ }
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
- if (ret < 0){
- fprintf(stderr, "*** GnuTLS Re-handshake failed ***\n");
- gnutls_perror (ret);
- retval = -1;
+ if(ret < 0){
+ fprintf_plus(stderr, "*** GnuTLS Re-handshake failed "
+ "***\n");
+ gnutls_perror(ret);
+ errno = EPROTO;
goto mandos_end;
}
break;
default:
- fprintf(stderr, "Unknown error while reading data from"
- " encrypted session with Mandos server\n");
- retval = -1;
- gnutls_bye (session, GNUTLS_SHUT_RDWR);
+ fprintf_plus(stderr, "Unknown error while reading data from"
+ " encrypted session with Mandos server\n");
+ gnutls_bye(session, GNUTLS_SHUT_RDWR);
+ errno = EIO;
goto mandos_end;
}
} else {
- buffer_length += (size_t) ret;
+ buffer_length += (size_t) sret;
}
}
if(debug){
- fprintf(stderr, "Closing TLS session\n");
- }
-
- gnutls_bye (session, GNUTLS_SHUT_RDWR);
-
- if (buffer_length > 0){
- decrypted_buffer_size = pgp_packet_decrypt(mc, buffer,
- buffer_length,
- &decrypted_buffer);
- if (decrypted_buffer_size >= 0){
+ 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){
+
written = 0;
while(written < (size_t) decrypted_buffer_size){
- ret = (int)fwrite (decrypted_buffer + written, 1,
- (size_t)decrypted_buffer_size - written,
- stdout);
+ if(quit_now){
+ errno = EINTR;
+ goto mandos_end;
+ }
+
+ 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(stderr, "Error writing encrypted data: %s\n",
- strerror(errno));
+ fprintf_plus(stderr, "Error writing encrypted data: %s\n",
+ strerror(errno));
}
- retval = -1;
- break;
+ errno = e;
+ goto mandos_end;
}
written += (size_t)ret;
}
- free(decrypted_buffer);
- } else {
- retval = -1;
+ retval = 0;
}
- } else {
- retval = -1;
}
/* Shutdown procedure */
mandos_end:
- free(buffer);
- ret = TEMP_FAILURE_RETRY(close(tcp_sd));
- if(ret == -1){
- perror("close");
+ {
+ 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;
+ }
}
- gnutls_deinit (session);
return retval;
}
static void resolve_callback(AvahiSServiceResolver *r,
AvahiIfIndex interface,
- AVAHI_GCC_UNUSED AvahiProtocol protocol,
+ AvahiProtocol proto,
AvahiResolverEvent event,
const char *name,
const char *type,
@@ -727,19 +1040,26 @@
AVAHI_GCC_UNUSED AvahiStringList *txt,
AVAHI_GCC_UNUSED AvahiLookupResultFlags
flags,
- void* userdata) {
- mandos_context *mc = userdata;
- assert(r);
+ void* mc){
+ if(r == NULL){
+ return;
+ }
/* Called whenever a service has been resolved successfully or
timed out */
- switch (event) {
+ if(quit_now){
+ return;
+ }
+
+ switch(event){
default:
case AVAHI_RESOLVER_FAILURE:
- 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)));
+ 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)));
break;
case AVAHI_RESOLVER_FOUND:
@@ -747,42 +1067,58 @@
char ip[AVAHI_ADDRESS_STR_MAX];
avahi_address_snprint(ip, sizeof(ip), address);
if(debug){
- fprintf(stderr, "Mandos server \"%s\" found on %s (%s, %"
- PRIu16 ") on port %d\n", name, host_name, ip,
- interface, port);
+ fprintf_plus(stderr, "Mandos server \"%s\" found on %s (%s, %"
+ PRIdMAX ") on port %" PRIu16 "\n", name,
+ host_name, ip, (intmax_t)interface, port);
}
- int ret = start_mandos_communication(ip, port, interface, mc);
- if (ret == 0){
- avahi_simple_poll_quit(mc->simple_poll);
+ 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);
+ }
}
}
}
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* userdata) {
- mandos_context *mc = userdata;
- assert(b);
+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;
+ }
/* Called whenever a new services becomes available on the LAN or
is removed from the LAN */
- switch (event) {
+ if(quit_now){
+ return;
+ }
+
+ switch(event){
default:
case AVAHI_BROWSER_FAILURE:
- fprintf(stderr, "(Avahi browser) %s\n",
- avahi_strerror(avahi_server_errno(mc->server)));
- avahi_simple_poll_quit(mc->simple_poll);
+ fprintf_plus(stderr, "(Avahi browser) %s\n",
+ avahi_strerror(avahi_server_errno
+ (((mandos_context*)mc)->server)));
+ avahi_simple_poll_quit(simple_poll);
return;
case AVAHI_BROWSER_NEW:
@@ -791,12 +1127,14 @@
the callback function is called the Avahi server will free the
resolver for us. */
- 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)));
+ 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)));
break;
case AVAHI_BROWSER_REMOVE:
@@ -805,343 +1143,1395 @@
case AVAHI_BROWSER_ALL_FOR_NOW:
case AVAHI_BROWSER_CACHE_EXHAUSTED:
if(debug){
- fprintf(stderr, "No Mandos server found, still searching...\n");
+ fprintf_plus(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);
+ 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;
+}
+
+int avahi_loop_with_timeout(AvahiSimplePoll *s, int retry_interval,
+ mandos_context *mc){
+ int ret;
+ struct timespec now;
+ struct timespec waited_time;
+ intmax_t block_time;
+
+ while(true){
+ if(mc->current_server == NULL){
+ if (debug){
+ fprintf_plus(stderr, "Wait until first server is found."
+ " No timeout!\n");
+ }
+ ret = avahi_simple_poll_iterate(s, -1);
+ } else {
+ if (debug){
+ fprintf_plus(stderr, "Check current_server if we should run"
+ " it, or wait\n");
+ }
+ /* the current time */
+ ret = clock_gettime(CLOCK_MONOTONIC, &now);
+ if(ret == -1){
+ perror_plus("clock_gettime");
+ return -1;
+ }
+ /* Calculating in ms how long time between now and server
+ who we visted longest time ago. Now - last seen. */
+ waited_time.tv_sec = (now.tv_sec
+ - mc->current_server->last_seen.tv_sec);
+ waited_time.tv_nsec = (now.tv_nsec
+ - mc->current_server->last_seen.tv_nsec);
+ /* total time is 10s/10,000ms.
+ Converting to s from ms by dividing by 1,000,
+ and ns to ms by dividing by 1,000,000. */
+ block_time = ((retry_interval
+ - ((intmax_t)waited_time.tv_sec * 1000))
+ - ((intmax_t)waited_time.tv_nsec / 1000000));
+
+ if (debug){
+ fprintf_plus(stderr, "Blocking for %" PRIdMAX " ms\n",
+ block_time);
+ }
+
+ if(block_time <= 0){
+ ret = start_mandos_communication(mc->current_server->ip,
+ mc->current_server->port,
+ mc->current_server->if_index,
+ mc->current_server->af, mc);
+ if(ret == 0){
+ avahi_simple_poll_quit(s);
+ return 0;
+ }
+ ret = clock_gettime(CLOCK_MONOTONIC,
+ &mc->current_server->last_seen);
+ if(ret == -1){
+ perror_plus("clock_gettime");
+ return -1;
+ }
+ mc->current_server = mc->current_server->next;
+ block_time = 0; /* Call avahi to find new Mandos
+ servers, but don't block */
+ }
+
+ ret = avahi_simple_poll_iterate(s, (int)block_time);
+ }
+ if(ret != 0){
+ if (ret > 0 or errno != EINTR){
+ return (ret != 1) ? ret : 0;
+ }
+ }
+ }
+}
+
+/* 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){
+ 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");
+ }
+ }
+#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[]){
- 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;
- char tempdir[] = "/tmp/mandosXXXXXX";
- AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
- const char *seckey = PATHDIR "/" SECKEY;
- const char *pubkey = PATHDIR "/" PUBKEY;
-
- mandos_context mc = { .simple_poll = NULL, .server = NULL,
- .dh_bits = 1024, .priority = "SECURE256"
- ":!CTYPE-X.509:+CTYPE-OPENPGP" };
- bool gnutls_initalized = false;
- bool gpgme_initalized = false;
-
- {
- 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 = "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 = 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 */
- debug = true;
- break;
- case 'c': /* --connect */
- connect_to = arg;
- break;
- case 'i': /* --interface */
- interface = arg;
- break;
- case 's': /* --seckey */
- seckey = arg;
- break;
- case 'p': /* --pubkey */
- pubkey = arg;
- break;
- case 129: /* --dh-bits */
- errno = 0;
- mc.dh_bits = (unsigned int) strtol(arg, NULL, 10);
- if (errno){
- perror("strtol");
- exit(EXIT_FAILURE);
- }
- break;
- case 130: /* --priority */
- 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 a 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;
- }
- }
-
- /* If the interface is down, bring it up */
- {
- sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
- if(sd < 0) {
- perror("socket");
- exitcode = EXIT_FAILURE;
- goto end;
- }
- strcpy(network.ifr_name, interface);
- ret = ioctl(sd, SIOCGIFFLAGS, &network);
- if(ret == -1){
- perror("ioctl SIOCGIFFLAGS");
- exitcode = EXIT_FAILURE;
- goto end;
- }
- if((network.ifr_flags & IFF_UP) == 0){
- network.ifr_flags |= IFF_UP;
- ret = ioctl(sd, SIOCSIFFLAGS, &network);
- if(ret == -1){
- perror("ioctl SIOCSIFFLAGS");
- exitcode = EXIT_FAILURE;
- goto end;
- }
- }
- ret = TEMP_FAILURE_RETRY(close(sd));
- if(ret == -1){
- perror("close");
- }
- }
-
- uid = getuid();
- gid = getgid();
-
- ret = setuid(uid);
- if (ret == -1){
- perror("setuid");
- }
-
- setgid(gid);
- if (ret == -1){
- perror("setgid");
- }
-
- ret = init_gnutls_global(&mc, pubkey, seckey);
- if (ret == -1){
- fprintf(stderr, "init_gnutls_global failed\n");
- exitcode = EXIT_FAILURE;
- goto end;
- } else {
- gnutls_initalized = true;
- }
-
- if(mkdtemp(tempdir) == NULL){
- perror("mkdtemp");
- tempdir[0] = '\0';
- goto end;
- }
-
- if(not init_gpgme(&mc, pubkey, seckey, tempdir)){
- fprintf(stderr, "gpgme_initalized failed\n");
- exitcode = EXIT_FAILURE;
- goto end;
- } else {
- gpgme_initalized = true;
- }
-
- 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;
- }
+ 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;
- uint16_t port = (uint16_t) strtol(address+1, NULL, 10);
- if(errno){
- perror("Bad port number");
- exitcode = EXIT_FAILURE;
+ 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");
goto end;
}
- *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);
-
+ 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");
+ }
+
end:
-
- 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);
-
- if (gnutls_initalized){
- gnutls_certificate_free_credentials(mc.cred);
- gnutls_global_deinit ();
- gnutls_dh_params_deinit(mc.dh_params);
- }
-
- if(gpgme_initalized){
- gpgme_release(mc.ctx);
- }
-
- /* Removes the temp directory used by GPGME */
- if(tempdir[0] != '\0'){
- DIR *d;
- struct dirent *direntry;
- d = opendir(tempdir);
- if(d == NULL){
- perror("opendir");
- } else {
- while(true){
- direntry = readdir(d);
- if(direntry == NULL){
- break;
- }
- if (direntry->d_type == DT_REG){
- char *fullname = NULL;
- ret = asprintf(&fullname, "%s/%s", tempdir,
- direntry->d_name);
- if(ret < 0){
- perror("asprintf");
- continue;
- }
- ret = unlink(fullname);
- if(ret == -1){
- fprintf(stderr, "unlink(\"%s\"): %s",
- fullname, strerror(errno));
- }
- free(fullname);
- }
- }
- closedir(d);
- }
- ret = rmdir(tempdir);
- if(ret == -1){
- perror("rmdir");
- }
- }
-
- return exitcode;
+
+ 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;
}
=== modified file 'plugins.d/mandos-client.xml'
--- plugins.d/mandos-client.xml 2008-09-12 19:12:40 +0000
+++ plugins.d/mandos-client.xml 2014-01-20 20:54:47 +0000
@@ -1,36 +1,40 @@
-
+
+
+%common;
]>
Mandos Manual
-
+
Mandos
- &VERSION;
+ &version;
&TIMESTAMP;
Björn
Påhlsson
- belorn@fukt.bsnet.se
+ belorn@recompile.se
Teddy
Hogeborn
- teddy@fukt.bsnet.se
+ teddy@recompile.se
2008
+ 2009
+ 2012
+ 2013
Teddy Hogeborn
Björn Påhlsson
@@ -61,11 +65,13 @@
>PORT
-
+
-
+ NAME,NAME
+
@@ -91,6 +97,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -120,11 +139,34 @@
&COMMANDNAME; is a client program that
communicates with mandos8
- to get a password. It uses IPv6 link-local addresses to get
- network connectivity, Zeroconf to find servers, and TLS with an
- OpenPGP key to ensure authenticity and confidentiality. It
- keeps running, trying all servers on the network, until it
- receives a satisfactory reply or a TERM signal is received.
+ 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
@@ -177,27 +219,51 @@
assumed to separate the address from the port number.
- This option is normally only useful for testing and
- debugging.
+ Normally, Zeroconf would be used to locate Mandos servers,
+ in which case this option would only be used when testing
+ and debugging.
-
+
+ NAME,NAME
- Network interface that will be brought up and scanned for
- Mandos servers to connect to. The default it
- eth0
.
-
-
- If the option is used, this
- specifies the interface to use to connect to the address
- given.
+ 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.
@@ -249,6 +315,47 @@
+
+
+
+
+
+ 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
.
+
+
+
@@ -315,8 +422,10 @@
plugin-runner
8mandos) is used to run
both this program and others in in parallel,
- one of which will prompt for passwords on
- the system console.
+ one of which (
+ password-prompt
+ 8mandos) will prompt for
+ passwords on the system console.
@@ -327,9 +436,9 @@
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 new
- Mandos servers as they appear, trying
- to get a decryptable password and print it.
+ error occurs. Otherwise, it will forever connect to any
+ discovered Mandos servers, trying to
+ get a decryptable password and print it.
@@ -343,7 +452,176 @@
-
+
+ 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
@@ -360,6 +638,17 @@
+
+ /lib/mandos/network-hooks.d
+
+
+ Directory where network hooks are located. Change this
+ with the option. See
+ .
+
+
+
@@ -379,8 +668,8 @@
- Normal invocation needs no options, if the network interface
- is eth0
:
+ Normal invocation needs no options, if the network interfaces
+ can be automatically determined:
&COMMANDNAME;
@@ -388,8 +677,8 @@
- Search for Mandos servers (and connect to them) using another
- interface:
+ Search for Mandos servers (and connect to them) using one
+ specific interface:
@@ -410,15 +699,15 @@
Run in debug mode, with a custom key, and do not use Zeroconf
- to locate a server; connect directly to the IPv6 address
- 2001:db8:f983:bd0b:30de:ae4a:71f2:f672
,
- port 4711, using interface eth2:
+ 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 2001:db8:f983:bd0b:30de:ae4a:71f2:f672:4711 --interface eth2
+&COMMANDNAME; --debug --pubkey keydir/pubkey.txt --seckey keydir/seckey.txt --connect fe80::aede:48ff:fe71:f6f2:4711 --interface eth2
@@ -449,11 +738,11 @@
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. The defense against this is
- that the server is supposed to notice the client disappearing
- and will stop giving out the encrypted data. Therefore, it is
- important to set the timeout and checker interval values tightly
- on the server. See mandos8.
@@ -474,6 +763,8 @@
SEE ALSO
+ intro
+ 8mandos,
cryptsetup
8,
crypttab
@@ -557,7 +848,7 @@
This client uses IPv6 link-local addresses, which are
immediately usable since a link-local addresses is
- automatically assigned to a network interfaces when it
+ automatically assigned to a network interface when it
is brought up.
=== modified file 'plugins.d/password-prompt.c'
--- plugins.d/password-prompt.c 2008-09-19 20:42:17 +0000
+++ plugins.d/password-prompt.c 2013-10-20 15:25:09 +0000
@@ -1,8 +1,9 @@
-/* -*- coding: utf-8 -*- */
+/* -*- coding: utf-8; mode: c; mode: orgtbl -*- */
/*
- * Passprompt - Read a password from the terminal and print it
- *
- * Copyright © 2008 Teddy Hogeborn & Björn Påhlsson
+ * Password-prompt - Read a password from the terminal and print it
+ *
+ * Copyright © 2008-2013 Teddy Hogeborn
+ * Copyright © 2008-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
@@ -18,49 +19,220 @@
* along with this program. If not, see
* .
*
- * Contact the authors at and
- * .
+ * Contact the authors at .
*/
-#define _GNU_SOURCE /* getline() */
+#define _GNU_SOURCE /* getline(), asprintf() */
-#include /* struct termios, tcsetattr(),
+#include /* struct termios, tcsetattr(),
TCSAFLUSH, tcgetattr(), ECHO */
#include /* struct termios, tcsetattr(),
STDIN_FILENO, TCSAFLUSH,
- tcgetattr(), ECHO */
+ tcgetattr(), ECHO, readlink() */
#include /* sig_atomic_t, raise(), struct
sigaction, sigemptyset(),
sigaction(), sigaddset(), SIGINT,
- SIGQUIT, SIGHUP, SIGTERM */
+ SIGQUIT, SIGHUP, SIGTERM,
+ raise() */
#include /* NULL, size_t, ssize_t */
-#include /* ssize_t */
+#include /* ssize_t, struct dirent, pid_t,
+ ssize_t, open() */
#include /* EXIT_SUCCESS, EXIT_FAILURE,
- getopt_long, getenv() */
+ getenv(), free() */
+#include /* scandir(), alphasort() */
#include /* fprintf(), stderr, getline(),
- stdin, feof(), perror(), fputc(),
- stdout, getopt_long */
-#include /* errno, EINVAL */
+ stdin, feof(), fputc(), vfprintf(),
+ vasprintf() */
+#include /* errno, EBADF, ENOTTY, EINVAL,
+ EFAULT, EFBIG, EIO, ENOSPC, EINTR
+ */
+#include /* error() */
#include /* or, not */
#include /* bool, false, true */
-#include /* strlen, rindex, strncmp, strcmp */
+#include /* strtoumax() */
+#include /* struct stat, lstat(), open() */
+#include /* strlen, rindex, memcmp, strerror()
+ */
#include /* struct argp_option, struct
argp_state, struct argp,
argp_parse(), error_t,
ARGP_KEY_ARG, ARGP_KEY_END,
ARGP_ERR_UNKNOWN */
+#include /* EX_SOFTWARE, EX_OSERR,
+ EX_UNAVAILABLE, EX_IOERR, EX_OK */
+#include /* open() */
+#include /* va_list, va_start(), ... */
-volatile bool quit_now = false;
+volatile sig_atomic_t quit_now = 0;
+int signal_received;
bool debug = false;
-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;
-}
+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;
+}
+
int main(int argc, char **argv){
- ssize_t ret;
+ ssize_t sret;
+ int ret;
size_t n;
struct termios t_new, t_old;
char *buffer = NULL;
@@ -76,111 +248,176 @@
.doc = "Prefix shown before the prompt", .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 }
};
-
- 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) {
+
+ __attribute__((nonnull(3)))
+ error_t parse_opt (int key, char *arg, struct argp_state *state){
+ errno = 0;
+ switch (key){
case 'p':
prefix = arg;
break;
case 128:
debug = true;
break;
- case ARGP_KEY_ARG:
- argp_usage (state);
- break;
- case ARGP_KEY_END:
+ /*
+ * 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);
break;
default:
return ARGP_ERR_UNKNOWN;
}
- return 0;
+ return errno;
}
-
+
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, 0, 0, NULL);
- if (ret == ARGP_ERR_UNKNOWN){
- fprintf(stderr, "Unknown error while parsing arguments\n");
- return EXIT_FAILURE;
+ 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;
}
}
-
- if (debug){
+
+ if(debug){
fprintf(stderr, "Starting %s\n", argv[0]);
}
- if (debug){
+
+ if (conflict_detection()){
+ if(debug){
+ fprintf(stderr, "Stopping %s because of conflict\n", argv[0]);
+ }
+ return EXIT_FAILURE;
+ }
+
+ if(debug){
fprintf(stderr, "Storing current terminal attributes\n");
}
- if (tcgetattr(STDIN_FILENO, &t_old) != 0){
- perror("tcgetattr");
- return EXIT_FAILURE;
+ 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;
+ }
}
sigemptyset(&new_action.sa_mask);
- sigaddset(&new_action.sa_mask, SIGINT);
- sigaddset(&new_action.sa_mask, SIGHUP);
- sigaddset(&new_action.sa_mask, SIGTERM);
+ 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]] |
+ */
ret = sigaction(SIGINT, NULL, &old_action);
if(ret == -1){
- perror("sigaction");
- return EXIT_FAILURE;
+ error_plus(0, errno, "sigaction");
+ return EX_OSERR;
}
- if (old_action.sa_handler != SIG_IGN){
+ if(old_action.sa_handler != SIG_IGN){
ret = sigaction(SIGINT, &new_action, NULL);
if(ret == -1){
- perror("sigaction");
- return EXIT_FAILURE;
+ error_plus(0, errno, "sigaction");
+ return EX_OSERR;
}
}
ret = sigaction(SIGHUP, NULL, &old_action);
if(ret == -1){
- perror("sigaction");
- return EXIT_FAILURE;
+ error_plus(0, errno, "sigaction");
+ return EX_OSERR;
}
- if (old_action.sa_handler != SIG_IGN){
+ if(old_action.sa_handler != SIG_IGN){
ret = sigaction(SIGHUP, &new_action, NULL);
if(ret == -1){
- perror("sigaction");
- return EXIT_FAILURE;
+ error_plus(0, errno, "sigaction");
+ return EX_OSERR;
}
}
ret = sigaction(SIGTERM, NULL, &old_action);
if(ret == -1){
- perror("sigaction");
- return EXIT_FAILURE;
+ error_plus(0, errno, "sigaction");
+ return EX_OSERR;
}
- if (old_action.sa_handler != SIG_IGN){
+ if(old_action.sa_handler != SIG_IGN){
ret = sigaction(SIGTERM, &new_action, NULL);
if(ret == -1){
- perror("sigaction");
- return EXIT_FAILURE;
+ error_plus(0, errno, "sigaction");
+ return EX_OSERR;
}
}
- if (debug){
+ if(debug){
fprintf(stderr, "Removing echo flag from terminal attributes\n");
}
t_new = t_old;
- t_new.c_lflag &= ~ECHO;
- if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_new) != 0){
- perror("tcsetattr-echo");
- return EXIT_FAILURE;
+ 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;
+ }
}
-
- if (debug){
+
+ if(debug){
fprintf(stderr, "Waiting for input from stdin \n");
}
while(true){
- if (quit_now){
+ if(quit_now){
if(debug){
fprintf(stderr, "Interrupted by signal, exiting.\n");
}
@@ -192,78 +429,135 @@
fprintf(stderr, "%s ", prefix);
}
{
- const char *cryptsource = getenv("cryptsource");
- const char *crypttarget = getenv("crypttarget");
- const char *const prompt
- = "Enter passphrase to unlock the disk";
+ 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";
if(cryptsource == NULL){
if(crypttarget == NULL){
- fprintf(stderr, "%s: ", prompt);
+ fprintf(stderr, "%s to unlock the disk: ", prompt2);
} else {
- fprintf(stderr, "%s (%s): ", prompt, crypttarget);
+ fprintf(stderr, "%s (%s)\n%s: ", prompt1, crypttarget,
+ prompt2);
}
} else {
if(crypttarget == NULL){
- fprintf(stderr, "%s %s: ", prompt, cryptsource);
+ fprintf(stderr, "%s %s\n%s: ", prompt1, cryptsource,
+ prompt2);
} else {
- fprintf(stderr, "%s %s (%s): ", prompt, cryptsource,
- crypttarget);
+ fprintf(stderr, "%s %s (%s)\n%s: ", prompt1, cryptsource,
+ crypttarget, prompt2);
}
}
}
- ret = getline(&buffer, &n, stdin);
- if (ret > 0){
+ sret = getline(&buffer, &n, stdin);
+ if(sret > 0){
status = EXIT_SUCCESS;
/* Make n = data size instead of allocated buffer size */
- n = (size_t)ret;
+ n = (size_t)sret;
/* Strip final newline */
- if(n>0 and buffer[n-1] == '\n'){
+ if(n > 0 and buffer[n-1] == '\n'){
buffer[n-1] = '\0'; /* not strictly necessary */
n--;
}
size_t written = 0;
while(written < n){
- ret = write(STDOUT_FILENO, buffer + written, n - written);
- if(ret < 0){
- perror("write");
- status = EXIT_FAILURE;
- break;
- }
- written += (size_t)ret;
+ 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 (ret < 0){
- if (errno != EINTR and not feof(stdin)){
- perror("getline");
- status = EXIT_FAILURE;
+ 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;
+ }
break;
}
}
- /* if(ret == 0), then the only sensible thing to do is to retry to
- read from stdin */
+ /* if(sret == 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 true, we were interrupted by a signal, and
+ /* 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){
- perror("tcsetattr+echo");
- }
-
- if (debug){
+ 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){
+ if(status == EXIT_SUCCESS or status == EX_OK){
fputc('\n', stderr);
}
=== modified file 'plugins.d/password-prompt.xml'
--- plugins.d/password-prompt.xml 2008-09-06 16:31:49 +0000
+++ plugins.d/password-prompt.xml 2011-12-31 23:05:34 +0000
@@ -1,9 +1,10 @@
-
+
+
+%common;
]>
@@ -11,26 +12,29 @@
Mandos Manual
Mandos
- &VERSION;
+ &version;
&TIMESTAMP;
Björn
Påhlsson
- belorn@fukt.bsnet.se
+ belorn@recompile.se
Teddy
Hogeborn
- teddy@fukt.bsnet.se
+ teddy@recompile.se
2008
+ 2009
+ 2011
+ 2012
Teddy Hogeborn
Björn Påhlsson
@@ -83,12 +87,14 @@
DESCRIPTION
All &COMMANDNAME; does is prompt for a
- password and output any given password to standard output. This
- 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.
+ 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 ENVIRONMENT
- cryptsource
- crypttarget
+ CRYPTTAB_SOURCE
+ CRYPTTAB_NAME
If set, these environment variables will be assumed to
@@ -288,6 +294,8 @@
SEE ALSO
+ intro
+ 8mandos
crypttab
5
mandos-client
=== added file 'plugins.d/plymouth.c'
--- plugins.d/plymouth.c 1970-01-01 00:00:00 +0000
+++ plugins.d/plymouth.c 2013-10-20 15:25:09 +0000
@@ -0,0 +1,487 @@
+/* -*- 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);
+}
=== added file 'plugins.d/plymouth.xml'
--- plugins.d/plymouth.xml 1970-01-01 00:00:00 +0000
+++ plugins.d/plymouth.xml 2011-12-31 23:05:34 +0000
@@ -0,0 +1,282 @@
+
+
+
+
+%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
+
+
+
+
+
+
+
+
=== modified file 'plugins.d/splashy.c'
--- plugins.d/splashy.c 2008-09-24 23:03:31 +0000
+++ plugins.d/splashy.c 2011-12-31 23:05:34 +0000
@@ -1,36 +1,108 @@
-#define _GNU_SOURCE /* asprintf() */
+/* -*- 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(),
- sigaction, SIGINT, SIG_IGN, SIGHUP,
- SIGTERM, kill(), SIGKILL */
+ sigemptyset(), sigaddset(), SIGINT,
+ SIGHUP, SIGTERM, sigaction,
+ SIG_IGN, kill(), SIGKILL */
#include /* NULL */
#include /* getenv() */
-#include /* asprintf(), perror() */
-#include /* EXIT_FAILURE, EXIT_SUCCESS,
- strtoul(), free() */
+#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(),
- _exit */
-#include /* memcmp() */
-#include /* and */
-#include /* errno */
+ 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;
-
-static void termination_handler(__attribute__((unused))int signum){
+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 */
- char *prompt = NULL;
{
const char *const cryptsource = getenv("cryptsource");
const char *const crypttarget = getenv("crypttarget");
@@ -53,27 +125,51 @@
}
}
if(ret == -1){
- return EXIT_FAILURE;
+ prompt = NULL;
+ exitstatus = EX_OSERR;
+ goto failure;
}
}
/* Find splashy process */
- pid_t splashy_pid = 0;
{
const char splashy_name[] = "/sbin/splashy";
- DIR *proc_dir = opendir("/proc");
+ proc_dir = opendir("/proc");
if(proc_dir == NULL){
- free(prompt);
- perror("opendir");
- return EXIT_FAILURE;
+ 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 = (pid_t) strtoul(proc_ent->d_name, NULL, 10);
- if(pid == 0){
- /* Not a process */
- continue;
+ 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 */
@@ -83,11 +179,42 @@
char *exe_link;
ret = asprintf(&exe_link, "/proc/%s/exe", proc_ent->d_name);
if(ret == -1){
- perror("asprintf");
- free(prompt);
- closedir(proc_dir);
- return EXIT_FAILURE;
- }
+ 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);
}
@@ -99,10 +226,11 @@
}
}
closedir(proc_dir);
+ proc_dir = NULL;
}
if(splashy_pid == 0){
- free(prompt);
- return EXIT_FAILURE;
+ exitstatus = EX_UNAVAILABLE;
+ goto failure;
}
/* Set up the signal handler */
@@ -111,110 +239,233 @@
new_action = { .sa_handler = termination_handler,
.sa_flags = 0 };
sigemptyset(&new_action.sa_mask);
- sigaddset(&new_action.sa_mask, SIGINT);
- sigaddset(&new_action.sa_mask, SIGHUP);
- sigaddset(&new_action.sa_mask, SIGTERM);
+ 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){
- perror("sigaction");
- free(prompt);
- return EXIT_FAILURE;
+ error_plus(0, errno, "sigaction");
+ exitstatus = EX_OSERR;
+ goto failure;
}
- if (old_action.sa_handler != SIG_IGN){
+ if(old_action.sa_handler != SIG_IGN){
ret = sigaction(SIGINT, &new_action, NULL);
if(ret == -1){
- perror("sigaction");
- free(prompt);
- return EXIT_FAILURE;
+ error_plus(0, errno, "sigaction");
+ exitstatus = EX_OSERR;
+ goto failure;
}
}
ret = sigaction(SIGHUP, NULL, &old_action);
if(ret == -1){
- perror("sigaction");
- free(prompt);
- return EXIT_FAILURE;
+ error_plus(0, errno, "sigaction");
+ exitstatus = EX_OSERR;
+ goto failure;
}
- if (old_action.sa_handler != SIG_IGN){
+ if(old_action.sa_handler != SIG_IGN){
ret = sigaction(SIGHUP, &new_action, NULL);
if(ret == -1){
- perror("sigaction");
- free(prompt);
- return EXIT_FAILURE;
+ error_plus(0, errno, "sigaction");
+ exitstatus = EX_OSERR;
+ goto failure;
}
}
ret = sigaction(SIGTERM, NULL, &old_action);
if(ret == -1){
- perror("sigaction");
- free(prompt);
- return EXIT_FAILURE;
+ error_plus(0, errno, "sigaction");
+ exitstatus = EX_OSERR;
+ goto failure;
}
- if (old_action.sa_handler != SIG_IGN){
+ if(old_action.sa_handler != SIG_IGN){
ret = sigaction(SIGTERM, &new_action, NULL);
if(ret == -1){
- perror("sigaction");
- free(prompt);
- return EXIT_FAILURE;
+ 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 */
- pid_t splashy_command_pid = 0;
- if(not interrupted_by_signal){
- splashy_command_pid = fork();
- if(splashy_command_pid == -1){
- if(not interrupted_by_signal){
- perror("fork");
- }
- return EXIT_FAILURE;
- }
- /* Child */
- if(splashy_command_pid == 0){
+ 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";
- ret = execl(splashy_command, splashy_command, prompt,
- (char *)NULL);
- if(not interrupted_by_signal and errno != ENOENT){
- /* Don't report "File not found", since splashy might not be
- installed. */
- perror("execl");
+ 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);
- return EXIT_FAILURE;
}
+ free(prompt);
+ _exit(EXIT_FAILURE);
}
/* Parent */
free(prompt);
+ prompt = NULL;
+
+ if(interrupted_by_signal){
+ goto failure;
+ }
/* Wait for command to complete */
- int status;
- while(not interrupted_by_signal){
- waitpid(splashy_command_pid, &status, 0);
- if(not interrupted_by_signal
- and WIFEXITED(status) and WEXITSTATUS(status)==0){
- return EXIT_SUCCESS;
- }
- }
- kill(splashy_pid, SIGTERM);
- if(interrupted_by_signal){
- kill(splashy_command_pid, SIGTERM);
- }
-
- pid_t new_splashy_pid = fork();
- if(new_splashy_pid == 0){
- /* Child; will become new splashy process */
- while(kill(splashy_pid, 0)){
- sleep(2);
- kill(splashy_pid, SIGKILL);
+ {
+ 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);
}
- ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace our stdout */
+ 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){
- perror("dup2");
- _exit(EXIT_FAILURE);
- }
- execl("/sbin/splashy", "/sbin/splashy", "boot", (char *)NULL);
+ 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 EXIT_FAILURE;
+ return exitstatus;
}
=== added file 'plugins.d/splashy.xml'
--- plugins.d/splashy.xml 1970-01-01 00:00:00 +0000
+++ plugins.d/splashy.xml 2011-12-31 23:05:34 +0000
@@ -0,0 +1,286 @@
+
+
+
+
+%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
+
+
+
+
+
+
+
+
=== modified file 'plugins.d/usplash.c'
--- plugins.d/usplash.c 2008-09-26 19:27:46 +0000
+++ plugins.d/usplash.c 2011-12-31 23:05:34 +0000
@@ -1,241 +1,346 @@
-#define _GNU_SOURCE /* asprintf() */
+/* -*- 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 /* size_t, ssize_t, pid_t, DIR,
- struct dirent */
+#include
+#include /* size_t, ssize_t, pid_t, DIR, struct
+ dirent */
#include /* NULL */
-#include /* strlen(), memcmp() */
-#include /* asprintf(), perror() */
+#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, strtoul(),
- realloc(), EXIT_SUCCESS, malloc(),
- _exit() */
-#include /* getenv() */
+#include /* free(), EXIT_FAILURE, realloc(),
+ EXIT_SUCCESS, malloc(), _exit(),
+ getenv() */
#include /* opendir(), readdir(), closedir() */
-
-
-
-#include /* and */
-#include /* waitpid(), WIFEXITED(),
- WEXITSTATUS() */
+#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;
-
-static void termination_handler(__attribute__((unused))int signum){
+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(const char *cmd, const char *arg){
+static bool usplash_write(int *fifo_fd_r,
+ const char *cmd, const char *arg){
/*
- * usplash_write("TIMEOUT", "15"); -> "TIMEOUT 15\0"
- * usplash_write("PULSATE", NULL); -> "PULSATE\0"
+ * 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;
- int fifo_fd;
- do{
- fifo_fd = open("/dev/.initramfs/usplash_fifo", O_WRONLY);
- if(fifo_fd == -1 and (errno != EINTR or interrupted_by_signal)){
+ if(*fifo_fd_r == -1){
+ ret = open("/dev/.initramfs/usplash_fifo", O_WRONLY);
+ if(ret == -1){
return false;
}
- }while(fifo_fd == -1);
+ *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);
- }else{
- do{
+ cmd_line_len = strlen(cmd) + 1;
+ } else {
+ do {
ret = asprintf(&cmd_line_alloc, "%s %s", cmd, arg);
- if(ret == -1 and (errno != EINTR or interrupted_by_signal)){
+ if(ret == -1){
int e = errno;
- close(fifo_fd);
+ TEMP_FAILURE_RETRY(close(*fifo_fd_r));
errno = e;
return false;
}
- }while(ret == -1);
+ } while(ret == -1);
cmd_line = cmd_line_alloc;
cmd_line_len = (size_t)ret + 1;
}
size_t written = 0;
- while(not interrupted_by_signal and written < cmd_line_len){
- ret = write(fifo_fd, cmd_line + written,
- cmd_line_len - written);
- if(ret == -1){
- if(errno != EINTR or interrupted_by_signal){
- int e = errno;
- close(fifo_fd);
- free(cmd_line_alloc);
- errno = e;
- return false;
- } else {
- continue;
- }
+ 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)ret;
+ written += (size_t)sret;
}
free(cmd_line_alloc);
- do{
- ret = close(fifo_fd);
- if(ret == -1 and (errno != EINTR or interrupted_by_signal)){
- return false;
- }
- }while(ret == -1);
- if(interrupted_by_signal){
- return false;
- }
+
return true;
}
-int main(__attribute__((unused))int argc,
- __attribute__((unused))char **argv){
- int ret = 0;
- ssize_t sret;
- bool an_error_occured = false;
-
- /* Create prompt string */
- char *prompt = NULL;
- {
- 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 EXIT_FAILURE;
- }
- }
-
- /* Find usplash process */
- pid_t usplash_pid = 0;
+/* 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;
- const char usplash_name[] = "/sbin/usplash";
- {
- DIR *proc_dir = opendir("/proc");
- if(proc_dir == NULL){
- free(prompt);
- perror("opendir");
- return EXIT_FAILURE;
- }
- for(struct dirent *proc_ent = readdir(proc_dir);
- proc_ent != NULL;
- proc_ent = readdir(proc_dir)){
- pid_t pid = (pid_t) strtoul(proc_ent->d_name, NULL, 10);
- if(pid == 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 */
- continue;
- }
- /* Find the executable name by doing readlink() on the
- /proc//exe link */
- char exe_target[sizeof(usplash_name)];
+ 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 *exe_link;
- ret = asprintf(&exe_link, "/proc/%s/exe", proc_ent->d_name);
+ char *cmdline_filename;
+ ret = asprintf(&cmdline_filename, "/proc/%s/cmdline",
+ proc_ent->d_name);
if(ret == -1){
- perror("asprintf");
- free(prompt);
- closedir(proc_dir);
- return EXIT_FAILURE;
+ error_plus(0, errno, "asprintf");
+ goto fail_find_usplash;
}
- sret = readlink(exe_link, exe_target, sizeof(exe_target));
- free(exe_link);
- if(sret == -1){
- continue;
+ cl_fd = open(cmdline_filename, O_RDONLY);
+ free(cmdline_filename);
+ if(cl_fd == -1){
+ error_plus(0, errno, "open");
+ goto fail_find_usplash;
}
}
- if((sret == ((ssize_t)sizeof(exe_target)-1))
- and (memcmp(usplash_name, exe_target,
- sizeof(exe_target)-1) == 0)){
- usplash_pid = pid;
- /* 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){
- perror("asprintf");
- free(prompt);
- closedir(proc_dir);
- return EXIT_FAILURE;
- }
- cl_fd = open(cmdline_filename, O_RDONLY);
- if(cl_fd == -1){
- perror("open");
- free(cmdline_filename);
- free(prompt);
- closedir(proc_dir);
- return EXIT_FAILURE;
- }
- free(cmdline_filename);
+ 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;
}
- size_t cmdline_allocated = 0;
- char *tmp;
- const size_t blocksize = 1024;
- do{
- if(cmdline_len + blocksize > cmdline_allocated){
- tmp = realloc(cmdline, cmdline_allocated + blocksize);
- if(tmp == NULL){
- perror("realloc");
- free(cmdline);
- free(prompt);
- closedir(proc_dir);
- return EXIT_FAILURE;
- }
- cmdline = tmp;
- cmdline_allocated += blocksize;
- }
- sret = read(cl_fd, cmdline + cmdline_len,
- cmdline_allocated - cmdline_len);
- if(sret == -1){
- perror("read");
- free(cmdline);
- free(prompt);
- closedir(proc_dir);
- return EXIT_FAILURE;
- }
- cmdline_len += (size_t)sret;
- } while(sret != 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");
close(cl_fd);
+ goto fail_find_usplash;
}
- break;
+ 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){
- free(prompt);
- return EXIT_FAILURE;
+ status = EX_UNAVAILABLE;
+ goto failure;
}
/* Set up the signal handler */
@@ -244,236 +349,336 @@
new_action = { .sa_handler = termination_handler,
.sa_flags = 0 };
sigemptyset(&new_action.sa_mask);
- sigaddset(&new_action.sa_mask, SIGINT);
- sigaddset(&new_action.sa_mask, SIGHUP);
- sigaddset(&new_action.sa_mask, SIGTERM);
+ 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){
- perror("sigaction");
- free(prompt);
- return EXIT_FAILURE;
+ if(errno != EINTR){
+ error_plus(0, errno, "sigaction");
+ status = EX_OSERR;
+ }
+ goto failure;
}
- if (old_action.sa_handler != SIG_IGN){
+ if(old_action.sa_handler != SIG_IGN){
ret = sigaction(SIGINT, &new_action, NULL);
if(ret == -1){
- perror("sigaction");
- free(prompt);
- return EXIT_FAILURE;
+ if(errno != EINTR){
+ error_plus(0, errno, "sigaction");
+ status = EX_OSERR;
+ }
+ goto failure;
}
}
ret = sigaction(SIGHUP, NULL, &old_action);
if(ret == -1){
- perror("sigaction");
- free(prompt);
- return EXIT_FAILURE;
+ if(errno != EINTR){
+ error_plus(0, errno, "sigaction");
+ status = EX_OSERR;
+ }
+ goto failure;
}
- if (old_action.sa_handler != SIG_IGN){
+ if(old_action.sa_handler != SIG_IGN){
ret = sigaction(SIGHUP, &new_action, NULL);
if(ret == -1){
- perror("sigaction");
- free(prompt);
- return EXIT_FAILURE;
+ if(errno != EINTR){
+ error_plus(0, errno, "sigaction");
+ status = EX_OSERR;
+ }
+ goto failure;
}
}
ret = sigaction(SIGTERM, NULL, &old_action);
if(ret == -1){
- perror("sigaction");
- free(prompt);
- return EXIT_FAILURE;
+ if(errno != EINTR){
+ error_plus(0, errno, "sigaction");
+ status = EX_OSERR;
+ }
+ goto failure;
}
- if (old_action.sa_handler != SIG_IGN){
+ if(old_action.sa_handler != SIG_IGN){
ret = sigaction(SIGTERM, &new_action, NULL);
if(ret == -1){
- perror("sigaction");
- free(prompt);
- return EXIT_FAILURE;
+ if(errno != EINTR){
+ error_plus(0, errno, "sigaction");
+ status = EX_OSERR;
+ }
+ goto failure;
}
}
}
+ usplash_accessed = true;
/* Write command to FIFO */
- if(not interrupted_by_signal){
- if(not usplash_write("TIMEOUT", "0")
- and (errno != EINTR)){
- perror("usplash_write");
- an_error_occured = true;
- }
- }
- if(not interrupted_by_signal and not an_error_occured){
- if(not usplash_write("INPUTQUIET", prompt)
- and (errno != EINTR)){
- perror("usplash_write");
- an_error_occured = true;
- }
- }
+ 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);
-
- /* This is not really a loop; while() is used to be able to "break"
- out of it; those breaks are marked "Big" */
- while(not interrupted_by_signal and not an_error_occured){
- char *buf = NULL;
- size_t buf_len = 0;
-
- /* Open FIFO */
- int fifo_fd;
- do{
- fifo_fd = open("/dev/.initramfs/usplash_outfifo", O_RDONLY);
- if(fifo_fd == -1){
- if(errno != EINTR){
- perror("open");
- an_error_occured = true;
- break;
- }
- if(interrupted_by_signal){
- break;
- }
- }
- }while(fifo_fd == -1);
- if(interrupted_by_signal or an_error_occured){
- break; /* Big */
- }
-
- /* Read from FIFO */
- 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){
- perror("realloc");
- an_error_occured = true;
- break;
- }
- buf = tmp;
- buf_allocated += blocksize;
- }
- do{
- sret = read(fifo_fd, buf + buf_len, buf_allocated - buf_len);
- if(sret == -1){
- if(errno != EINTR){
- perror("read");
- an_error_occured = true;
- break;
- }
- if(interrupted_by_signal){
- break;
- }
- }
- }while(sret == -1);
- if(interrupted_by_signal or an_error_occured){
- break;
- }
-
- buf_len += (size_t)sret;
- }while(sret != 0);
- close(fifo_fd);
- if(interrupted_by_signal or an_error_occured){
- break; /* Big */
- }
-
- if(not usplash_write("TIMEOUT", "15")
- and (errno != EINTR)){
- perror("usplash_write");
- an_error_occured = true;
- }
- if(interrupted_by_signal or an_error_occured){
- break; /* Big */
- }
-
- /* 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){
- perror("write");
- an_error_occured = true;
- break;
- }
- if(interrupted_by_signal){
- break;
- }
- }
- }while(sret == -1);
- if(interrupted_by_signal or an_error_occured){
- break;
- }
- written += (size_t)sret;
- }
- free(buf);
- if(not interrupted_by_signal and not an_error_occured){
- free(cmdline);
- return EXIT_SUCCESS;
- }
- break; /* Big */
- }
-
- /* If we got here, an error or interrupt must have happened */
-
- int cmdline_argc = 0;
- char **cmdline_argv = malloc(sizeof(char *));
- /* Create argv and argc for new usplash*/
- {
- size_t position = 0;
- while(position < cmdline_len){
- char **tmp = realloc(cmdline_argv,
- (sizeof(char *)
- * (size_t)(cmdline_argc + 2)));
+ 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){
- perror("realloc");
- free(cmdline_argv);
- return EXIT_FAILURE;
- }
- cmdline_argv = tmp;
- cmdline_argv[cmdline_argc] = cmdline + position;
- cmdline_argc++;
- position += strlen(cmdline + position) + 1;
- }
- cmdline_argv[cmdline_argc] = 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);
+ 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) */
+ the real user ID (_mandos) */
ret = setuid(geteuid());
- if (ret == -1){
- perror("setuid");
+ 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){
- perror("dup2");
- _exit(EXIT_FAILURE);
+ error_plus(0, errno, "dup2");
+ _exit(EX_OSERR);
}
execv(usplash_name, cmdline_argv);
- perror("execv");
+ if(not interrupted_by_signal){
+ error_plus(0, errno, "execv");
+ }
free(cmdline);
free(cmdline_argv);
- _exit(EXIT_FAILURE);
+ _exit(EX_OSERR);
}
free(cmdline);
free(cmdline_argv);
sleep(2);
- if(not usplash_write("PULSATE", NULL)
- and (errno != EINTR)){
- perror("usplash_write");
- }
-
- return EXIT_FAILURE;
+ 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;
}
=== added file 'plugins.d/usplash.xml'
--- plugins.d/usplash.xml 1970-01-01 00:00:00 +0000
+++ plugins.d/usplash.xml 2011-12-31 23:05:34 +0000
@@ -0,0 +1,301 @@
+
+
+
+
+%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
+
+
+
+
+
+
+
+