=== added directory '.bzr-builddeb'
=== added file '.bzr-builddeb/default.conf'
--- .bzr-builddeb/default.conf 1970-01-01 00:00:00 +0000
+++ .bzr-builddeb/default.conf 2008-09-17 00:34:09 +0000
@@ -0,0 +1,2 @@
+[BUILDDEB]
+split = True
=== added file '.bzrignore'
--- .bzrignore 1970-01-01 00:00:00 +0000
+++ .bzrignore 2008-10-03 09:32:30 +0000
@@ -0,0 +1,14 @@
+*.5
+*.8
+*.8mandos
+confdir
+debian/po/messages.mo
+debian/po/templates.pot
+keydir
+man
+plugin-runner
+plugins.d/askpass-fifo
+plugins.d/mandos-client
+plugins.d/password-prompt
+plugins.d/splashy
+plugins.d/usplash
=== added file 'DBUS-API'
--- DBUS-API 1970-01-01 00:00:00 +0000
+++ DBUS-API 2010-09-26 18:32:58 +0000
@@ -0,0 +1,172 @@
+ -*- 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.bsnet.fukt.Mandos"
+
+
+* Object Paths:
+
+ | Path | Object |
+ |-----------------------+-------------------|
+ | "/" | The Mandos Server |
+ | "/clients/CLIENTNAME" | Mandos Client |
+
+
+* Mandos Server Interface:
+ Interface name: "se.bsnet.fukt.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.bsnet.fukt.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 |
+ | Fingerprint | s | Read | fingerprint |
+ | Host | s | Read/Write | host |
+ | Interval (a) | t | Read/Write | interval |
+ | LastApprovalRequest (f) | s | Read | N/A |
+ | LastCheckedOK (g) | s | Read/Write | N/A |
+ | LastEnabled (h) | s | Read | N/A |
+ | Name | s | Read | (Section name) |
+ | ObjectPath | o | Read | N/A |
+ | Secret (i) | 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 a RFC 3339 string.
+
+ e) Setting this property is equivalent to calling Enable() or
+ Disable().
+
+ f) The time of the last approval request, as a RFC 3339 string, or
+ an empty string if this has not happened.
+
+ g) The last time a checker was successful, as a 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.
+
+ h) The last time this client was enabled, as a RFC 3339 string, or
+ an empty string if this has not happened.
+
+ i) 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 Teddy Hogeborn
+ Copyright © 2010 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
=== added file 'INSTALL'
--- INSTALL 1970-01-01 00:00:00 +0000
+++ INSTALL 2009-02-15 09:28:06 +0000
@@ -0,0 +1,133 @@
+-*- org -*-
+
+* Prerequisites
+
+** Operating System
+
+ Debian 5.0 "lenny" or Ubuntu 8.04 "Hardy Heron".
+
+ This is mostly for the support scripts which make sure that the
+ client is installed and started in the initial RAM disk environment
+ and that the initrd.img file is automatically made unreadable. The
+ server and client programs themselves *could* be run in other
+ distributions, but they *are* specific to GNU/Linux systems, and
+ are not intended to be portable to other Unixes.
+
+** Libraries
+
+ The following libraries and packages are needed. (It is possible
+ that it might work with older versions of some of these, but these
+ versions are confirmed to work. Newer versions are almost
+ certainly OK.)
+
+*** Documentation
+ These are required to build the manual pages for both the server
+ and client:
+
+ + DocBook 4.5 http://www.docbook.org/
+ Note: DocBook 5.0 is not compatible.
+ + DocBook XSL stylesheets 1.71.0
+ http://wiki.docbook.org/topic/DocBookXslStylesheets
+
+ Package names:
+ docbook docbook-xsl
+
+ To build just the documentation, run the command "make doc". Then
+ the manual page "mandos.8", for example, can be read by running
+ "man -l mandos.8".
+
+*** Mandos Server
+ + GnuTLS 2.4 http://www.gnu.org/software/gnutls/
+ + Avahi 0.6.16 http://www.avahi.org/
+ + Python 2.5 http://www.python.org/
+ + Python-GnuTLS 1.1.5 http://pypi.python.org/pypi/python-gnutls/
+ + dbus-python 0.82.4 http://dbus.freedesktop.org/doc/dbus-python/
+ + python-ctypes 1.0.0 http://pypi.python.org/pypi/ctypes
+ + PyGObject 2.14.2 http://library.gnome.org/devel/pygobject/
+
+ Strongly recommended:
+ + fping 2.4b2-to-ipv6 http://www.fping.com/
+
+ Package names:
+ python-gnutls avahi-daemon python python-avahi python-dbus
+ python-ctypes python-gobject
+
+*** Mandos Client
+ + initramfs-tools 0.85i
+ http://packages.qa.debian.org/i/initramfs-tools.html
+ + GnuTLS 2.4 http://www.gnu.org/software/gnutls/
+ + Avahi 0.6.16 http://www.avahi.org/
+ + GnuPG 1.4.9 http://www.gnupg.org/
+ + GPGME 1.1.6 http://www.gnupg.org/related_software/gpgme/
+
+ Package names:
+ initramfs-tools libgnutls-dev libavahi-core-dev gnupg
+ libgpgme11-dev
+
+* Installing the Mandos server
+
+ 1. Do "make doc".
+
+ 2. On the computer to run as a Mandos server, run the following
+ command:
+ For Debian: su -c 'make install-server'
+ For Ubuntu: sudo make install-server
+
+ (This creates a configuration without any clients configured; you
+ need an actually configured client to do that; see below.)
+
+* Installing the Mandos client.
+
+ 1. Do "make all doc".
+
+ 2. On the computer to run as a Mandos client, run the following
+ command:
+ For Debian: su -c 'make install-client'
+ For Ubuntu: sudo make install-client
+
+ This will also create an OpenPGP key, which will take some time
+ and entropy, so be patient.
+
+ 3. Run the following command:
+ For Debian: su -c 'mandos-keygen --password'
+ For Ubuntu: sudo mandos-keygen --password
+
+ When prompted, enter the password/passphrase for the encrypted
+ root file system on this client computer. The command will
+ output a section of text, starting with a [section header]. Copy
+ and append this to the file "/etc/mandos/clients.conf" *on the
+ server computer*.
+
+ 4. Configure the client to use the correct network interface. The
+ default is "eth0", and if this needs to be adjusted, it will be
+ necessary to edit /etc/mandos/plugin-runner.conf to uncomment and
+ change the line there. If that file is changed, the initrd.img
+ file must be updated, possibly using the following command:
+
+ # update-initramfs -k all -u
+
+ 5. On the server computer, start the server by running the command
+ For Debian: su -c 'invoke-rc.d mandos start'
+ For Ubuntu: sudo invoke-rc.d mandos start
+
+ At this point, it is possible to verify that the correct password
+ will be received by the client by running the command:
+
+ # /usr/lib/mandos/plugins.d/mandos-client \
+ --pubkey=/etc/keys/mandos/pubkey.txt \
+ --seckey=/etc/keys/mandos/seckey.txt; echo
+
+ This command should retrieve the password from the server,
+ decrypt it, and output it to standard output.
+
+ After this, the client computer should be able to reboot without
+ needing a password entered on the console, as long as it does not
+ take more than an hour to reboot.
+
+* Further customizations
+
+ You may want to tighten or loosen the timeouts in the server
+ configuration files; see mandos.conf(5) and mandos-clients.conf(5).
+ If IPsec is not used, it is suggested that a more cryptographically
+ secure checker program is used and configured, since without IPsec
+ ping packets can be faked.
=== modified file 'Makefile'
--- Makefile 2008-08-19 23:44:17 +0000
+++ Makefile 2010-09-23 20:14:08 +0000
@@ -1,39 +1,63 @@
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 -Wunreachable-code -Winline \
- -Wvolatile-register-var
-DEBUG=-ggdb3
+ -Wpacked -Wnested-externs -Winline -Wvolatile-register-var
+# -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
-# PREFIX=/usr/local
-PREFIX=/usr
-# CONFDIR=/usr/local/lib/mandos
-CONFDIR=/etc/mandos
-# MANDIR=/usr/local/man
-MANDIR=/usr/share/man
-
-GNUTLS_CFLAGS=$(shell libgnutls-config --cflags)
-GNUTLS_LIBS=$(shell libgnutls-config --libs)
+htmldir=man
+version=1.0.14
+SED=sed
+
+## Use these settings for a traditional /usr/local install
+# PREFIX=$(DESTDIR)/usr/local
+# CONFDIR=$(DESTDIR)/etc/mandos
+# KEYDIR=$(DESTDIR)/etc/mandos/keys
+# MANDIR=$(PREFIX)/man
+# INITRAMFSTOOLS=$(DESTDIR)/etc/initramfs-tools
+##
+
+## These settings are for a package-type install
+PREFIX=$(DESTDIR)/usr
+CONFDIR=$(DESTDIR)/etc/mandos
+KEYDIR=$(DESTDIR)/etc/keys/mandos
+MANDIR=$(PREFIX)/share/man
+INITRAMFSTOOLS=$(DESTDIR)/usr/share/initramfs-tools
+##
+
+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)
+ $(LANGUAGE) $(GNUTLS_CFLAGS) $(AVAHI_CFLAGS) $(GPGME_CFLAGS) \
+ -DVERSION='"$(version)"'
+LDFLAGS=$(COVERAGE) $(LINK_FORTIFY) $(foreach flag,$(LINK_FORTIFY_LD),-Xlinker $(flag))
-# Commands to format a DocBook refentry document into a manual page
-DOCBOOKTOMAN=cd $(dir $^); xsltproc --nonet --xinclude \
+# Commands to format a DocBook document into a manual page
+DOCBOOKTOMAN=$(strip cd $(dir $<); xsltproc --nonet --xinclude \
--param man.charmap.use.subset 0 \
--param make.year.ranges 1 \
--param make.single.year.ranges 1 \
@@ -41,148 +65,364 @@
--param man.authors.section.enabled 0 \
/usr/share/xml/docbook/stylesheet/nwalsh/manpages/docbook.xsl \
$(notdir $<); \
- $(MANPOST) $(notdir $@)
-# DocBook-to-man post-processing to fix a \n escape bug
-MANPOST=sed --in-place --expression='s,\\en,\en,g;s,\\een,\\en,g'
-
-PLUGINS=plugins.d/password-prompt plugins.d/password-request
-PROGS=plugin-runner $(PLUGINS)
-DOCS=mandos.8 plugin-runner.8mandos mandos-keygen.8 \
- plugins.d/password-request.8mandos \
- plugins.d/password-prompt.8mandos mandos.conf.5 \
- mandos-clients.conf.5
-
-objects=$(addsuffix .o,$(PROGS))
-
-all: $(PROGS)
+ $(MANPOST) $(notdir $@))
+# DocBook-to-man post-processing to fix a '\n' escape bug
+MANPOST=$(SED) --in-place --expression='s,\\\\en,\\en,g;s,\\n,\\en,g'
+
+DOCBOOKTOHTML=$(strip xsltproc --nonet --xinclude \
+ --param make.year.ranges 1 \
+ --param make.single.year.ranges 1 \
+ --param man.output.quietly 1 \
+ --param man.authors.section.enabled 0 \
+ --param citerefentry.link 1 \
+ --output $@ \
+ /usr/share/xml/docbook/stylesheet/nwalsh/xhtml/docbook.xsl \
+ $<; $(HTMLPOST) $@)
+# Fix citerefentry links
+HTMLPOST=$(SED) --in-place \
+ --expression='s/\(\)\([^<]*\)\(<\/span>(\)\([^)]*\)\()<\/span><\/a>\)/\1\3.\5\2\3\4\5\6/g'
+
+PLUGINS=plugins.d/password-prompt plugins.d/mandos-client \
+ plugins.d/usplash plugins.d/splashy plugins.d/askpass-fifo \
+ plugins.d/plymouth
+CPROGS=plugin-runner $(PLUGINS)
+PROGS=mandos mandos-keygen mandos-ctl mandos-monitor $(CPROGS)
+DOCS=mandos.8 mandos-keygen.8 mandos-monitor.8 mandos-ctl.8 \
+ mandos.conf.5 mandos-clients.conf.5 plugin-runner.8mandos \
+ plugins.d/mandos-client.8mandos \
+ plugins.d/password-prompt.8mandos plugins.d/usplash.8mandos \
+ plugins.d/splashy.8mandos plugins.d/askpass-fifo.8mandos \
+ plugins.d/plymouth.8mandos
+
+htmldocs=$(addsuffix .xhtml,$(DOCS))
+
+objects=$(addsuffix .o,$(CPROGS))
+
+all: $(PROGS) mandos.lsm
doc: $(DOCS)
-%.5: %.xml
- $(DOCBOOKTOMAN)
-
-%.8: %.xml
- $(DOCBOOKTOMAN)
-
-%.8mandos: %.xml
- $(DOCBOOKTOMAN)
-
-mandos.8: mandos.xml mandos-options.xml
- $(DOCBOOKTOMAN)
-
-mandos.conf.5: mandos.conf.xml mandos-options.xml
- $(DOCBOOKTOMAN)
-
-plugins.d/password-request: plugins.d/password-request.o
- $(LINK.o) $(GNUTLS_LIBS) $(AVAHI_LIBS) $(GPGME_LIBS) \
- $(COMMON) $^ $(LOADLIBES) $(LDLIBS) -o $@
-
-.PHONY : all doc clean distclean run-client run-server install \
+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)
+
+mandos.8: mandos.xml common.ent mandos-options.xml overview.xml \
+ legalnotice.xml
+ $(DOCBOOKTOMAN)
+mandos.8.xhtml: mandos.xml common.ent mandos-options.xml \
+ overview.xml legalnotice.xml
+ $(DOCBOOKTOHTML)
+
+mandos-keygen.8: mandos-keygen.xml common.ent overview.xml \
+ legalnotice.xml
+ $(DOCBOOKTOMAN)
+mandos-keygen.8.xhtml: mandos-keygen.xml common.ent overview.xml \
+ legalnotice.xml
+ $(DOCBOOKTOHTML)
+
+mandos-monitor.8: mandos-monitor.xml common.ent overview.xml \
+ legalnotice.xml
+ $(DOCBOOKTOMAN)
+mandos-monitor.8.xhtml: mandos-monitor.xml common.ent overview.xml \
+ legalnotice.xml
+ $(DOCBOOKTOHTML)
+
+mandos-ctl.8: mandos-ctl.xml common.ent overview.xml \
+ legalnotice.xml
+ $(DOCBOOKTOMAN)
+mandos-ctl.8.xhtml: mandos-ctl.xml common.ent overview.xml \
+ legalnotice.xml
+ $(DOCBOOKTOHTML)
+
+mandos.conf.5: mandos.conf.xml common.ent mandos-options.xml \
+ legalnotice.xml
+ $(DOCBOOKTOMAN)
+mandos.conf.5.xhtml: mandos.conf.xml common.ent mandos-options.xml \
+ legalnotice.xml
+ $(DOCBOOKTOHTML)
+
+plugin-runner.8mandos: plugin-runner.xml common.ent overview.xml \
+ legalnotice.xml
+ $(DOCBOOKTOMAN)
+plugin-runner.8mandos.xhtml: plugin-runner.xml common.ent \
+ overview.xml legalnotice.xml
+ $(DOCBOOKTOHTML)
+
+plugins.d/mandos-client.8mandos: plugins.d/mandos-client.xml \
+ common.ent \
+ mandos-options.xml \
+ overview.xml legalnotice.xml
+ $(DOCBOOKTOMAN)
+plugins.d/mandos-client.8mandos.xhtml: plugins.d/mandos-client.xml \
+ common.ent \
+ mandos-options.xml \
+ overview.xml legalnotice.xml
+ $(DOCBOOKTOHTML)
+
+# Update all these files with version number $(version)
+common.ent: Makefile
+ $(strip $(SED) --in-place \
+ --expression='s/^\($$/\1$(version)">/' \
+ $@)
+
+mandos: Makefile
+ $(strip $(SED) --in-place \
+ --expression='s/^\(version = "\)[^"]*"$$/\1$(version)"/' \
+ $@)
+
+mandos-keygen: Makefile
+ $(strip $(SED) --in-place \
+ --expression='s/^\(VERSION="\)[^"]*"$$/\1$(version)"/' \
+ $@)
+
+mandos-ctl: Makefile
+ $(strip $(SED) --in-place \
+ --expression='s/^\(version = "\)[^"]*"$$/\1$(version)"/' \
+ $@)
+
+mandos-monitor: Makefile
+ $(strip $(SED) --in-place \
+ --expression='s/^\(version = "\)[^"]*"$$/\1$(version)"/' \
+ $@)
+
+mandos.lsm: Makefile
+ $(strip $(SED) --in-place \
+ --expression='s/^\(Version:\).*/\1\t$(version)/' \
+ $@)
+ $(strip $(SED) --in-place \
+ --expression='s/^\(Entered-date:\).*/\1\t$(shell date --rfc-3339=date --reference=Makefile)/' \
+ $@)
+ $(strip $(SED) --in-place \
+ --expression='s/\(mandos_\)[0-9.]\+\(\.orig\.tar\.gz\)/\1$(version)\2/' \
+ $@)
+
+plugins.d/mandos-client: plugins.d/mandos-client.c
+ $(LINK.c) $(GNUTLS_LIBS) $(AVAHI_LIBS) $(GPGME_LIBS) $(strip\
+ ) $(COMMON) $^ $(LOADLIBES) $(LDLIBS) -o $@
+
+.PHONY : all doc html clean distclean run-client run-server install \
install-server install-client uninstall uninstall-server \
uninstall-client purge purge-server purge-client
clean:
- -rm --force $(PROGS) $(objects) $(DOCS) core
+ -rm --force $(CPROGS) $(objects) $(htmldocs) $(DOCS) core
distclean: clean
mostlyclean: clean
maintainer-clean: clean
- -rm --force --recursive keydir
+ -rm --force --recursive keydir confdir
-check:
+check: all
./mandos --check
-run-client: all
- -mkdir keydir
- -./mandos-keygen --dir keydir
+# 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 \
- --options-for=password-request:--keydir=keydir
-
-run-server:
- ./mandos --debug --configdir=.
-
-install: install-server install-client
+ --config-file=plugin-runner.conf \
+ --options-for=mandos-client:--seckey=keydir/seckey.txt,--pubkey=keydir/pubkey.txt \
+ $(CLIENTARGS)
+
+# Used by run-client
+keydir/seckey.txt keydir/pubkey.txt: mandos-keygen
+ install --directory keydir
+ ./mandos-keygen --dir keydir --force
+
+# Run the server with a local config
+run-server: confdir/mandos.conf confdir/clients.conf
+ @echo "#################################################################"
+ @echo "# NOTE: Please IGNORE the error about \"Could not open file #"
+ @echo "# u'/var/run/mandos.pid'\" - it is harmless and is caused by #"
+ @echo "# the server not running as root. Do NOT run \"make run-server\" #"
+ @echo "# server as root if you didn't also unpack and compile it thus. #"
+ @echo "#################################################################"
+ ./mandos --debug --no-dbus --configdir=confdir $(SERVERARGS)
+
+# Used by run-server
+confdir/mandos.conf: mandos.conf
+ install --directory confdir
+ install --mode=u=rw,go=r $^ $@
+confdir/clients.conf: clients.conf keydir/seckey.txt
+ install --directory confdir
+ install --mode=u=rw $< $@
+# Add a client password
+ ./mandos-keygen --dir keydir --password >> $@
+
+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
- mkdir --mode=0755 --parents $(CONFDIR) $(MANDIR)/man5 \
- $(MANDIR)/man8
- install --mode=0755 mandos $(PREFIX)/sbin/mandos
- install --mode=0644 --target-directory=$(CONFDIR) mandos.conf
- install --mode=0640 --target-directory=$(CONFDIR) \
+ install --directory $(CONFDIR)
+ 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
+ install --mode=u=rw,go=r default-mandos \
+ $(DESTDIR)/etc/default/mandos
+ if [ -z $(DESTDIR) ]; then \
+ update-rc.d mandos defaults 25 15;\
+ fi
gzip --best --to-stdout mandos.8 \
> $(MANDIR)/man8/mandos.8.gz
+ gzip --best --to-stdout mandos-monitor.8 \
+ > $(MANDIR)/man8/mandos-monitor.8.gz
+ gzip --best --to-stdout mandos-ctl.8 \
+ > $(MANDIR)/man8/mandos-ctl.8.gz
gzip --best --to-stdout mandos.conf.5 \
> $(MANDIR)/man5/mandos.conf.5.gz
gzip --best --to-stdout mandos-clients.conf.5 \
> $(MANDIR)/man5/mandos-clients.conf.5.gz
-install-client: all doc /usr/share/initramfs-tools/hooks/.
- mkdir --mode=0755 --parents $(PREFIX)/lib/mandos $(CONFDIR) \
- $(MANDIR)/man8
- -mkdir --mode=0700 $(PREFIX)/lib/mandos/plugins.d
- chmod u=rwx,g=,o= $(PREFIX)/lib/mandos/plugins.d
- install --mode=0755 --target-directory=$(PREFIX)/lib/mandos \
- plugin-runner
- install --mode=0755 --target-directory=$(PREFIX)/sbin \
+install-client-nokey: all doc
+ install --directory $(PREFIX)/lib/mandos $(CONFDIR)
+ install --directory --mode=u=rwx $(KEYDIR) \
+ $(PREFIX)/lib/mandos/plugins.d
+ if [ "$(CONFDIR)" != "$(PREFIX)/lib/mandos" ]; then \
+ install --mode=u=rwx \
+ --directory "$(CONFDIR)/plugins.d"; \
+ fi
+ install --mode=u=rwx,go=rx \
+ --target-directory=$(PREFIX)/lib/mandos plugin-runner
+ install --mode=u=rwx,go=rx --target-directory=$(PREFIX)/sbin \
mandos-keygen
- install --mode=0755 \
+ install --mode=u=rwx,go=rx \
--target-directory=$(PREFIX)/lib/mandos/plugins.d \
plugins.d/password-prompt
- install --mode=4755 \
- --target-directory=$(PREFIX)/lib/mandos/plugins.d \
- plugins.d/password-request
+ install --mode=u=rwxs,go=rx \
+ --target-directory=$(PREFIX)/lib/mandos/plugins.d \
+ plugins.d/mandos-client
+ install --mode=u=rwxs,go=rx \
+ --target-directory=$(PREFIX)/lib/mandos/plugins.d \
+ plugins.d/usplash
+ install --mode=u=rwxs,go=rx \
+ --target-directory=$(PREFIX)/lib/mandos/plugins.d \
+ plugins.d/splashy
+ install --mode=u=rwxs,go=rx \
+ --target-directory=$(PREFIX)/lib/mandos/plugins.d \
+ plugins.d/askpass-fifo
+ install --mode=u=rwxs,go=rx \
+ --target-directory=$(PREFIX)/lib/mandos/plugins.d \
+ plugins.d/plymouth
install initramfs-tools-hook \
- /usr/share/initramfs-tools/hooks/mandos
- install initramfs-tools-hook-conf \
- /usr/share/initramfs-tools/conf-hooks.d/mandos
+ $(INITRAMFSTOOLS)/hooks/mandos
+ install --mode=u=rw,go=r initramfs-tools-hook-conf \
+ $(INITRAMFSTOOLS)/conf-hooks.d/mandos
install initramfs-tools-script \
- /usr/share/initramfs-tools/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/password-request.8mandos \
- > $(MANDIR)/man8/password-request.8mandos.gz
- -$(PREFIX)/sbin/mandos-keygen
+ gzip --best --to-stdout plugins.d/usplash.8mandos \
+ > $(MANDIR)/man8/usplash.8mandos.gz
+ gzip --best --to-stdout plugins.d/splashy.8mandos \
+ > $(MANDIR)/man8/splashy.8mandos.gz
+ gzip --best --to-stdout plugins.d/askpass-fifo.8mandos \
+ > $(MANDIR)/man8/askpass-fifo.8mandos.gz
+ gzip --best --to-stdout plugins.d/plymouth.8mandos \
+ > $(MANDIR)/man8/plymouth.8mandos.gz
+
+install-client: install-client-nokey
+# Post-installation stuff
+ -$(PREFIX)/sbin/mandos-keygen --dir "$(KEYDIR)"
update-initramfs -k all -u
+ echo "Now run mandos-keygen --password --dir $(KEYDIR)"
uninstall: uninstall-server uninstall-client
-uninstall-server: $(PREFIX)/sbin/mandos
+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
-rmdir $(CONFDIR)
uninstall-client:
# Refuse to uninstall client if /etc/crypttab is explicitly configured
# to use it.
! grep --regexp='^ *[^ #].*keyscript=[^,=]*/mandos/' \
- /etc/crypttab
+ $(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/password-request \
- /usr/share/initramfs-tools/hooks/mandos \
- /usr/share/initramfs-tools/conf-hooks.d/mandos \
+ $(PREFIX)/lib/mandos/plugins.d/mandos-client \
+ $(PREFIX)/lib/mandos/plugins.d/usplash \
+ $(PREFIX)/lib/mandos/plugins.d/splashy \
+ $(PREFIX)/lib/mandos/plugins.d/askpass-fifo \
+ $(PREFIX)/lib/mandos/plugins.d/plymouth \
+ $(INITRAMFSTOOLS)/hooks/mandos \
+ $(INITRAMFSTOOLS)/conf-hooks.d/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/password-request.8mandos.gz
+ $(MANDIR)/man8/usplash.8mandos.gz \
+ $(MANDIR)/man8/splashy.8mandos.gz \
+ $(MANDIR)/man8/askpass-fifo.8mandos.gz \
+ $(MANDIR)/man8/plymouth.8mandos.gz \
-rmdir $(PREFIX)/lib/mandos/plugins.d $(CONFDIR)/plugins.d \
- $(PREFIX)/lib/mandos $(CONFDIR)
+ $(PREFIX)/lib/mandos $(CONFDIR) $(KEYDIR)
update-initramfs -k all -u
purge: purge-server purge-client
purge-server: uninstall-server
- -rm --force $(CONFDIR)/mandos.conf $(CONFDIR)/clients.conf
+ -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 \
+ $(DESTDIR)/var/run/mandos.pid
-rmdir $(CONFDIR)
purge-client: uninstall-client
- -rm --force $(CONFDIR)/seckey.txt $(CONFDIR)/pubkey.txt
- -rmdir $(CONFDIR) $(CONFDIR)/plugins.d
+ -shred --remove $(KEYDIR)/seckey.txt
+ -rm --force $(CONFDIR)/plugin-runner.conf \
+ $(KEYDIR)/pubkey.txt $(KEYDIR)/seckey.txt
+ -rmdir $(KEYDIR) $(CONFDIR)/plugins.d $(CONFDIR)
=== added file 'NEWS'
--- NEWS 1970-01-01 00:00:00 +0000
+++ NEWS 2010-09-09 18:16:14 +0000
@@ -0,0 +1,127 @@
+This NEWS file records noteworthy changes, very tersely.
+See the manual for detailed information.
+
+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.
=== added file 'README'
--- README 1970-01-01 00:00:00 +0000
+++ README 2010-09-26 18:32:58 +0000
@@ -0,0 +1,182 @@
+-*- org -*-
+
+* Mandos
+ - Have your cake and eat it too!
+
+ You know how it is. You’ve heard of it happening. The Man comes
+ and takes away your servers, your friends’ servers, the servers of
+ everybody in the same hosting facility. The servers of their
+ neighbors, and their neighbors’ friends. The servers of people who
+ owe them money. And like *that*, they’re gone. And you doubt
+ you’ll ever see them again.
+
+ That is why your servers have encrypted root file systems. However,
+ there’s a downside. There’s no going around it: rebooting is a
+ pain. Dragging out that rarely-used keyboard and screen and
+ unraveling cables behind your servers to plug them in to type in
+ that password is messy, especially if you have many servers. There
+ are some people who do clever things like using serial line consoles
+ and daisy-chain it to the next server, and keep all the servers
+ connected in a ring with serial cables, which will work, if your
+ servers are physically close enough. There are also other
+ out-of-band management solutions, but with *all* these, you still
+ have to be on hand and manually type in the password at boot time.
+ Otherwise the server just sits there, waiting for a password.
+
+ Wouldn’t it be great if you could have the security of encrypted
+ root file systems and still have servers that could boot up
+ automatically if there was a short power outage while you were
+ asleep? That you could reboot at will, without having someone run
+ over to the server to type in the password?
+
+ Well, with Mandos, you (almost) can! The gain in convenience will
+ only be offset by a small loss in security. The setup is as
+ follows:
+
+ The server will still have its encrypted root file system. The
+ password to this file system will be stored on another computer
+ (henceforth known as the Mandos server) on the same local network.
+ The password will *not* be stored in plaintext, but encrypted with
+ OpenPGP. To decrypt this password, a key is needed. This key (the
+ Mandos client key) will not be stored there, but back on the
+ original server (henceforth known as the Mandos client) in the
+ initial RAM disk image. Oh, and all network Mandos client/server
+ communications will be encrypted, using TLS (SSL).
+
+ So, at boot time, the Mandos client will ask for its encrypted data
+ over the network, decrypt it to get the password, use it to decrypt
+ the root file, and continue booting.
+
+ Now, of course the initial RAM disk image is not on the encrypted
+ root file system, so anyone who had physical access could take the
+ Mandos client computer offline and read the disk with their own
+ tools to get the authentication keys used by a client. *But*, by
+ then the Mandos server should notice that the original server has
+ been offline for too long, and will no longer give out the encrypted
+ key. The timing here is the only real weak point, and the method,
+ frequency and timeout of the server’s checking can be adjusted to
+ any desired level of paranoia
+
+ (The encrypted keys on the Mandos server is on its normal file
+ system, so those are safe, provided the root file system of *that*
+ server is encrypted.)
+
+* FAQ - couldn’t the security be defeated by...
+
+** Grabbing the Mandos client key from the initrd *really quickly*?
+ This, as mentioned above, is the only real weak point. But if you
+ set the timing values tight enough, this will be really difficult
+ to do. An attacker would have to physically disassemble the client
+ computer, extract the key from the initial RAM disk image, and then
+ connect to a *still online* Mandos server to get the encrypted key,
+ and do all this *before* the Mandos server timeout kicks in and the
+ Mandos server refuses to give out the key to anyone.
+
+ Now, as the typical procedure seems to be to barge in and turn off
+ and grab *all* computers, to maybe look at them months later, this
+ is not likely. If someone does that, the whole system *will* lock
+ itself up completely, since Mandos servers are no longer running.
+
+ For sophisticated attackers who *could* do the clever thing, *and*
+ had physical access to the server for enough time, it would be
+ simpler to get a key for an encrypted file system by using hardware
+ memory scanners and reading it right off the memory bus.
+
+** Replay attacks?
+ Nope, the network stuff is all done over TLS, which provides
+ protection against that.
+
+** Man-in-the-middle?
+ No. The server only gives out the passwords to clients which have
+ *in the TLS handshake* proven that they do indeed hold the OpenPGP
+ private key corresponding to that client.
+
+** Physically grabbing the Mandos server computer?
+ You could protect *that* computer the old-fashioned way, with a
+ must-type-in-the-password-at-boot method. Or you could have two
+ computers be the Mandos server for each other.
+
+ Multiple Mandos servers can coexist on a network without any
+ trouble. They do not clash, and clients will try all available
+ servers. This means that if just one reboots then the other can
+ bring it back up, but if both reboot at the same time they will
+ stay down until someone types in the password on one of them.
+
+** Faking ping replies?
+ The default for the server is to use "fping", the replies to which
+ could be faked to eliminate the timeout. But this could easily be
+ changed to any shell command, with any security measures you like.
+ It could, for instance, be changed to an SSH command with strict
+ keychecking, which could not be faked. Or IPsec could be used for
+ the ping packets, making them secure.
+
+* Security Summary
+ So, in summary: The only weakness in the Mandos system is from
+ people who have:
+ 1. The power to come in and physically take your servers, *and*
+ 2. The cunning and patience to do it carefully, one at a time, and
+ *quickly*, faking Mandos client/server responses for each one
+ before the timeout.
+
+ While there are some who may be threatened by people who have *both*
+ these attributes, they do not, probably, constitute the majority.
+
+ If you *do* face such opponents, you must figure that they could
+ just as well open your servers and read the file system keys right
+ off the memory by running wires to the memory bus.
+
+ What Mandos is designed to protect against is *not* such determined,
+ focused, and competent attacks, but against the early morning knock
+ on your door and the sudden absence of all the servers in your
+ server room. Which it does nicely.
+
+* The Plugin System
+ In the early designs, the mandos-client(8mandos) program (which
+ retrieves a password from the Mandos server) also prompted for a
+ password on the terminal, in case a Mandos server could not be
+ found. Other ways of retrieving a password could easily be
+ envisoned, but this multiplicity of purpose was seen to be too
+ complex to be a viable way to continue. Instead, the original
+ program was separated into mandos-client(8mandos) and
+ password-prompt(8mandos), and a plugin-runner(8mandos) exist to run
+ them both in parallel, allowing the first successful plugin to
+ provide the password. This opened up for any number of additional
+ plugins to run, all competing to be the first to find a password and
+ provide it to the plugin runner.
+
+ Four additional plugins are provided:
+ * plymouth(8mandos)
+ This prompts for a password when using plymouth(8).
+ * usplash(8mandos)
+ This prompts for a password when using usplash(8).
+ * splashy(8mandos)
+ This prompts for a password when using splashy(8).
+ * askpass-fifo(8mandos)
+ To provide compatibility with the "askpass" program from
+ cryptsetup, this plugin listens to the same FIFO as askpass would
+ do.
+
+ More plugins can easily be written and added by the system
+ administrator; see the section called "WRITING PLUGINS" in
+ plugin-runner(8mandos) to learn the plugin requirements.
+
+* Copyright
+
+ Copyright © 2008-2010 Teddy Hogeborn
+ Copyright © 2008-2010 Björn Påhlsson
+
+** License:
+
+ This program is free software: you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
+ .
=== modified file 'TODO'
--- TODO 2008-08-18 05:57:11 +0000
+++ TODO 2010-09-26 18:32:58 +0000
@@ -1,141 +1,134 @@
-*- org -*-
-* [#A] README file
+* Use _attribute_((nonnull)) wherever possible.
+
+* mandos-client
+** TODO [#B] use scandir(3) instead of readdir(3)
+** TODO [#B] Prefix all debug output with "Mandos plugin " + program_invocation_short_name
+** TODO [#B] Retry a server which has a non-definite reply:
+*** A closed connection during the TLS handshake
+*** A TCP timeout
+** TODO [#B] Use capabilities instead of seteuid().
+** TODO [#A] Retry --connect forever
+
+* splashy
+** TODO [#B] use scandir(3) instead of readdir(3)
+** TODO [#B] Prefix all debug output with "Mandos plugin " + program_invocation_short_name
+
+* usplash
+** TODO [#A] Make it work again
+** TODO [#B] use scandir(3) instead of readdir(3)
+** TODO [#B] Prefix all debug output with "Mandos plugin " + program_invocation_short_name
+** TODO Use [[info:libc:Argz%20Functions][argz_extract]]
+
+* askpass-fifo
+** TODO [#B] Prefix all debug output with "Mandos plugin " + program_invocation_short_name
+** TODO [#B] Drop privileges after opening FIFO.
+
+* password-prompt
+** TODO [#B] Prefix all debug output with "Mandos plugin " + program_invocation_short_name
+** TODO [#B] lock stdin (with flock()?)
+
+* TODO [#B] passdev
* plugin-runner
-** [#B] Add more comments to code
-** [#B] Add more if(debug) calls
-** [#B] Seperate more code to function for more readability
-** [#A] Man page: man8/plugin-runner.8mandos
-*** EXIT STATUS
-*** EXAMPLES
- Examples of normal usage, debug usage, debugging single or all
- plugins, etc.
-*** FILES
-*** SECURITY
- Note the danger of using this program, since you might lock
- yourself out of your system without any means of entering the root
- file system password. This is, however, very unlikely considering
- the fallback to getpass(3).
-*** BUGS
-*** SEE ALSO
- Explaining text on what you can read
-
-* password-request
-** [#A] Man page: man8/password-request.8mandos
-*** SYNOPSIS
- Document short options
-*** DESCRIPTION
- State that this command is not meant to be invoked directly, but
- is run as a plugin from mandos-client(8) and only run in the
- initrd environment, not the real system.
-*** PURPOSE
- As in mandos.xml
-*** OVERVIEW
- As in mandos.xml
-*** EXIT STATUS
-*** ENVIRONMENT
- Note that it does *not* currently use cryptsource or crypttarget.
-*** FILES
- Describe the key files and the key ring files. Also note that
- they should normally have been automatically created.
-*** BUGS
-*** EXAMPLE
- Examples of normal usage, debug usage, debugging by connecting
- directly, etc.
-*** SECURITY
-*** SEE ALSO
- Update from mandos.xml
-** [#B] Temporarily lower kernel log level
- for less printouts during sucessfull boot.
-** IPv4 support
-** use strsep instead of strtok?
-** Do not depend on GnuPG key rings on disk
- This would mean creating new GnuPG key rings with GPGME by
- importing the key files from scratch on every program start.
-** Keydir move: /etc/mandos -> /etc/keys/mandos
- Must create in preinst if not pre-depending on cryptsetup
-
-* password-prompt
-** [#A] Man page: man8/password-prompt.8mandos
-*** SYNOPSIS
- Document short options
-*** DESCRIPTION
- Note that this is more or less a simple getpass(3) wrapper, even
- though actual use of getpass(3) is not guaranteed.
-*** EXIT STATUS
-*** ENVIRONMENT
- Document use of "cryptsource" and "crypttarget".
-*** FILES
-*** BUGS
-*** EXAMPLE
- Examples of normal usage, debug usage, with a prefix, etc.
-*** SECURITY
- Not much to do here but it is noteworthy to state the danger of
- not having a fallback option.
-*** SEE ALSO
- Refer to mandos-client(8mandos) and password-request(8mandos)
- and also, perhaps, to cryptsetup(8)?
-** Use getpass(3)?
- Man page says "obsolete", but [[info:libc:getpass][GNU LibC Manual: Reading Passwords]]
- does not. See also [[http://sources.redhat.com/ml/libc-alpha/2003-05/msg00251.html][Marcus Brinkmann: Re: getpass obsolete?]] and
- [[http://article.gmane.org/gmane.comp.lib.glibc.alpha/4906][Petter Reinholdtsen: Re: getpass obsolete?]], and especially also
- [[http://www.steve.org.uk/Reference/Unix/faq_4.html#SEC48][Unix Programming FAQ 3.1 How can I make my program not echo input?]]
+** 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)
-** [#A] Config file man page: man5/mandos.conf (mandos.conf)
-** [#A] Config file man page: man5/mandos-clients.conf (clients.conf)
-** [#A] /etc/init.d/mandos-server :teddy:
-** [#B] Log level :bugs:
-** /etc/mandos/clients.d/*.conf
- Watch this directory and add/remove/update clients?
-** config for TXT record
-** [#B] Run-time communication with server :bugs:
- Probably using D-Bus
- See also [[*Mandos-tools]]
-** Implement --foreground :bugs:
- [[info:standards:Option%20Table][Table of Long Options]]
-** Implement --socket
- [[info:standards:Option%20Table][Table of Long Options]]
-** Date+time on console log messages :bugs:
+** TODO [#B] Log level :BUGS:
+** TODO Persistent state :BUGS:
+ /var/lib/mandos/*
+*** TODO /etc/mandos/clients.d/*.conf
+ Watch this directory and add/remove/update clients?
+** TODO [#C] config for TXT record
+** TODO Log level option
+ syslogger.setLevel(logging.WARNING)
+ + SetLogLevel D-Bus call
+** TODO Implement --foreground :BUGS:
+ [[info:standards:Option%20Table][Table of Long Options]]
+** TODO Implement --socket
+ [[info:standards:Option%20Table][Table of Long Options]]
+** TODO Date+time on console log messages :BUGS:
Is this the default?
-
-* Mandos-tools/utilities
- All of this probably using D-Bus
-** List clients
-** Disable client
-** Enable client
-
-* Installer
-** Client-side
-*** Update initrd.img after installation
- This seems to use some kind of "trigger" system
-*** Keydir move: /etc/mandos -> /etc/keys/mandos
- Must create in preinst if not pre-depending on cryptsetup
-*** mandos-keygen
-**** [#A] Output cut-and-paste ready snippet for clients.conf.
-** Server-side
-*** [#A] Create mandos user and group for server
-*** [#A] Create /var/run/mandos directory with perm and ownership
-
-* [#A] Package
+** TODO [#C] DBusServiceObjectUsingSuper
+** TODO [#B] Global enable/disable flag
+** TODO [#B] By-client countdown on secrets given
+** TODO [#B] Fix problem with fsck taking a really long time
+ Whenever a client successfully gets a secret it could get a
+ one-time timeout boost to allow for an fsck-incurred delay
+** TODO [#A] Delay before client receives key
+ This would give an operator opportunity to cancel the request if
+ desired.
+** TODO [#A] Client manual approval mode
+ A client needs manual approval on the server before it gets the
+ secret
+** TODO [#B] Support RFC 3339 time duration syntax
+** More D-Bus methods
+*** NeedsApproval(50, True) -> timeout, default approve
+ Default approval is configurable, but True by default
+ + Approve(True) -> approve sending saved
+ + Approve(False) -> Close client connection immediately
+*** NeedsPassword(50) - Timeout, default disapprove
+ + SetPass(u"gazonk", True) -> Approval, persistent
+ + Approve(False) -> Close client connection immediately
+** TODO [#C] python-parsedatetime
+** TODO [#C] systemd/launchd
+ http://0pointer.de/blog/projects/systemd.html
+** TODO Separate logging logic to own object
+** TODO make clients to a dict!
+** 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
+
+* 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 Support RFC 3339 time duration syntax
+
+* TODO mandos-dispatch
+ Listens for specified D-Bus signals and spawns shell commands with
+ arguments.
+
+* mandos-monitor
+** TODO help should be toggable
+** Urwid client data displayer
+ Better view of client data in the listing
+*** Properties popup
+** Nicer crashes. Stack traces Messes up shell.
+*** Print a nice "We are sorry" message, save stack trace to log.
+** Show timeout countdown for approval
+
+* mandos-keygen
+** TODO Loop until passwords match when run interactively
+** TODO "--secfile" option
+ Using the "secfile" option instead of "secret"
+** TODO [#B] "--test" option
+ For testing decryption before rebooting.
+
+* Makefile
+** TODO Add "--Xlinker --as-needed"
+ http://udrepper.livejournal.com/19395.html
+** TODO [#C] Implement DEB_BUILD_OPTIONS
+ http://www.debian.org/doc/debian-policy/ch-source.html#s-debianrules-options
+
+* Package
** /usr/share/initramfs-tools/hooks/mandos
-*** Do not install in initrd.img if configured not to.
- Use "/etc/initramfs-tools/conf.d/mandos"? Definitely a debconf
- question.
-** /etc/bash_completion.d/mandos
+*** 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?
-** unperish
-** bzr-builddeb
-
-* INSTALL file
-
-* Web site
-
-* Mailing list
-
-* Announce project on news
- [[news:comp.os.linux.announce]]
+
+* Side Stuff
+** TODO Locate which packet move the other bin/sh when busy box is deactivated
+** TODO contact owner of packet, and ask them to have that shell static in position regardless of busybox
#+STARTUP: showall
=== modified file 'clients.conf'
--- clients.conf 2008-08-10 20:35:01 +0000
+++ clients.conf 2010-09-12 03:00:40 +0000
@@ -2,20 +2,29 @@
# 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.
+# How long until a client is disabled and not be allowed to get the
+# data this server holds.
;timeout = 1h
# How often to run the checker to confirm that a client is still up.
# Note: a new checker will not be started if an old one is still
# running. The server will wait for a checker to complete until the
-# "timeout" above occurs, at which time the client will be marked
-# invalid, and any running checker killed.
+# above "timeout" occurs, at which time the client will be disabled,
+# and any running checker killed.
;interval = 5m
# What command to run as "the checker".
;checker = fping -q -- %%(host)s
+# Whether to approve a client by default after the approval delay.
+;approved_by_default = True
+
+# How long to wait for approval.
+;approval_delay = 0s
+
+# How long one approval will last.
+;approval_duration = 1s
+
;####
;# Example client
@@ -43,7 +52,6 @@
; 5MHdW9AYsNJZAQSOpirE4Xi31CSlWAi9KV+cUCmWF5zOFy1x23P6PjdaRm
; 4T2zw4dxS5NswXWU0sVEXxjs6PYxuIiCTL7vdpx8QjBkrPWDrAbcMyBr2O
; QlnHIvPzEArRQLo=
-; =iHhv
;
;# Host name; used only by the checker, not used by the server itself.
;host = foo.example.org
@@ -56,11 +64,16 @@
;fingerprint = 3e393aeaefb84c7e89e2f547b3a107558fca3a27
;
;# If "secret" is not specified, a file can be read for the data.
-;;secfile = /etc/mandos/bar-secret.txt.asc
+;secfile = /etc/mandos/bar-secret.bin
;
;# An IP address for host is also fine, if the checker accepts it.
;host = 192.0.2.3
;
;# Parameters from the [DEFAULT] section can be overridden per client.
;interval = 5m
+;
+;# This client requires manual approval before it receives its secret.
+;approved_by_default = False
+;# Require approval within 30 seconds.
+;approval_delay = 30s
;####
=== added file 'common.ent'
--- common.ent 1970-01-01 00:00:00 +0000
+++ common.ent 2010-09-09 18:16:14 +0000
@@ -0,0 +1,3 @@
+
+
+
=== added file 'dbus-mandos.conf'
--- dbus-mandos.conf 1970-01-01 00:00:00 +0000
+++ dbus-mandos.conf 2009-11-09 07:35:16 +0000
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
=== added directory 'debian'
=== added file 'debian/changelog'
--- debian/changelog 1970-01-01 00:00:00 +0000
+++ debian/changelog 2010-09-09 18:16:14 +0000
@@ -0,0 +1,155 @@
+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
=== added file 'debian/compat'
--- debian/compat 1970-01-01 00:00:00 +0000
+++ debian/compat 2008-09-17 00:34:09 +0000
@@ -0,0 +1,1 @@
+7
=== added file 'debian/control'
--- debian/control 1970-01-01 00:00:00 +0000
+++ debian/control 2010-09-26 18:32:58 +0000
@@ -0,0 +1,54 @@
+Source: mandos
+Section: admin
+Priority: extra
+Maintainer: Mandos Maintainers
+Uploaders: Teddy Hogeborn ,
+ Björn Påhlsson
+Build-Depends: debhelper (>= 7), docbook-xml, docbook-xsl,
+ libavahi-core-dev, libgpgme11-dev, libgnutls-dev, xsltproc,
+ pkg-config
+Standards-Version: 3.8.3
+Vcs-Bzr: http://ftp.fukt.bsnet.se/pub/mandos/trunk
+Vcs-Browser: http://bzr.fukt.bsnet.se/loggerhead/mandos/trunk/files
+Homepage: http://www.fukt.bsnet.se/mandos
+
+Package: mandos
+Architecture: all
+Depends: ${misc:Depends}, python (>=2.5), python-gnutls, python-dbus,
+ python-avahi, python-gobject, avahi-daemon, adduser,
+ python-urwid, python (>=2.6) | python-multiprocessing
+Recommends: fping
+Description: a server giving encrypted passwords to Mandos clients
+ This is the server part of the Mandos system, which allows
+ computers to have encrypted root file systems and at the
+ same time be capable of remote and/or unattended reboots.
+ .
+ The computers run a small client program in the initial RAM
+ disk environment which will communicate with a server over a
+ network. All network communication is encrypted using TLS.
+ The clients are identified by the server using an OpenPGP
+ key; each client has one unique to it. The server sends the
+ clients an encrypted password. The encrypted password is
+ decrypted by the clients using the same OpenPGP key, and the
+ password is then used to unlock the root file system,
+ whereupon the computers can continue booting normally.
+
+Package: mandos-client
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, cryptsetup,
+ gnupg (<< 2)
+Enhances: cryptsetup
+Description: do unattended reboots with an encrypted root file system
+ This is the client part of the Mandos system, which allows
+ 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.
=== added file 'debian/copyright'
--- debian/copyright 1970-01-01 00:00:00 +0000
+++ debian/copyright 2010-09-26 18:32:58 +0000
@@ -0,0 +1,26 @@
+Format-Specification:
+ http://wiki.debian.org/Proposals/CopyrightFormat?action=recall&rev=233
+Upstream-Name: Mandos
+Upstream-Maintainer: Mandos Maintainers
+Upstream-Source:
+
+Files: *
+Copyright: Copyright © 2008-2010 Teddy Hogeborn
+Copyright: Copyright © 2008-2010 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".
=== added file 'debian/mandos-client.README.Debian'
--- debian/mandos-client.README.Debian 1970-01-01 00:00:00 +0000
+++ debian/mandos-client.README.Debian 2010-09-26 18:32:58 +0000
@@ -0,0 +1,89 @@
+* Choose the Client Network Interface
+
+ Please make sure that the correct network interface is specified in
+ the DEVICE setting in the "/etc/initramfs-tools/initramfs.conf"
+ file. If the setting is empty, the interface will be autodetected
+ at boot time, which may not be correct. *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 be overridden at boot time on the Linux kernel
+ command line using the sixth colon-separated field of the "ip="
+ option; for exact syntax, read the documentation in the file
+ "/usr/share/doc/linux-doc-*/Documentation/filesystems/nfsroot.txt",
+ available in the "linux-doc-*" package.
+
+ Note that since this network interface is used in the initial RAM
+ disk environment, the network interface *must* exist at that stage.
+ Thus, the interface can *not* be a pseudo-interface such as "br0" or
+ "tun0"; instead, a real interface (such as "eth0") must be used.
+
+* Adding a Client Password to the Server
+
+ 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/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.
+
+* 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.
+
+* Emergency Escape
+
+ If it ever should be necessary, the Mandos client can be temporarily
+ prevented from running at startup by passing the parameter
+ "mandos=off" to the kernel.
+
+* Non-local Connection (Not Using ZeroConf)
+
+ If the "ip=" kernel command line option is used to specify a
+ complete IP address and device name, as noted above, it then becomes
+ possible to specify a specific IP address and port to connect to,
+ instead of using ZeroConf. The syntax for doing this is
+ "mandos=connect::".
+
+ Warning: this will cause the client to make exactly one attempt at
+ connecting, and then fail if it does not succeed.
+
+ For very advanced users, it it possible to specify simply
+ "mandos=connect" on the kernel command line to make the system only
+ set up the network (using the data in the "ip=" option) and not pass
+ any extra "--connect" options to mandos-client at boot. For this to
+ work, "--options-for=mandos-client:--connect=:" needs
+ to be manually added to the file "/etc/mandos/plugin-runner.conf".
+
+ -- Teddy Hogeborn , Sun, 26 Sep 2010 20:08:05 +0200
=== added file 'debian/mandos-client.dirs'
--- debian/mandos-client.dirs 1970-01-01 00:00:00 +0000
+++ debian/mandos-client.dirs 2009-02-07 04:50:39 +0000
@@ -0,0 +1,5 @@
+usr/share/man/man8
+usr/sbin
+usr/share/initramfs-tools/hooks
+usr/share/initramfs-tools/conf-hooks.d
+usr/share/initramfs-tools/scripts/init-premount
=== added file 'debian/mandos-client.docs'
--- debian/mandos-client.docs 1970-01-01 00:00:00 +0000
+++ debian/mandos-client.docs 2008-10-18 11:17:22 +0000
@@ -0,0 +1,3 @@
+NEWS
+README
+TODO
=== added file 'debian/mandos-client.links'
--- debian/mandos-client.links 1970-01-01 00:00:00 +0000
+++ debian/mandos-client.links 2008-09-19 13:50:22 +0000
@@ -0,0 +1,1 @@
+usr/share/man/man8/plugin-runner.8mandos.gz usr/share/man/man5/plugin-runner.conf.5mandos.gz
=== added file 'debian/mandos-client.lintian-overrides'
--- debian/mandos-client.lintian-overrides 1970-01-01 00:00:00 +0000
+++ debian/mandos-client.lintian-overrides 2009-01-18 06:41:57 +0000
@@ -0,0 +1,27 @@
+# This directory contains secret client key files.
+#
+mandos-client binary: non-standard-dir-perm etc/keys/mandos/ 0700 != 0755
+
+# The directory /usr/lib/mandos/plugins.d contains setuid binaries
+# which are not meant to be run outside an initial RAM disk
+# environment (except for test purposes). It would be insecure to
+# allow anyone to run them.
+#
+mandos-client binary: non-standard-dir-perm usr/lib/mandos/plugins.d/ 0700 != 0755
+
+# These binaries must be setuid root, since they need root powers, but
+# are started by plugin-runner(8mandos), which runs all plugins as
+# user/group "_mandos". These binaries are not run in a running
+# system, but in an initial RAM disk environment. Here they are
+# protected from non-root access by the directory permissions, above.
+#
+mandos-client binary: setuid-binary usr/lib/mandos/plugins.d/mandos-client 4755 root/root
+mandos-client binary: setuid-binary usr/lib/mandos/plugins.d/askpass-fifo 4755 root/root
+mandos-client binary: setuid-binary usr/lib/mandos/plugins.d/splashy 4755 root/root
+mandos-client binary: setuid-binary usr/lib/mandos/plugins.d/usplash 4755 root/root
+
+# 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
=== added file 'debian/mandos-client.postinst'
--- debian/mandos-client.postinst 1970-01-01 00:00:00 +0000
+++ debian/mandos-client.postinst 2009-05-24 23:36:15 +0000
@@ -0,0 +1,81 @@
+#!/bin/sh -e
+# This script can be called in the following ways:
+#
+# After the package was installed:
+# configure
+#
+#
+# If prerm fails during upgrade or fails on failed upgrade:
+# abort-upgrade
+#
+# If prerm fails during deconfiguration of a package:
+# abort-deconfigure in-favour
+# removing
+#
+# If prerm fails during replacement due to conflict:
+# abort-remove in-favour
+
+# Update the initial RAM file system image
+update_initramfs()
+{
+ if [ -x /usr/sbin/update-initramfs ]; then
+ update-initramfs -u -k all
+ fi
+
+ if dpkg --compare-versions "$2" lt-nl "1.0.10-1"; then
+ # Make old initrd.img files unreadable too, in case they were
+ # created with mandos-client 1.0.8 or older.
+ find /boot -maxdepth 1 -type f -name "initrd.img-*.bak" \
+ -print0 | xargs --null --no-run-if-empty chmod o-r
+ fi
+}
+
+# Add user and group
+add_mandos_user(){
+ # Rename old "mandos" user and group
+ if dpkg --compare-versions "$2" lt "1.0.3-1"; then
+ case "`getent passwd mandos`" in
+ *:Mandos\ password\ system,,,:/nonexistent:/bin/false)
+ usermod --login _mandos mandos
+ groupmod --new-name _mandos mandos
+ return
+ ;;
+ esac
+ fi
+ # Create new user and group
+ if ! getent passwd _mandos >/dev/null; then
+ adduser --system --force-badname --quiet --home /nonexistent \
+ --no-create-home --group --disabled-password \
+ --gecos "Mandos password system" _mandos
+ fi
+}
+
+# Create client key pair
+create_key(){
+ if [ -r /etc/keys/mandos/pubkey.txt \
+ -a -r /etc/keys/mandos/seckey.txt ]; then
+ return 0
+ fi
+ if [ -x /usr/sbin/mandos-keygen ]; then
+ mandos-keygen
+ fi
+}
+
+case "$1" in
+ configure)
+ add_mandos_user "$@"
+ create_key "$@"
+ update_initramfs "$@"
+ ;;
+ abort-upgrade|abort-deconfigure|abort-remove)
+ ;;
+
+ *)
+ echo "$0 called with unknown argument '$1'" 1>&2
+ exit 1
+ ;;
+esac
+
+#DEBHELPER#
+
+exit 0
=== added file 'debian/mandos-client.postrm'
--- debian/mandos-client.postrm 1970-01-01 00:00:00 +0000
+++ debian/mandos-client.postrm 2009-01-18 00:16:57 +0000
@@ -0,0 +1,60 @@
+#!/bin/sh -e
+# This script can be called in the following ways:
+#
+# After the package was removed:
+# remove
+#
+# After the package was purged:
+# purge
+#
+# After the package was upgraded:
+# upgrade
+# if that fails:
+# failed-upgrade
+#
+#
+# After all of the packages files have been replaced:
+# disappear
+#
+#
+# If preinst fails during install:
+# abort-install
+#
+# If preinst fails during upgrade of removed package:
+# abort-install
+#
+# If preinst fails during upgrade:
+# abort-upgrade
+
+
+# Update the initial RAM file system image
+update_initramfs()
+{
+ if [ -x /usr/sbin/update-initramfs ]; then
+ update-initramfs -u -k all
+ fi
+}
+
+case "$1" in
+ remove)
+ update_initramfs
+ ;;
+
+ purge)
+ shred --remove /etc/keys/mandos/seckey.txt 2>/dev/null || :
+ rm --force /etc/mandos/plugin-runner.conf \
+ /etc/keys/mandos/pubkey.txt \
+ /etc/keys/mandos/seckey.txt 2>/dev/null
+ ;;
+ upgrade|failed-upgrade|disappear|abort-install|abort-upgrade)
+ ;;
+
+ *)
+ echo "$0 called with unknown argument '$1'" 1>&2
+ exit 1
+ ;;
+esac
+
+#DEBHELPER#
+
+exit 0
=== added file 'debian/mandos.README.Debian'
--- debian/mandos.README.Debian 1970-01-01 00:00:00 +0000
+++ debian/mandos.README.Debian 2009-09-08 06:28:20 +0000
@@ -0,0 +1,10 @@
+The Mandos server is useless without at least one configured client in
+/etc/mandos/clients.conf. To create one, install the "mandos-client"
+package on a client computer, and run the command
+
+ # mandos-keygen --password
+
+there to get a config file stanza. Append the output of that command
+to the file "/etc/mandos/clients.conf" on the Mandos server.
+
+ -- Teddy Hogeborn , Tue, 8 Sep 2009 06:57:45 +0200
=== added file 'debian/mandos.dirs'
--- debian/mandos.dirs 1970-01-01 00:00:00 +0000
+++ debian/mandos.dirs 2010-09-15 17:33:14 +0000
@@ -0,0 +1,6 @@
+usr/share/man/man5
+usr/share/man/man8
+etc/init.d
+etc/default
+etc/dbus-1/system.d
+usr/sbin
=== added file 'debian/mandos.docs'
--- debian/mandos.docs 1970-01-01 00:00:00 +0000
+++ debian/mandos.docs 2010-09-12 03:00:40 +0000
@@ -0,0 +1,4 @@
+NEWS
+README
+TODO
+DBUS-API
=== added file 'debian/mandos.lintian-overrides'
--- debian/mandos.lintian-overrides 1970-01-01 00:00:00 +0000
+++ debian/mandos.lintian-overrides 2008-10-01 15:29:01 +0000
@@ -0,0 +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
=== added file 'debian/mandos.postinst'
--- debian/mandos.postinst 1970-01-01 00:00:00 +0000
+++ debian/mandos.postinst 2009-05-24 23:28:04 +0000
@@ -0,0 +1,49 @@
+#!/bin/sh -e
+# This script can be called in the following ways:
+#
+# After the package was installed:
+# configure
+#
+#
+# If prerm fails during upgrade or fails on failed upgrade:
+# abort-upgrade
+#
+# If prerm fails during deconfiguration of a package:
+# abort-deconfigure in-favour
+# removing
+#
+# If prerm fails during replacement due to conflict:
+# abort-remove in-favour
+
+case "$1" in
+ configure)
+ # Rename old "mandos" user and group
+ if dpkg --compare-versions "$2" lt "1.0.3-1"; then
+ case "`getent passwd mandos`" in
+ *:Mandos\ password\ system,,,:/nonexistent:/bin/false)
+ usermod --login _mandos mandos
+ groupmod --new-name _mandos mandos
+ ;;
+ esac
+ fi
+ # Create new user and group
+ if ! getent passwd _mandos >/dev/null; then
+ adduser --system --force-badname --quiet \
+ --home /nonexistent --no-create-home --group \
+ --disabled-password --gecos "Mandos password system" \
+ _mandos
+ fi
+ ;;
+
+ abort-upgrade|abort-deconfigure|abort-remove)
+ ;;
+
+ *)
+ echo "$0 called with unknown argument '$1'" 1>&2
+ exit 1
+ ;;
+esac
+
+#DEBHELPER#
+
+exit 0
=== added file 'debian/mandos.prerm'
--- debian/mandos.prerm 1970-01-01 00:00:00 +0000
+++ debian/mandos.prerm 2009-01-18 00:16:57 +0000
@@ -0,0 +1,38 @@
+#! /bin/sh
+# prerm script for mandos
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * 'remove'
+# * 'upgrade'
+# * 'failed-upgrade'
+# * 'remove' 'in-favour'
+# * 'deconfigure' 'in-favour'
+# 'removing'
+#
+# for details, see /usr/share/doc/packaging-manual/
+
+case "$1" in
+ remove|deconfigure)
+ if [ -x /etc/init.d/mandos ]; then
+ if [ -x /usr/sbin/invoke-rc.d ]; then
+ invoke-rc.d mandos stop
+ else
+ /etc/init.d/mandos stop
+ fi
+ fi
+ ;;
+ upgrade|failed-upgrade)
+ ;;
+ *)
+ echo "prerm called with unknown argument '$1'" >&2
+ exit 0
+ ;;
+esac
+
+#DEBHELPER#
+
+exit 0
=== added directory 'debian/po'
=== added file 'debian/rules'
--- debian/rules 1970-01-01 00:00:00 +0000
+++ debian/rules 2010-09-09 18:16:14 +0000
@@ -0,0 +1,106 @@
+#!/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
+
+# -pie was broken briefly on the mips and mipsel architectures, see
+#
+BINUTILS_V := $(shell dpkg-query --showformat='$${Version}' \
+ --show binutils)
+ifeq (yes,$(shell dpkg --compare-versions $(BINUTILS_V) lt 2.20-3 \
+ && dpkg --compare-versions $(BINUTILS_V) ge 2.19.1-1 \
+ && echo yes))
+ ifneq (,$(strip $(findstring :$(DEB_HOST_ARCH):,:mips:mipsel:) \
+ $(findstring :$(DEB_BUILD_ARCH):,:mips:mipsel:)))
+ BROKEN_PIE := yes
+ export BROKEN_PIE
+ endif
+endif
+
+configure: configure-stamp
+configure-stamp:
+ dh_testdir
+ touch configure-stamp
+
+build: build-arch build-indep
+
+build-arch: build-arch-stamp
+build-arch-stamp: configure-stamp
+ dh_auto_build -- all doc
+ touch $@
+
+build-indep: build-indep-stamp
+build-indep-stamp: configure-stamp
+ dh_auto_build -- doc
+ touch $@
+
+clean:
+ dh_testdir
+ dh_testroot
+ rm -f build-arch-stamp build-indep-stamp configure-stamp
+ dh_auto_clean
+ dh_clean
+
+install: install-indep install-arch
+install-indep:
+ dh_testdir
+ dh_testroot
+ dh_prep
+ dh_installdirs --indep
+ $(MAKE) DESTDIR=$(CURDIR)/debian/mandos install-server
+ dh_lintian
+ dh_installinit --onlyscripts \
+ --update-rcd-params="defaults 25 15"
+ dh_install --indep
+
+install-arch:
+ dh_testdir
+ dh_testroot
+ dh_prep
+ dh_installdirs --same-arch
+ $(MAKE) DESTDIR=$(CURDIR)/debian/mandos-client install-client-nokey
+ dh_lintian
+ dh_install --same-arch
+
+binary-common:
+ dh_testdir
+ dh_testroot
+ dh_installchangelogs
+ dh_installdocs
+ dh_link
+ dh_strip
+ dh_compress
+ dh_fixperms --exclude etc/keys/mandos \
+ --exclude etc/mandos/clients.conf \
+ --exclude etc/mandos/plugins.d \
+ --exclude 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
=== added file 'debian/watch'
--- debian/watch 1970-01-01 00:00:00 +0000
+++ debian/watch 2010-09-15 17:17:46 +0000
@@ -0,0 +1,2 @@
+version=3
+ftp://ftp.fukt.bsnet.se/pub/mandos/mandos[-_]([^\s]+?)(?:\.orig)?\.tar\.(?:gz|bz2|7z|xz)
=== added file 'default-mandos'
--- default-mandos 1970-01-01 00:00:00 +0000
+++ default-mandos 2008-09-17 00:34:09 +0000
@@ -0,0 +1,7 @@
+# Directory where configuration files are located. Default is
+# "/etc/mandos".
+#
+#CONFIGDIR=/etc/mandos
+
+# Additional options that are passed to the Daemon.
+DAEMON_ARGS=""
=== added file 'init.d-mandos'
--- init.d-mandos 1970-01-01 00:00:00 +0000
+++ init.d-mandos 2009-09-16 23:28:39 +0000
@@ -0,0 +1,159 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides: mandos
+# Required-Start: $remote_fs $syslog avahi
+# Required-Stop: $remote_fs $syslog avahi
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Mandos server
+# Description: Gives encrypted passwords to Mandos clients
+### END INIT INFO
+
+# Author: Teddy Hogeborn
+# Author: Björn Påhlsson
+#
+# Please remove the "Author" lines above and replace them
+# with your own name if you copy and modify this script.
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="Mandos root file system password server"
+NAME=mandos
+DAEMON=/usr/sbin/$NAME
+DAEMON_ARGS=""
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+if [ -n "$CONFIGDIR" ]; then
+ DAEMON_ARGS="$DAEMON_ARGS --configdir $CONFIGDIR"
+fi
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+ # Return
+ # 0 if daemon has been started
+ # 1 if daemon was already running
+ # 2 if daemon could not be started
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
+ || return 1
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
+ $DAEMON_ARGS \
+ || return 2
+ # Add code here, if necessary, that waits for the process to be ready
+ # to handle requests from services started subsequently which depend
+ # on this one. As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+ # Return
+ # 0 if daemon has been stopped
+ # 1 if daemon was already stopped
+ # 2 if daemon could not be stopped
+ # other if a failure occurred
+ start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
+ RETVAL="$?"
+ [ "$RETVAL" = 2 ] && return 2
+ # Wait for children to finish too if this is a daemon that forks
+ # and if the daemon is only ever run from this initscript.
+ # If the above conditions are not satisfied then add some other code
+ # that waits for the process to drop all resources that could be
+ # needed by services started subsequently. A last resort is to
+ # sleep for some time.
+ start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
+ [ "$?" = 2 ] && return 2
+ # Many daemons don't delete their pidfiles when they exit.
+ rm -f $PIDFILE
+ return "$RETVAL"
+}
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+ #
+ # If the daemon can reload its configuration without
+ # restarting (for example, when it is sent a SIGHUP),
+ # then implement that here.
+ #
+ start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
+ return 0
+}
+
+case "$1" in
+ start)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+ do_start
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ stop)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ #reload|force-reload)
+ #
+ # If do_reload() is not implemented then leave this commented out
+ # and leave 'force-reload' as an alias for 'restart'.
+ #
+ #log_daemon_msg "Reloading $DESC" "$NAME"
+ #do_reload
+ #log_end_msg $?
+ #;;
+ restart|force-reload)
+ #
+ # If the "reload" option is implemented then remove the
+ # 'force-reload' alias
+ #
+ log_daemon_msg "Restarting $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1)
+ do_start
+ case "$?" in
+ 0) log_end_msg 0 ;;
+ 1) log_end_msg 1 ;; # Old process is still running
+ *) log_end_msg 1 ;; # Failed to start
+ esac
+ ;;
+ *)
+ # Failed to stop
+ log_end_msg 1
+ ;;
+ esac
+ ;;
+ *)
+ #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+ echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
+ exit 3
+ ;;
+esac
+
+:
=== modified file 'initramfs-tools-hook'
--- initramfs-tools-hook 2008-08-14 02:24:59 +0000
+++ initramfs-tools-hook 2010-09-09 18:16:14 +0000
@@ -29,15 +29,36 @@
. /usr/share/initramfs-tools/hook-functions
-if [ -d /usr/lib/mandos ]; then
- prefix=/usr
-elif [ -d /usr/local/lib/mandos ]; then
- prefix=/usr/local
-else
+for d in /usr /usr/local; do
+ if [ -d "$d"/lib/mandos ]; then
+ prefix="$d"
+ break
+ fi
+done
+if [ -z "$prefix" ]; then
# Mandos not found
exit 1
fi
+for d in /etc/keys/mandos /etc/mandos/keys; do
+ if [ -d "$d" ]; then
+ keydir="$d"
+ break
+ fi
+done
+if [ -z "$keydir" ]; then
+ # Mandos key directory not found
+ exit 1
+fi
+
+set `{ getent passwd _mandos \
+ || getent passwd nobody \
+ || echo ::65534:65534:::; } \
+ | cut --delimiter=: --fields=3,4 --only-delimited \
+ --output-delimiter=" "`
+mandos_user="$1"
+mandos_group="$2"
+
# The Mandos network client uses the network
auto_add_modules net
# The Mandos network client uses IPv6
@@ -49,11 +70,13 @@
PLUGINDIR="${MANDOSDIR}/plugins.d"
# Make directories
-mkdir --parents "${DESTDIR}${CONFDIR}"
-mkdir --parents "${DESTDIR}${PLUGINDIR}"
+install --directory --mode=u=rwx,go=rx "${DESTDIR}${CONFDIR}" \
+ "${DESTDIR}${MANDOSDIR}"
+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 "${DESTDIR}${MANDOSDIR}"
+copy_exec "$prefix"/lib/mandos/plugin-runner "${MANDOSDIR}"
# Copy the plugins
@@ -65,8 +88,10 @@
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
@@ -74,31 +99,47 @@
for file in /etc/mandos/plugins.d/*; do
base="`basename \"$file\"`"
case "$base" in
- *~|.*|*.dpkg-old|*.dpkg-new|*.dpkg-divert) : ;;
- *) copy_exec "$file" "${PLUGINDIR}";;
+ *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert)
+ : ;;
+ "*") : ;;
+ *) copy_exec "$file" "${PLUGINDIR}" ;;
esac
done
# GPGME needs /usr/bin/gpg
-if ! [ -e "${DESTDIR}/usr/bin/gpg" ] \
- && [ -n "`ls \"${DESTDIR}\"/usr/lib/libgpgme.so* 2>/dev/null`" ]; then
+if [ ! -e "${DESTDIR}/usr/bin/gpg" \
+ -a -n "`ls \"${DESTDIR}\"/usr/lib/libgpgme.so* \
+ 2>/dev/null`" ]; then
copy_exec /usr/bin/gpg
fi
-# Key files
-for file in /etc/mandos/*; do
- if [ -d "$file" ]; then
- continue
- fi
- cp --archive --sparse=always "$file" "${DESTDIR}${CONFDIR}"
-done
-# Create key ring files
-gpg --no-random-seed-file --quiet --batch --no-tty \
- --no-default-keyring --no-options \
- --homedir "${DESTDIR}${CONFDIR}" --no-permission-warning \
- --import-options import-minimal \
- --import "${DESTDIR}${CONFDIR}/seckey.txt"
-chown nobody "${DESTDIR}${CONFDIR}/secring.gpg"
+# Config files
+for file in /etc/mandos/plugin-runner.conf; do
+ if [ -d "$file" ]; then
+ continue
+ fi
+ cp --archive --sparse=always "$file" "${DESTDIR}${CONFDIR}"
+done
+
+if [ ${mandos_user} != 65534 ]; then
+ sed --in-place --expression="1i--userid=${mandos_user}" \
+ "${DESTDIR}${CONFDIR}/plugin-runner.conf"
+fi
+
+if [ ${mandos_group} != 65534 ]; then
+ sed --in-place --expression="1i--groupid=${mandos_group}" \
+ "${DESTDIR}${CONFDIR}/plugin-runner.conf"
+fi
+
+# Key files
+for file in "$keydir"/*; do
+ if [ -d "$file" ]; then
+ continue
+ fi
+ cp --archive --sparse=always "$file" "${DESTDIR}${CONFDIR}"
+ chown ${mandos_user}:${mandos_group} \
+ "${DESTDIR}${CONFDIR}/`basename \"$file\"`"
+done
# /lib/mandos/plugin-runner will drop priviliges, but needs access to
# its plugin directory and its config file. However, since almost all
@@ -112,7 +153,7 @@
# condition. This umask is set by "initramfs-tools-hook-conf",
# installed as "/usr/share/initramfs-tools/conf-hooks.d/mandos".)
#
-for full in "${PLUGINDIR}" "${CONFDIR}"; do
+for full in "${MANDOSDIR}" "${CONFDIR}"; do
while [ "$full" != "/" ]; do
chmod a+rX "${DESTDIR}$full"
full="`dirname \"$full\"`"
@@ -122,8 +163,13 @@
# Reset some other things to sane permissions which we have
# inadvertently affected with our umask setting.
for dir in / /bin /etc /keyscripts /sbin /scripts /usr /usr/bin; do
- chmod a+rX "${DESTDIR}$dir"
+ if [ -d "${DESTDIR}$dir" ]; then
+ chmod a+rX "${DESTDIR}$dir"
+ fi
done
-for dir in /lib /usr/lib; do
- chmod --recursive a+rX "${DESTDIR}$dir"
+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-08-14 02:24:59 +0000
+++ initramfs-tools-script 2009-09-16 23:28:39 +0000
@@ -6,25 +6,102 @@
#
# 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
- ;;
-esac
-
-test -w /conf/conf.d/cryptroot
+ prereqs
+ exit 0
+ ;;
+esac
+
+. /scripts/functions
+
+for param in `cat /proc/cmdline`; do
+ case "$param" in
+ ip=*) IPOPTS="${param#ip=}" ;;
+ mandos=*)
+ # Split option line on commas
+ old_ifs="$IFS"
+ IFS="$IFS,"
+ for mpar in ${param#mandos=}; do
+ IFS="$old_ifs"
+ case "$mpar" in
+ off) exit 0 ;;
+ connect) connect="" ;;
+ connect:*) connect="${mpar#connect:}" ;;
+ *) log_warning_msg "$0: Bad option ${mpar}" ;;
+ esac
+ done
+ unset mpar
+ IFS="$old_ifs"
+ unset old_ifs
+ ;;
+ esac
+done
+unset param
+
+chmod a=rwxt /tmp
+
+test -r /conf/conf.d/cryptroot
+test -w /conf/conf.d
+
+# Get DEVICE from /conf/initramfs.conf and other files
+. /conf/initramfs.conf
+for conf in /conf/conf.d/*; do
+ [ -f ${conf} ] && . ${conf}
+done
+if [ -e /conf/param.conf ]; then
+ . /conf/param.conf
+fi
+
+# Override DEVICE from sixth field of ip= kernel option, if passed
+case "$IPOPTS" in
+ *:*:*:*:*:*) # At least six fields
+ # Remove the first five fields
+ device="${IPOPTS#*:*:*:*:*:}"
+ # Remove all fields except the first one
+ DEVICE="${device%%:*}"
+ ;;
+esac
+
+# Add device setting (if any) to plugin-runner.conf
+if [ "${DEVICE+set}" = set ]; then
+ # Did we get the device from an ip= option?
+ if [ "${device+set}" = set ]; then
+ # Let ip= option override local config; append:
+ cat <<-EOF >>/conf/conf.d/mandos/plugin-runner.conf
+
+ --options-for=mandos-client:--interface=${DEVICE}
+EOF
+ else
+ # Prepend device setting so any later options would override:
+ sed -i -e \
+ '1i--options-for=mandos-client:--interface='"${DEVICE}" \
+ /conf/conf.d/mandos/plugin-runner.conf
+ fi
+fi
+unset device
+
+# If we are connecting directly, run "configure_networking" (from
+# /scripts/functions); it needs IPOPTS and DEVICE
+if [ "${connect+set}" = set ]; then
+ configure_networking
+ if [ -n "$connect" ]; then
+ cat <<-EOF >>/conf/conf.d/mandos/plugin-runner.conf
+
+ --options-for=mandos-client:--connect=${connect}
+EOF
+ fi
+fi
# Do not replace cryptroot file unless we need to.
replace_cryptroot=no
=== added file 'legalnotice.xml'
--- legalnotice.xml 1970-01-01 00:00:00 +0000
+++ legalnotice.xml 2008-09-06 17:24:58 +0000
@@ -0,0 +1,27 @@
+
+
+
+
+ This manual page is free software: you can redistribute it and/or
+ modify it under the terms of the GNU General
+ Public License as published by the Free Software Foundation,
+ either version 3 of the License, or (at your option) any later
+ version.
+
+
+
+ This manual page is distributed in the hope that it will be
+ useful, but WITHOUT ANY WARRANTY; without even the implied
+ warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more
+ details.
+
+
+
+ You should have received a copy of the GNU
+ General Public License along with this program. If not, see
+ http://www.gnu.org/licenses/.
+
+
=== modified file 'mandos'
--- mandos 2008-08-18 23:55:28 +0000
+++ mandos 2010-09-26 18:32:58 +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 © 2007-2008 Teddy Hogeborn & Björn Påhlsson
+# Copyright © 2008-2010 Teddy Hogeborn
+# Copyright © 2008-2010 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
@@ -24,17 +25,17 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# along with this program. If not, see
+# .
#
# Contact the authors at .
#
-from __future__ import division
+from __future__ import division, with_statement, absolute_import
-import SocketServer
+import SocketServer as socketserver
import socket
-import select
-from optparse import OptionParser
+import optparse
import datetime
import errno
import gnutls.crypto
@@ -43,44 +44,67 @@
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 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'))
+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.0.14"
+
+#logger = logging.getLogger(u'mandos')
+logger = logging.Logger(u'mandos')
+syslogger = (logging.handlers.SysLogHandler
+ (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
+ address = "/dev/log"))
+syslogger.setFormatter(logging.Formatter
+ (u'Mandos [%(process)d]: %(levelname)s:'
+ u' %(message)s'))
logger.addHandler(syslogger)
console = logging.StreamHandler()
-console.setFormatter(logging.Formatter('%(name)s: %(levelname)s:'
- ' %(message)s'))
+console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:'
+ u' %(levelname)s:'
+ u' %(message)s'))
logger.addHandler(console)
class AvahiError(Exception):
- def __init__(self, value):
+ 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
@@ -91,11 +115,12 @@
class AvahiService(object):
"""An Avahi (Zeroconf) service.
+
Attributes:
interface: integer; avahi.IF_UNSPEC or an interface index.
Used to optionally bind to the specified interface.
- name: string; Example: 'Mandos'
- type: string; Example: '_mandos._tcp'.
+ name: string; Example: u'Mandos'
+ type: string; Example: u'_mandos._tcp'.
See
port: integer; what port to announce
TXT: list of strings; TXT record for the service
@@ -104,211 +129,307 @@
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 = u"", host = u"", 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
def rename(self):
"""Derived from the Avahi example code"""
if self.rename_count >= self.max_renames:
- logger.critical(u"No suitable service name found after %i"
- u" retries, exiting.", rename_count)
- raise AvahiServiceError("Too many renames")
- self.name = server.GetAlternativeServiceName(self.name)
- logger.info(u"Changing name to %r ...", str(self.name))
- syslogger.setFormatter(logging.Formatter\
- ('Mandos (%s): %%(levelname)s:'
- ' %%(message)s' % self.name))
+ logger.critical(u"No suitable Zeroconf service name found"
+ u" after %i retries, exiting.",
+ self.rename_count)
+ raise AvahiServiceError(u"Too many renames")
+ self.name = unicode(self.server.GetAlternativeServiceName(self.name))
+ logger.info(u"Changing Zeroconf service name to %r ...",
+ self.name)
+ syslogger.setFormatter(logging.Formatter
+ (u'Mandos (%s) [%%(process)d]:'
+ u' %%(levelname)s: %%(message)s'
+ % self.name))
self.remove()
- self.add()
+ try:
+ self.add()
+ except dbus.exceptions.DBusException, error:
+ logger.critical(u"DBusException: %s", 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.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 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
+ 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.group.connect_to_signal('StateChanged',
+ self
+ .entry_group_state_changed)
+ logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
+ self.name, self.type)
+ self.group.AddService(
+ self.interface,
+ 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(u"Avahi entry group 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.")
+ self.rename()
+ elif state == avahi.ENTRY_GROUP_FAILURE:
+ logger.critical(u"Avahi: Error in group state changed %s",
+ unicode(error))
+ raise AvahiGroupError(u"State changed: %s"
+ % unicode(error))
+ def cleanup(self):
+ """Derived from the Avahi example code"""
+ if self.group is not None:
+ self.group.Free()
+ self.group = None
+ def server_state_changed(self, state):
+ """Derived from the Avahi example code"""
+ logger.debug(u"Avahi server state change: %i", state)
+ if state == avahi.SERVER_COLLISION:
+ logger.error(u"Zeroconf server name collision")
+ self.remove()
+ elif state == avahi.SERVER_RUNNING:
+ self.add()
+ def activate(self):
+ """Derived from the Avahi example code"""
+ if self.server is None:
+ self.server = dbus.Interface(
+ self.bus.get_object(avahi.DBUS_NAME,
+ avahi.DBUS_PATH_SERVER),
+ avahi.DBUS_INTERFACE_SERVER)
+ self.server.connect_to_signal(u"StateChanged",
+ self.server_state_changed)
+ self.server_state_changed(self.server.GetState())
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
+ current_checker_command: string; current running checker_command
+ disable_hook: If set, called by disable() as disable_hook(self)
+ 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_enabled: datetime.datetime(); (UTC)
+ 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
+ runtime_expansions: Allowed attributes for runtime expansion.
"""
- 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={}):
+
+ runtime_expansions = (u"approval_delay", u"approval_duration",
+ u"created", u"enabled", u"fingerprint",
+ u"host", u"interval", u"last_checked_ok",
+ u"last_enabled", u"name", u"timeout")
+
+ @staticmethod
+ def _timedelta_to_milliseconds(td):
+ "Convert a datetime.timedelta() to milliseconds"
+ return ((td.days * 24 * 60 * 60 * 1000)
+ + (td.seconds * 1000)
+ + (td.microseconds // 1000))
+
+ def timeout_milliseconds(self):
+ "Return the 'timeout' attribute in milliseconds"
+ return self._timedelta_to_milliseconds(self.timeout)
+
+ def interval_milliseconds(self):
+ "Return the 'interval' attribute in milliseconds"
+ return self._timedelta_to_milliseconds(self.interval)
+
+ def approval_delay_milliseconds(self):
+ return self._timedelta_to_milliseconds(self.approval_delay)
+
+ def __init__(self, name = None, disable_hook=None, config=None):
"""Note: the 'checker' key in 'config' sets the
'checker_command' attribute and *not* the 'checker'
attribute."""
self.name = name
+ if config is None:
+ config = {}
logger.debug(u"Creating client %r", self.name)
# Uppercase and remove spaces from fingerprint for later
# comparison purposes with return value from the fingerprint()
# function
- self.fingerprint = config["fingerprint"].upper()\
- .replace(u" ", u"")
+ self.fingerprint = (config[u"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()
+ if u"secret" in config:
+ self.secret = config[u"secret"].decode(u"base64")
+ elif u"secfile" in config:
+ with open(os.path.expanduser(os.path.expandvars
+ (config[u"secfile"])),
+ "rb") as secfile:
+ self.secret = secfile.read()
else:
raise TypeError(u"No secret or secfile for client %s"
% self.name)
- self.host = config.get("host", "")
- self.created = datetime.datetime.now()
+ self.host = config.get(u"host", u"")
+ self.created = datetime.datetime.utcnow()
+ self.enabled = False
+ self.last_approval_request = None
+ self.last_enabled = None
self.last_checked_ok = None
- self.timeout = string_to_delta(config["timeout"])
- self.interval = string_to_delta(config["interval"])
- self.stop_hook = stop_hook
+ self.timeout = string_to_delta(config[u"timeout"])
+ self.interval = string_to_delta(config[u"interval"])
+ self.disable_hook = disable_hook
self.checker = None
self.checker_initiator_tag = None
- self.stop_initiator_tag = None
+ self.disable_initiator_tag = None
self.checker_callback_tag = None
- self.check_command = config["checker"]
- def start(self):
+ self.checker_command = config[u"checker"]
+ self.current_checker_command = None
+ self.last_connect = None
+ self._approved = None
+ self.approved_by_default = config.get(u"approved_by_default",
+ True)
+ self.approvals_pending = 0
+ self.approval_delay = string_to_delta(
+ config[u"approval_delay"])
+ self.approval_duration = string_to_delta(
+ config[u"approval_duration"])
+ self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
+
+ def send_changedstate(self):
+ self.changedstate.acquire()
+ self.changedstate.notify_all()
+ self.changedstate.release()
+
+ def enable(self):
"""Start this client's checker and timeout hooks"""
+ if getattr(self, u"enabled", False):
+ # Already enabled
+ return
+ self.send_changedstate()
+ self.last_enabled = datetime.datetime.utcnow()
# 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)
+ self.checker_initiator_tag = (gobject.timeout_add
+ (self.interval_milliseconds(),
+ self.start_checker))
+ # Schedule a disable() when 'timeout' has passed
+ self.disable_initiator_tag = (gobject.timeout_add
+ (self.timeout_milliseconds(),
+ self.disable))
+ self.enabled = True
# Also start a new checker *right now*.
self.start_checker()
- # 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:
+
+ def disable(self, quiet=True):
+ """Disable this client."""
+ if not getattr(self, "enabled", False):
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):
+ if not quiet:
+ self.send_changedstate()
+ if not quiet:
+ logger.info(u"Disabling client %s", self.name)
+ if getattr(self, u"disable_initiator_tag", False):
+ gobject.source_remove(self.disable_initiator_tag)
+ self.disable_initiator_tag = None
+ if getattr(self, u"checker_initiator_tag", False):
gobject.source_remove(self.checker_initiator_tag)
self.checker_initiator_tag = None
self.stop_checker()
- if self.stop_hook:
- self.stop_hook(self)
+ if self.disable_hook:
+ self.disable_hook(self)
+ self.enabled = False
# 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):
+ self.disable_hook = None
+ self.disable()
+
+ 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):
+ if os.WIFEXITED(condition):
+ exitstatus = os.WEXITSTATUS(condition)
+ if exitstatus == 0:
+ logger.info(u"Checker for %(name)s succeeded",
+ vars(self))
+ self.checked_ok()
+ else:
+ logger.info(u"Checker for %(name)s failed",
+ vars(self))
+ else:
logger.warning(u"Checker for %(name)s crashed?",
vars(self))
- else:
- logger.info(u"Checker for %(name)s failed",
- vars(self))
+
+ def checked_ok(self):
+ """Bump up the timeout for this client.
+
+ This should only be called when the client has been seen,
+ alive and well.
+ """
+ self.last_checked_ok = datetime.datetime.utcnow()
+ gobject.source_remove(self.disable_initiator_tag)
+ self.disable_initiator_tag = (gobject.timeout_add
+ (self.timeout_milliseconds(),
+ self.disable))
+
+ 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
@@ -317,329 +438,1219 @@
# 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, OSError), error:
+ if (isinstance(error, OSError)
+ and error.errno != errno.ECHILD):
+ raise error
+ else:
+ if pid:
+ logger.warning(u"Checker was a zombie")
+ gobject.source_remove(self.checker_callback_tag)
+ self.checker_callback(pid, status,
+ self.current_checker_command)
+ # Start a new checker if needed
if self.checker is None:
try:
- # In case check_command has exactly one % operator
- command = self.check_command % self.host
+ # In case checker_command has exactly one % operator
+ command = self.checker_command % self.host
except TypeError:
# Escape attributes for the shell
- escaped_attrs = dict((key, re.escape(str(val)))
- for key, val in
- vars(self).iteritems())
+ escaped_attrs = dict(
+ (attr,
+ re.escape(unicode(str(getattr(self, attr, u"")),
+ errors=
+ u'replace')))
+ for attr in
+ self.runtime_expansions)
+
try:
- command = self.check_command % escaped_attrs
+ command = self.checker_command % escaped_attrs
except TypeError, error:
logger.error(u'Could not format string "%s":'
- u' %s', self.check_command, error)
+ u' %s', self.checker_command, error)
return True # Try again later
+ self.current_checker_command = command
try:
logger.info(u"Starting checker %r for %s",
command, self.name)
+ # We don't need to redirect stdout and stderr, since
+ # in normal mode, that is already done by daemon(),
+ # and in debug mode we don't want to. (Stdin is
+ # always replaced by /dev/null.)
self.checker = subprocess.Popen(command,
close_fds=True,
- shell=True, cwd="/")
- self.checker_callback_tag = gobject.child_watch_add\
- (self.checker.pid,
- self.checker_callback)
- except subprocess.OSError, error:
+ shell=True, cwd=u"/")
+ self.checker_callback_tag = (gobject.child_watch_add
+ (self.checker.pid,
+ self.checker_callback,
+ data=command))
+ # The checker may have completed before the gobject
+ # watch was added. Check for this.
+ pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
+ if pid:
+ gobject.source_remove(self.checker_callback_tag)
+ self.checker_callback(pid, status, command)
+ except OSError, error:
logger.error(u"Failed to start subprocess: %s",
error)
# Re-run this periodically if run by gobject.timeout_add
return True
+
def stop_checker(self):
"""Force the checker process, if any, to stop."""
if self.checker_callback_tag:
gobject.source_remove(self.checker_callback_tag)
self.checker_callback_tag = None
- if getattr(self, "checker", None) is None:
+ if getattr(self, u"checker", None) is None:
return
logger.debug(u"Stopping checker for %(name)s", vars(self))
try:
os.kill(self.checker.pid, signal.SIGTERM)
- #os.sleep(0.5)
+ #time.sleep(0.5)
#if self.checker.poll() is None:
# os.kill(self.checker.pid, signal.SIGKILL)
except OSError, error:
if error.errno != errno.ESRCH: # No such process
raise
self.checker = None
- def still_valid(self):
- """Has the timeout not yet passed for this client?"""
- now = datetime.datetime.now()
+
+def dbus_service_property(dbus_interface, signature=u"v",
+ access=u"readwrite", byte_arrays=False):
+ """Decorators for marking methods of a DBusObjectWithProperties to
+ become properties on the D-Bus.
+
+ The decorated method will be called with no arguments by "Get"
+ and with one argument by "Set".
+
+ The parameters, where they are supported, are the same as
+ dbus.service.method, except there is only "signature", since the
+ type from Get() and the type sent to Set() is the same.
+ """
+ # Encoding deeply encoded byte arrays is not supported yet by the
+ # "Set" method, so we fail early here:
+ if byte_arrays and signature != u"ay":
+ raise ValueError(u"Byte arrays not supported for non-'ay'"
+ u" signature %r" % signature)
+ def decorator(func):
+ func._dbus_is_property = True
+ func._dbus_interface = dbus_interface
+ func._dbus_signature = signature
+ func._dbus_access = access
+ func._dbus_name = func.__name__
+ if func._dbus_name.endswith(u"_dbus_property"):
+ func._dbus_name = func._dbus_name[:-14]
+ func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
+ return func
+ return decorator
+
+
+class DBusPropertyException(dbus.exceptions.DBusException):
+ """A base class for D-Bus property-related exceptions
+ """
+ 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_property(obj):
+ return getattr(obj, u"_dbus_is_property", False)
+
+ def _get_all_dbus_properties(self):
+ """Returns a generator of (name, attribute) pairs
+ """
+ return ((prop._dbus_name, prop)
+ for name, prop in
+ inspect.getmembers(self, self._is_dbus_property))
+
+ def _get_dbus_property(self, interface_name, property_name):
+ """Returns a bound method if one exists which is a D-Bus
+ property with the specified name and interface.
+ """
+ for name in (property_name,
+ property_name + u"_dbus_property"):
+ prop = getattr(self, name, None)
+ if (prop is None
+ or not self._is_dbus_property(prop)
+ or prop._dbus_name != property_name
+ or (interface_name and prop._dbus_interface
+ and interface_name != prop._dbus_interface)):
+ continue
+ return prop
+ # No such property
+ raise DBusPropertyNotFound(self.dbus_object_path + u":"
+ + interface_name + u"."
+ + property_name)
+
+ @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
+ out_signature=u"v")
+ def Get(self, interface_name, property_name):
+ """Standard D-Bus property Get() method, see D-Bus standard.
+ """
+ prop = self._get_dbus_property(interface_name, property_name)
+ if prop._dbus_access == u"write":
+ raise DBusPropertyAccessException(property_name)
+ value = prop()
+ if not hasattr(value, u"variant_level"):
+ return value
+ return type(value)(value, variant_level=value.variant_level+1)
+
+ @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
+ def Set(self, interface_name, property_name, value):
+ """Standard D-Bus property Set() method, see D-Bus standard.
+ """
+ prop = self._get_dbus_property(interface_name, property_name)
+ if prop._dbus_access == u"read":
+ raise DBusPropertyAccessException(property_name)
+ if prop._dbus_get_args_options[u"byte_arrays"]:
+ # The byte_arrays option is not supported yet on
+ # signatures other than "ay".
+ if prop._dbus_signature != u"ay":
+ raise ValueError
+ value = dbus.ByteArray(''.join(unichr(byte)
+ for byte in value))
+ prop(value)
+
+ @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
+ out_signature=u"a{sv}")
+ def GetAll(self, interface_name):
+ """Standard D-Bus property GetAll() method, see D-Bus
+ standard.
+
+ Note: Will not include properties with access="write".
+ """
+ all = {}
+ for name, prop in self._get_all_dbus_properties():
+ if (interface_name
+ and interface_name != prop._dbus_interface):
+ # Interface non-empty but did not match
+ continue
+ # Ignore write-only properties
+ if prop._dbus_access == u"write":
+ continue
+ value = prop()
+ if not hasattr(value, u"variant_level"):
+ all[name] = value
+ continue
+ all[name] = type(value)(value, variant_level=
+ value.variant_level+1)
+ return dbus.Dictionary(all, signature=u"sv")
+
+ @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
+ out_signature=u"s",
+ path_keyword='object_path',
+ connection_keyword='connection')
+ def Introspect(self, object_path, connection):
+ """Standard D-Bus method, overloaded to insert property 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(u"property")
+ e.setAttribute(u"name", name)
+ e.setAttribute(u"type", prop._dbus_signature)
+ e.setAttribute(u"access", prop._dbus_access)
+ return e
+ for if_tag in document.getElementsByTagName(u"interface"):
+ for tag in (make_tag(document, name, prop)
+ for name, prop
+ in self._get_all_dbus_properties()
+ if prop._dbus_interface
+ == if_tag.getAttribute(u"name")):
+ if_tag.appendChild(tag)
+ # Add the names to the return values for the
+ # "org.freedesktop.DBus.Properties" methods
+ if (if_tag.getAttribute(u"name")
+ == u"org.freedesktop.DBus.Properties"):
+ for cn in if_tag.getElementsByTagName(u"method"):
+ if cn.getAttribute(u"name") == u"Get":
+ for arg in cn.getElementsByTagName(u"arg"):
+ if (arg.getAttribute(u"direction")
+ == u"out"):
+ arg.setAttribute(u"name", u"value")
+ elif cn.getAttribute(u"name") == u"GetAll":
+ for arg in cn.getElementsByTagName(u"arg"):
+ if (arg.getAttribute(u"direction")
+ == u"out"):
+ arg.setAttribute(u"name", u"props")
+ xmlstring = document.toxml(u"utf-8")
+ document.unlink()
+ except (AttributeError, xml.dom.DOMException,
+ xml.parsers.expat.ExpatError), error:
+ logger.error(u"Failed to override Introspection method",
+ error)
+ return xmlstring
+
+
+class ClientDBus(Client, DBusObjectWithProperties):
+ """A Client class using D-Bus
+
+ Attributes:
+ dbus_object_path: dbus.ObjectPath
+ bus: dbus.SystemBus()
+ """
+
+ runtime_expansions = (Client.runtime_expansions
+ + (u"dbus_object_path",))
+
+ # dbus.service.Object doesn't use super(), so we can't either.
+
+ def __init__(self, bus = None, *args, **kwargs):
+ self._approvals_pending = 0
+ 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(u"."): ord(u"_"),
+ ord(u"-"): ord(u"_")})
+ self.dbus_object_path = (dbus.ObjectPath
+ (u"/clients/" + client_object_name))
+ DBusObjectWithProperties.__init__(self, self.bus,
+ self.dbus_object_path)
+
+ def _get_approvals_pending(self):
+ return self._approvals_pending
+ def _set_approvals_pending(self, value):
+ old_value = self._approvals_pending
+ self._approvals_pending = value
+ bval = bool(value)
+ if (hasattr(self, "dbus_object_path")
+ and bval is not bool(old_value)):
+ dbus_bool = dbus.Boolean(bval, variant_level=1)
+ self.PropertyChanged(dbus.String(u"ApprovalPending"),
+ dbus_bool)
+
+ approvals_pending = property(_get_approvals_pending,
+ _set_approvals_pending)
+ del _get_approvals_pending, _set_approvals_pending
+
+ @staticmethod
+ def _datetime_to_dbus(dt, variant_level=0):
+ """Convert a UTC datetime.datetime() to a D-Bus type."""
+ return dbus.String(dt.isoformat(),
+ variant_level=variant_level)
+
+ def enable(self):
+ oldstate = getattr(self, u"enabled", False)
+ r = Client.enable(self)
+ if oldstate != self.enabled:
+ # Emit D-Bus signals
+ self.PropertyChanged(dbus.String(u"Enabled"),
+ dbus.Boolean(True, variant_level=1))
+ self.PropertyChanged(
+ dbus.String(u"LastEnabled"),
+ self._datetime_to_dbus(self.last_enabled,
+ variant_level=1))
+ return r
+
+ def disable(self, quiet = False):
+ oldstate = getattr(self, u"enabled", False)
+ r = Client.disable(self, quiet=quiet)
+ if not quiet and oldstate != self.enabled:
+ # Emit D-Bus signal
+ self.PropertyChanged(dbus.String(u"Enabled"),
+ dbus.Boolean(False, variant_level=1))
+ return r
+
+ def __del__(self, *args, **kwargs):
+ try:
+ self.remove_from_connection()
+ except LookupError:
+ pass
+ if hasattr(DBusObjectWithProperties, u"__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
+ # Emit D-Bus signal
+ self.PropertyChanged(dbus.String(u"CheckerRunning"),
+ dbus.Boolean(False, variant_level=1))
+ 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 checked_ok(self, *args, **kwargs):
+ r = Client.checked_ok(self, *args, **kwargs)
+ # Emit D-Bus signal
+ self.PropertyChanged(
+ dbus.String(u"LastCheckedOK"),
+ (self._datetime_to_dbus(self.last_checked_ok,
+ variant_level=1)))
+ return r
+
+ def need_approval(self, *args, **kwargs):
+ r = Client.need_approval(self, *args, **kwargs)
+ # Emit D-Bus signal
+ self.PropertyChanged(
+ dbus.String(u"LastApprovalRequest"),
+ (self._datetime_to_dbus(self.last_approval_request,
+ variant_level=1)))
+ return r
+
+ 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)
+ self.PropertyChanged(
+ dbus.String(u"CheckerRunning"),
+ dbus.Boolean(True, variant_level=1))
+ return r
+
+ def stop_checker(self, *args, **kwargs):
+ old_checker = getattr(self, u"checker", None)
+ r = Client.stop_checker(self, *args, **kwargs)
+ if (old_checker is not None
+ and getattr(self, u"checker", None) is None):
+ self.PropertyChanged(dbus.String(u"CheckerRunning"),
+ dbus.Boolean(False, variant_level=1))
+ return r
+
+ def _reset_approved(self):
+ self._approved = None
+ return False
+
+ def approve(self, value=True):
+ self.send_changedstate()
+ self._approved = value
+ gobject.timeout_add(self._timedelta_to_milliseconds
+ (self.approval_duration),
+ self._reset_approved)
+
+
+ ## D-Bus methods, signals & properties
+ _interface = u"se.bsnet.fukt.Mandos.Client"
+
+ ## Signals
+
+ # CheckerCompleted - signal
+ @dbus.service.signal(_interface, signature=u"nxs")
+ def CheckerCompleted(self, exitcode, waitstatus, command):
+ "D-Bus signal"
+ pass
+
+ # CheckerStarted - signal
+ @dbus.service.signal(_interface, signature=u"s")
+ def CheckerStarted(self, command):
+ "D-Bus signal"
+ pass
+
+ # PropertyChanged - signal
+ @dbus.service.signal(_interface, signature=u"sv")
+ def PropertyChanged(self, property, value):
+ "D-Bus signal"
+ pass
+
+ # GotSecret - signal
+ @dbus.service.signal(_interface)
+ def GotSecret(self):
+ """D-Bus signal
+ Is sent after a successful transfer of secret from the Mandos
+ server to mandos-client
+ """
+ pass
+
+ # Rejected - signal
+ @dbus.service.signal(_interface, signature=u"s")
+ def Rejected(self, reason):
+ "D-Bus signal"
+ pass
+
+ # NeedApproval - signal
+ @dbus.service.signal(_interface, signature=u"tb")
+ def NeedApproval(self, timeout, default):
+ "D-Bus signal"
+ return self.need_approval()
+
+ ## Methods
+
+ # Approve - method
+ @dbus.service.method(_interface, in_signature=u"b")
+ def Approve(self, value):
+ self.approve(value)
+
+ # CheckedOK - method
+ @dbus.service.method(_interface)
+ def CheckedOK(self):
+ return 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=u"b", access=u"read")
+ def ApprovalPending_dbus_property(self):
+ return dbus.Boolean(bool(self.approvals_pending))
+
+ # ApprovedByDefault - property
+ @dbus_service_property(_interface, signature=u"b",
+ access=u"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)
+ # Emit D-Bus signal
+ self.PropertyChanged(dbus.String(u"ApprovedByDefault"),
+ dbus.Boolean(value, variant_level=1))
+
+ # ApprovalDelay - property
+ @dbus_service_property(_interface, signature=u"t",
+ access=u"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)
+ # Emit D-Bus signal
+ self.PropertyChanged(dbus.String(u"ApprovalDelay"),
+ dbus.UInt64(value, variant_level=1))
+
+ # ApprovalDuration - property
+ @dbus_service_property(_interface, signature=u"t",
+ access=u"readwrite")
+ def ApprovalDuration_dbus_property(self, value=None):
+ if value is None: # get
+ return dbus.UInt64(self._timedelta_to_milliseconds(
+ self.approval_duration))
+ self.approval_duration = datetime.timedelta(0, 0, 0, value)
+ # Emit D-Bus signal
+ self.PropertyChanged(dbus.String(u"ApprovalDuration"),
+ dbus.UInt64(value, variant_level=1))
+
+ # Name - property
+ @dbus_service_property(_interface, signature=u"s", access=u"read")
+ def Name_dbus_property(self):
+ return dbus.String(self.name)
+
+ # Fingerprint - property
+ @dbus_service_property(_interface, signature=u"s", access=u"read")
+ def Fingerprint_dbus_property(self):
+ return dbus.String(self.fingerprint)
+
+ # Host - property
+ @dbus_service_property(_interface, signature=u"s",
+ access=u"readwrite")
+ def Host_dbus_property(self, value=None):
+ if value is None: # get
+ return dbus.String(self.host)
+ self.host = value
+ # Emit D-Bus signal
+ self.PropertyChanged(dbus.String(u"Host"),
+ dbus.String(value, variant_level=1))
+
+ # Created - property
+ @dbus_service_property(_interface, signature=u"s", access=u"read")
+ def Created_dbus_property(self):
+ return dbus.String(self._datetime_to_dbus(self.created))
+
+ # LastEnabled - property
+ @dbus_service_property(_interface, signature=u"s", access=u"read")
+ def LastEnabled_dbus_property(self):
+ if self.last_enabled is None:
+ return dbus.String(u"")
+ return dbus.String(self._datetime_to_dbus(self.last_enabled))
+
+ # Enabled - property
+ @dbus_service_property(_interface, signature=u"b",
+ access=u"readwrite")
+ def Enabled_dbus_property(self, value=None):
+ if value is None: # get
+ return dbus.Boolean(self.enabled)
+ if value:
+ self.enable()
+ else:
+ self.disable()
+
+ # LastCheckedOK - property
+ @dbus_service_property(_interface, signature=u"s",
+ access=u"readwrite")
+ def LastCheckedOK_dbus_property(self, value=None):
+ if value is not None:
+ self.checked_ok()
+ return
if self.last_checked_ok is None:
- return now < (self.created + self.timeout)
- else:
- return now < (self.last_checked_ok + self.timeout)
-
-
-def peer_certificate(session):
- "Return the peer's OpenPGP certificate as a bytestring"
- # If not an OpenPGP certificate...
- if gnutls.library.functions.gnutls_certificate_type_get\
- (session._c_object) \
- != gnutls.library.constants.GNUTLS_CRT_OPENPGP:
- # ...do the normal thing
- return session.peer_certificate
- list_size = ctypes.c_uint()
- cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
- (session._c_object, ctypes.byref(list_size))
- if list_size.value == 0:
- return None
- cert = cert_list[0]
- return ctypes.string_at(cert.data, cert.size)
-
-
-def fingerprint(openpgp):
- "Convert an OpenPGP bytestring to a hexdigit fingerprint string"
- # New GnuTLS "datum" with the OpenPGP public key
- datum = gnutls.library.types.gnutls_datum_t\
- (ctypes.cast(ctypes.c_char_p(openpgp),
- ctypes.POINTER(ctypes.c_ubyte)),
- ctypes.c_uint(len(openpgp)))
- # New empty GnuTLS certificate
- crt = gnutls.library.types.gnutls_openpgp_crt_t()
- gnutls.library.functions.gnutls_openpgp_crt_init\
- (ctypes.byref(crt))
- # Import the OpenPGP public key into the certificate
- gnutls.library.functions.gnutls_openpgp_crt_import\
- (crt, ctypes.byref(datum),
- gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
- # New buffer for the fingerprint
- buffer = ctypes.create_string_buffer(20)
- buffer_length = ctypes.c_size_t()
- # Get the fingerprint from the certificate into the buffer
- gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
- (crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
- # Deinit the certificate
- gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
- # Convert the buffer to a Python bytestring
- fpr = ctypes.string_at(buffer, buffer_length.value)
- # Convert the bytestring to hexadecimal notation
- hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
- return hex_fpr
-
-
-class tcp_handler(SocketServer.BaseRequestHandler, object):
- """A TCP request handler class.
- Instantiated by IPv6_TCPServer for each request to handle it.
+ return dbus.String(u"")
+ return dbus.String(self._datetime_to_dbus(self
+ .last_checked_ok))
+
+ # LastApprovalRequest - property
+ @dbus_service_property(_interface, signature=u"s", access=u"read")
+ def LastApprovalRequest_dbus_property(self):
+ if self.last_approval_request is None:
+ return dbus.String(u"")
+ return dbus.String(self.
+ _datetime_to_dbus(self
+ .last_approval_request))
+
+ # Timeout - property
+ @dbus_service_property(_interface, signature=u"t",
+ access=u"readwrite")
+ def Timeout_dbus_property(self, value=None):
+ if value is None: # get
+ return dbus.UInt64(self.timeout_milliseconds())
+ self.timeout = datetime.timedelta(0, 0, 0, value)
+ # Emit D-Bus signal
+ self.PropertyChanged(dbus.String(u"Timeout"),
+ dbus.UInt64(value, variant_level=1))
+ if getattr(self, u"disable_initiator_tag", None) is None:
+ return
+ # Reschedule timeout
+ gobject.source_remove(self.disable_initiator_tag)
+ self.disable_initiator_tag = None
+ time_to_die = (self.
+ _timedelta_to_milliseconds((self
+ .last_checked_ok
+ + self.timeout)
+ - datetime.datetime
+ .utcnow()))
+ if time_to_die <= 0:
+ # The timeout has passed
+ self.disable()
+ else:
+ self.disable_initiator_tag = (gobject.timeout_add
+ (time_to_die, self.disable))
+
+ # Interval - property
+ @dbus_service_property(_interface, signature=u"t",
+ access=u"readwrite")
+ def Interval_dbus_property(self, value=None):
+ if value is None: # get
+ return dbus.UInt64(self.interval_milliseconds())
+ self.interval = datetime.timedelta(0, 0, 0, value)
+ # Emit D-Bus signal
+ self.PropertyChanged(dbus.String(u"Interval"),
+ dbus.UInt64(value, variant_level=1))
+ if getattr(self, u"checker_initiator_tag", None) is None:
+ return
+ # 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=u"s",
+ access=u"readwrite")
+ def Checker_dbus_property(self, value=None):
+ if value is None: # get
+ return dbus.String(self.checker_command)
+ self.checker_command = value
+ # Emit D-Bus signal
+ self.PropertyChanged(dbus.String(u"Checker"),
+ dbus.String(self.checker_command,
+ variant_level=1))
+
+ # CheckerRunning - property
+ @dbus_service_property(_interface, signature=u"b",
+ access=u"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=u"o", access=u"read")
+ def ObjectPath_dbus_property(self):
+ return self.dbus_object_path # is already a dbus.ObjectPath
+
+ # Secret = property
+ @dbus_service_property(_interface, signature=u"ay",
+ access=u"write", byte_arrays=True)
+ def Secret_dbus_property(self, value):
+ self.secret = str(value)
+
+ del _interface
+
+
+class ProxyClient(object):
+ def __init__(self, child_pipe, 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(u"TCP connection from: %s",
+ unicode(self.client_address))
+ logger.debug(u"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 = u':'.join((u"NONE", u"+VERS-TLS1.1",
+ # u"+AES-256-CBC", u"+SHA1",
+ # u"+COMP-NULL", u"+CTYPE-OPENPGP",
+ # u"+DHE-DSS"))
+ # Use a fallback default, since this MUST be set.
+ priority = self.server.gnutls_priority
+ if priority is None:
+ priority = u"NORMAL"
+ (gnutls.library.functions
+ .gnutls_priority_set_direct(session._c_object,
+ priority, None))
+
+ # Start communication using the Mandos protocol
+ # Get protocol number
+ line = self.request.makefile().readline()
+ logger.debug(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
+
+ # Start GnuTLS connection
+ 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
+ logger.debug(u"Handshake succeeded")
+
+ approval_required = False
+ try:
+ try:
+ fpr = self.fingerprint(self.peer_certificate
+ (session))
+ except (TypeError, gnutls.errors.GNUTLSError), error:
+ logger.warning(u"Bad certificate: %s", error)
+ return
+ logger.debug(u"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.warning(u"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(u"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(u"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
+ #x = float(client._timedelta_to_milliseconds(delay))
+ time = datetime.datetime.now()
+ client.changedstate.acquire()
+ client.changedstate.wait(float(client._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), error:
+ logger.warning("gnutls send failed")
+ return
+ logger.debug(u"Sent: %d, remaining: %d",
+ sent, len(client.secret)
+ - (sent_size + sent))
+ sent_size += sent
+
+ logger.info(u"Sending secret to %s", client.name)
+ # bump the timeout as if seen
+ client.checked_ok()
+ 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), error:
+ logger.warning("GnuTLS bye failed")
+
+ @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(u"error getting peer"
+ u" certificate")
+ if list_size.value == 0:
+ return None
+ cert = cert_list[0]
+ return ctypes.string_at(cert.data, cert.size)
+
+ @staticmethod
+ def 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
+ (u"Verify failed"))
+ # New buffer for the fingerprint
+ buf = ctypes.create_string_buffer(20)
+ buf_len = ctypes.c_size_t()
+ # Get the fingerprint from the certificate into the buffer
+ (gnutls.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 = u''.join(u"%02X" % ord(char) for char in fpr)
+ return hex_fpr
+
+
+class MultiprocessingMixIn(object):
+ """Like socketserver.ThreadingMixIn, but with multiprocessing"""
+ def sub_process_main(self, request, address):
+ try:
+ self.finish_request(request, address)
+ except:
+ self.handle_error(request, address)
+ self.close_request(request)
+
+ def process_request(self, request, address):
+ """Start a new process to process the request."""
+ multiprocessing.Process(target = self.sub_process_main,
+ args = (request, address)).start()
+
+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()
+
+ super(MultiprocessingMixInWithPipe,
+ self).process_request(request, client_address)
+ self.child_pipe.close()
+ self.add_pipe(parent_pipe)
+
+ def add_pipe(self, parent_pipe):
+ """Dummy function; override as necessary"""
+ pass
+
+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"]
- return super(type(self), self).__init__(*args, **kwargs)
+ def __init__(self, server_address, RequestHandlerClass,
+ interface=None, use_ipv6=True):
+ self.interface = interface
+ if use_ipv6:
+ self.address_family = socket.AF_INET6
+ 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(u"SO_BINDTODEVICE does not exist;"
+ u" cannot bind to interface %s",
+ self.interface)
+ else:
+ try:
+ self.socket.setsockopt(socket.SOL_SOCKET,
+ SO_BINDTODEVICE,
+ str(self.interface
+ + u'\0'))
+ except socket.error, error:
+ if error[0] == errno.EPERM:
+ logger.error(u"No permission to"
+ u" bind to interface %s",
+ self.interface)
+ elif error[0] == errno.ENOPROTOOPT:
+ logger.error(u"SO_BINDTODEVICE not available;"
+ u" cannot bind to interface %s",
+ self.interface)
+ else:
+ raise
# Only bind(2) the socket if we really need to.
if self.server_address[0] or self.server_address[1]:
if not self.server_address[0]:
- in6addr_any = "::"
- self.server_address = (in6addr_any,
+ if self.address_family == socket.AF_INET6:
+ any_address = u"::" # in6addr_any
+ else:
+ any_address = socket.INADDR_ANY
+ self.server_address = (any_address,
self.server_address[1])
elif not self.server_address[1]:
self.server_address = (self.server_address[0],
0)
-# 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):
+ self.enabled = False
+ self.clients = clients
+ if self.clients is None:
+ self.clients = set()
+ self.use_dbus = use_dbus
+ self.gnutls_priority = gnutls_priority
+ IPv6_TCPServer.__init__(self, server_address,
+ RequestHandlerClass,
+ interface = interface,
+ use_ipv6 = use_ipv6)
+ def server_activate(self):
+ if self.enabled:
+ return socketserver.TCPServer.server_activate(self)
+ def enable(self):
+ self.enabled = True
+ def add_pipe(self, parent_pipe):
+ # 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))
+
+ def handle_ipc(self, source, condition, parent_pipe=None,
+ client_object=None):
+ condition_names = {
+ gobject.IO_IN: u"IN", # There is data to read.
+ gobject.IO_OUT: u"OUT", # Data can be written (without
+ # blocking).
+ gobject.IO_PRI: u"PRI", # There is urgent data to read.
+ gobject.IO_ERR: u"ERR", # Error condition.
+ gobject.IO_HUP: u"HUP" # Hung up (the connection has been
+ # broken, usually for pipes and
+ # sockets).
+ }
+ conditions_string = ' | '.join(name
+ for cond, name in
+ condition_names.iteritems()
+ if cond & condition)
+ # error or the other end of multiprocessing.Pipe has closed
+ if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
+ 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:
+ if c.fingerprint == fpr:
+ client = c
+ break
+ else:
+ logger.warning(u"Client not found for fingerprint: %s, ad"
+ u"dress: %s", fpr, address)
+ if self.use_dbus:
+ # Emit D-Bus signal
+ mandos_dbus_service.ClientNotFound(fpr, address[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,
+ 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 string_to_delta(interval):
"""Parse a string and return a datetime.timedelta
-
- >>> string_to_delta('7d')
+
+ >>> string_to_delta(u'7d')
datetime.timedelta(7)
- >>> string_to_delta('60s')
+ >>> string_to_delta(u'60s')
datetime.timedelta(0, 60)
- >>> string_to_delta('60m')
+ >>> string_to_delta(u'60m')
datetime.timedelta(0, 3600)
- >>> string_to_delta('24h')
+ >>> string_to_delta(u'24h')
datetime.timedelta(1)
>>> string_to_delta(u'1w')
datetime.timedelta(7)
+ >>> string_to_delta(u'5m 30s')
+ datetime.timedelta(0, 330)
"""
- try:
- suffix=unicode(interval[-1])
- value=int(interval[:-1])
- if suffix == u"d":
- delta = datetime.timedelta(value)
- elif suffix == u"s":
- delta = datetime.timedelta(0, value)
- elif suffix == u"m":
- delta = datetime.timedelta(0, 0, 0, 0, value)
- elif suffix == u"h":
- delta = datetime.timedelta(0, 0, 0, 0, 0, value)
- elif suffix == u"w":
- delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
- else:
- raise ValueError
- except (ValueError, IndexError):
- raise ValueError
- return delta
-
-
-def server_state_changed(state):
- """Derived from the Avahi example code"""
- if state == avahi.SERVER_COLLISION:
- logger.error(u"Server name collision")
- service.remove()
- elif state == avahi.SERVER_RUNNING:
- service.add()
-
-
-def entry_group_state_changed(state, error):
- """Derived from the Avahi example code"""
- logger.debug(u"state change: %i", state)
-
- if state == avahi.ENTRY_GROUP_ESTABLISHED:
- logger.debug(u"Service established.")
- elif state == avahi.ENTRY_GROUP_COLLISION:
- logger.warning(u"Service name collision.")
- service.rename()
- elif state == avahi.ENTRY_GROUP_FAILURE:
- logger.critical(u"Error in group state changed %s",
- unicode(error))
- raise AvahiGroupError("State changed: %s", str(error))
+ timevalue = datetime.timedelta(0)
+ for s in interval.split():
+ try:
+ suffix = unicode(s[-1])
+ value = int(s[:-1])
+ if suffix == u"d":
+ delta = datetime.timedelta(value)
+ elif suffix == u"s":
+ delta = datetime.timedelta(0, value)
+ elif suffix == u"m":
+ delta = datetime.timedelta(0, 0, 0, 0, value)
+ elif suffix == u"h":
+ delta = datetime.timedelta(0, 0, 0, 0, 0, value)
+ elif suffix == u"w":
+ delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
+ else:
+ raise ValueError(u"Unknown suffix %r" % suffix)
+ except (ValueError, IndexError), e:
+ raise ValueError(e.message)
+ timevalue += delta
+ return timevalue
+
def if_nametoindex(interface):
- """Call the C function if_nametoindex(), or equivalent"""
+ """Call the C function if_nametoindex(), or equivalent
+
+ Note: This function cannot accept a unicode string."""
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
+ if_nametoindex = (ctypes.cdll.LoadLibrary
+ (ctypes.util.find_library(u"c"))
+ .if_nametoindex)
except (OSError, AttributeError):
- if "struct" not in sys.modules:
- import struct
- if "fcntl" not in sys.modules:
- import fcntl
+ logger.warning(u"Doing if_nametoindex the hard way")
def if_nametoindex(interface):
"Get an interface index the hard way, i.e. using fcntl()"
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
- s = socket.socket()
- ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
- struct.pack("16s16x", interface))
- s.close()
- interface_index = struct.unpack("I", ifreq[16:20])[0]
+ with contextlib.closing(socket.socket()) as s:
+ ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
+ struct.pack(str(u"16s16x"),
+ interface))
+ interface_index = struct.unpack(str(u"I"),
+ ifreq[16:20])[0]
return interface_index
return if_nametoindex(interface)
def daemon(nochdir = False, noclose = False):
"""See daemon(3). Standard BSD Unix function.
+
This should really exist as os.daemon, but it doesn't (yet)."""
if os.fork():
sys.exit()
os.setsid()
if not nochdir:
- os.chdir("/")
+ os.chdir(u"/")
if os.fork():
sys.exit()
if not noclose:
@@ -647,7 +1658,8 @@
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
if not stat.S_ISCHR(os.fstat(null).st_mode):
raise OSError(errno.ENODEV,
- "/dev/null not a character device")
+ u"%s not a character device"
+ % os.path.devnull)
os.dup2(null, sys.stdin.fileno())
os.dup2(null, sys.stdout.fileno())
os.dup2(null, sys.stderr.fileno())
@@ -656,30 +1668,38 @@
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 = optparse.OptionParser(version = "%%prog %s" % version)
+ parser.add_option("-i", u"--interface", type=u"string",
+ metavar="IF", help=u"Bind to interface IF")
+ parser.add_option("-a", u"--address", type=u"string",
+ help=u"Address to listen for requests on")
+ parser.add_option("-p", u"--port", type=u"int",
+ help=u"Port number to receive requests on")
+ parser.add_option("--check", action=u"store_true",
+ help=u"Run self-test")
+ parser.add_option("--debug", action=u"store_true",
+ help=u"Debug mode; run in foreground and log to"
+ u" terminal")
+ parser.add_option("--debuglevel", type=u"string", metavar="Level",
+ help=u"Debug level for stdout output")
+ parser.add_option("--priority", type=u"string", help=u"GnuTLS"
+ u" priority string (see GnuTLS documentation)")
+ parser.add_option("--servicename", type=u"string",
+ metavar=u"NAME", help=u"Zeroconf service name")
+ parser.add_option("--configdir", type=u"string",
+ default=u"/etc/mandos", metavar=u"DIR",
+ help=u"Directory to search for configuration"
+ u" files")
+ parser.add_option("--no-dbus", action=u"store_false",
+ dest=u"use_dbus", help=u"Do not provide D-Bus"
+ u" system bus interface")
+ parser.add_option("--no-ipv6", action=u"store_false",
+ dest=u"use_ipv6", help=u"Do not use IPv6")
+ options = parser.parse_args()[0]
if options.check:
import doctest
@@ -687,166 +1707,341 @@
sys.exit()
# Default values for config file for server-global settings
- server_defaults = { "interface": "",
- "address": "",
- "port": "",
- "debug": "False",
- "priority":
- "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
- "servicename": "Mandos",
+ server_defaults = { u"interface": u"",
+ u"address": u"",
+ u"port": u"",
+ u"debug": u"False",
+ u"priority":
+ u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
+ u"servicename": u"Mandos",
+ u"use_dbus": u"True",
+ u"use_ipv6": u"True",
+ u"debuglevel": u"",
}
# 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,
+ u"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 (u"debug", u"use_dbus", u"use_ipv6"):
+ server_settings[option] = server_config.getboolean(u"DEFAULT",
+ option)
+ if server_settings["port"]:
+ server_settings["port"] = server_config.getint(u"DEFAULT",
+ u"port")
del server_config
# Override the settings from the config file with command line
# options, if set.
- for option in ("interface", "address", "port", "debug",
- "priority", "servicename", "configdir"):
+ for option in (u"interface", u"address", u"port", u"debug",
+ u"priority", u"servicename", u"configdir",
+ u"use_dbus", u"use_ipv6", u"debuglevel"):
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])
# Now we have our good server settings in "server_settings"
- debug = server_settings["debug"]
-
+ ##################################################################
+
+ # For convenience
+ debug = server_settings[u"debug"]
+ debuglevel = server_settings[u"debuglevel"]
+ use_dbus = server_settings[u"use_dbus"]
+ use_ipv6 = server_settings[u"use_ipv6"]
+
+ if server_settings[u"servicename"] != u"Mandos":
+ syslogger.setFormatter(logging.Formatter
+ (u'Mandos (%s) [%%(process)d]:'
+ u' %%(levelname)s: %%(message)s'
+ % server_settings[u"servicename"]))
+
+ # Parse config file with clients
+ client_defaults = { u"timeout": u"1h",
+ u"interval": u"5m",
+ u"checker": u"fping -q -- %%(host)s",
+ u"host": u"",
+ u"approval_delay": u"0s",
+ u"approval_duration": u"1s",
+ }
+ client_config = configparser.SafeConfigParser(client_defaults)
+ client_config.read(os.path.join(server_settings[u"configdir"],
+ u"clients.conf"))
+
+ global mandos_dbus_service
+ mandos_dbus_service = None
+
+ tcp_server = MandosServer((server_settings[u"address"],
+ server_settings[u"port"]),
+ ClientHandler,
+ interface=(server_settings[u"interface"]
+ or None),
+ use_ipv6=use_ipv6,
+ gnutls_priority=
+ server_settings[u"priority"],
+ use_dbus=use_dbus)
if not debug:
+ pidfilename = u"/var/run/mandos.pid"
+ try:
+ pidfile = open(pidfilename, u"w")
+ except IOError:
+ logger.error(u"Could not open file %r", pidfilename)
+
+ try:
+ uid = pwd.getpwnam(u"_mandos").pw_uid
+ gid = pwd.getpwnam(u"_mandos").pw_gid
+ except KeyError:
+ try:
+ uid = pwd.getpwnam(u"mandos").pw_uid
+ gid = pwd.getpwnam(u"mandos").pw_gid
+ except KeyError:
+ try:
+ uid = pwd.getpwnam(u"nobody").pw_uid
+ gid = pwd.getpwnam(u"nobody").pw_gid
+ except KeyError:
+ uid = 65534
+ gid = 65534
+ try:
+ os.setgid(gid)
+ os.setuid(uid)
+ except OSError, error:
+ if error[0] != errno.EPERM:
+ raise error
+
+ if not debug and not debuglevel:
syslogger.setLevel(logging.WARNING)
console.setLevel(logging.WARNING)
-
- if server_settings["servicename"] != "Mandos":
- syslogger.setFormatter(logging.Formatter\
- ('Mandos (%s): %%(levelname)s:'
- ' %%(message)s'
- % server_settings["servicename"]))
-
- # Parse config file with clients
- client_defaults = { "timeout": "1h",
- "interval": "5m",
- "checker": "fping -q -- %%(host)s",
- }
- client_config = ConfigParser.SafeConfigParser(client_defaults)
- client_config.read(os.path.join(server_settings["configdir"],
- "clients.conf"))
-
- global service
- service = AvahiService(name = server_settings["servicename"],
- type = "_mandos._tcp", );
- if server_settings["interface"]:
- service.interface = if_nametoindex(server_settings["interface"])
+ if debuglevel:
+ level = getattr(logging, debuglevel.upper())
+ syslogger.setLevel(level)
+ console.setLevel(level)
+
+ 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(u"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.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)
+
global main_loop
- global bus
- global server
# From the Avahi example code
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
-
- clients = Set()
- def remove_from_clients(client):
- clients.remove(client)
- if not clients:
- logger.critical(u"No clients left, exiting")
- sys.exit()
-
- clients.update(Set(Client(name = section,
- stop_hook = remove_from_clients,
- config
- = dict(client_config.items(section)))
- for section in client_config.sections()))
- if not clients:
- logger.critical(u"No clients defined")
- sys.exit(1)
-
+ if use_dbus:
+ try:
+ bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
+ bus, do_not_queue=True)
+ except dbus.exceptions.NameExistsException, e:
+ logger.error(unicode(e) + u", disabling D-Bus")
+ use_dbus = False
+ server_settings[u"use_dbus"] = False
+ tcp_server.use_dbus = False
+ protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
+ service = AvahiService(name = server_settings[u"servicename"],
+ servicetype = u"_mandos._tcp",
+ protocol = protocol, bus = bus)
+ if server_settings["interface"]:
+ service.interface = (if_nametoindex
+ (str(server_settings[u"interface"])))
+
if not debug:
- logger.removeHandler(console)
+ # Close all input and output, do double fork, etc.
daemon()
-
- pidfilename = "/var/run/mandos/mandos.pid"
- pid = os.getpid()
- try:
- pidfile = open(pidfilename, "w")
- pidfile.write(str(pid) + "\n")
- pidfile.close()
- del pidfile
- except IOError, err:
- logger.error(u"Could not write %s file with PID %d",
- pidfilename, os.getpid())
+
+ global multiprocessing_manager
+ multiprocessing_manager = multiprocessing.Manager()
+
+ client_class = Client
+ if use_dbus:
+ client_class = functools.partial(ClientDBus, bus = bus)
+ def client_config_items(config, section):
+ special_settings = {
+ "approved_by_default":
+ lambda: config.getboolean(section,
+ "approved_by_default"),
+ }
+ for name, value in config.items(section):
+ try:
+ yield (name, special_settings[name]())
+ except KeyError:
+ yield (name, value)
+
+ tcp_server.clients.update(set(
+ client_class(name = section,
+ config= dict(client_config_items(
+ client_config, section)))
+ for section in client_config.sections()))
+ if not tcp_server.clients:
+ logger.warning(u"No clients defined")
+
+ if not debug:
+ try:
+ with pidfile:
+ pid = os.getpid()
+ pidfile.write(str(pid) + "\n")
+ del pidfile
+ except IOError:
+ logger.error(u"Could not write to file %r with PID %d",
+ pidfilename, pid)
+ except NameError:
+ # "pidfile" was never created
+ pass
+ del pidfilename
+
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
+
+ signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
+ signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
+
+ if use_dbus:
+ class MandosDBusService(dbus.service.Object):
+ """A D-Bus proxy object"""
+ def __init__(self):
+ dbus.service.Object.__init__(self, bus, u"/")
+ _interface = u"se.bsnet.fukt.Mandos"
+
+ @dbus.service.signal(_interface, signature=u"o")
+ def ClientAdded(self, objpath):
+ "D-Bus signal"
+ pass
+
+ @dbus.service.signal(_interface, signature=u"ss")
+ def ClientNotFound(self, fingerprint, address):
+ "D-Bus signal"
+ pass
+
+ @dbus.service.signal(_interface, signature=u"os")
+ def ClientRemoved(self, objpath, name):
+ "D-Bus signal"
+ pass
+
+ @dbus.service.method(_interface, out_signature=u"ao")
+ def GetAllClients(self):
+ "D-Bus method"
+ return dbus.Array(c.dbus_object_path
+ for c in tcp_server.clients)
+
+ @dbus.service.method(_interface,
+ out_signature=u"a{oa{sv}}")
+ def GetAllClientsWithProperties(self):
+ "D-Bus method"
+ return dbus.Dictionary(
+ ((c.dbus_object_path, c.GetAll(u""))
+ for c in tcp_server.clients),
+ signature=u"oa{sv}")
+
+ @dbus.service.method(_interface, in_signature=u"o")
+ def RemoveClient(self, object_path):
+ "D-Bus method"
+ for c in tcp_server.clients:
+ if c.dbus_object_path == object_path:
+ tcp_server.clients.remove(c)
+ 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
+ service.cleanup()
- while clients:
- client = clients.pop()
- client.stop_hook = None
- client.stop()
+ while tcp_server.clients:
+ client = tcp_server.clients.pop()
+ if use_dbus:
+ client.remove_from_connection()
+ client.disable_hook = None
+ # 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)
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()
-
- tcp_server = IPv6_TCPServer((server_settings["address"],
- server_settings["port"]),
- tcp_handler,
- settings=server_settings,
- clients=clients)
+ for client in tcp_server.clients:
+ if use_dbus:
+ # Emit D-Bus signal
+ mandos_dbus_service.ClientAdded(client.dbus_object_path)
+ client.enable()
+
+ tcp_server.enable()
+ tcp_server.server_activate()
+
# Find out what port we got
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(u"Now listening on address %r, port %d,"
+ " flowinfo %d, scope_id %d"
+ % tcp_server.socket.getsockname())
+ else: # IPv4
+ logger.info(u"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())
+ service.activate()
except dbus.exceptions.DBusException, error:
logger.critical(u"DBusException: %s", error)
+ cleanup()
sys.exit(1)
# End of Avahi example code
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
lambda *args, **kwargs:
- tcp_server.handle_request\
- (*args[2:], **kwargs) or True)
+ (tcp_server.handle_request
+ (*args[2:], **kwargs) or True))
logger.debug(u"Starting main loop")
- main_loop_started = True
main_loop.run()
except AvahiError, error:
- logger.critical(u"AvahiError: %s" + unicode(error))
+ logger.critical(u"AvahiError: %s", error)
+ cleanup()
sys.exit(1)
except KeyboardInterrupt:
if debug:
- print
+ print >> sys.stderr
+ logger.debug(u"Server received KeyboardInterrupt")
+ logger.debug(u"Server exiting")
+ # Must run before the D-Bus bus name gets deregistered
+ cleanup()
if __name__ == '__main__':
main()
=== modified file 'mandos-clients.conf.xml'
--- mandos-clients.conf.xml 2008-08-09 01:39:09 +0000
+++ mandos-clients.conf.xml 2010-09-26 18:32:58 +0000
@@ -1,19 +1,20 @@
-
-
+
/etc/mandos/clients.conf">
+
+
+%common;
]>
-
+
- &CONFNAME;
-
- &CONFNAME;
- &VERSION;
+ Mandos Manual
+
+ Mandos
+ &version;
+ &TIMESTAMP;
Björn
@@ -32,33 +33,14 @@
2008
- Teddy Hogeborn & Björn Påhlsson
+ 2009
+ 2010
+ Teddy Hogeborn
+ Björn Påhlsson
-
-
- This manual page is free software: you can redistribute it
- and/or modify it under the terms of the GNU General Public
- License as published by the Free Software Foundation,
- either version 3 of the License, or (at your option) any
- later version.
-
-
-
- This manual page is distributed in the hope that it will
- be useful, but WITHOUT ANY WARRANTY; without even the
- implied warranty of MERCHANTABILITY or FITNESS FOR A
- PARTICULAR PURPOSE. See the GNU General Public License
- for more details.
-
-
-
- You should have received a copy of the GNU General Public
- License along with this program; If not, see
- .
-
-
+
-
+
&CONFNAME;
5
@@ -67,146 +49,355 @@
&CONFNAME;
- Configuration file for Mandos clients
+ Configuration file for the Mandos server
-
+
-
- &CONFPATH;
-
+ &CONFPATH;
-
+
DESCRIPTION
- The file &CONFPATH; is the configuration file for mandos where
- each client that will be abel to use the service need to be
- specified. The configuration file is looked on at the startup of
- the service, so to reenable timedout clients one need to only
- restart the server. The format starts with a section under []
- which is eather [DEFAULT] or a client
- name. Values is set through the use of VAR = VALUE pair. Values
- may not be empty.
+ The file &CONFPATH; is a configuration file for mandos
+ 8, read by it at startup.
+ The file needs to list all clients that should be able to use
+ the service. All clients listed will be regarded as enabled,
+ even if a client was disabled in a previous run of the server.
+
+
+ The format starts with a [section
+ header] which is either
+ [DEFAULT] or [client
+ name]. The client
+ name can be anything, and is not tied to a host
+ name. Following the section header is any number of
+ option=value
entries,
+ with continuations in the style of RFC 822. option: value
is also accepted. Note that
+ leading whitespace is removed from values. Values can contain
+ format strings which refer to other values in the same section,
+ or values in the DEFAULT
section (see ). Lines beginning with #
+ or ;
are ignored and may be used to provide
+ comments.
-
-
- DEFAULTS
-
- The paramters for [DEFAULT] are:
-
-
+
+
+ OPTIONS
+
+ Note: all option values are subject to
+ start time expansion, see .
+
+
+ Unknown options are ignored. The used options are as follows:
+
+
- timeout
-
-
- This option allows you to override the default timeout
- that clients will get. By default mandos will use 1hr.
-
-
-
-
-
- interval
-
-
- This option allows you to override the default interval
- used between checkups for disconnected clients. By default
- mandos will use 5m.
-
-
-
-
-
- checker
-
+
+
+
+ This option is optional.
+
+
+ How long to wait for external approval before resorting to
+ use the value. The
+ default is 0s
, i.e. not to wait.
+
+
+ The format of TIME is the same
+ as for timeout below.
+
+
+
+
+
+
+
+
+ This option is optional.
+
+
+ How long an external approval lasts. The default is 1
+ second.
+
+
+ The format of TIME is the same
+ as for timeout below.
+
+
+
+
+
+
+
+
+ Whether to approve a client by default after
+ the . The default
+ is True
.
+
+
+
+
+
+
+
+
+ This option is optional.
+
This option allows you to override the default shell
- command that the server will use to check up if the client
- is still up. By default mandos will "fping -q -- %%(host)s"
-
-
-
-
-
-
-
-
- CLIENTS
-
- The paramters for clients are:
-
-
-
-
-
- fingerprint
-
-
- This option sets the openpgp fingerprint that identifies
- the public certificate that clients authenticates themself
- through gnutls. The string need to be in hex-decimal form.
-
-
-
-
-
- secret
-
-
- Base 64 encoded OpenPGP encrypted password encrypted by
- the clients openpgp certificate.
-
-
-
-
-
- secfile
-
-
- Base 64 encoded OpenPGP encrypted password encrypted by
- the clients openpgp certificate as a binary file.
-
-
-
-
-
- host
-
-
- Host name that can be used in for checking that the client is up.
-
-
-
-
-
- checker
-
-
- Shell command that the server will use to check up if a
- client is still up.
-
-
-
-
-
- timeout
-
-
- Duration that a client can be down whitout be removed from
- the client list.
-
-
-
-
-
-
-
-
- EXAMPLES
+ 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
.
+
+
+ In addition to normal start time expansion, this option
+ will also be subject to runtime expansion; see .
+
+
+
+
+
+
+
+
+ This option is required.
+
+
+ This option sets the OpenPGP fingerprint that identifies
+ the public key that clients authenticate themselves with
+ through TLS. The string needs to be in hexidecimal form,
+ but spaces or upper/lower case are not significant.
+
+
+
+
+
+
+
+
+ This option is optional, but highly
+ recommended unless the
+ option is modified to a
+ non-standard value without %%(host)s
in it.
+
+
+ Host name for this client. This is not used by the server
+ directly, but can be, and is by default, used by the
+ checker. See the option.
+
+
+
+
+
+
+
+
+ This option is optional.
+
+
+ How often to run the checker to confirm that a client is
+ still up. Note: a new checker will
+ not be started if an old one is still running. The server
+ will wait for a checker to complete until the below
+ timeout
occurs, at which
+ time the client will be disabled, and any running checker
+ killed. The default interval is 5 minutes.
+
+
+ The format of TIME is the same
+ as for timeout below.
+
+
+
+
+
+
+
+
+ This option is only used if is not
+ specified, in which case this option is
+ required.
+
+
+ Similar to the , except the secret
+ data is in an external file. The contents of the file
+ should not be base64-encoded, but
+ will be sent to clients verbatim.
+
+
+ File names of the form ~user/foo/bar
+ and $ENVVAR/foo/bar
+ are supported.
+
+
+
+
+
+
+
+
+ If this option is not specified, the option is required
+ to be present.
+
+
+ If present, this option must be set to a string of
+ base64-encoded binary data. It will be decoded and sent
+ to the client matching the above
+ . This should, of course, be
+ OpenPGP encrypted data, decryptable only by the client.
+ The program mandos-keygen8 can, using its
+ option, be used to generate
+ this, if desired.
+
+
+ Note: this value of this option will probably be very
+ long. A useful feature to avoid having unreadably-long
+ lines is that a line beginning with white space adds to
+ the value of the previous line, RFC 822-style.
+
+
+
+
+
+
+
+
+ This option is optional.
+
+
+ The timeout is how long the server will wait (for either a
+ successful checker run or a client receiving its secret)
+ until a client is disabled and not allowed to get the data
+ this server holds. By default Mandos will use 1 hour.
+
+
+ The TIME is specified as a
+ space-separated number of values, each of which is a
+ number and a one-character suffix. The suffix must be one
+ of d
, s
, m
,
+ h
, and w
for days, seconds,
+ minutes, hours, and weeks, respectively. The values are
+ added together to give the total time value, so all of
+ 330s
,
+ 110s 110s 110s
, and
+ 5m 30s
will give a value
+ of five minutes and thirty seconds.
+
+
+
+
+
+
+
+
+ EXPANSION
+
+ There are two forms of expansion: Start time expansion and
+ runtime expansion.
+
+
+ START TIME EXPANSION
+
+ Any string in an option value of the form
+ %(foo)s
will be replaced by the value of the option
+ foo either in the same section, or, if it
+ does not exist there, the [DEFAULT]
+ section. This is done at start time, when the configuration
+ file is read.
+
+
+ Note that this means that, in order to include an actual
+ percent character (%
) in an option value, two
+ percent characters in a row (%%
) must be
+ entered.
+
+
+
+ RUNTIME EXPANSION
+
+ This is currently only done for the checker
+ option.
+
+
+ Any string in an option value of the form
+ %%(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.
+
+
+ Note that this means that, in order to include an actual
+ percent character (%
) in a
+ checker option, four
+ percent characters in a row (%%%%
) must be
+ entered. Also, a bad format here will lead to an immediate
+ but silent run-time fatal exit; debug
+ mode is needed to expose an error of this kind.
+
+
+
+
+
+
+ FILES
+
+ The file described here is &CONFPATH;
+
+
+
+
+ BUGS
+
+ The format for specifying times for timeout
+ and interval is not very good.
+
+
+ The difference between
+ %%(foo)s and
+ %(foo)s is
+ obscure.
+
+
+
+
+ EXAMPLE
[DEFAULT]
@@ -214,9 +405,9 @@
interval = 5m
checker = fping -q -- %%(host)s
-[example_client]
+# Client "foo"
+[foo]
fingerprint = 7788 2722 5BA7 DE53 9C5A 7CFA 59CF F7CD BD9A 5920
-
secret =
hQIOA6QdEjBs2L/HEAf/TCyrDe5Xnm9esa+Pb/vWF9CUqfn4srzVgSu234
REJMVv7lBSrPE2132Lmd2gqF1HeLKDJRSVxJpt6xoWOChGHg+TMyXDxK+N
@@ -233,18 +424,34 @@
5MHdW9AYsNJZAQSOpirE4Xi31CSlWAi9KV+cUCmWF5zOFy1x23P6PjdaRm
4T2zw4dxS5NswXWU0sVEXxjs6PYxuIiCTL7vdpx8QjBkrPWDrAbcMyBr2O
QlnHIvPzEArRQLo=
- =iHhv
+host = foo.example.org
+interval = 1m
-host = localhost
-interval = 5m
+# Client "bar"
+[bar]
+fingerprint = 3e393aeaefb84c7e89e2f547b3a107558fca3a27
+secfile = /etc/mandos/bar-secret
+timeout = 15m
+approved_by_default = False
+approval_delay = 30s
-
+
-
- FILES
+
+ SEE ALSO
- The file described here is &CONFPATH;
+ mandos-keygen
+ 8,
+ mandos.conf
+ 5,
+ mandos
+ 8
+
+
+
+
+
=== added file 'mandos-ctl'
--- mandos-ctl 1970-01-01 00:00:00 +0000
+++ mandos-ctl 2010-09-26 18:32:58 +0000
@@ -0,0 +1,337 @@
+#!/usr/bin/python
+# -*- mode: python; coding: utf-8 -*-
+#
+# Mandos Monitor - Control and monitor the Mandos server
+#
+# Copyright © 2008-2010 Teddy Hogeborn
+# Copyright © 2008-2010 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
+import sys
+import dbus
+from optparse import OptionParser
+import locale
+import datetime
+import re
+import os
+
+locale.setlocale(locale.LC_ALL, u'')
+
+tablewords = {
+ 'Name': u'Name',
+ 'Enabled': u'Enabled',
+ 'Timeout': u'Timeout',
+ 'LastCheckedOK': u'Last Successful Check',
+ 'LastApprovalRequest': u'Last Approval Request',
+ 'Created': u'Created',
+ 'Interval': u'Interval',
+ 'Host': u'Host',
+ 'Fingerprint': u'Fingerprint',
+ 'CheckerRunning': u'Check Is Running',
+ 'LastEnabled': u'Last Enabled',
+ 'ApprovalPending': u'Approval Is Pending',
+ 'ApprovedByDefault': u'Approved By Default',
+ 'ApprovalDelay': u"Approval Delay",
+ 'ApprovalDuration': u"Approval Duration",
+ 'Checker': u'Checker',
+ }
+defaultkeywords = ('Name', 'Enabled', 'Timeout', 'LastCheckedOK')
+domain = 'se.bsnet.fukt'
+busname = domain + '.Mandos'
+server_path = '/'
+server_interface = domain + '.Mandos'
+client_interface = domain + '.Mandos.Client'
+version = "1.0.14"
+
+def timedelta_to_milliseconds(td):
+ "Convert a datetime.timedelta object to milliseconds"
+ return ((td.days * 24 * 60 * 60 * 1000)
+ + (td.seconds * 1000)
+ + (td.microseconds // 1000))
+
+def milliseconds_to_string(ms):
+ td = datetime.timedelta(0, 0, 0, ms)
+ return (u"%(days)s%(hours)02d:%(minutes)02d:%(seconds)02d"
+ % { "days": "%dT" % td.days if td.days else "",
+ "hours": td.seconds // 3600,
+ "minutes": (td.seconds % 3600) // 60,
+ "seconds": td.seconds % 60,
+ })
+
+
+def string_to_delta(interval):
+ """Parse a string and return a datetime.timedelta
+
+ >>> string_to_delta('7d')
+ datetime.timedelta(7)
+ >>> string_to_delta('60s')
+ datetime.timedelta(0, 60)
+ >>> string_to_delta('60m')
+ datetime.timedelta(0, 3600)
+ >>> string_to_delta('24h')
+ datetime.timedelta(1)
+ >>> string_to_delta(u'1w')
+ datetime.timedelta(7)
+ >>> string_to_delta('5m 30s')
+ datetime.timedelta(0, 330)
+ """
+ timevalue = datetime.timedelta(0)
+ regexp = re.compile("\d+[dsmhw]")
+
+ for s in regexp.findall(interval):
+ try:
+ suffix = unicode(s[-1])
+ value = int(s[:-1])
+ if suffix == u"d":
+ delta = datetime.timedelta(value)
+ elif suffix == u"s":
+ delta = datetime.timedelta(0, value)
+ elif suffix == u"m":
+ delta = datetime.timedelta(0, 0, 0, 0, value)
+ elif suffix == u"h":
+ delta = datetime.timedelta(0, 0, 0, 0, 0, value)
+ elif suffix == u"w":
+ delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
+ else:
+ raise ValueError
+ except (ValueError, IndexError):
+ raise ValueError
+ timevalue += delta
+ return timevalue
+
+def print_clients(clients, keywords):
+ def valuetostring(value, keyword):
+ if type(value) is dbus.Boolean:
+ return u"Yes" if value else u"No"
+ if keyword in (u"Timeout", u"Interval", u"ApprovalDelay",
+ u"ApprovalDuration"):
+ return milliseconds_to_string(value)
+ return unicode(value)
+
+ # Create format string to print table rows
+ format_string = u' '.join(u'%%-%ds' %
+ max(len(tablewords[key]),
+ max(len(valuetostring(client[key],
+ key))
+ for client in
+ clients))
+ for key in keywords)
+ # Print header line
+ print format_string % tuple(tablewords[key] for key in keywords)
+ for client in clients:
+ print format_string % tuple(valuetostring(client[key], key)
+ for key in keywords)
+
+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.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 = OptionParser(version = "%%prog %s" % version)
+ parser.add_option("-a", "--all", action="store_true",
+ help="Select all clients")
+ parser.add_option("-v", "--verbose", action="store_true",
+ help="Print all fields")
+ parser.add_option("-e", "--enable", action="store_true",
+ help="Enable client")
+ parser.add_option("-d", "--disable", action="store_true",
+ help="disable client")
+ parser.add_option("-b", "--bump-timeout", action="store_true",
+ help="Bump timeout for client")
+ parser.add_option("--start-checker", action="store_true",
+ help="Start checker for client")
+ parser.add_option("--stop-checker", action="store_true",
+ help="Stop checker for client")
+ parser.add_option("-V", "--is-enabled", action="store_true",
+ help="Check if client is enabled")
+ parser.add_option("-r", "--remove", action="store_true",
+ help="Remove client")
+ parser.add_option("-c", "--checker", type="string",
+ help="Set checker command for client")
+ parser.add_option("-t", "--timeout", type="string",
+ help="Set timeout for client")
+ parser.add_option("-i", "--interval", type="string",
+ help="Set checker interval for client")
+ parser.add_option("--approve-by-default", action="store_true",
+ dest=u"approved_by_default",
+ help="Set client to be approved by default")
+ parser.add_option("--deny-by-default", action="store_false",
+ dest=u"approved_by_default",
+ help="Set client to be denied by default")
+ parser.add_option("--approval-delay", type="string",
+ help="Set delay before client approve/deny")
+ parser.add_option("--approval-duration", type="string",
+ help="Set duration of one client approval")
+ parser.add_option("-H", "--host", type="string",
+ help="Set host for client")
+ parser.add_option("-s", "--secret", type="string",
+ help="Set password blob (file) for client")
+ parser.add_option("-A", "--approve", action="store_true",
+ help="Approve any current client request")
+ parser.add_option("-D", "--deny", action="store_true",
+ help="Deny any current client request")
+ options, client_names = parser.parse_args()
+
+ if has_actions(options) and not client_names and not 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.')
+
+ try:
+ bus = dbus.SystemBus()
+ mandos_dbus_objc = bus.get_object(busname, server_path)
+ except dbus.exceptions.DBusException:
+ print >> sys.stderr, "Could not connect to Mandos server"
+ 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, e:
+ print >> sys.stderr, "Access denied: Accessing mandos server through dbus."
+ sys.exit(1)
+
+ # Compile dict of (clients: properties) to process
+ clients={}
+
+ if options.all or not client_names:
+ clients = dict((bus.get_object(busname, path), properties)
+ for path, properties in
+ mandos_clients.iteritems())
+ else:
+ for name in client_names:
+ 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 >> sys.stderr, "Client not found on server: %r" % name
+ 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')
+ else:
+ keywords = defaultkeywords
+
+ print_clients(clients.values(), keywords)
+ else:
+ # Process each client in the list by all selected options
+ for client in clients:
+ if options.remove:
+ mandos_serv.RemoveClient(client.__dbus_object_path__)
+ if options.enable:
+ client.Enable(dbus_interface=client_interface)
+ if options.disable:
+ client.Disable(dbus_interface=client_interface)
+ if options.bump_timeout:
+ client.CheckedOK(dbus_interface=client_interface)
+ if options.start_checker:
+ client.StartChecker(dbus_interface=client_interface)
+ if options.stop_checker:
+ client.StopChecker(dbus_interface=client_interface)
+ if options.is_enabled:
+ sys.exit(0 if client.Get(client_interface,
+ u"Enabled",
+ dbus_interface=dbus.PROPERTIES_IFACE)
+ else 1)
+ if options.checker:
+ client.Set(client_interface, u"Checker", options.checker,
+ dbus_interface=dbus.PROPERTIES_IFACE)
+ if options.host:
+ client.Set(client_interface, u"Host", options.host,
+ dbus_interface=dbus.PROPERTIES_IFACE)
+ if options.interval:
+ client.Set(client_interface, u"Interval",
+ timedelta_to_milliseconds
+ (string_to_delta(options.interval)),
+ dbus_interface=dbus.PROPERTIES_IFACE)
+ if options.approval_delay:
+ client.Set(client_interface, u"ApprovalDelay",
+ timedelta_to_milliseconds
+ (string_to_delta(options.
+ approval_delay)),
+ dbus_interface=dbus.PROPERTIES_IFACE)
+ if options.approval_duration:
+ client.Set(client_interface, u"ApprovalDuration",
+ timedelta_to_milliseconds
+ (string_to_delta(options.
+ approval_duration)),
+ dbus_interface=dbus.PROPERTIES_IFACE)
+ if options.timeout:
+ client.Set(client_interface, u"Timeout",
+ timedelta_to_milliseconds
+ (string_to_delta(options.timeout)),
+ dbus_interface=dbus.PROPERTIES_IFACE)
+ if options.secret:
+ client.Set(client_interface, u"Secret",
+ dbus.ByteArray(open(options.secret,
+ u'rb').read()),
+ dbus_interface=dbus.PROPERTIES_IFACE)
+ if options.approved_by_default is not None:
+ client.Set(client_interface, u"ApprovedByDefault",
+ dbus.Boolean(options
+ .approved_by_default),
+ dbus_interface=dbus.PROPERTIES_IFACE)
+ 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 2010-09-25 23:52:17 +0000
@@ -0,0 +1,570 @@
+
+
+
+
+%common;
+]>
+
+
+
+ Mandos Manual
+
+ Mandos
+ &version;
+ &TIMESTAMP;
+
+
+ Björn
+ Påhlsson
+
+ belorn@fukt.bsnet.se
+
+
+
+ Teddy
+ Hogeborn
+
+ teddy@fukt.bsnet.se
+
+
+
+
+ 2010
+ Teddy Hogeborn
+ Björn Påhlsson
+
+
+
+
+
+ &COMMANDNAME;
+ 8
+
+
+
+ &COMMANDNAME;
+
+ Control the operation of the Mandos server
+
+
+
+
+
+ &COMMANDNAME;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ CLIENT
+
+
+
+
+ &COMMANDNAME;
+
+
+
+
+
+
+ CLIENT
+
+
+
+
+ &COMMANDNAME;
+
+
+
+
+ CLIENT
+
+
+ &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 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.
+
+
+
+
+
+
+
+
+ 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
+
+ mandos
+ 8,
+ mandos-clients.conf
+ 5,
+ mandos-monitor
+ 8
+
+
+
+
+
+
+
+
+
=== modified file 'mandos-keygen'
--- mandos-keygen 2008-08-18 05:24:20 +0000
+++ mandos-keygen 2010-09-26 18:32:58 +0000
@@ -2,7 +2,8 @@
#
# Mandos key generator - create a new OpenPGP key for a Mandos client
#
-# Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson
+# Copyright © 2008-2010 Teddy Hogeborn
+# Copyright © 2008-2010 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
@@ -20,53 +21,84 @@
# Contact the authors at .
#
-VERSION="1.0"
+VERSION="1.0.14"
-KEYDIR="/etc/mandos"
+KEYDIR="/etc/keys/mandos"
KEYTYPE=DSA
-KEYLENGTH=1024
-KEYNAME="`hostname --fqdn`"
+KEYLENGTH=2048
+SUBKEYTYPE=ELG-E
+SUBKEYLENGTH=2048
+KEYNAME="`hostname --fqdn 2>/dev/null || hostname`"
KEYEMAIL=""
KEYCOMMENT="Mandos client key"
KEYEXPIRE=0
FORCE=no
KEYCOMMENT_ORIG="$KEYCOMMENT"
+mode=keygen
+
+if [ ! -d "$KEYDIR" ]; then
+ KEYDIR="/etc/mandos/keys"
+fi
# Parse options
-TEMP=`getopt --options vhd:t:l:n:e:c:x:f \
- --longoptions version,help,dir:,type:,length:,name:,email:,comment:,expire:,force \
+TEMP=`getopt --options vhpF:d:t:l:s:L:n:e:c:x:f \
+ --longoptions version,help,password,passfile:,dir:,type:,length:,subtype:,sublength:,name:,email:,comment:,expire:,force \
--name "$0" -- "$@"`
help(){
+basename="`basename $0`"
cat <&2
exit 1
fi
-if [ -w "$KEYDIR" ]; then :; else
- echo "Directory $KEYDIR not writeable" >&2
- exit 1
-fi
-
-if [ -z "$KEYTYPE" ]; then
- echo "Empty key type" >&2
- exit 1
-fi
-
-if [ -z "$KEYNAME" ]; then
- echo "Empty key name" >&2
- exit 1
-fi
-
-if [ -z "$KEYLENGTH" ] || [ "$KEYLENGTH" -lt 512 ]; then
- echo "Invalid key length" >&2
- exit 1
-fi
-
-if [ -z "$KEYEXPIRE" ]; then
- echo "Empty key expiration" >&2
- exit 1
-fi
-
-# Make FORCE be 0 or 1
-case "$FORCE" in
- [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]) FORCE=1;;
- [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|*) FORCE=0;;
-esac
-
-if { [ -e "$SECKEYFILE" ] || [ -e "$PUBKEYFILE" ]; } \
- && [ "$FORCE" -eq 0 ]; then
- echo "Refusing to overwrite old key files; use --force" >&2
- exit 1
-fi
-
-# Set lines for GnuPG batch file
-if [ -n "$KEYCOMMENT" ]; then
- KEYCOMMENTLINE="Name-Comment: $KEYCOMMENT"
-fi
-if [ -n "$KEYEMAIL" ]; then
- KEYEMAILLINE="Name-Email: $KEYEMAIL"
-fi
-
-# Create temp files
-BATCHFILE="`mktemp -t mandos-gpg-batch.XXXXXXXXXX`"
-SECRING="`mktemp -t mandos-gpg-secring.XXXXXXXXXX`"
-PUBRING="`mktemp -t mandos-gpg-pubring.XXXXXXXXXX`"
+if [ ! -r "$KEYDIR" ]; then
+ echo "Directory $KEYDIR not readable" >&2
+ exit 1
+fi
+
+if [ "$mode" = keygen ]; then
+ if [ ! -w "$KEYDIR" ]; then
+ echo "Directory $KEYDIR not writeable" >&2
+ exit 1
+ fi
+ if [ -z "$KEYTYPE" ]; then
+ echo "Empty key type" >&2
+ exit 1
+ fi
+
+ if [ -z "$KEYNAME" ]; then
+ echo "Empty key name" >&2
+ exit 1
+ fi
+
+ if [ -z "$KEYLENGTH" ] || [ "$KEYLENGTH" -lt 512 ]; then
+ echo "Invalid key length" >&2
+ exit 1
+ fi
+
+ if [ -z "$KEYEXPIRE" ]; then
+ echo "Empty key expiration" >&2
+ exit 1
+ fi
+
+ # Make FORCE be 0 or 1
+ case "$FORCE" in
+ [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]) FORCE=1;;
+ [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|*) FORCE=0;;
+ esac
+
+ if [ \( -e "$SECKEYFILE" -o -e "$PUBKEYFILE" \) \
+ -a "$FORCE" -eq 0 ]; then
+ echo "Refusing to overwrite old key files; use --force" >&2
+ exit 1
+ fi
+
+ # Set lines for GnuPG batch file
+ if [ -n "$KEYCOMMENT" ]; then
+ KEYCOMMENTLINE="Name-Comment: $KEYCOMMENT"
+ fi
+ if [ -n "$KEYEMAIL" ]; then
+ KEYEMAILLINE="Name-Email: $KEYEMAIL"
+ fi
+
+ # Create temporary gpg batch file
+ BATCHFILE="`mktemp -t mandos-keygen-batch.XXXXXXXXXX`"
+fi
+
+if [ "$mode" = password ]; then
+ # Create temporary encrypted password file
+ SECFILE="`mktemp -t mandos-keygen-secfile.XXXXXXXXXX`"
+fi
+
+# Create temporary key ring directory
+RINGDIR="`mktemp -d -t mandos-keygen-keyrings.XXXXXXXXXX`"
# Remove temporary files on exit
-trap "rm --force $PUBRING $BATCHFILE; shred --remove $SECRING" EXIT
-
-# Create batch file for GnuPG
-cat >"$BATCHFILE" </dev/null; then
- shred --remove "$SECKEYFILE"
-fi
-if cp --backup=numbered --force "$PUBKEYFILE" "$PUBKEYFILE" \
- 2>/dev/null; then
- rm --force "$PUBKEYFILE"
-fi
-
-FILECOMMENT="Mandos client key for $KEYNAME"
-if [ "$KEYCOMMENT" != "$KEYCOMMENT_ORIG" ]; then
- FILECOMMENT="$FILECOMMENT ($KEYCOMMENT)"
-fi
-
-if [ -n "$KEYEMAIL" ]; then
- FILECOMMENT="$FILECOMMENT <$KEYEMAIL>"
-fi
-
-# Export keys from key rings to key files
-gpg --no-random-seed-file --quiet --batch --no-tty --armor \
- --no-default-keyring --no-options --secret-keyring "$SECRING" \
- --keyring "$PUBRING" --export-options export-minimal \
- --comment "$FILECOMMENT" --output "$SECKEYFILE" \
- --export-secret-keys
-gpg --no-random-seed-file --quiet --batch --no-tty --armor \
- --no-default-keyring --no-options --secret-keyring "$SECRING" \
- --keyring "$PUBRING" --export-options export-minimal \
- --comment "$FILECOMMENT" --output "$PUBKEYFILE" \
- --export
+trap "
+set +e; \
+test -n \"$SECFILE\" && shred --remove \"$SECFILE\"; \
+shred --remove \"$RINGDIR\"/sec*;
+test -n \"$BATCHFILE\" && rm --force \"$BATCHFILE\"; \
+rm --recursive --force \"$RINGDIR\";
+stty echo; \
+" EXIT
+
+umask 077
+
+if [ "$mode" = keygen ]; then
+ # Create batch file for GnuPG
+ cat >"$BATCHFILE" <<-EOF
+ Key-Type: $KEYTYPE
+ Key-Length: $KEYLENGTH
+ #Key-Usage: encrypt,sign,auth
+ Subkey-Type: $SUBKEYTYPE
+ Subkey-Length: $SUBKEYLENGTH
+ #Subkey-Usage: encrypt,sign,auth
+ Name-Real: $KEYNAME
+ $KEYCOMMENTLINE
+ $KEYEMAILLINE
+ Expire-Date: $KEYEXPIRE
+ #Preferences:
+ #Handle:
+ #%pubring pubring.gpg
+ #%secring secring.gpg
+ %commit
+ EOF
+
+ if tty --quiet; then
+ cat <<-EOF
+ Note: Due to entropy requirements, key generation could take
+ anything from a few minutes to SEVERAL HOURS. Please be
+ patient and/or supply the system with more entropy if needed.
+ EOF
+ echo -n "Started: "
+ date
+ fi
+
+ # Generate a new key in the key rings
+ gpg --quiet --batch --no-tty --no-options --enable-dsa2 \
+ --homedir "$RINGDIR" --trust-model always \
+ --gen-key "$BATCHFILE"
+ rm --force "$BATCHFILE"
+
+ if tty --quiet; then
+ echo -n "Finished: "
+ date
+ fi
+
+ # Backup any old key files
+ if cp --backup=numbered --force "$SECKEYFILE" "$SECKEYFILE" \
+ 2>/dev/null; then
+ shred --remove "$SECKEYFILE"
+ fi
+ if cp --backup=numbered --force "$PUBKEYFILE" "$PUBKEYFILE" \
+ 2>/dev/null; then
+ rm --force "$PUBKEYFILE"
+ fi
+
+ FILECOMMENT="Mandos client key for $KEYNAME"
+ if [ "$KEYCOMMENT" != "$KEYCOMMENT_ORIG" ]; then
+ FILECOMMENT="$FILECOMMENT ($KEYCOMMENT)"
+ fi
+
+ if [ -n "$KEYEMAIL" ]; then
+ FILECOMMENT="$FILECOMMENT <$KEYEMAIL>"
+ fi
+
+ # Export key from key rings to key files
+ gpg --quiet --batch --no-tty --no-options --enable-dsa2 \
+ --homedir "$RINGDIR" --armor --export-options export-minimal \
+ --comment "$FILECOMMENT" --output "$SECKEYFILE" \
+ --export-secret-keys
+ gpg --quiet --batch --no-tty --no-options --enable-dsa2 \
+ --homedir "$RINGDIR" --armor --export-options export-minimal \
+ --comment "$FILECOMMENT" --output "$PUBKEYFILE" --export
+fi
+
+if [ "$mode" = password ]; then
+ # Import key into temporary key rings
+ gpg --quiet --batch --no-tty --no-options --enable-dsa2 \
+ --homedir "$RINGDIR" --trust-model always --armor \
+ --import "$SECKEYFILE"
+ gpg --quiet --batch --no-tty --no-options --enable-dsa2 \
+ --homedir "$RINGDIR" --trust-model always --armor \
+ --import "$PUBKEYFILE"
+
+ # Get fingerprint of key
+ FINGERPRINT="`gpg --quiet --batch --no-tty --no-options \
+ --enable-dsa2 --homedir \"$RINGDIR\" --trust-model always \
+ --fingerprint --with-colons \
+ | sed --quiet \
+ --expression='/^fpr:/{s/^fpr:.*:\\([0-9A-Z]*\\):\$/\\1/p;q}'`"
+
+ test -n "$FINGERPRINT"
+
+ FILECOMMENT="Encrypted password for a Mandos client"
+
+ if [ -n "$PASSFILE" ]; then
+ cat "$PASSFILE"
+ else
+ stty -echo
+ echo -n "Enter passphrase: " >&2
+ first="$(head --lines=1 | tr --delete '\n')"
+ echo >&2
+ echo -n "Repeat passphrase: " >&2
+ second="$(head --lines=1 | tr --delete '\n')"
+ echo >&2
+ stty echo
+ 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
+ exit 1
+ fi
+
+ cat <<-EOF
+ [$KEYNAME]
+ host = $KEYNAME
+ fingerprint = $FINGERPRINT
+ secret =
+ EOF
+ sed --quiet --expression='
+ /^-----BEGIN PGP MESSAGE-----$/,/^-----END PGP MESSAGE-----$/{
+ /^$/,${
+ # Remove 24-bit Radix-64 checksum
+ s/=....$//
+ # Indent four spaces
+ /^[^-]/s/^/ /p
+ }
+ }' < "$SECFILE"
+fi
trap - EXIT
+set +e
+# Remove the password file, if any
+if [ -n "$SECFILE" ]; then
+ shred --remove "$SECFILE"
+fi
# Remove the key rings
-shred --remove "$SECRING"
-rm --force "$PUBRING"
+shred --remove "$RINGDIR"/sec*
+rm --recursive --force "$RINGDIR"
=== modified file 'mandos-keygen.xml'
--- mandos-keygen.xml 2008-08-19 23:44:17 +0000
+++ mandos-keygen.xml 2009-01-04 21:54:55 +0000
@@ -1,16 +1,19 @@
-
+
+
+
+%common;
]>
- &COMMANDNAME;
-
- &COMMANDNAME;
- &VERSION;
+ Mandos Manual
+
+ Mandos
+ &version;
+ &TIMESTAMP;
Björn
@@ -29,33 +32,13 @@
2008
- Teddy Hogeborn & Björn Påhlsson
+ 2009
+ Teddy Hogeborn
+ Björn Påhlsson
-
-
- This manual page is free software: you can redistribute it
- and/or modify it under the terms of the GNU General Public
- License as published by the Free Software Foundation,
- either version 3 of the License, or (at your option) any
- later version.
-
-
-
- This manual page is distributed in the hope that it will
- be useful, but WITHOUT ANY WARRANTY; without even the
- implied warranty of MERCHANTABILITY or FITNESS FOR A
- PARTICULAR PURPOSE. See the GNU General Public License
- for more details.
-
-
-
- You should have received a copy of the GNU General Public
- License along with this program; If not, see
- .
-
-
+
-
+
&COMMANDNAME;
8
@@ -64,188 +47,256 @@
&COMMANDNAME;
- Generate keys for password-request
- 8mandos
+ Generate key and password for Mandos client and server.
-
+
&COMMANDNAME;
-
-
- directory
-
-
-
- type
-
-
-
- bits
-
-
-
- NAME
-
-
-
- EMAIL
-
-
-
- COMMENT
-
-
-
- TIME
-
-
-
-
-
-
- &COMMANDNAME;
-
-
- directory
-
-
-
- type
-
-
-
- bits
-
-
-
- NAME
-
-
-
- EMAIL
-
-
-
- COMMENT
-
-
-
- TIME
-
-
-
-
-
-
- &COMMANDNAME;
-
-
-
-
-
-
- &COMMANDNAME;
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ &COMMANDNAME;
+
+
+
+
+
+ FILE
+
+
+
+
+
+
+
+
+
+
+
+
+
+ &COMMANDNAME;
+
+
+
+
+
+
+ &COMMANDNAME;
+
+
+
-
+
DESCRIPTION
&COMMANDNAME; is a program to generate the
- OpenPGP keys used by
- password-request
- 8mandos. The keys are
+ OpenPGP key used by
+ mandos-client
+ 8mandos. The key is
normally written to /etc/mandos for later installation into the
- initrd image, but this, like most things, can be changed with
- command line options.
+ initrd image, but this, and most other things, can be changed
+ with command line options.
+
+
+ This program can also be used with the
+ or
+ options to generate a ready-made section for
+ clients.conf (see
+ mandos-clients.conf
+ 5).
PURPOSE
-
The purpose of this is to enable remote and unattended
rebooting of client host computer with an
encrypted root file system. See for details.
-
OPTIONS
-
+
- -h, --help
+
+
Show a help message and exit
-
-
- -d, --dir
- directory
-
-
- Target directory for key files.
-
-
-
-
-
- -t, --type
- type
-
-
- Key type. Default is DSA.
-
-
-
-
-
- -l, --length
- bits
-
-
- Key length in bits. Default is 1024.
-
-
-
-
-
- -e, --email
- address
+
+
+
+
+
+
+ Target directory for key files. Default is
+ /etc/mandos.
+
+
+
+
+
+
+
+
+
+ Key type. Default is DSA
.
+
+
+
+
+
+
+
+
+
+ Key length in bits. Default is 2048.
+
+
+
+
+
+
+
+
+
+ Subkey type. Default is ELG-E
(Elgamal
+ encryption-only).
+
+
+
+
+
+
+
+
+
+ Subkey length in bits. Default is 2048.
+
+
+
+
+
+
+
Email address of key. Default is empty.
-
+
- -c, --comment
- comment
+
+
Comment field for key. The default value is
- "Mandos client key".
+ Mandos client key
.
-
+
- -x, --expire
- time
+
+
Key expire time. Default is no expiration. See
@@ -254,32 +305,65 @@
-
-
- -f, --force
-
-
- Force overwriting old keys.
+
+
+
+
+
+
+ Force overwriting old key.
+
+
+
+
+
+
+
+
+ Prompt for a password and encrypt it with the key already
+ present in either /etc/mandos or the
+ directory specified with the
+ option. Outputs, on standard output, a section suitable
+ for inclusion in mandos-clients.conf8. The host name or the name
+ specified with the option is used
+ for the section header. All other options are ignored,
+ and no key is created.
+
+
+
+
+
+
+
+
+ The same as , but read from
+ FILE, not the terminal.
-
+
OVERVIEW
- This program is a small program to generate new OpenPGP keys for
- new Mandos clients.
+ 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
- The exit status will be 0 if new keys were successfully created,
- otherwise not.
+ The exit status will be 0 if a new key (or password, if the
+ option was used) was successfully
+ created, otherwise not.
@@ -287,7 +371,7 @@
ENVIRONMENT
- TMPDIR
+ TMPDIR
If set, temporary files will be created here. See
@@ -299,7 +383,7 @@
-
+
FILES
Use the option to change where
@@ -336,14 +420,13 @@
-
-
- BUGS
-
- None are known at this time.
-
-
-
+
+
+
+
+
+
+
EXAMPLE
@@ -351,47 +434,78 @@
Normal invocation needs no options:
- mandos-keygen
+ &COMMANDNAME;
- Create keys in another directory and of another type. Force
+ Create key in another directory and of another type. Force
overwriting old key files:
-mandos-keygen --dir ~/keydir --type RSA --force
+&COMMANDNAME; --dir ~/keydir --type RSA --force
+
+
+
+
+
+ Prompt for a password, encrypt it with the key in
+ /etc/mandos and output a section suitable
+ for clients.conf.
+
+
+ &COMMANDNAME; --password
+
+
+
+
+ Prompt for a password, encrypt it with the key in the
+ client-key directory and output a section
+ suitable for clients.conf.
+
+
+
+
+&COMMANDNAME; --password --dir client-key
-
+
SECURITY
- The and
- options can be used to create keys of insufficient security. If
- in doubt, leave them to the default values.
+ The , ,
+ , and
+ options can be used to create keys of low security. If in
+ doubt, leave them to the default values.
- The key expire time is not guaranteed to be honored by
- mandos
+ The key expire time is not guaranteed to be
+ honored by mandos
8.
-
+
SEE ALSO
- password-request
- 8mandos,
- mandos
- 8, and
gpg
- 1
+ 1,
+ mandos-clients.conf
+ 5,
+ mandos
+ 8,
+ mandos-client
+ 8mandos
+
+
+
+
+
=== added file 'mandos-monitor'
--- mandos-monitor 1970-01-01 00:00:00 +0000
+++ mandos-monitor 2010-09-26 18:32:58 +0000
@@ -0,0 +1,733 @@
+#!/usr/bin/python
+# -*- mode: python; coding: utf-8 -*-
+#
+# Mandos Monitor - Control and monitor the Mandos server
+#
+# Copyright © 2009,2010 Teddy Hogeborn
+# Copyright © 2009,2010 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, with_statement
+
+import sys
+import os
+import signal
+
+import datetime
+
+import urwid.curses_display
+import urwid
+
+from dbus.mainloop.glib import DBusGMainLoop
+import gobject
+
+import dbus
+
+import UserList
+
+import locale
+
+locale.setlocale(locale.LC_ALL, u'')
+
+import logging
+logging.getLogger('dbus.proxies').setLevel(logging.CRITICAL)
+
+# Some useful constants
+domain = 'se.bsnet.fukt'
+server_interface = domain + '.Mandos'
+client_interface = domain + '.Mandos.Client'
+version = "1.0.15"
+
+# Always run in monochrome mode
+urwid.curses_display.curses.has_colors = lambda : False
+
+# Urwid doesn't support blinking, but we want it. Since we have no
+# use for underline on its own, we make underline also always blink.
+urwid.curses_display.curses.A_UNDERLINE |= (
+ urwid.curses_display.curses.A_BLINK)
+
+def isoformat_to_datetime(iso):
+ "Parse an ISO 8601 date string to a datetime.datetime()"
+ if not iso:
+ return None
+ d, t = iso.split(u"T", 1)
+ year, month, day = d.split(u"-", 2)
+ hour, minute, second = t.split(u":", 2)
+ second, fraction = divmod(float(second), 1)
+ return datetime.datetime(int(year),
+ int(month),
+ int(day),
+ int(hour),
+ int(minute),
+ int(second), # Whole seconds
+ int(fraction*1000000)) # Microseconds
+
+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, *args, **kwargs):
+ self.proxy = proxy_object # Mandos Client proxy object
+
+ self.properties = dict()
+ self.proxy.connect_to_signal(u"PropertyChanged",
+ self.property_changed,
+ client_interface,
+ byte_arrays=True)
+
+ self.properties.update(
+ self.proxy.GetAll(client_interface,
+ dbus_interface = dbus.PROPERTIES_IFACE))
+
+ #XXX This break good super behaviour!
+# super(MandosClientPropertyCache, self).__init__(
+# *args, **kwargs)
+
+ def property_changed(self, property=None, value=None):
+ """This is called whenever we get a PropertyChanged signal
+ It updates the changed property in the "properties" dict.
+ """
+ # Update properties dict with new value
+ self.properties[property] = value
+
+
+class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
+ """A Mandos Client which is visible on the screen.
+ """
+
+ def __init__(self, server_proxy_object=None, update_hook=None,
+ delete_hook=None, logger=None, *args, **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
+ self._update_timer_callback_lock = 0
+ self.last_checker_failed = False
+
+ # The widget shown normally
+ self._text_widget = urwid.Text(u"")
+ # The widget shown when we have focus
+ self._focus_text_widget = urwid.Text(u"")
+ super(MandosClientWidget, self).__init__(
+ update_hook=update_hook, delete_hook=delete_hook,
+ *args, **kwargs)
+ self.update()
+ self.opened = False
+
+ last_checked_ok = isoformat_to_datetime(self.properties
+ [u"LastCheckedOK"])
+ if last_checked_ok is None:
+ self.last_checker_failed = True
+ else:
+ self.last_checker_failed = ((datetime.datetime.utcnow()
+ - last_checked_ok)
+ > datetime.timedelta
+ (milliseconds=
+ self.properties
+ [u"Interval"]))
+
+ if self.last_checker_failed:
+ self.using_timer(True)
+
+ if self.need_approval:
+ self.using_timer(True)
+
+ self.proxy.connect_to_signal(u"CheckerCompleted",
+ self.checker_completed,
+ client_interface,
+ byte_arrays=True)
+ self.proxy.connect_to_signal(u"CheckerStarted",
+ self.checker_started,
+ client_interface,
+ byte_arrays=True)
+ self.proxy.connect_to_signal(u"GotSecret",
+ self.got_secret,
+ client_interface,
+ byte_arrays=True)
+ self.proxy.connect_to_signal(u"NeedApproval",
+ self.need_approval,
+ client_interface,
+ byte_arrays=True)
+ self.proxy.connect_to_signal(u"Rejected",
+ self.rejected,
+ client_interface,
+ byte_arrays=True)
+
+ def property_changed(self, property=None, value=None):
+ super(self, MandosClientWidget).property_changed(property,
+ value)
+ if property == u"ApprovalPending":
+ using_timer(bool(value))
+
+ def using_timer(self, flag):
+ """Call this method with True or False when timer should be
+ activated or deactivated.
+ """
+ old = self._update_timer_callback_lock
+ if flag:
+ self._update_timer_callback_lock += 1
+ else:
+ self._update_timer_callback_lock -= 1
+ if old == 0 and self._update_timer_callback_lock:
+ self._update_timer_callback_tag = (gobject.timeout_add
+ (1000,
+ self.update_timer))
+ elif old and self._update_timer_callback_lock == 0:
+ gobject.source_remove(self._update_timer_callback_tag)
+ self._update_timer_callback_tag = None
+
+ def checker_completed(self, exitstatus, condition, command):
+ if exitstatus == 0:
+ if self.last_checker_failed:
+ self.last_checker_failed = False
+ self.using_timer(False)
+ #self.logger(u'Checker for client %s (command "%s")'
+ # u' was successful'
+ # % (self.properties[u"Name"], command))
+ self.update()
+ return
+ # Checker failed
+ if not self.last_checker_failed:
+ self.last_checker_failed = True
+ self.using_timer(True)
+ if os.WIFEXITED(condition):
+ self.logger(u'Checker for client %s (command "%s")'
+ u' failed with exit code %s'
+ % (self.properties[u"Name"], command,
+ os.WEXITSTATUS(condition)))
+ elif os.WIFSIGNALED(condition):
+ self.logger(u'Checker for client %s (command "%s")'
+ u' was killed by signal %s'
+ % (self.properties[u"Name"], command,
+ os.WTERMSIG(condition)))
+ elif os.WCOREDUMP(condition):
+ self.logger(u'Checker for client %s (command "%s")'
+ u' dumped core'
+ % (self.properties[u"Name"], command))
+ else:
+ self.logger(u'Checker for client %s completed'
+ u' mysteriously')
+ self.update()
+
+ def checker_started(self, command):
+ #self.logger(u'Client %s started checker "%s"'
+ # % (self.properties[u"Name"], unicode(command)))
+ pass
+
+ def got_secret(self):
+ self.last_checker_failed = False
+ self.logger(u'Client %s received its secret'
+ % self.properties[u"Name"])
+
+ def need_approval(self, timeout, default):
+ if not default:
+ message = u'Client %s needs approval within %s seconds'
+ else:
+ message = u'Client %s will get its secret in %s seconds'
+ self.logger(message
+ % (self.properties[u"Name"], timeout/1000))
+ self.using_timer(True)
+
+ def rejected(self, reason):
+ self.logger(u'Client %s was rejected; reason: %s'
+ % (self.properties[u"Name"], reason))
+
+ def selectable(self):
+ """Make this a "selectable" widget.
+ This overrides the method from urwid.FlowWidget."""
+ return True
+
+ def rows(self, (maxcol,), focus=False):
+ """How many rows this widget will occupy might depend on
+ whether we have focus or not.
+ This overrides the method from urwid.FlowWidget"""
+ return self.current_widget(focus).rows((maxcol,), focus=focus)
+
+ def current_widget(self, focus=False):
+ if focus or self.opened:
+ return self._focus_widget
+ return self._widget
+
+ def update(self):
+ "Called when what is visible on the screen should be updated."
+ # How to add standout mode to a style
+ with_standout = { u"normal": u"standout",
+ u"bold": u"bold-standout",
+ u"underline-blink":
+ u"underline-blink-standout",
+ u"bold-underline-blink":
+ u"bold-underline-blink-standout",
+ }
+
+ # Rebuild focus and non-focus widgets using current properties
+
+ # Base part of a client. Name!
+ base = (u'%(name)s: '
+ % {u"name": self.properties[u"Name"]})
+ if not self.properties[u"Enabled"]:
+ message = u"DISABLED"
+ elif self.properties[u"ApprovalPending"]:
+ timeout = datetime.timedelta(milliseconds
+ = self.properties
+ [u"ApprovalDelay"])
+ last_approval_request = isoformat_to_datetime(
+ self.properties[u"LastApprovalRequest"])
+ if last_approval_request is not None:
+ timer = timeout - (datetime.datetime.utcnow()
+ - last_approval_request)
+ else:
+ timer = datetime.timedelta()
+ if self.properties[u"ApprovedByDefault"]:
+ message = u"Approval in %s. (d)eny?"
+ else:
+ message = u"Denial in %s. (a)pprove?"
+ message = message % unicode(timer).rsplit(".", 1)[0]
+ elif self.last_checker_failed:
+ timeout = datetime.timedelta(milliseconds
+ = self.properties
+ [u"Timeout"])
+ last_ok = isoformat_to_datetime(
+ max((self.properties[u"LastCheckedOK"]
+ or self.properties[u"Created"]),
+ self.properties[u"LastEnabled"]))
+ timer = timeout - (datetime.datetime.utcnow() - last_ok)
+ message = (u'A checker has failed! Time until client'
+ u' gets disabled: %s'
+ % unicode(timer).rsplit(".", 1)[0])
+ else:
+ message = u"enabled"
+ self._text = "%s%s" % (base, message)
+
+ if not urwid.supports_unicode():
+ self._text = self._text.encode("ascii", "replace")
+ textlist = [(u"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"
+ self.update()
+ return True # Keep calling this
+
+ def delete(self):
+ if self._update_timer_callback_tag is not None:
+ gobject.source_remove(self._update_timer_callback_tag)
+ self._update_timer_callback_tag = None
+ if self.delete_hook is not None:
+ self.delete_hook(self)
+
+ def render(self, (maxcol,), focus=False):
+ """Render differently if we have focus.
+ This overrides the method from urwid.FlowWidget"""
+ return self.current_widget(focus).render((maxcol,),
+ focus=focus)
+
+ def keypress(self, (maxcol,), key):
+ """Handle keys.
+ This overrides the method from urwid.FlowWidget"""
+ if key == u"+":
+ self.proxy.Enable(dbus_interface = client_interface)
+ elif key == u"-":
+ self.proxy.Disable(dbus_interface = client_interface)
+ elif key == u"a":
+ self.proxy.Approve(dbus.Boolean(True, variant_level=1),
+ dbus_interface = client_interface)
+ elif key == u"d":
+ self.proxy.Approve(dbus.Boolean(False, variant_level=1),
+ dbus_interface = client_interface)
+ elif key == u"r" or key == u"_" or key == u"ctrl k":
+ self.server_proxy_object.RemoveClient(self.proxy
+ .object_path)
+ elif key == u"s":
+ self.proxy.StartChecker(dbus_interface = client_interface)
+ elif key == u"S":
+ self.proxy.StopChecker(dbus_interface = client_interface)
+ elif key == u"C":
+ self.proxy.CheckedOK(dbus_interface = client_interface)
+ # xxx
+# elif key == u"p" or key == "=":
+# self.proxy.pause()
+# elif key == u"u" or key == ":":
+# self.proxy.unpause()
+# elif key == u"RET":
+# self.open()
+ else:
+ return key
+
+ def property_changed(self, property=None, value=None,
+ *args, **kwargs):
+ """Call self.update() if old value is not new value.
+ This overrides the method from MandosClientPropertyCache"""
+ property_name = unicode(property)
+ old_value = self.properties.get(property_name)
+ super(MandosClientWidget, self).property_changed(
+ property=property, value=value, *args, **kwargs)
+ if self.properties.get(property_name) != old_value:
+ self.update()
+
+
+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, (maxcol, maxrow), key):
+ ret = super(ConstrainedListBox, self).keypress((maxcol,
+ maxrow), key)
+ if ret in (u"up", u"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((
+ (u"normal",
+ u"default", u"default", None),
+ (u"bold",
+ u"default", u"default", u"bold"),
+ (u"underline-blink",
+ u"default", u"default", u"underline"),
+ (u"standout",
+ u"default", u"default", u"standout"),
+ (u"bold-underline-blink",
+ u"default", u"default", (u"bold", u"underline")),
+ (u"bold-standout",
+ u"default", u"default", (u"bold", u"standout")),
+ (u"underline-blink-standout",
+ u"default", u"default", (u"underline", u"standout")),
+ (u"bold-underline-blink-standout",
+ u"default", u"default", (u"bold", u"underline",
+ u"standout")),
+ ))
+
+ if urwid.supports_unicode():
+ self.divider = u"─" # \u2500
+ #self.divider = u"━" # \u2501
+ else:
+ #self.divider = u"-" # \u002d
+ self.divider = u"_" # \u005f
+
+ self.screen.start()
+
+ self.size = self.screen.get_cols_rows()
+
+ self.clients = urwid.SimpleListWalker([])
+ self.clients_dict = {}
+
+ # We will add Text widgets to this list
+ self.log = []
+ 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 = u"any"
+
+ self.rebuild()
+ self.log_message_raw((u"bold",
+ u"Mandos Monitor version " + version))
+ self.log_message_raw((u"bold",
+ u"q: Quit ?: Help"))
+
+ self.busname = domain + '.Mandos'
+ self.main_loop = gobject.MainLoop()
+ self.bus = dbus.SystemBus()
+ mandos_dbus_objc = self.bus.get_object(
+ self.busname, u"/", follow_name_owner_changes=True)
+ self.mandos_serv = dbus.Interface(mandos_dbus_objc,
+ dbus_interface
+ = server_interface)
+ try:
+ mandos_clients = (self.mandos_serv
+ .GetAllClientsWithProperties())
+ except dbus.exceptions.DBusException:
+ mandos_clients = dbus.Dictionary()
+
+ (self.mandos_serv
+ .connect_to_signal(u"ClientRemoved",
+ self.find_and_remove_client,
+ dbus_interface=server_interface,
+ byte_arrays=True))
+ (self.mandos_serv
+ .connect_to_signal(u"ClientAdded",
+ self.add_new_client,
+ dbus_interface=server_interface,
+ byte_arrays=True))
+ (self.mandos_serv
+ .connect_to_signal(u"ClientNotFound",
+ self.client_not_found,
+ dbus_interface=server_interface,
+ byte_arrays=True))
+ for path, client in mandos_clients.iteritems():
+ client_proxy_object = self.bus.get_object(self.busname,
+ path)
+ self.add_client(MandosClientWidget(server_proxy_object
+ =self.mandos_serv,
+ proxy_object
+ =client_proxy_object,
+ properties=client,
+ update_hook
+ =self.refresh,
+ delete_hook
+ =self.remove_client,
+ logger
+ =self.log_message),
+ path=path)
+
+ def client_not_found(self, fingerprint, address):
+ self.log_message((u"Client with address %s and fingerprint %s"
+ u" could not be found" % (address,
+ fingerprint)))
+
+ def rebuild(self):
+ """This rebuilds the User Interface.
+ Call this when the widget layout needs to change"""
+ self.uilist = []
+ #self.uilist.append(urwid.ListBox(self.clients))
+ self.uilist.append(urwid.Frame(ConstrainedListBox(self.
+ clients),
+ #header=urwid.Divider(),
+ header=None,
+ footer=
+ urwid.Divider(div_char=
+ self.divider)))
+ if self.log_visible:
+ self.uilist.append(self.logbox)
+ pass
+ self.topwidget = urwid.Pile(self.uilist)
+
+ def log_message(self, message):
+ timestamp = datetime.datetime.now().isoformat()
+ self.log_message_raw(timestamp + u": " + 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=u"above")
+ self.refresh()
+
+ def toggle_log_display(self):
+ """Toggle visibility of the log buffer."""
+ self.log_visible = not self.log_visible
+ self.rebuild()
+ #self.log_message(u"Log visibility changed to: "
+ # + unicode(self.log_visible))
+
+ def change_log_display(self):
+ """Change type of log display.
+ Currently, this toggles wrapping of text lines."""
+ if self.log_wrap == u"clip":
+ self.log_wrap = u"any"
+ else:
+ self.log_wrap = u"clip"
+ for textwidget in self.log:
+ textwidget.set_wrap_mode(self.log_wrap)
+ #self.log_message(u"Wrap mode: " + self.log_wrap)
+
+ def find_and_remove_client(self, path, name):
+ """Find an client from its object path and remove it.
+
+ This is connected to the ClientRemoved signal from the
+ Mandos server object."""
+ try:
+ client = self.clients_dict[path]
+ except KeyError:
+ # not found?
+ return
+ self.remove_client(client, path)
+
+ def add_new_client(self, path):
+ client_proxy_object = self.bus.get_object(self.busname, path)
+ self.add_client(MandosClientWidget(server_proxy_object
+ =self.mandos_serv,
+ proxy_object
+ =client_proxy_object,
+ update_hook
+ =self.refresh,
+ delete_hook
+ =self.remove_client,
+ logger
+ =self.log_message),
+ 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(None, lambda c: c.properties[u"Name"])
+ self.refresh()
+
+ def remove_client(self, client, path=None):
+ self.clients.remove(client)
+ if path is None:
+ path = client.proxy.object_path
+ del self.clients_dict[path]
+ if not self.clients_dict:
+ # Work around bug in Urwid 0.9.8.3 - if a SimpleListWalker
+ # is completely emptied, we need to recreate it.
+ self.clients = urwid.SimpleListWalker([])
+ self.rebuild()
+ self.refresh()
+
+ def refresh(self):
+ """Redraw the screen"""
+ canvas = self.topwidget.render(self.size, focus=True)
+ self.screen.draw_screen(self.size, canvas)
+
+ def run(self):
+ """Start the main loop and exit when it's done."""
+ self.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 = { u"ctrl n": u"down", # Emacs
+ u"ctrl p": u"up", # Emacs
+ u"ctrl v": u"page down", # Emacs
+ u"meta v": u"page up", # Emacs
+ u" ": u"page down", # less
+ u"f": u"page down", # less
+ u"b": u"page up", # less
+ u"j": u"down", # vi
+ u"k": u"up", # vi
+ }
+ for key in keys:
+ try:
+ key = translations[key]
+ except KeyError: # :-)
+ pass
+
+ if key == u"q" or key == u"Q":
+ self.stop()
+ break
+ elif key == u"window resize":
+ self.size = self.screen.get_cols_rows()
+ self.refresh()
+ elif key == u"\f": # Ctrl-L
+ self.refresh()
+ elif key == u"l" or key == u"D":
+ self.toggle_log_display()
+ self.refresh()
+ elif key == u"w" or key == u"i":
+ self.change_log_display()
+ self.refresh()
+ elif key == u"?" or key == u"f1" or key == u"esc":
+ if not self.log_visible:
+ self.log_visible = True
+ self.rebuild()
+ self.log_message_raw((u"bold",
+ u" ".
+ join((u"q: Quit",
+ u"?: Help",
+ u"l: Log window toggle",
+ u"TAB: Switch window",
+ u"w: Wrap (log)"))))
+ self.log_message_raw((u"bold",
+ u" "
+ .join((u"Clients:",
+ u"+: Enable",
+ u"-: Disable",
+ u"r: Remove",
+ u"s: Start new checker",
+ u"S: Stop checker",
+ u"C: Checker OK",
+ u"a: Approve",
+ u"d: Deny"))))
+ self.refresh()
+ elif key == u"tab":
+ if self.topwidget.get_focus() is self.logbox:
+ self.topwidget.set_focus(0)
+ else:
+ self.topwidget.set_focus(self.logbox)
+ self.refresh()
+ #elif (key == u"end" or key == u"meta >" or key == u"G"
+ # or key == u">"):
+ # pass # xxx end-of-buffer
+ #elif (key == u"home" or key == u"meta <" or key == u"g"
+ # or key == u"<"):
+ # pass # xxx beginning-of-buffer
+ #elif key == u"ctrl e" or key == u"$":
+ # pass # xxx move-end-of-line
+ #elif key == u"ctrl a" or key == u"^":
+ # pass # xxx move-beginning-of-line
+ #elif key == u"ctrl b" or key == u"meta (" or key == u"h":
+ # pass # xxx left
+ #elif key == u"ctrl f" or key == u"meta )" or key == u"l":
+ # pass # xxx right
+ #elif key == u"a":
+ # pass # scroll up log
+ #elif key == u"z":
+ # pass # scroll down log
+ elif self.topwidget.selectable():
+ self.topwidget.keypress(self.size, key)
+ self.refresh()
+ return True
+
+ui = UserInterface()
+try:
+ ui.run()
+except KeyboardInterrupt:
+ ui.screen.stop()
+except Exception, e:
+ ui.log_message(unicode(e))
+ ui.screen.stop()
+ raise
=== added file 'mandos-monitor.xml'
--- mandos-monitor.xml 1970-01-01 00:00:00 +0000
+++ mandos-monitor.xml 2010-09-14 18:22:03 +0000
@@ -0,0 +1,233 @@
+
+
+
+
+%common;
+]>
+
+
+
+ Mandos Manual
+
+ Mandos
+ &version;
+ &TIMESTAMP;
+
+
+ Björn
+ Påhlsson
+
+ belorn@fukt.bsnet.se
+
+
+
+ Teddy
+ Hogeborn
+
+ teddy@fukt.bsnet.se
+
+
+
+
+ 2010
+ 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
+
+ mandos
+ 8,
+ mandos-ctl
+ 8
+
+
+
+
+
+
+
+
+
=== modified file 'mandos-options.xml'
--- mandos-options.xml 2008-08-19 23:44:17 +0000
+++ mandos-options.xml 2009-02-13 05:38:21 +0000
@@ -1,10 +1,12 @@
-
@@ -12,22 +14,24 @@
If this is specified, the server will only announce the service
- and listen to requests on network interface
- IF. Default is to use all available
- interfaces. Note: a failure to bind to the
- specified interface is not considered critical, and the server
- does not exit.
+ and listen to requests on the specified network interface.
+ Default is to use all available interfaces. Note: a failure to bind to the specified
+ interface is not considered critical, and the server will not
+ exit, but instead continue normally.
- If this option is used, the server will only listen to a specific
- 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. Also, this must currently be an IPv6
- address; an IPv4 address can only be specified using the
- ::FFFF:192.0.2.3
format.
+ If this option is used, the server will only listen to the
+ specified IPv6 address. If a link-local address is specified, an
+ interface should be set, since a link-local address is only valid
+ on a single interface. By default, the server will listen to all
+ available addresses. If set, this must normally be an IPv6
+ address; an IPv4 address can only be specified using IPv4-mapped
+ IPv6 address syntax: ::FFFF:192.0.2.3
. (Only if IPv6 usage is
+ disabled (see below) must this be an IPv4
+ address.)
@@ -38,29 +42,48 @@
If the server is run in debug mode, it will run in the foreground
- and print a lot of debugging information. The default is
- not to run in debug mode.
+ and print a lot of debugging information. The default is to
+ not run in debug mode.
- GnuTLS priority string for the TLS handshake with the clients.
- The default is
- SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP
.
- See gnutls_priority_init
- 3 for the
- syntax. Warning: changing this may make the
- TLS handshake fail, making communication with clients impossible.
+ GnuTLS priority string for the TLS handshake.
+ The default is SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP
. See
+ gnutls_priority_init
+ 3 for the syntax.
+ Warning: changing this may make the
+ TLS handshake fail, making server-client
+ communication impossible.
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.
+
+
=== modified file 'mandos.conf'
--- mandos.conf 2008-08-18 23:55:28 +0000
+++ mandos.conf 2009-02-13 05:38:21 +0000
@@ -36,3 +36,9 @@
# 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
=== modified file 'mandos.conf.xml'
--- mandos.conf.xml 2008-08-19 23:44:17 +0000
+++ mandos.conf.xml 2009-02-25 01:14:29 +0000
@@ -1,18 +1,20 @@
-
+
/etc/mandos/mandos.conf">
-
+
+
+%common;
]>
- &CONFNAME;
+ Mandos Manual
- &CONFNAME;
- &VERSION;
+ Mandos
+ &version;
+ &TIMESTAMP;
Björn
@@ -31,34 +33,13 @@
2008
+ 2009
Teddy Hogeborn
Björn Påhlsson
-
-
- This manual page is free software: you can redistribute it
- and/or modify it under the terms of the GNU General Public
- License as published by the Free Software Foundation,
- either version 3 of the License, or (at your option) any
- later version.
-
-
-
- This manual page is distributed in the hope that it will
- be useful, but WITHOUT ANY WARRANTY; without even the
- implied warranty of MERCHANTABILITY or FITNESS FOR A
- PARTICULAR PURPOSE. See the GNU General Public License
- for more details.
-
-
-
- You should have received a copy of the GNU General Public
- License along with this program; If not, see
- .
-
-
+
-
+
&CONFNAME;
5
@@ -70,100 +51,108 @@
Configuration file for the Mandos server
-
+
-
- &CONFPATH;
-
+ &CONFPATH;
-
+
DESCRIPTION
The file &CONFPATH; is a simple configuration file for
mandos
8, and is read by it at
- startup. The configuration file starts with
- [DEFAULT]
on a line by itself,
- followed by any number of
- option=value
- entries, with continuations in the style of RFC 822.
- option:
- value
is also accepted. Note
- that leading whitespace is removed from values. Lines beginning
- with #
or ;
are ignored and may be
- used to provide comments.
+ startup. The configuration file starts with [DEFAULT]
on a line by itself, followed by
+ any number of option=value
entries,
+ with continuations in the style of RFC 822. option: value
is also accepted. Note that
+ leading whitespace is removed from values. Lines beginning with
+ #
or ;
are ignored and may be used
+ to provide comments.
-
+
OPTIONS
- interface
+
- interface = IF
-
-
+
- address
+
- address = ADDRESS
-
-
+
- port
+
- port = PORT
-
-
+
- debug
-
- debug = {
+ >false | off }
+
-
+
- priority
+
- priority = PRIORITY
-
-
+
- servicename
+
- servicename = NAME
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -179,7 +168,7 @@
The [DEFAULT] is necessary because the Python
built-in module ConfigParser
- requres it.
+ requires it.
@@ -201,12 +190,77 @@
[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
+
+
+ SEE ALSO
+
+ gnutls_priority_init3,
+ mandos
+ 8,
+ mandos-clients.conf
+ 5
+
+
+
+
+
+ RFC 4291: IP Version 6 Addressing
+ Architecture
+
+
+
+
+ Section 2.2: Text Representation of
+ Addresses
+
+
+
+ Section 2.5.5.2: IPv4-Mapped IPv6
+ Address
+
+
+
+ Section 2.5.6, Link-Local IPv6 Unicast
+ Addresses
+
+
+ The clients use IPv6 link-local addresses, which are
+ immediately usable since a link-local addresses is
+ automatically assigned to a network interface when it
+ is brought up.
+
+
+
+
+
+
+
+
+ Zeroconf
+
+
+
+ Zeroconf is the network protocol standard used by clients
+ for finding the Mandos server on the local network.
+
+
+
+
+
+
+
+
+
+
=== added file 'mandos.lsm'
--- mandos.lsm 1970-01-01 00:00:00 +0000
+++ mandos.lsm 2010-09-14 18:22:03 +0000
@@ -0,0 +1,22 @@
+Begin4
+Title: Mandos
+Version: 1.0.14
+Entered-date: 2010-09-14
+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@fukt.bsnet.se (Teddy Hogeborn),
+ belorn@fukt.bsnet.se (Björn Påhlsson)
+Maintained-by: teddy@fukt.bsnet.se (Teddy Hogeborn),
+ belorn@fukt.bsnet.se (Björn Påhlsson)
+Primary-site: http://www.fukt.bsnet.se/mandos
+ 103K mandos_1.0.14.orig.tar.gz
+Alternate-site: ftp://ftp.fukt.bsnet.se/pub/mandos
+ 103K mandos_1.0.14.orig.tar.gz
+Platforms: Requires GCC, GNU libC, Avahi, GnuPG, Python 2.5, and
+various other libraries. While made for Debian GNU/Linux, it is
+probably portable to other distributions, but not other Unixes.
+Copying-policy: GNU General Public License version 3.0 or later
+End
=== modified file 'mandos.xml'
--- mandos.xml 2008-08-19 23:44:17 +0000
+++ mandos.xml 2010-09-26 18:32:58 +0000
@@ -1,16 +1,19 @@
+
+
+%common;
]>
-
- &COMMANDNAME;
+
+ Mandos Manual
- &COMMANDNAME;
- &VERSION;
+ Mandos
+ &version;
+ &TIMESTAMP;
Björn
@@ -29,34 +32,14 @@
2008
+ 2009
+ 2010
Teddy Hogeborn
Björn Påhlsson
-
-
- This manual page is free software: you can redistribute it
- and/or modify it under the terms of the GNU General Public
- License as published by the Free Software Foundation,
- either version 3 of the License, or (at your option) any
- later version.
-
-
-
- This manual page is distributed in the hope that it will
- be useful, but WITHOUT ANY WARRANTY; without even the
- implied warranty of MERCHANTABILITY or FITNESS FOR A
- PARTICULAR PURPOSE. See the GNU General Public License
- for more details.
-
-
-
- You should have received a copy of the GNU General Public
- License along with this program; If not, see
- .
-
-
+
-
+
&COMMANDNAME;
8
@@ -65,48 +48,66 @@
&COMMANDNAME;
- Sends encrypted passwords to authenticated Mandos clients
+ Gives encrypted passwords to authenticated Mandos clients
-
+
&COMMANDNAME;
- --interfaceIF
- --addressADDRESS
- --portPORT
- --priorityPRIORITY
- --servicenameNAME
- --configdirDIRECTORY
- --debug
-
-
- &COMMANDNAME;
- -iIF
- -aADDRESS
- -pPORT
- --priorityPRIORITY
- --servicenameNAME
- --configdirDIRECTORY
- --debug
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
&COMMANDNAME;
- -h
- --help
+
+
&COMMANDNAME;
- --version
+
&COMMANDNAME;
- --check
+
-
+
DESCRIPTION
@@ -121,60 +122,63 @@
Any authenticated client is then given the stored pre-encrypted
password for that specific client.
-
PURPOSE
-
The purpose of this is to enable remote and unattended
rebooting of client host computer with an
encrypted root file system. See for details.
-
OPTIONS
-
- -h, --help
+
+
Show a help message and exit
-
+
- -i, --interface
- IF
+
+ NAME
+
+ NAME
-
+
- -a, --address
- ADDRESS
+
+
-
+
- -p, --port
- PORT
+
+
-
+
- --check
+
Run the server’s self-tests. This includes any unit
@@ -182,34 +186,34 @@
-
+
- --debug
+
-
+
- --priority
- PRIORITY
+
-
+
- --servicename NAME
-
+
-
+
- --configdir DIR
-
+
Directory to search for configuration files. Default is
@@ -221,28 +225,45 @@
-
+
- --version
+
Prints the program version and exit.
+
+
+
+
+
+
+ See also .
+
+
+
+
+
+
+
+
+
+
-
+
OVERVIEW
This program is the server part. It is a normal server program
and will run in a normal system environment, not in an initial
- RAM disk environment.
+ RAM disk environment.
-
+
NETWORK PROTOCOL
@@ -300,21 +321,44 @@
-
+
CHECKING
The server will, by default, continually check that the clients
are still up. If a client has not been confirmed as being up
for some time, the client is assumed to be compromised and is no
- longer eligible to receive the encrypted password. The timeout,
+ longer eligible to receive the encrypted password. (Manual
+ intervention is required to re-enable a client.) The timeout,
checker program, and interval between checks can be configured
both globally and per client; see
mandos-clients.conf
- 5.
-
-
-
+ 5. A client successfully
+ receiving its password will also be treated as a successful
+ checker run.
+
+
+
+
+ APPROVAL
+
+ The server can be configured to require manual approval for a
+ client before it is sent its secret. The delay to wait for such
+ approval and the default action (approve or deny) can be
+ configured both globally and per client; see
+ mandos-clients.conf
+ 5. By default all clients
+ will be approved immediately without delay.
+
+
+ This can be used to deny a client its secret if not manually
+ approved within a specified time. It can also be used to make
+ the server delay before giving a client its secret, allowing
+ optional manual denying of this specific client.
+
+
+
+
LOGGING
@@ -324,7 +368,17 @@
and also show them on the console.
-
+
+
+ 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
@@ -332,12 +386,12 @@
critical error is encountered.
-
+
ENVIRONMENT
- PATH
+ PATH
To start the configured checker (see
-
-
+
+
FILES
Use the option to change where
@@ -382,11 +436,11 @@
- /var/run/mandos/mandos.pid
+ /var/run/mandos.pid
- The file containing the process id of
- &COMMANDNAME;.
+ The file containing the process id of the
+ &COMMANDNAME; process started last.
@@ -420,15 +474,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.
+ Currently, if a client is disabled due to having timed out, the
+ server does not record this fact onto permanent storage. This
+ has some security implications, see .
There is no fine-grained control over logging and debug output.
@@ -437,7 +485,11 @@
Debug mode is conflated with running in the foreground.
- The console log messages does not show a timestamp.
+ The console log messages do not show a time stamp.
+
+
+ This server does not check the expire time of clients’ OpenPGP
+ keys.
@@ -448,7 +500,7 @@
Normal invocation needs no options:
- mandos
+ &COMMANDNAME;
@@ -461,7 +513,7 @@
-mandos --debug --configdir ~/mandos --servicename Test
+&COMMANDNAME; --debug --configdir ~/mandos --servicename Test
@@ -473,24 +525,24 @@
-mandos --interface eth7 --address fe80::aede:48ff:fe71:f6f2
+&COMMANDNAME; --interface eth7 --address fe80::aede:48ff:fe71:f6f2
-
+
SECURITY
-
+
SERVER
Running this &COMMANDNAME; server program
should not in itself present any security risk to the host
- computer running it. The program does not need any special
- privileges to run, and is designed to run as a non-root user.
+ computer running it. The program switches to a non-root user
+ soon after startup.
-
+
CLIENTS
The server only gives out its stored data to clients which
@@ -503,7 +555,7 @@
mandos-clients.conf
5)
must be made non-readable by anyone
- except the user running the server.
+ except the user starting the server (usually root).
As detailed in , the status of all
@@ -512,49 +564,43 @@
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.
+ by the server which would therefore disable the client. But
+ if the server was ever restarted, it would re-read its client
+ list from its configuration file and again regard all clients
+ therein as enabled, and hence eligible to receive their
+ passwords. Therefore, be careful when restarting servers if
+ it is suspected that a client has, in fact, been compromised
+ by parties who may now be running a fake Mandos client with
+ the keys from the non-encrypted initial RAM
+ image of the client host. What should be done in that case
+ (if restarting the server program really is necessary) is to
+ stop the server program, edit the configuration file to omit
+ any suspect clients, and restart the server program.
For more details on client-side security, see
- password-request
+ mandos-client
8mandos.
-
+
SEE ALSO
+
+
+ mandos-clients.conf
+ 5,
+ mandos.conf
+ 5,
+ mandos-client
+ 8mandos,
+ sh1
+
+
-
- password-request
- 8mandos
-
-
-
-
- This is the actual program which talks to this server.
- Note that it is normally not invoked directly, and is only
- run in the initial RAM disk environment, and not on a
- fully started system.
-
-
-
-
-
Zeroconf
@@ -577,8 +623,8 @@
- GnuTLS
+ GnuTLS
@@ -590,23 +636,40 @@
- RFC 4291: IP Version 6 Addressing
- Architecture, section 2.5.6, Link-Local IPv6
- Unicast Addresses
+ RFC 4291: IP Version 6 Addressing
+ Architecture
-
- The clients use IPv6 link-local addresses, which are
- immediately usable since a link-local addresses is
- automatically assigned to a network interfaces when it is
- brought up.
-
+
+
+ Section 2.2: Text Representation of
+ Addresses
+
+
+
+ Section 2.5.5.2: IPv4-Mapped IPv6
+ Address
+
+
+
+ Section 2.5.6, Link-Local IPv6 Unicast
+ Addresses
+
+
+ The clients use IPv6 link-local addresses, which are
+ immediately usable since a link-local addresses is
+ automatically assigned to a network interfaces when it
+ is brought up.
+
+
+
+
- RFC 4346: The Transport Layer Security
- (TLS) Protocol Version 1.1
+ RFC 4346: The Transport Layer Security (TLS)
+ Protocol Version 1.1
@@ -616,8 +679,7 @@
- RFC 4880: OpenPGP Message
- Format
+ RFC 4880: OpenPGP Message Format
@@ -627,8 +689,8 @@
- RFC 5081: Using OpenPGP Keys for
- Transport Layer Security
+ RFC 5081: Using OpenPGP Keys for Transport Layer
+ Security
@@ -640,3 +702,8 @@
+
+
+
+
+
=== modified file 'overview.xml'
--- overview.xml 2008-08-19 13:25:14 +0000
+++ overview.xml 2008-09-13 15:36:18 +0000
@@ -1,15 +1,17 @@
-
- This is part of the Mandos system for allowing host computers to
- have encrypted root file systems and also be capable of remote and
- unattended reboots. The host computers run a small client program
- in the initial RAM disk environment which will communicate with a
- server over a network. The clients are identified by the server
- using a OpenPGP key; each client has one unique to it. The server
- sends the clients an encrypted password. The encrypted password is
- decrypted by the clients using the same OpenPGP key, and the
- password is then used to unlock the root file system, whereupon the
- host computers can continue booting normally.
+ This is part of the Mandos system for allowing computers to have
+ encrypted root file systems and at the same time be capable of
+ remote and/or unattended reboots. The computers run a small client
+ program in the initial RAM disk environment which
+ will communicate with a server over a network. All network
+ communication is encrypted using TLS. The
+ clients are identified by the server using an OpenPGP key; each
+ client has one unique to it. The server sends the clients an
+ encrypted password. The encrypted password is decrypted by the
+ clients using the same OpenPGP key, and the password is then used to
+ unlock the root file system, whereupon the computers can continue
+ booting normally.
=== modified file 'plugin-runner.c'
--- plugin-runner.c 2008-08-16 18:15:07 +0000
+++ plugin-runner.c 2010-09-09 22:06:10 +0000
@@ -1,8 +1,9 @@
-/* -*- coding: utf-8 -*- */
+/* -*- coding: utf-8; mode: c; mode: orgtbl -*- */
/*
* Mandos plugin runner - Run Mandos plugins
*
- * Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson
+ * Copyright © 2008,2009 Teddy Hogeborn
+ * Copyright © 2008,2009 Björn Påhlsson
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -22,14 +23,14 @@
*/
#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, popen(), fileno(),
- fprintf(), stderr, STDOUT_FILENO */
-#include /* DIR, opendir(), stat(), struct
+#include /* fileno(), fprintf(),
+ stderr, STDOUT_FILENO */
+#include /* DIR, fdopendir(), stat(), struct
stat, waitpid(), WIFEXITED(),
WEXITSTATUS(), wait(), pid_t,
uid_t, gid_t, getuid(), getgid(),
@@ -37,52 +38,49 @@
#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(),
F_GETFD, F_SETFD, FD_CLOEXEC,
access(), pipe(), fork(), close()
- dup2, STDOUT_FILENO, _exit(),
+ dup2(), STDOUT_FILENO, _exit(),
execv(), write(), read(),
close() */
#include /* fcntl(), F_GETFD, F_SETFD,
FD_CLOEXEC */
-#include /* strsep, strlen(), asprintf() */
+#include /* strsep, strlen(), asprintf(),
+ strsignal(), strcmp(), strncmp() */
#include /* errno */
#include /* struct argp_option, struct
argp_state, struct argp,
argp_parse(), ARGP_ERR_UNKNOWN,
- ARGP_KEY_END, ARGP_KEY_ARG, error_t */
+ ARGP_KEY_END, ARGP_KEY_ARG,
+ error_t */
#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 ARGFILE "/conf/conf.d/mandos/plugin-runner.conf"
-
-const char *argp_program_version = "plugin-runner 1.0";
+
+#define PDIR "/lib/mandos/plugins.d"
+#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
+
+const char *argp_program_version = "plugin-runner " VERSION;
const char *argp_program_bug_address = "";
-struct process;
-
-typedef struct process{
- pid_t pid;
- int fd;
- char *buffer;
- size_t buffer_size;
- size_t buffer_length;
- bool eof;
- bool completed;
- int status;
- struct process *next;
-} process;
-
typedef struct plugin{
char *name; /* can be NULL or any plugin name */
char **argv;
@@ -90,49 +88,85 @@
char **environ;
int envc;
bool disabled;
+
+ /* Variables used for running processes*/
+ pid_t pid;
+ int fd;
+ char *buffer;
+ size_t buffer_size;
+ size_t buffer_length;
+ bool eof;
+ volatile sig_atomic_t completed;
+ int status;
struct plugin *next;
} plugin;
-static plugin *getplugin(char *name, plugin **plugin_list){
- for (plugin *p = *plugin_list; p != NULL; p = p->next){
- if ((p->name == name)
- or (p->name and name and (strcmp(p->name, name) == 0))){
+static plugin *plugin_list = NULL;
+
+/* Gets an existing plugin based on name,
+ or if none is found, creates a new one */
+static plugin *getplugin(char *name){
+ /* Check for existing plugin with that name */
+ for(plugin *p = plugin_list; p != NULL; p = p->next){
+ if((p->name == name)
+ or (p->name and name and (strcmp(p->name, name) == 0))){
return p;
}
}
/* Create a new plugin */
- plugin *new_plugin = malloc(sizeof(plugin));
- if (new_plugin == NULL){
- return NULL;
- }
- char *copy_name = strdup(name);
- if(copy_name == NULL){
- return NULL;
- }
-
- *new_plugin = (plugin) { .name = copy_name,
- .argc = 1,
- .envc = 0,
- .disabled = false,
- .next = *plugin_list };
-
- new_plugin->argv = malloc(sizeof(char *) * 2);
- if (new_plugin->argv == NULL){
+ plugin *new_plugin = NULL;
+ do {
+ new_plugin = malloc(sizeof(plugin));
+ } while(new_plugin == NULL and errno == EINTR);
+ if(new_plugin == NULL){
+ return NULL;
+ }
+ char *copy_name = NULL;
+ if(name != NULL){
+ do {
+ copy_name = strdup(name);
+ } while(copy_name == NULL and errno == EINTR);
+ if(copy_name == NULL){
+ int e = errno;
+ free(new_plugin);
+ errno = e;
+ return NULL;
+ }
+ }
+
+ *new_plugin = (plugin){ .name = copy_name,
+ .argc = 1,
+ .disabled = false,
+ .next = plugin_list };
+
+ do {
+ new_plugin->argv = malloc(sizeof(char *) * 2);
+ } while(new_plugin->argv == NULL and errno == EINTR);
+ if(new_plugin->argv == NULL){
+ int e = errno;
+ free(copy_name);
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;
+
/* Append the new plugin to the list */
- *plugin_list = new_plugin;
+ plugin_list = new_plugin;
return new_plugin;
}
@@ -140,14 +174,19 @@
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;
}
@@ -168,58 +207,87 @@
}
/* Add to a plugin's environment */
-static bool add_environment(plugin *p, const char *def){
+static bool add_environment(plugin *p, const char *def, bool replace){
if(p == NULL){
return false;
}
+ /* namelen = length of name of environment variable */
+ size_t namelen = (size_t)(strchrnul(def, '=') - def);
+ /* Search for this environment variable */
+ for(char **e = p->environ; *e != NULL; e++){
+ if(strncmp(*e, def, namelen + 1) == 0){
+ /* It already exists */
+ if(replace){
+ char *new;
+ do {
+ new = realloc(*e, strlen(def) + 1);
+ } while(new == NULL and errno == EINTR);
+ if(new == NULL){
+ return false;
+ }
+ *e = new;
+ strcpy(*e, def);
+ }
+ return true;
+ }
+ }
return add_to_char_array(def, &(p->environ), &(p->envc));
}
-
/*
* Based on the example in the GNU LibC manual chapter 13.13 "File
* Descriptor Flags".
- * *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);
+static int set_cloexec_flag(int fd){
+ int ret = (int)TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD, 0));
/* If reading the flags failed, return error indication now. */
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));
}
-process *process_list = NULL;
-/* Mark a process as completed when it exits, and save its exit
+/* Mark processes as completed when they exit, and save their exit
status. */
-void handle_sigchld(__attribute__((unused)) int sig){
- process *proc = process_list;
- int status;
- pid_t pid = wait(&status);
- if(pid == -1){
- perror("wait");
- return;
- }
- while(proc != NULL and proc->pid != pid){
- proc = proc->next;
- }
- if(proc == NULL){
- /* Process not found in process list */
- return;
- }
- proc->status = status;
- proc->completed = true;
+static void handle_sigchld(__attribute__((unused)) int sig){
+ int old_errno = errno;
+ while(true){
+ plugin *proc = plugin_list;
+ int status;
+ pid_t pid = waitpid(-1, &status, WNOHANG);
+ if(pid == 0){
+ /* Only still running child processes */
+ break;
+ }
+ if(pid == -1){
+ if(errno == ECHILD){
+ /* No child processes */
+ break;
+ }
+ error(0, errno, "waitpid");
+ }
+
+ /* A child exited, find it in process_list */
+ while(proc != NULL and proc->pid != pid){
+ proc = proc->next;
+ }
+ if(proc == NULL){
+ /* Process not found in process list */
+ continue;
+ }
+ proc->status = status;
+ proc->completed = 1;
+ }
+ errno = old_errno;
}
-bool print_out_password(const char *buffer, size_t length){
+/* Prints out a password to stdout */
+static bool print_out_password(const char *buffer, size_t length){
ssize_t ret;
- if(length>0 and buffer[length-1] == '\n'){
- length--;
- }
for(size_t written = 0; written < length; written += (size_t)ret){
ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buffer + written,
length - written));
@@ -230,46 +298,45 @@
return true;
}
-char **add_to_argv(char **argv, int *argc, char *arg){
- if (argv == NULL){
- *argc = 1;
- argv = malloc(sizeof(char*) * 2);
- if(argv == NULL){
- return NULL;
+/* Removes and free a plugin from the plugin list */
+static void free_plugin(plugin *plugin_node){
+
+ for(char **arg = plugin_node->argv; *arg != NULL; arg++){
+ free(*arg);
+ }
+ free(plugin_node->argv);
+ for(char **env = plugin_node->environ; *env != NULL; env++){
+ free(*env);
+ }
+ free(plugin_node->environ);
+ free(plugin_node->buffer);
+
+ /* Removes the plugin from the singly-linked list */
+ if(plugin_node == plugin_list){
+ /* First one - simple */
+ plugin_list = plugin_list->next;
+ } else {
+ /* Second one or later */
+ for(plugin *p = plugin_list; p != NULL; p = p->next){
+ if(p->next == plugin_node){
+ p->next = plugin_node->next;
+ break;
+ }
}
- argv[0] = NULL; /* Will be set to argv[0] in main before parsing */
- argv[1] = NULL;
- }
- *argc += 1;
- argv = realloc(argv, sizeof(char *)
- * ((unsigned int) *argc + 1));
- if(argv == NULL){
- return NULL;
- }
- argv[*argc-1] = arg;
- argv[*argc] = NULL;
- return argv;
+ }
+
+ free(plugin_node);
}
-static void free_plugin_list(plugin *plugin_list){
- for(plugin *next = plugin_list; plugin_list != NULL; plugin_list = next){
- next = plugin_list->next;
- free(plugin_list->name);
- for(char **arg = plugin_list->argv; *arg != NULL; arg++){
- free(*arg);
- }
- free(plugin_list->argv);
- for(char **env = plugin_list->environ; *env != NULL; env++){
- free(*env);
- }
- free(plugin_list->environ);
- free(plugin_list);
- }
+static void free_plugin_list(void){
+ while(plugin_list != NULL){
+ free_plugin(plugin_list);
+ }
}
int main(int argc, char *argv[]){
- const char *plugindir = "/lib/mandos/plugins.d";
- const char *argfile = ARGFILE;
+ char *plugindir = NULL;
+ char *argfile = NULL;
FILE *conffp;
size_t d_name_len;
DIR *dir = NULL;
@@ -277,6 +344,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;
@@ -291,14 +359,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;
}
@@ -307,168 +375,274 @@
{ .name = "global-options", .key = 'g',
.arg = "OPTION[,OPTION[,...]]",
.doc = "Options passed to all plugins" },
- { .name = "global-envs", .key = 'e',
+ { .name = "global-env", .key = 'G',
.arg = "VAR=value",
.doc = "Environment variable passed to all plugins" },
{ .name = "options-for", .key = 'o',
.arg = "PLUGIN:OPTION[,OPTION[,...]]",
.doc = "Options passed only to specified plugin" },
- { .name = "envs-for", .key = 'f',
+ { .name = "env-for", .key = 'E',
.arg = "PLUGIN:ENV=value",
.doc = "Environment variable passed to specified plugin" },
{ .name = "disable", .key = 'd',
.arg = "PLUGIN",
.doc = "Disable a specific plugin", .group = 1 },
+ { .name = "enable", .key = 'e',
+ .arg = "PLUGIN",
+ .doc = "Enable a specific plugin", .group = 1 },
{ .name = "plugin-dir", .key = 128,
.arg = "DIRECTORY",
.doc = "Specify a different plugin directory", .group = 2 },
- { .name = "userid", .key = 129,
- .arg = "ID", .flags = 0,
- .doc = "User ID the plugins will run as", .group = 2 },
- { .name = "groupid", .key = 130,
- .arg = "ID", .flags = 0,
- .doc = "Group ID the plugins will run as", .group = 2 },
- { .name = "debug", .key = 131,
- .doc = "Debug mode", .group = 3 },
+ { .name = "config-file", .key = 129,
+ .arg = "FILE",
+ .doc = "Specify a different configuration file", .group = 2 },
+ { .name = "userid", .key = 130,
+ .arg = "ID", .flags = 0,
+ .doc = "User ID the plugins will run as", .group = 3 },
+ { .name = "groupid", .key = 131,
+ .arg = "ID", .flags = 0,
+ .doc = "Group ID the plugins will run as", .group = 3 },
+ { .name = "debug", .key = 132,
+ .doc = "Debug mode", .group = 4 },
+ /*
+ * These reproduce what we would get without ARGP_NO_HELP
+ */
+ { .name = "help", .key = '?',
+ .doc = "Give this help list", .group = -1 },
+ { .name = "usage", .key = -3,
+ .doc = "Give a short usage message", .group = -1 },
+ { .name = "version", .key = 'V',
+ .doc = "Print program version", .group = -1 },
{ .name = 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. */
- plugin **plugins = state->input;
- switch (key) {
- case 'g':
- if (arg != NULL){
- char *p;
- while((p = strsep(&arg, ",")) != NULL){
- if(p[0] == '\0'){
- continue;
- }
- if(not add_argument(getplugin(NULL, plugins), p)){
- perror("add_argument");
- return ARGP_ERR_UNKNOWN;
- }
- }
- }
- break;
- case 'e':
- if(arg == NULL){
- break;
- }
- {
- char *envdef = strdup(arg);
- if(envdef == NULL){
- break;
- }
- if(not add_environment(getplugin(NULL, plugins), envdef)){
- perror("add_environment");
- }
- }
- break;
- case 'o':
- if (arg != NULL){
- char *p_name = strsep(&arg, ":");
- if(p_name[0] == '\0'){
- break;
- }
- char *opt = strsep(&arg, ":");
- if(opt[0] == '\0'){
- break;
- }
- if(opt != NULL){
- char *p;
- while((p = strsep(&opt, ",")) != NULL){
- if(p[0] == '\0'){
- continue;
- }
- if(not add_argument(getplugin(p_name, plugins), p)){
- perror("add_argument");
- return ARGP_ERR_UNKNOWN;
- }
- }
- }
- }
- break;
- case 'f':
- if(arg == NULL){
- break;
- }
+ error_t parse_opt(int key, char *arg, struct argp_state *state){
+ errno = 0;
+ switch(key){
+ char *tmp;
+ intmax_t tmpmax;
+ case 'g': /* --global-options */
+ {
+ char *plugin_option;
+ while((plugin_option = strsep(&arg, ",")) != NULL){
+ if(not add_argument(getplugin(NULL), plugin_option)){
+ break;
+ }
+ }
+ }
+ break;
+ case 'G': /* --global-env */
+ add_environment(getplugin(NULL), arg, true);
+ break;
+ case 'o': /* --options-for */
+ {
+ char *option_list = strchr(arg, ':');
+ if(option_list == NULL){
+ argp_error(state, "No colon in \"%s\"", arg);
+ errno = EINVAL;
+ break;
+ }
+ *option_list = '\0';
+ option_list++;
+ if(arg[0] == '\0'){
+ argp_error(state, "Empty plugin name");
+ errno = EINVAL;
+ break;
+ }
+ char *option;
+ while((option = strsep(&option_list, ",")) != NULL){
+ if(not add_argument(getplugin(arg), option)){
+ break;
+ }
+ }
+ }
+ break;
+ case 'E': /* --env-for */
{
char *envdef = strchr(arg, ':');
if(envdef == NULL){
- break;
- }
- char *p_name = strndup(arg, (size_t) (envdef-arg));
- if(p_name == NULL){
- break;
- }
+ argp_error(state, "No colon in \"%s\"", arg);
+ errno = EINVAL;
+ break;
+ }
+ *envdef = '\0';
envdef++;
- if(not add_environment(getplugin(p_name, plugins), envdef)){
- perror("add_environment");
- }
- }
- break;
- case 'd':
- if (arg != NULL){
- plugin *p = getplugin(arg, plugins);
- if(p == NULL){
- return ARGP_ERR_UNKNOWN;
- }
- p->disabled = true;
- }
- break;
- case 128:
- plugindir = arg;
- break;
- case 129:
- uid = (uid_t)strtol(arg, NULL, 10);
- break;
- case 130:
- gid = (gid_t)strtol(arg, NULL, 10);
- break;
- case 131:
+ if(arg[0] == '\0'){
+ argp_error(state, "Empty plugin name");
+ errno = EINVAL;
+ break;
+ }
+ add_environment(getplugin(arg), envdef, true);
+ }
+ break;
+ case 'd': /* --disable */
+ {
+ plugin *p = getplugin(arg);
+ if(p != NULL){
+ p->disabled = true;
+ }
+ }
+ break;
+ case 'e': /* --enable */
+ {
+ plugin *p = getplugin(arg);
+ if(p != NULL){
+ p->disabled = false;
+ }
+ }
+ break;
+ case 128: /* --plugin-dir */
+ free(plugindir);
+ plugindir = strdup(arg);
+ break;
+ case 129: /* --config-file */
+ /* This is already done by parse_opt_config_file() */
+ break;
+ case 130: /* --userid */
+ tmpmax = strtoimax(arg, &tmp, 10);
+ if(errno != 0 or tmp == arg or *tmp != '\0'
+ or tmpmax != (uid_t)tmpmax){
+ argp_error(state, "Bad user ID number: \"%s\", using %"
+ PRIdMAX, arg, (intmax_t)uid);
+ break;
+ }
+ uid = (uid_t)tmpmax;
+ break;
+ case 131: /* --groupid */
+ tmpmax = strtoimax(arg, &tmp, 10);
+ if(errno != 0 or tmp == arg or *tmp != '\0'
+ or tmpmax != (gid_t)tmpmax){
+ argp_error(state, "Bad group ID number: \"%s\", using %"
+ PRIdMAX, arg, (intmax_t)gid);
+ break;
+ }
+ gid = (gid_t)tmpmax;
+ break;
+ case 132: /* --debug */
debug = true;
break;
- case ARGP_KEY_ARG:
- fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
- break;
- case ARGP_KEY_END:
- break;
- default:
- return ARGP_ERR_UNKNOWN;
- }
- return 0;
- }
-
- plugin *plugin_list = NULL;
-
- struct argp argp = { .options = options, .parser = parse_opt,
- .args_doc = "[+PLUS_SEPARATED_OPTIONS]",
+ /*
+ * These reproduce what we would get without ARGP_NO_HELP
+ */
+ case '?': /* --help */
+ state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
+ argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP);
+ case -3: /* --usage */
+ state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
+ argp_state_help(state, state->out_stream,
+ ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK);
+ case 'V': /* --version */
+ fprintf(state->out_stream, "%s\n", argp_program_version);
+ exit(EXIT_SUCCESS);
+ break;
+/*
+ * When adding more options before this line, remember to also add a
+ * "case" to the "parse_opt_config_file" function below.
+ */
+ case ARGP_KEY_ARG:
+ /* Cryptsetup always passes an argument, which is an empty
+ string if "none" was specified in /etc/crypttab. So if
+ argument was empty, we ignore it silently. */
+ if(arg[0] == '\0'){
+ break;
+ }
+ default:
+ return ARGP_ERR_UNKNOWN;
+ }
+ return errno; /* Set to 0 at start */
+ }
+
+ /* This option parser is the same as parse_opt() above, except it
+ ignores everything but the --config-file option. */
+ error_t parse_opt_config_file(int key, char *arg,
+ __attribute__((unused))
+ struct argp_state *state){
+ errno = 0;
+ switch(key){
+ case 'g': /* --global-options */
+ case 'G': /* --global-env */
+ case 'o': /* --options-for */
+ case 'E': /* --env-for */
+ case 'd': /* --disable */
+ case 'e': /* --enable */
+ case 128: /* --plugin-dir */
+ break;
+ case 129: /* --config-file */
+ free(argfile);
+ argfile = strdup(arg);
+ break;
+ case 130: /* --userid */
+ case 131: /* --groupid */
+ case 132: /* --debug */
+ case '?': /* --help */
+ case -3: /* --usage */
+ case 'V': /* --version */
+ case ARGP_KEY_ARG:
+ break;
+ default:
+ return ARGP_ERR_UNKNOWN;
+ }
+ return errno;
+ }
+
+ struct argp argp = { .options = options,
+ .parser = parse_opt_config_file,
+ .args_doc = "",
.doc = "Mandos plugin runner -- Run plugins" };
- ret = argp_parse (&argp, argc, argv, 0, 0, &plugin_list);
- if (ret == ARGP_ERR_UNKNOWN){
- fprintf(stderr, "Unknown error while parsing arguments\n");
- exitstatus = EXIT_FAILURE;
- goto fallback;
- }
-
- conffp = fopen(argfile, "r");
+ /* Parse using parse_opt_config_file() in order to get the custom
+ config file location, if any. */
+ ret = argp_parse(&argp, argc, argv,
+ ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP,
+ NULL, NULL);
+ switch(ret){
+ case 0:
+ break;
+ case ENOMEM:
+ default:
+ errno = ret;
+ error(0, errno, "argp_parse");
+ exitstatus = EX_OSERR;
+ goto fallback;
+ case EINVAL:
+ exitstatus = EX_USAGE;
+ goto fallback;
+ }
+
+ /* Reset to the normal argument parser */
+ argp.parser = parse_opt;
+
+ /* Open the configfile if available */
+ if(argfile == NULL){
+ conffp = fopen(AFILE, "r");
+ } else {
+ conffp = fopen(argfile, "r");
+ }
if(conffp != NULL){
char *org_line = NULL;
char *p, *arg, *new_arg, *line;
size_t size = 0;
- ssize_t sret;
const char whitespace_delims[] = " \r\t\f\v\n";
const char comment_delim[] = "#";
-
+
+ custom_argc = 1;
+ custom_argv = malloc(sizeof(char*) * 2);
+ if(custom_argv == NULL){
+ error(0, errno, "malloc");
+ exitstatus = EX_OSERR;
+ goto fallback;
+ }
+ custom_argv[0] = argv[0];
+ custom_argv[1] = NULL;
+
+ /* for each line in the config file, strip whitespace and ignore
+ commented text */
while(true){
sret = getline(&org_line, &size, conffp);
if(sret == -1){
break;
}
-
+
line = org_line;
arg = strsep(&line, comment_delim);
while((p = strsep(&arg, whitespace_delims)) != NULL){
@@ -476,35 +650,84 @@
continue;
}
new_arg = strdup(p);
- custom_argv = add_to_argv(custom_argv, &custom_argc, new_arg);
- if (custom_argv == NULL){
- perror("add_to_argv");
- exitstatus = EXIT_FAILURE;
- goto fallback;
- }
+ if(new_arg == NULL){
+ error(0, errno, "strdup");
+ exitstatus = EX_OSERR;
+ free(org_line);
+ goto fallback;
+ }
+
+ custom_argc += 1;
+ custom_argv = realloc(custom_argv, sizeof(char *)
+ * ((unsigned int) custom_argc + 1));
+ if(custom_argv == NULL){
+ error(0, errno, "realloc");
+ exitstatus = EX_OSERR;
+ free(org_line);
+ goto fallback;
+ }
+ custom_argv[custom_argc-1] = new_arg;
+ custom_argv[custom_argc] = NULL;
}
}
+ do {
+ ret = fclose(conffp);
+ } while(ret == EOF and errno == EINTR);
+ if(ret == EOF){
+ error(0, errno, "fclose");
+ exitstatus = EX_IOERR;
+ goto fallback;
+ }
free(org_line);
- } else{
+ } else {
/* Check for harmful errors and go to fallback. Other errors might
not affect opening plugins */
- if (errno == EMFILE or errno == ENFILE or errno == ENOMEM){
- 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 were any arguments from the configuration file, pass
+ them to parser as command line arguments */
if(custom_argv != NULL){
- custom_argv[0] = argv[0];
- ret = argp_parse (&argp, custom_argc, custom_argv, 0, 0, &plugin_list);
- if (ret == ARGP_ERR_UNKNOWN){
- fprintf(stderr, "Unknown error while parsing arguments\n");
- exitstatus = EXIT_FAILURE;
+ ret = argp_parse(&argp, custom_argc, custom_argv,
+ ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP,
+ NULL, NULL);
+ switch(ret){
+ case 0:
+ break;
+ case ENOMEM:
+ default:
+ errno = ret;
+ error(0, errno, "argp_parse");
+ exitstatus = EX_OSERR;
+ goto fallback;
+ case EINVAL:
+ exitstatus = EX_CONFIG;
goto fallback;
}
}
+ /* Parse actual command line arguments, to let them override the
+ config file */
+ ret = argp_parse(&argp, argc, argv,
+ ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP,
+ NULL, NULL);
+ switch(ret){
+ case 0:
+ break;
+ case ENOMEM:
+ default:
+ errno = ret;
+ error(0, errno, "argp_parse");
+ exitstatus = EX_OSERR;
+ goto fallback;
+ case EINVAL:
+ exitstatus = EX_USAGE;
+ goto fallback;
+ }
+
if(debug){
for(plugin *p = plugin_list; p != NULL; p=p->next){
fprintf(stderr, "Plugin: %s has %d arguments\n",
@@ -512,53 +735,82 @@
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 */
+ setgid(gid);
+ if(ret == -1){
+ error(0, errno, "setgid");
+ }
ret = setuid(uid);
- if (ret == -1){
- perror("setuid");
- }
-
- setgid(gid);
- if (ret == -1){
- perror("setgid");
- }
-
- dir = opendir(plugindir);
- if(dir == NULL){
- perror("Could not open plugin dir");
- exitstatus = EXIT_FAILURE;
- goto fallback;
- }
-
- /* Set the FD_CLOEXEC flag on the directory, if possible */
+ 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;
}
}
FD_ZERO(&rfds_all);
+ /* 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
+ /* 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;
@@ -566,7 +818,7 @@
d_name_len = strlen(dirst->d_name);
- // Ignore dotfiles, backup files and other junk
+ /* Ignore dotfiles, backup files and other junk */
{
bool bad_name = false;
@@ -574,6 +826,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);
@@ -587,15 +840,13 @@
break;
}
}
-
if(bad_name){
continue;
}
-
for(const char **suf = bad_suffixes; *suf != NULL; suf++){
size_t suf_len = strlen(*suf);
if((d_name_len >= suf_len)
- and (strcmp((dirst->d_name)+d_name_len-suf_len, *suf)
+ and (strcmp((dirst->d_name) + d_name_len-suf_len, *suf)
== 0)){
if(debug){
fprintf(stderr, "Ignoring plugin dir entry \"%s\""
@@ -610,22 +861,31 @@
continue;
}
}
-
+
char *filename;
- ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name);
+ if(plugindir == NULL){
+ ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, PDIR "/%s",
+ dirst->d_name));
+ } else {
+ ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, "%s/%s",
+ plugindir,
+ dirst->d_name));
+ }
if(ret < 0){
- perror("asprintf");
+ 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;
}
- if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
+ /* Ignore non-executable files */
+ 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);
@@ -633,9 +893,10 @@
free(filename);
continue;
}
- plugin *p = getplugin(dirst->d_name, &plugin_list);
+
+ plugin *p = getplugin(dirst->d_name);
if(p == NULL){
- perror("getplugin");
+ error(0, errno, "getplugin");
free(filename);
continue;
}
@@ -649,17 +910,17 @@
}
{
/* Add global arguments to argument list for this plugin */
- plugin *g = getplugin(NULL, &plugin_list);
+ plugin *g = getplugin(NULL);
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)){
- perror("add_environment");
+ if(not add_environment(p, *e, false)){
+ error(0, errno, "add_environment");
}
}
}
@@ -669,67 +930,68 @@
process, too. */
if(p->environ[0] != NULL){
for(char **e = environ; *e != NULL; e++){
- char *copy = strdup(*e);
- if(copy == NULL){
- perror("strdup");
- continue;
- }
- if(not add_environment(p, copy)){
- perror("add_environment");
+ if(not add_environment(p, *e, false)){
+ error(0, errno, "add_environment");
}
}
}
- int pipefd[2];
- ret = pipe(pipefd);
- if (ret == -1){
- perror("pipe");
- exitstatus = EXIT_FAILURE;
+ int pipefd[2];
+ 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();
+ /* Starting a new process to be watched */
+ 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);
+ 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){
@@ -739,224 +1001,258 @@
}
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 */
+ /* Parent process */
+ TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of
+ pipe */
free(filename);
- close(pipefd[1]); /* close unused write end of pipe */
- process *new_process = malloc(sizeof(process));
- if (new_process == NULL){
- perror("malloc");
- ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
+ plugin *new_plugin = getplugin(dirst->d_name);
+ if(new_plugin == NULL){
+ error(0, errno, "getplugin");
+ ret = (int)(TEMP_FAILURE_RETRY
+ (sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask,
+ NULL)));
if(ret < 0){
- perror("sigprocmask");
+ error(0, errno, "sigprocmask");
}
- exitstatus = EXIT_FAILURE;
+ exitstatus = EX_OSERR;
goto fallback;
}
- *new_process = (struct process){ .pid = pid,
- .fd = pipefd[0],
- .next = process_list };
- // List handling
- process_list = new_process;
+ new_plugin->pid = pid;
+ new_plugin->fd = pipefd[0];
+
/* 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_process->fd, &rfds_all);
+ FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
+ -Wconversion */
- if (maxfd < new_process->fd){
- maxfd = new_process->fd;
+ if(maxfd < new_plugin->fd){
+ maxfd = new_plugin->fd;
}
-
}
- free_plugin_list(plugin_list);
-
- closedir(dir);
+ TEMP_FAILURE_RETRY(closedir(dir));
dir = NULL;
-
- if (process_list == NULL){
- fprintf(stderr, "No plugin processes started. Incorrect plugin"
- " directory?\n");
- process_list = NULL;
+ free_plugin(getplugin(NULL));
+
+ for(plugin *p = plugin_list; p != NULL; p = p->next){
+ if(p->pid != 0){
+ break;
+ }
+ if(p->next == NULL){
+ fprintf(stderr, "No plugin processes started. Incorrect plugin"
+ " directory?\n");
+ free_plugin_list();
+ }
}
- while(process_list){
+
+ /* Main loop while running plugins exist */
+ 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(process *proc = process_list; proc ; proc = proc->next){
+ 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);
+ FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
+ -Wconversion */
+
/* 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;
}
- /* Delete this process entry from the list */
- if(process_list == proc){
- /* First one - simple */
- process_list = proc->next;
- } else {
- /* Second one or later */
- for(process *p = process_list; p != NULL; p = p->next){
- if(p->next == proc){
- p->next = proc->next;
- break;
- }
- }
- }
+
+ plugin *next_plugin = proc->next;
+ free_plugin(proc);
+ 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");
- }
- free(proc->buffer);
- free(proc);
- /* We deleted this process from the list, so we can't go
- proc->next. Therefore, start over from the beginning of
- the process list */
- break;
+ error(0, errno, "sigprocmask");
+ exitstatus = EX_OSERR;
+ goto fallback;
+ }
+
+ if(plugin_list == NULL){
+ break;
+ }
+
+ continue;
}
+
/* This process exited nicely, so print its buffer */
-
- bool bret = print_out_password(proc->buffer, proc->buffer_length);
+
+ bool bret = print_out_password(proc->buffer,
+ proc->buffer_length);
if(not bret){
- 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(proc->eof or not FD_ISSET(proc->fd, &rfds)){ /* Spurious
+ warning from
+ -Wconversion */
/* This process had nothing to say at this time */
+ proc = proc->next;
continue;
}
/* Before reading, make the process' data buffer large enough */
if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
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(process_list == NULL or exitstatus != EXIT_SUCCESS){
- /* Fallback if all plugins failed, none are found or an error occured */
+ 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;
fprintf(stderr, "Going to fallback mode using getpass(3)\n");
char *passwordbuffer = getpass("Password: ");
- bret = print_out_password(passwordbuffer, strlen(passwordbuffer));
+ size_t len = strlen(passwordbuffer);
+ /* Strip trailing newline */
+ if(len > 0 and passwordbuffer[len-1] == '\n'){
+ passwordbuffer[len-1] = '\0'; /* not strictly necessary */
+ len--;
+ }
+ bret = print_out_password(passwordbuffer, len);
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){
- for(char **arg = custom_argv; *arg != NULL; arg++){
+ for(char **arg = custom_argv+1; *arg != NULL; arg++){
free(*arg);
}
free(custom_argv);
}
- free_plugin_list(plugin_list);
if(dir != NULL){
closedir(dir);
}
- /* Free the process list and kill the processes */
- for(process *next; process_list != NULL; process_list = next){
- next = process_list->next;
- close(process_list->fd);
- ret = kill(process_list->pid, SIGTERM);
- if(ret == -1 and errno != ESRCH){
- /* set-uid proccesses migth not get closed */
- perror("kill");
+ /* Kill the processes */
+ for(plugin *p = plugin_list; p != NULL; p = p->next){
+ if(p->pid != 0){
+ close(p->fd);
+ ret = kill(p->pid, SIGTERM);
+ if(ret == -1 and errno != ESRCH){
+ /* Set-uid proccesses might not get closed */
+ error(0, errno, "kill");
+ }
}
- free(process_list->buffer);
- free(process_list);
}
/* Wait for any remaining child processes to terminate */
- do{
+ do {
ret = wait(NULL);
} while(ret >= 0);
if(errno != ECHILD){
- perror("wait");
+ error(0, errno, "wait");
}
+ free_plugin_list();
+
+ free(plugindir);
+ free(argfile);
+
return exitstatus;
}
=== added file 'plugin-runner.conf'
--- plugin-runner.conf 1970-01-01 00:00:00 +0000
+++ plugin-runner.conf 2009-04-17 08:26:17 +0000
@@ -0,0 +1,10 @@
+## This is the configuration file for plugin-runner(8mandos). This
+## file should be installed as "/etc/mandos/plugin-runner.conf", and
+## will be copied to "/conf/conf.d/mandos/plugin-runner.conf" in the
+## initrd.img file.
+##
+## After editing this file, the initrd image file must be updated for
+## the changes to take effect!
+
+## Example:
+#--options-for=mandos-client:--debug
=== modified file 'plugin-runner.xml'
--- plugin-runner.xml 2008-08-16 20:31:21 +0000
+++ plugin-runner.xml 2009-01-18 06:41:57 +0000
@@ -1,18 +1,19 @@
-
-
+
+
+
+%common;
]>
-
+
- &COMMANDNAME;
-
- &COMMANDNAME;
- &VERSION;
+ Mandos Manual
+
+ Mandos
+ &version;
+ &TIMESTAMP;
Björn
@@ -31,33 +32,13 @@
2008
- Teddy Hogeborn & Björn Påhlsson
+ 2009
+ Teddy Hogeborn
+ Björn Påhlsson
-
-
- This manual page is free software: you can redistribute it
- and/or modify it under the terms of the GNU General Public
- License as published by the Free Software Foundation,
- either version 3 of the License, or (at your option) any
- later version.
-
-
-
- This manual page is distributed in the hope that it will
- be useful, but WITHOUT ANY WARRANTY; without even the
- implied warranty of MERCHANTABILITY or FITNESS FOR A
- PARTICULAR PURPOSE. See the GNU General Public License
- for more details.
-
-
-
- You should have received a copy of the GNU General Public
- License along with this program; If not, see
- .
-
-
+
-
+
&COMMANDNAME;
8mandos
@@ -66,209 +47,594 @@
&COMMANDNAME;
- get password for encrypted rootdisk
+ Run Mandos plugins, pass data from first to succeed.
-
+
&COMMANDNAME;
- --global-optionsOPTIONS
- --options-forPLUGIN:OPTIONS
- --disablePLUGIN
- --groupidID
- --useridID
- --plugin-dirDIRECTORY
- --debug
-
-
- &COMMANDNAME;
- --help
-
-
- &COMMANDNAME;
- --usage
-
-
- &COMMANDNAME;
- --version
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ &COMMANDNAME;
+
+
+
+
+
+
+ &COMMANDNAME;
+
+
+
+ &COMMANDNAME;
+
+
+
+
+
-
+
DESCRIPTION
- &COMMANDNAME; is a plugin runner that waits
- for any of its plugins to return sucessfull with a password, and
- passes it to cryptsetup as stdout message. This command is not
- meant to be invoked directly, but is instead meant to be run by
- cryptsetup by being specified in /etc/crypttab as a keyscript
- and subsequlently started in the initrd environment. See
+ &COMMANDNAME; is a program which is meant to
+ be specified as a keyscript
for the root disk in
crypttab
- 5 for more information on
- keyscripts.
-
-
-
- plugins is looked for in the plugins directory which by default will be
- /conf/conf.d/mandos/plugins.d if not changed by option --plugin-dir.
-
-
+ 5. The aim of this
+ program is therefore to output a password, which then
+ cryptsetup
+ 8 will use to unlock the
+ root disk.
+
+
+ This program is not meant to be invoked directly, but can be in
+ order to test it. Note that any password obtained will simply
+ be output on standard output.
+
+
+
+
+ PURPOSE
+
+ The purpose of this is to enable remote and unattended
+ rebooting of client host computer with an
+ encrypted root file system. See for details.
+
+
+
OPTIONS
- -g,--global-options
- OPTIONS
-
-
- Global options given to all plugins as additional start
- arguments. Options are specified with a -o flag followed
- by a comma separated string of options.
-
-
-
-
-
- -o, --options-for
- PLUGIN:OPTION
-
-
-
- Plugin specific options given to the plugin as additional
- start arguments. Options are specified with a -o flag
- followed by a comma separated string of options.
-
-
-
-
-
- -d, --disable
- PLUGIN
-
-
-
- Disable a specific plugin
-
-
-
-
-
- --groupid ID
-
-
-
- Group ID the plugins will run as
-
-
-
-
-
- --userid ID
-
-
-
- User ID the plugins will run as
-
-
-
-
-
- --plugin-dir DIRECTORY
-
-
-
- Specify a different plugin directory
-
-
-
-
-
- --debug
-
-
- Debug mode
-
-
-
-
-
- -?, --help
-
-
- Gives a help message
-
-
-
-
-
- --usage
-
-
- Gives a short usage message
-
-
-
-
-
- -V, --version
-
-
- Prints the program version
-
-
-
+
+
+
+
+ This option will add an environment variable setting to
+ all plugins. This will override any inherited environment
+ variable.
+
+
+
+
+
+
+
+
+
+ This option will add an environment variable setting to
+ the PLUGIN plugin. This will
+ override any inherited environment variables or
+ environment variables specified using
+ .
+
+
+
+
+
+
+
+
+
+ Pass some options to all plugins.
+ OPTIONS is a comma separated
+ list of options. This is not a very useful option, except
+ for specifying the
+ option to all plugins.
+
+
+
+
+
+
+
+
+
+ Pass some options to a specific plugin. PLUGIN is the name (file basename) of a
+ plugin, and OPTIONS is a comma
+ separated list of options.
+
+
+ Note that since options are not split on whitespace, the
+ way to pass, to the plugin
+ foo
, the option
+ with the option argument
+ baz
is either
+ --options-for=foo:--bar=baz or
+ --options-for=foo:--bar,baz. Using
+ --options-for="foo:--bar baz". will
+ not work.
+
+
+
+
+
+
+
+
+
+ Disable the plugin named
+ PLUGIN. The plugin will not be
+ started.
+
+
+
+
+
+
+
+
+
+ Re-enable the plugin named
+ PLUGIN. This is only useful to
+ undo a previous option, maybe
+ from the configuration file.
+
+
+
+
+
+
+
+
+ Change to group ID ID on
+ startup. The default is 65534. All plugins will be
+ started using this group ID. Note:
+ This must be a number, not a name.
+
+
+
+
+
+
+
+
+ Change to user ID ID on
+ startup. The default is 65534. All plugins will be
+ started using this user ID. Note:
+ This must be a number, not a name.
+
+
+
+
+
+
+
+
+ Specify a different plugin directory. The default is
+ /lib/mandos/plugins.d, which will
+ exist in the initial RAM disk
+ environment.
+
+
+
+
+
+
+
+
+ Specify a different file to read additional options from.
+ See . Other command line options
+ will override options specified in the file.
+
+
+
+
+
+
+
+
+ Enable debug mode. This will enable a lot of output to
+ standard error about what the program is doing. The
+ program will still perform all other functions normally.
+ The default is to not run in debug
+ mode.
+
+
+ The plugins will not be affected by
+ this option. Use
+
+ if complete debugging eruption is desired.
+
+
+
+
+
+
+
+
+
+ Gives a help message about options and their meanings.
+
+
+
+
+
+
+
+
+ Gives a short usage message.
+
+
+
+
+
+
+
+
+
+ Prints the program version.
+
+
+
-
+
+
+ OVERVIEW
+
+
+ This program will run on the client side in the initial
+ RAM disk environment, and is responsible for
+ getting a password. It does this by running plugins, one of
+ which will normally be the actual client program communicating
+ with the server.
+
+
+
+ PLUGINS
+
+ This program will get a password by running a number of
+ plugins, which are simply executable
+ programs in a directory in the initial RAM
+ disk environment. The default directory is
+ /lib/mandos/plugins.d, but this can be
+ changed with the option. The
+ plugins are started in parallel, and the first plugin to output
+ a password and exit with a successful exit
+ code will make this plugin-runner output the password from that
+ plugin, stop any other plugins, and exit.
+
+
+
+ WRITING PLUGINS
+
+ A plugin is simply a program which prints a password to its
+ standard output and then exits with a successful (zero) exit
+ status. If the exit status is not zero, any output on
+ standard output will be ignored by the plugin runner. Any
+ output on its standard error channel will simply be passed to
+ the standard error of the plugin runner, usually the system
+ console.
+
+
+ If the password is a single-line, manually entered passprase,
+ a final trailing newline character should
+ not be printed.
+
+
+ The plugin will run in the initial RAM disk environment, so
+ care must be taken not to depend on any files or running
+ services not available there.
+
+
+ The plugin must exit cleanly and free all allocated resources
+ upon getting the TERM signal, since this is what the plugin
+ runner uses to stop all other plugins when one plugin has
+ output a password and exited cleanly.
+
+
+ The plugin must not use resources, like for instance reading
+ from the standard input, without knowing that no other plugin
+ is also using it.
+
+
+ It is useful, but not required, for the plugin to take the
+ option.
+
+
+
+
+
+ FALLBACK
+
+ If no plugins succeed, this program will, as a fallback, ask for
+ a password on the console using getpass3,
+ and output it. This is not meant to be the normal mode of
+ operation, as there is a separate plugin for getting a password
+ from the console.
+
+
+
EXIT STATUS
-
-
-
-
+ Exit status of this program is zero if no errors were
+ encountered, and otherwise not. The fallback (see ) may or may not have succeeded in either
+ case.
+
+
+
+
+ ENVIRONMENT
+
+ This program does not use any environment variables itself, it
+ only passes on its environment to all the plugins. The
+ environment passed to plugins can be modified using the
+ and
+ options.
+
+
+
+
FILES
-
-
-
-
- NOTES
-
+
+
+ /conf/conf.d/mandos/plugin-runner.conf
+
+
+ Since this program will be run as a keyscript, there is
+ little to no opportunity to pass command line arguments
+ to it. Therefore, it will also
+ read this file and use its contents as
+ whitespace-separated command line options. Also,
+ everything from a #
character to the end
+ of a line is ignored.
+
+
+ This program is meant to run in the initial RAM disk
+ environment, so that is where this file is assumed to
+ exist. The file does not need to exist in the normal
+ file system.
+
+
+ This file will be processed before
+ the normal command line options, so the latter can
+ override the former, if need be.
+
+
+ This file name is the default; the file to read for
+ arguments can be changed using the
+ option.
+
+
+
+
BUGS
+ The option is ignored when
+ specified from within a configuration file.
-
-
+
+
- EXAMPLES
-
-
+ EXAMPLE
+
+
+ Normal invocation needs no options:
+
+
+ &COMMANDNAME;
+
+
+
+
+ Run the program, but not the plugins, in debug mode:
+
+
+
+
+ &COMMANDNAME; --debug
+
+
+
+
+
+ Run all plugins, but run the foo
plugin in
+ debug mode:
+
+
+
+
+ &COMMANDNAME; --options-for=foo:--debug
+
+
+
+
+
+ Run all plugins, but not the program, in debug mode:
+
+
+
+
+ &COMMANDNAME; --global-options=--debug
+
+
+
+
+
+ Run plugins from a different directory, read a different
+ configuration file, and add two options to the
+ mandos-client
+ 8mandos plugin:
+
+
+
+
+cd /etc/keys/mandos; &COMMANDNAME; --config-file=/etc/mandos/plugin-runner.conf --plugin-dir /usr/lib/mandos/plugins.d --options-for=mandos-client:--pubkey=pubkey.txt,--seckey=seckey.txt
+
+
+
-
SECURITY
+ This program will, when starting, try to switch to another user.
+ If it is started as root, it will succeed, and will by default
+ switch to user and group 65534, which are assumed to be
+ non-privileged. This user and group is then what all plugins
+ will be started as. Therefore, the only way to run a plugin as
+ a privileged user is to have the set-user-ID or set-group-ID bit
+ set on the plugin executable file (see
+ execve2
+ ).
+
+
+ If this program is used as a keyscript in crypttab5
+ , there is a slight risk that if this program
+ fails to work, there might be no way to boot the system except
+ for booting from another media and editing the initial RAM disk
+ image to not run this program. This is, however, unlikely,
+ since the password-prompt8mandos
+ plugin will read a password from the console in
+ case of failure of the other plugins, and this plugin runner
+ will also, in case of catastrophic failure, itself fall back to
+ asking and outputting a password on the console (see ).
-
+
SEE ALSO
+ cryptsetup
+ 8,
+ crypttab
+ 5,
+ execve
+ 2,
mandos
- 8,
- password-request
- 8mandos,
- password-prompt
- 8mandos, and
- cryptsetup
- 8
+ 8,
+ password-prompt
+ 8mandos,
+ mandos-client
+ 8mandos
-
+
+
+
+
+
+
=== added file 'plugins.d/askpass-fifo.c'
--- plugins.d/askpass-fifo.c 1970-01-01 00:00:00 +0000
+++ plugins.d/askpass-fifo.c 2010-09-26 18:32:58 +0000
@@ -0,0 +1,174 @@
+/* -*- coding: utf-8 -*- */
+/*
+ * Askpass-FIFO - Read a password from a FIFO and output it
+ *
+ * Copyright © 2008-2010 Teddy Hogeborn
+ * Copyright © 2008-2010 Björn Påhlsson
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * .
+ *
+ * Contact the authors at .
+ */
+
+#define _GNU_SOURCE /* TEMP_FAILURE_RETRY() */
+#include /* ssize_t */
+#include /* mkfifo(), S_IRUSR, S_IWUSR */
+#include /* and */
+#include /* errno, EACCES, ENOTDIR, ELOOP,
+ ENAMETOOLONG, ENOSPC, EROFS,
+ ENOENT, EEXIST, EFAULT, EMFILE,
+ ENFILE, ENOMEM, EBADF, EINVAL, EIO,
+ EISDIR, EFBIG */
+#include /* error() */
+#include /* 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 */
+
+
+int main(__attribute__((unused))int argc,
+ __attribute__((unused))char **argv){
+ int ret = 0;
+ ssize_t sret;
+
+ /* Create FIFO */
+ const char passfifo[] = "/lib/cryptsetup/passfifo";
+ ret = mkfifo(passfifo, S_IRUSR | S_IWUSR);
+ if(ret == -1){
+ int e = errno;
+ error(0, errno, "mkfifo");
+ switch(e){
+ case EACCES:
+ case ENOTDIR:
+ case ELOOP:
+ return EX_OSFILE;
+ case ENAMETOOLONG:
+ case ENOSPC:
+ case EROFS:
+ default:
+ return EX_OSERR;
+ case ENOENT:
+ return EX_UNAVAILABLE; /* no "/lib/cryptsetup"? */
+ case EEXIST:
+ break; /* not an error */
+ }
+ }
+
+ /* Open FIFO */
+ int fifo_fd = open(passfifo, O_RDONLY);
+ if(fifo_fd == -1){
+ int e = errno;
+ error(0, errno, "open");
+ switch(e){
+ case EACCES:
+ case ENOENT:
+ case EFAULT:
+ return EX_UNAVAILABLE;
+ case ENAMETOOLONG:
+ case EMFILE:
+ case ENFILE:
+ case ENOMEM:
+ default:
+ return EX_OSERR;
+ case ENOTDIR:
+ case ELOOP:
+ return EX_OSFILE;
+ }
+ }
+
+ /* Read from FIFO */
+ char *buf = NULL;
+ size_t buf_len = 0;
+ {
+ size_t buf_allocated = 0;
+ const size_t blocksize = 1024;
+ do {
+ if(buf_len + blocksize > buf_allocated){
+ char *tmp = realloc(buf, buf_allocated + blocksize);
+ if(tmp == NULL){
+ error(0, errno, "realloc");
+ free(buf);
+ return EX_OSERR;
+ }
+ buf = tmp;
+ buf_allocated += blocksize;
+ }
+ sret = read(fifo_fd, buf + buf_len, buf_allocated - buf_len);
+ if(sret == -1){
+ int e = errno;
+ free(buf);
+ errno = e;
+ error(0, errno, "read");
+ switch(e){
+ case EBADF:
+ case EFAULT:
+ case EINVAL:
+ default:
+ return EX_OSERR;
+ case EIO:
+ return EX_IOERR;
+ case EISDIR:
+ return EX_UNAVAILABLE;
+ }
+ }
+ buf_len += (size_t)sret;
+ } while(sret != 0);
+ }
+
+ /* Close FIFO */
+ close(fifo_fd);
+
+ /* Print password to stdout */
+ size_t written = 0;
+ while(written < buf_len){
+ sret = write(STDOUT_FILENO, buf + written, buf_len - written);
+ if(sret == -1){
+ int e = errno;
+ free(buf);
+ errno = e;
+ error(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(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 2009-01-04 21:54:55 +0000
@@ -0,0 +1,162 @@
+
+
+
+
+%common;
+]>
+
+
+
+ Mandos Manual
+
+ Mandos
+ &version;
+ &TIMESTAMP;
+
+
+ Björn
+ Påhlsson
+
+ belorn@fukt.bsnet.se
+
+
+
+ Teddy
+ Hogeborn
+
+ teddy@fukt.bsnet.se
+
+
+
+
+ 2008
+ 2009
+ 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
+
+ fifo
+ 7,
+ plugin-runner
+ 8mandos
+
+
+
+
+
+
+
+
=== renamed file 'plugins.d/password-request.c' => 'plugins.d/mandos-client.c'
--- plugins.d/password-request.c 2008-08-17 22:42:28 +0000
+++ plugins.d/mandos-client.c 2010-09-12 18:12:11 +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 © 2007-2008 Teddy Hogeborn & Björn Påhlsson
+ * Copyright © 2008,2009 Teddy Hogeborn
+ * Copyright © 2008,2009 Björn Påhlsson
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -29,45 +30,64 @@
*/
/* 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() */
+ stdout, ferror(), remove() */
#include /* uint16_t, uint32_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 */
-#include /* PRIu16 */
+ SOCK_STREAM, uid_t, gid_t, open(),
+ opendir(), DIR */
+#include /* open() */
#include /* socket(), struct sockaddr_in6,
- struct in6_addr, inet_pton(),
- connect() */
+ inet_pton(), connect() */
+#include /* open() */
+#include /* opendir(), struct dirent, readdir()
+ */
+#include /* PRIu16, PRIdMAX, intmax_t,
+ strtoimax() */
#include /* assert() */
#include /* perror(), errno */
-#include /* time() */
+#include /* nanosleep(), time(), sleep() */
#include /* ioctl, ifreq, SIOCGIFFLAGS, IFF_UP,
SIOCSIFFLAGS, if_indextoname(),
if_nametoindex(), IF_NAMESIZE */
+#include /* IN6_IS_ADDR_LINKLOCAL,
+ INET_ADDRSTRLEN, INET6_ADDRSTRLEN
+ */
#include /* close(), SEEK_SET, off_t, write(),
- getuid(), getgid(), setuid(),
- setgid() */
-#include
+ getuid(), getgid(), seteuid(),
+ setgid(), pause() */
#include /* inet_pton(), htons */
-#include /* not, and */
+#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 */
+
+#ifdef __linux__
+#include /* klogctl() */
+#endif /* __linux__ */
/* Avahi */
/* All Avahi types, constants and functions
@@ -86,7 +106,8 @@
gnutls_*
init_gnutls_session(),
GNUTLS_* */
-#include /* gnutls_certificate_set_openpgp_key_file(),
+#include
+ /* gnutls_certificate_set_openpgp_key_file(),
GNUTLS_OPENPGP_FMT_BASE64 */
/* GPGME */
@@ -98,11 +119,16 @@
#define BUFFER_SIZE 256
+#define PATHDIR "/conf/conf.d/mandos"
+#define SECKEY "seckey.txt"
+#define PUBKEY "pubkey.txt"
+
bool debug = false;
-static const char *keydir = "/conf/conf.d/mandos";
static const char mandos_protocol_version[] = "1";
-const char *argp_program_version = "password-request 1.0";
+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;
/* Used for passing in values through the Avahi callback functions */
typedef struct {
@@ -112,18 +138,27 @@
unsigned int dh_bits;
gnutls_dh_params_t dh_params;
const char *priority;
+ gpgme_ctx_t ctx;
} mandos_context;
+/* global context so signal handler can reach it*/
+mandos_context mc = { .simple_poll = NULL, .server = NULL,
+ .dh_bits = 1024, .priority = "SECURE256"
+ ":!CTYPE-X.509:+CTYPE-OPENPGP" };
+
+sig_atomic_t quit_now = 0;
+int signal_received = 0;
+
/*
- * 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 incbuffer(char **buffer, size_t buffer_length,
size_t buffer_capacity){
- if (buffer_length + BUFFER_SIZE > 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;
@@ -132,58 +167,119 @@
}
/*
- * Decrypt OpenPGP data using keyrings in HOMEDIR.
- * Returns -1 on error
+ * Initialize GPGME.
*/
-static ssize_t pgp_packet_decrypt (const char *cryptotext,
- size_t crypto_size,
- char **plaintext,
- const char *homedir){
- gpgme_data_t dh_crypto, dh_plain;
- gpgme_ctx_t ctx;
+static bool init_gpgme(const char *seckey,
+ const char *pubkey, const char *tempdir){
gpgme_error_t rc;
- ssize_t ret;
- size_t plaintext_capacity = 0;
- ssize_t plaintext_length = 0;
gpgme_engine_info_t engine_info;
- if (debug){
- fprintf(stderr, "Trying to decrypt OpenPGP data\n");
+
+ /*
+ * Helper function to insert pub and seckey to the engine keyring.
+ */
+ bool import_key(const char *filename){
+ int ret;
+ int fd;
+ gpgme_data_t pgp_data;
+
+ fd = (int)TEMP_FAILURE_RETRY(open(filename, O_RDONLY));
+ if(fd == -1){
+ perror("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));
+ 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));
+ return false;
+ }
+
+ ret = (int)TEMP_FAILURE_RETRY(close(fd));
+ if(ret == -1){
+ perror("close");
+ }
+ gpgme_data_release(pgp_data);
+ return true;
+ }
+
+ if(debug){
+ fprintf(stderr, "Initializing GPGME\n");
}
/* Init GPGME */
gpgme_check_version(NULL);
rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
- if (rc != GPG_ERR_NO_ERROR){
+ if(rc != GPG_ERR_NO_ERROR){
fprintf(stderr, "bad gpgme_engine_check_version: %s: %s\n",
gpgme_strsource(rc), gpgme_strerror(rc));
- return -1;
+ return false;
}
- /* Set GPGME home directory for the OpenPGP engine only */
- rc = gpgme_get_engine_info (&engine_info);
- if (rc != GPG_ERR_NO_ERROR){
+ /* 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));
- return -1;
+ return false;
}
while(engine_info != NULL){
if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){
gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP,
- engine_info->file_name, homedir);
+ engine_info->file_name, tempdir);
break;
}
engine_info = engine_info->next;
}
if(engine_info == NULL){
- fprintf(stderr, "Could not set GPGME home dir to %s\n", homedir);
- return -1;
+ fprintf(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;
+}
+
+/*
+ * Decrypt OpenPGP data.
+ * Returns -1 on error
+ */
+static ssize_t pgp_packet_decrypt(const char *cryptotext,
+ size_t crypto_size,
+ char **plaintext){
+ 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");
}
/* 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){
+ if(rc != GPG_ERR_NO_ERROR){
fprintf(stderr, "bad gpgme_data_new_from_mem: %s: %s\n",
gpgme_strsource(rc), gpgme_strerror(rc));
return -1;
@@ -191,52 +287,35 @@
/* Create new empty GPGME data buffer for the plaintext */
rc = gpgme_data_new(&dh_plain);
- if (rc != GPG_ERR_NO_ERROR){
+ if(rc != GPG_ERR_NO_ERROR){
fprintf(stderr, "bad gpgme_data_new: %s: %s\n",
gpgme_strsource(rc), gpgme_strerror(rc));
gpgme_data_release(dh_crypto);
return -1;
}
- /* Create new GPGME "context" */
- rc = gpgme_new(&ctx);
- if (rc != GPG_ERR_NO_ERROR){
- fprintf(stderr, "bad gpgme_new: %s: %s\n",
- gpgme_strsource(rc), gpgme_strerror(rc));
- plaintext_length = -1;
- goto decrypt_end;
- }
-
/* Decrypt data from the cryptotext data buffer to the plaintext
data buffer */
- rc = gpgme_op_decrypt(ctx, dh_crypto, dh_plain);
- if (rc != GPG_ERR_NO_ERROR){
+ 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));
plaintext_length = -1;
- goto decrypt_end;
- }
-
- if(debug){
- fprintf(stderr, "Decryption of OpenPGP data succeeded\n");
- }
-
- if (debug){
- gpgme_decrypt_result_t result;
- result = gpgme_op_decrypt_result(ctx);
- if (result == NULL){
- fprintf(stderr, "gpgme_op_decrypt_result failed\n");
- } else {
- fprintf(stderr, "Unsupported algorithm: %s\n",
- result->unsupported_algorithm);
- fprintf(stderr, "Wrong key usage: %u\n",
- result->wrong_key_usage);
- if(result->file_name != NULL){
- fprintf(stderr, "File name: %s\n", result->file_name);
- }
- gpgme_recipient_t recipient;
- recipient = result->recipients;
- if(recipient){
+ 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");
+ } else {
+ fprintf(stderr, "Unsupported algorithm: %s\n",
+ result->unsupported_algorithm);
+ fprintf(stderr, "Wrong key usage: %u\n",
+ result->wrong_key_usage);
+ if(result->file_name != NULL){
+ fprintf(stderr, "File name: %s\n", result->file_name);
+ }
+ gpgme_recipient_t recipient;
+ recipient = result->recipients;
while(recipient != NULL){
fprintf(stderr, "Public key algorithm: %s\n",
gpgme_pubkey_algo_name(recipient->pubkey_algo));
@@ -248,22 +327,27 @@
}
}
}
+ goto decrypt_end;
+ }
+
+ if(debug){
+ fprintf(stderr, "Decryption of OpenPGP data succeeded\n");
}
/* Seek back to the beginning of the GPGME plaintext data buffer */
- if (gpgme_data_seek(dh_plain, (off_t) 0, SEEK_SET) == -1){
- perror("pgpme_data_seek");
+ if(gpgme_data_seek(dh_plain, (off_t)0, SEEK_SET) == -1){
+ perror("gpgme_data_seek");
plaintext_length = -1;
goto decrypt_end;
}
*plaintext = NULL;
while(true){
- plaintext_capacity = adjustbuffer(plaintext,
+ plaintext_capacity = incbuffer(plaintext,
(size_t)plaintext_length,
plaintext_capacity);
- if (plaintext_capacity == 0){
- perror("adjustbuffer");
+ if(plaintext_capacity == 0){
+ perror("incbuffer");
plaintext_length = -1;
goto decrypt_end;
}
@@ -271,7 +355,7 @@
ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length,
BUFFER_SIZE);
/* Print the data, if any */
- if (ret == 0){
+ if(ret == 0){
/* EOF */
break;
}
@@ -282,7 +366,7 @@
}
plaintext_length += ret;
}
-
+
if(debug){
fprintf(stderr, "Decrypted password is: ");
for(ssize_t i = 0; i < plaintext_length; i++){
@@ -301,9 +385,10 @@
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); /* Spurious warning from
+ -Wunreachable-code */
+ if(ret == NULL)
ret = "(unknown)";
return ret;
}
@@ -314,8 +399,7 @@
fprintf(stderr, "GnuTLS: %s", string);
}
-static int init_gnutls_global(mandos_context *mc,
- const char *pubkeyfilename,
+static int init_gnutls_global(const char *pubkeyfilename,
const char *seckeyfilename){
int ret;
@@ -324,13 +408,13 @@
}
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(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
*/
@@ -339,95 +423,112 @@
}
/* 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 ();
+ gnutls_certificate_allocate_credentials(&mc.cred);
+ if(ret != GNUTLS_E_SUCCESS){
+ fprintf(stderr, "GnuTLS memory error: %s\n", /* Spurious warning
+ from
+ -Wunreachable-code
+ */
+ safer_gnutls_strerror(ret));
+ gnutls_global_deinit();
return -1;
}
if(debug){
- fprintf(stderr, "Attempting to use OpenPGP certificate %s"
- " and keyfile %s as GnuTLS credentials\n", pubkeyfilename,
+ fprintf(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,
+ (mc.cred, pubkeyfilename, seckeyfilename,
GNUTLS_OPENPGP_FMT_BASE64);
- if (ret != GNUTLS_E_SUCCESS) {
+ if(ret != GNUTLS_E_SUCCESS){
fprintf(stderr,
"Error[%d] while reading the OpenPGP key pair ('%s',"
" '%s')\n", ret, pubkeyfilename, seckeyfilename);
- fprintf(stdout, "The GnuTLS error is: %s\n",
+ fprintf(stderr, "The GnuTLS error is: %s\n",
safer_gnutls_strerror(ret));
goto globalfail;
}
/* GnuTLS server initialization */
- ret = gnutls_dh_params_init(&mc->dh_params);
- if (ret != GNUTLS_E_SUCCESS) {
- fprintf (stderr, "Error in GnuTLS DH parameter initialization:"
- " %s\n", safer_gnutls_strerror(ret));
- goto globalfail;
- }
- ret = gnutls_dh_params_generate2(mc->dh_params, mc->dh_bits);
- if (ret != GNUTLS_E_SUCCESS) {
- fprintf (stderr, "Error in GnuTLS prime generation: %s\n",
- safer_gnutls_strerror(ret));
- goto globalfail;
- }
-
- gnutls_certificate_set_dh_params(mc->cred, mc->dh_params);
-
+ ret = gnutls_dh_params_init(&mc.dh_params);
+ if(ret != GNUTLS_E_SUCCESS){
+ fprintf(stderr, "Error in GnuTLS DH parameter initialization:"
+ " %s\n", safer_gnutls_strerror(ret));
+ goto globalfail;
+ }
+ ret = gnutls_dh_params_generate2(mc.dh_params, mc.dh_bits);
+ if(ret != GNUTLS_E_SUCCESS){
+ fprintf(stderr, "Error in GnuTLS prime generation: %s\n",
+ safer_gnutls_strerror(ret));
+ goto globalfail;
+ }
+
+ gnutls_certificate_set_dh_params(mc.cred, mc.dh_params);
+
return 0;
-
+
globalfail:
-
- gnutls_certificate_free_credentials(mc->cred);
+
+ gnutls_certificate_free_credentials(mc.cred);
gnutls_global_deinit();
+ gnutls_dh_params_deinit(mc.dh_params);
return -1;
-
}
-static int init_gnutls_session(mandos_context *mc,
- gnutls_session_t *session){
+static int init_gnutls_session(gnutls_session_t *session){
int ret;
/* GnuTLS session creation */
- ret = gnutls_init(session, GNUTLS_SERVER);
- if (ret != GNUTLS_E_SUCCESS){
+ 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(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) {
+ 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(stderr, "Syntax error at: %s\n", err);
fprintf(stderr, "GnuTLS error: %s\n",
safer_gnutls_strerror(ret));
- gnutls_deinit (*session);
+ gnutls_deinit(*session);
return -1;
}
}
- ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE,
- mc->cred);
- if (ret != GNUTLS_E_SUCCESS) {
+ 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(stderr, "Error setting GnuTLS credentials: %s\n",
safer_gnutls_strerror(ret));
- gnutls_deinit (*session);
+ 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;
}
@@ -439,66 +540,137 @@
/* Called when a Mandos server is found */
static int start_mandos_communication(const char *ip, uint16_t port,
AvahiIfIndex if_index,
- mandos_context *mc){
- int ret, tcp_sd;
- union { struct sockaddr in; struct sockaddr_in6 in6; } to;
+ int af){
+ 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){
+ 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(stderr, "Bad address family: %d\n", af);
+ errno = EINVAL;
+ return -1;
+ }
+
+ ret = init_gnutls_session(&session);
+ if(ret != 0){
return -1;
}
if(debug){
- fprintf(stderr, "Setting up a tcp connection to %s, port %" PRIu16
+ 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) {
+ tcp_sd = socket(pf, SOCK_STREAM, 0);
+ if(tcp_sd < 0){
+ int e = errno;
perror("socket");
- return -1;
+ errno = e;
+ goto mandos_end;
}
-
- if(debug){
- if(if_indextoname((unsigned int)if_index, interface) == NULL){
- perror("if_indextoname");
- return -1;
- }
- fprintf(stderr, "Binding to interface %s\n", interface);
+
+ 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 ){
+ 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("inet_pton");
- return -1;
+ errno = e;
+ goto mandos_end;
}
if(ret == 0){
+ int e = errno;
fprintf(stderr, "Bad address: %s\n", ip);
- return -1;
- }
- to.in6.sin6_port = htons(port); /* Spurious warning */
+ errno = e;
+ goto mandos_end;
+ }
+ if(af == AF_INET6){
+ to.in6.sin6_port = htons(port); /* Spurious warnings from
+ -Wconversion and
+ -Wunreachable-code */
+
+ if(IN6_IS_ADDR_LINKLOCAL /* Spurious warnings from */
+ (&to.in6.sin6_addr)){ /* -Wstrict-aliasing=2 or lower and
+ -Wunreachable-code*/
+ if(if_index == AVAHI_IF_UNSPEC){
+ fprintf(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); /* Spurious warnings from
+ -Wconversion and
+ -Wunreachable-code */
+ }
- 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){
+ if(af == AF_INET6 and if_index != AVAHI_IF_UNSPEC){
+ char interface[IF_NAMESIZE];
+ if(if_indextoname((unsigned int)if_index, interface) == NULL){
+ perror("if_indextoname");
+ } else {
+ fprintf(stderr, "Connection to: %s%%%s, port %" PRIu16 "\n",
+ ip, interface, port);
+ }
+ } else {
+ fprintf(stderr, "Connection to: %s, port %" PRIu16 "\n", ip,
+ 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("inet_ntop");
} else {
if(strcmp(addrstr, ip) != 0){
@@ -507,101 +679,156 @@
}
}
- 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("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,
+ ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written,
out_size - written));
- if (ret == -1){
+ if(ret == -1){
+ int e = errno;
perror("write");
- retval = -1;
+ 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);
+ if(quit_now){
+ errno = EINTR;
+ goto mandos_end;
+ }
+
+ gnutls_transport_set_ptr(session, (gnutls_transport_ptr_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);
+ 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",
+ fprintf(stderr, "Retrieving OpenPGP encrypted password from %s\n",
ip);
}
-
+
while(true){
- buffer_capacity = adjustbuffer(&buffer, buffer_length,
+
+ if(quit_now){
+ errno = EINTR;
+ goto mandos_end;
+ }
+
+ buffer_capacity = incbuffer(&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(buffer_capacity == 0){
+ int e = errno;
+ perror("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){
+ if(ret < 0){
fprintf(stderr, "*** GnuTLS Re-handshake failed ***\n");
- gnutls_perror (ret);
- retval = -1;
+ 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);
+ gnutls_bye(session, GNUTLS_SHUT_RDWR);
+ errno = EIO;
goto mandos_end;
}
} else {
- buffer_length += (size_t) ret;
+ buffer_length += (size_t) sret;
}
}
@@ -609,47 +836,80 @@
fprintf(stderr, "Closing TLS session\n");
}
- gnutls_bye (session, GNUTLS_SHUT_RDWR);
-
- if (buffer_length > 0){
+ 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,
- keydir);
- if (decrypted_buffer_size >= 0){
+ &decrypted_buffer);
+ 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));
}
- retval = -1;
- break;
+ errno = e;
+ goto mandos_end;
}
written += (size_t)ret;
}
- free(decrypted_buffer);
- } else {
- retval = -1;
+ retval = 0;
}
}
/* Shutdown procedure */
mandos_end:
- free(buffer);
- close(tcp_sd);
- gnutls_deinit (session);
+ {
+ 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("close");
+ }
+ gnutls_deinit(session);
+ if(quit_now){
+ e = EINTR;
+ retval = -1;
+ }
+ errno = e;
+ }
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,
@@ -660,19 +920,22 @@
AVAHI_GCC_UNUSED AvahiStringList *txt,
AVAHI_GCC_UNUSED AvahiLookupResultFlags
flags,
- void* userdata) {
- mandos_context *mc = userdata;
+ AVAHI_GCC_UNUSED void* userdata){
assert(r);
/* 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)));
+ avahi_strerror(avahi_server_errno(mc.server)));
break;
case AVAHI_RESOLVER_FOUND:
@@ -681,41 +944,45 @@
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);
+ 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, port, interface,
+ avahi_proto_to_af(proto));
+ if(ret == 0){
+ avahi_simple_poll_quit(mc.simple_poll);
}
}
}
avahi_s_service_resolver_free(r);
}
-static void browse_callback( AvahiSServiceBrowser *b,
- AvahiIfIndex interface,
- AvahiProtocol protocol,
- AvahiBrowserEvent event,
- const char *name,
- const char *type,
- const char *domain,
- AVAHI_GCC_UNUSED AvahiLookupResultFlags
- flags,
- void* userdata) {
- mandos_context *mc = userdata;
+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,
+ AVAHI_GCC_UNUSED void* userdata){
assert(b);
/* 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);
+ avahi_strerror(avahi_server_errno(mc.server)));
+ avahi_simple_poll_quit(mc.simple_poll);
return;
case AVAHI_BROWSER_NEW:
@@ -724,12 +991,11 @@
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)))
+ if(avahi_s_service_resolver_new(mc.server, interface, protocol,
+ name, type, domain, protocol, 0,
+ resolve_callback, NULL) == NULL)
fprintf(stderr, "Avahi: Failed to resolve service '%s': %s\n",
- name, avahi_strerror(avahi_server_errno(mc->server)));
+ name, avahi_strerror(avahi_server_errno(mc.server)));
break;
case AVAHI_BROWSER_REMOVE:
@@ -744,309 +1010,809 @@
}
}
-/* Combines file name and path and returns the malloced new
- string. some sane checks could/should be added */
-static char *combinepath(const char *first, const char *second){
+/* stop main loop after sigterm has been called */
+static void handle_sigterm(int sig){
+ if(quit_now){
+ return;
+ }
+ quit_now = 1;
+ signal_received = sig;
+ int old_errno = errno;
+ if(mc.simple_poll != NULL){
+ avahi_simple_poll_quit(mc.simple_poll);
+ }
+ errno = old_errno;
+}
+
+/*
+ * 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){
+ ssize_t ssret;
+ char *flagname = NULL;
+ int ret = asprintf(&flagname, "%s/%s/flags", sys_class_net,
+ if_entry->d_name);
+ if(ret < 0){
+ perror("asprintf");
+ return 0;
+ }
+ if(if_entry->d_name[0] == '.'){
+ return 0;
+ }
+ int flags_fd = (int)TEMP_FAILURE_RETRY(open(flagname, O_RDONLY));
+ if(flags_fd == -1){
+ perror("open");
+ return 0;
+ }
+ typedef short ifreq_flags; /* ifreq.ifr_flags in netdevice(7) */
+ /* read line from flags_fd */
+ ssize_t to_read = (sizeof(ifreq_flags)*2)+3; /* "0x1003\n" */
+ char *flagstring = malloc((size_t)to_read+1); /* +1 for final \0 */
+ flagstring[(size_t)to_read] = '\0';
+ if(flagstring == NULL){
+ perror("malloc");
+ close(flags_fd);
+ return 0;
+ }
+ while(to_read > 0){
+ ssret = (ssize_t)TEMP_FAILURE_RETRY(read(flags_fd, flagstring,
+ (size_t)to_read));
+ if(ssret == -1){
+ perror("read");
+ free(flagstring);
+ close(flags_fd);
+ return 0;
+ }
+ to_read -= ssret;
+ if(ssret == 0){
+ break;
+ }
+ }
+ close(flags_fd);
+ intmax_t tmpmax;
char *tmp;
- int ret = asprintf(&tmp, "%s/%s", first, second);
- if(ret < 0){
- return NULL;
- }
- return tmp;
+ errno = 0;
+ tmpmax = strtoimax(flagstring, &tmp, 0);
+ if(errno != 0 or tmp == flagstring or (*tmp != '\0'
+ and not (isspace(*tmp)))
+ or tmpmax != (ifreq_flags)tmpmax){
+ if(debug){
+ fprintf(stderr, "Invalid flags \"%s\" for interface \"%s\"\n",
+ flagstring, if_entry->d_name);
+ }
+ free(flagstring);
+ return 0;
+ }
+ free(flagstring);
+ ifreq_flags flags = (ifreq_flags)tmpmax;
+ /* Reject the loopback device */
+ if(flags & IFF_LOOPBACK){
+ if(debug){
+ fprintf(stderr, "Rejecting loopback interface \"%s\"\n",
+ if_entry->d_name);
+ }
+ return 0;
+ }
+ /* Accept point-to-point devices only if connect_to is specified */
+ if(connect_to != NULL and (flags & IFF_POINTOPOINT)){
+ if(debug){
+ fprintf(stderr, "Accepting point-to-point interface \"%s\"\n",
+ if_entry->d_name);
+ }
+ return 1;
+ }
+ /* Otherwise, reject non-broadcast-capable devices */
+ if(not (flags & IFF_BROADCAST)){
+ if(debug){
+ fprintf(stderr, "Rejecting non-broadcast interface \"%s\"\n",
+ if_entry->d_name);
+ }
+ return 0;
+ }
+ /* Accept this device */
+ if(debug){
+ fprintf(stderr, "Interface \"%s\" is acceptable\n",
+ if_entry->d_name);
+ }
+ return 1;
}
-
int main(int argc, char *argv[]){
- AvahiSServiceBrowser *sb = NULL;
- int error;
- int ret;
- int exitcode = EXIT_SUCCESS;
- const char *interface = "eth0";
- struct ifreq network;
- int sd;
- uid_t uid;
- gid_t gid;
- char *connect_to = NULL;
- AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
- char *pubkeyfilename = NULL;
- char *seckeyfilename = NULL;
- const char *pubkeyname = "pubkey.txt";
- const char *seckeyname = "seckey.txt";
- mandos_context mc = { .simple_poll = NULL, .server = NULL,
- .dh_bits = 1024, .priority = "SECURE256"};
- bool gnutls_initalized = false;
-
- {
- struct argp_option options[] = {
- { .name = "debug", .key = 128,
- .doc = "Debug mode", .group = 3 },
- { .name = "connect", .key = 'c',
- .arg = "IP",
- .doc = "Connect directly to a sepcified mandos server",
- .group = 1 },
- { .name = "interface", .key = 'i',
- .arg = "INTERFACE",
- .doc = "Interface that Avahi will conntect through",
- .group = 1 },
- { .name = "keydir", .key = 'd',
- .arg = "KEYDIR",
- .doc = "Directory where the openpgp keyring is",
- .group = 1 },
- { .name = "seckey", .key = 's',
- .arg = "SECKEY",
- .doc = "Secret openpgp key for gnutls authentication",
- .group = 1 },
- { .name = "pubkey", .key = 'p',
- .arg = "PUBKEY",
- .doc = "Public openpgp key for gnutls authentication",
- .group = 2 },
- { .name = "dh-bits", .key = 129,
- .arg = "BITS",
- .doc = "dh-bits to use in gnutls communication",
- .group = 2 },
- { .name = "priority", .key = 130,
- .arg = "PRIORITY",
- .doc = "GNUTLS priority", .group = 1 },
- { .name = NULL }
- };
-
-
- error_t parse_opt (int key, char *arg,
- struct argp_state *state) {
- /* Get the INPUT argument from `argp_parse', which we know is
- a pointer to our plugin list pointer. */
- switch (key) {
- case 128:
- debug = true;
- break;
- case 'c':
- connect_to = arg;
- break;
- case 'i':
- interface = arg;
- break;
- case 'd':
- keydir = arg;
- break;
- case 's':
- seckeyname = arg;
- break;
- case 'p':
- pubkeyname = arg;
- break;
- case 129:
- errno = 0;
- mc.dh_bits = (unsigned int) strtol(arg, NULL, 10);
- if (errno){
- perror("strtol");
- exit(EXIT_FAILURE);
- }
- break;
- case 130:
- mc.priority = arg;
- break;
- case ARGP_KEY_ARG:
- argp_usage (state);
- case ARGP_KEY_END:
- break;
- default:
- return ARGP_ERR_UNKNOWN;
- }
- return 0;
- }
-
- struct argp argp = { .options = options, .parser = parse_opt,
- .args_doc = "",
- .doc = "Mandos client -- Get and decrypt"
- " passwords from mandos server" };
- ret = argp_parse (&argp, argc, argv, 0, 0, NULL);
- if (ret == ARGP_ERR_UNKNOWN){
- fprintf(stderr, "Unknown error while parsing arguments\n");
+ AvahiSServiceBrowser *sb = NULL;
+ int error;
+ int ret;
+ intmax_t tmpmax;
+ char *tmp;
+ int exitcode = EXIT_SUCCESS;
+ const char *interface = "";
+ struct ifreq network;
+ int sd = -1;
+ bool take_down_interface = false;
+ uid_t uid;
+ gid_t gid;
+ char tempdir[] = "/tmp/mandosXXXXXX";
+ bool tempdir_created = false;
+ AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
+ const char *seckey = PATHDIR "/" SECKEY;
+ const char *pubkey = PATHDIR "/" PUBKEY;
+
+ bool gnutls_initialized = false;
+ bool gpgme_initialized = false;
+ float delay = 2.5f;
+
+ 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("setgid");
+ }
+
+ /* Lower user privileges (temporarily) */
+ errno = 0;
+ ret = seteuid(uid);
+ if(ret == -1){
+ perror("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 },
+ /*
+ * These reproduce what we would get without ARGP_NO_HELP
+ */
+ { .name = "help", .key = '?',
+ .doc = "Give this help list", .group = -1 },
+ { .name = "usage", .key = -3,
+ .doc = "Give a short usage message", .group = -1 },
+ { .name = "version", .key = 'V',
+ .doc = "Print program version", .group = -1 },
+ { .name = NULL }
+ };
+
+ error_t parse_opt(int key, char *arg,
+ struct argp_state *state){
+ errno = 0;
+ switch(key){
+ case 128: /* --debug */
+ debug = true;
+ break;
+ case 'c': /* --connect */
+ connect_to = arg;
+ break;
+ case 'i': /* --interface */
+ interface = arg;
+ 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");
+ }
+ break;
+ /*
+ * These reproduce what we would get without ARGP_NO_HELP
+ */
+ case '?': /* --help */
+ argp_state_help(state, state->out_stream,
+ (ARGP_HELP_STD_HELP | ARGP_HELP_EXIT_ERR)
+ & ~(unsigned int)ARGP_HELP_EXIT_OK);
+ case -3: /* --usage */
+ argp_state_help(state, state->out_stream,
+ ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR);
+ case 'V': /* --version */
+ fprintf(state->out_stream, "%s\n", argp_program_version);
+ exit(argp_err_exit_status);
+ 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("argp_parse");
+ exitcode = EX_OSERR;
+ goto end;
+ case EINVAL:
+ exitcode = EX_USAGE;
+ goto end;
+ }
+ }
+
+ if(not debug){
+ avahi_set_log_function(empty_log);
+ }
+
+ if(interface[0] == '\0'){
+ struct dirent **direntries;
+ ret = scandir(sys_class_net, &direntries, good_interface,
+ alphasort);
+ if(ret >= 1){
+ /* Pick the first good interface */
+ interface = strdup(direntries[0]->d_name);
+ if(debug){
+ fprintf(stderr, "Using interface \"%s\"\n", interface);
+ }
+ if(interface == NULL){
+ perror("malloc");
+ free(direntries);
exitcode = EXIT_FAILURE;
goto end;
}
- }
-
- pubkeyfilename = combinepath(keydir, pubkeyname);
- if (pubkeyfilename == NULL){
- perror("combinepath");
- exitcode = EXIT_FAILURE;
- goto end;
- }
-
- seckeyfilename = combinepath(keydir, seckeyname);
- if (seckeyfilename == NULL){
- perror("combinepath");
- exitcode = EXIT_FAILURE;
- goto end;
- }
-
- ret = init_gnutls_global(&mc, pubkeyfilename, seckeyfilename);
- if (ret == -1){
- fprintf(stderr, "init_gnutls_global failed\n");
- exitcode = EXIT_FAILURE;
- goto end;
+ free(direntries);
} else {
- gnutls_initalized = true;
- }
-
- /* If the interface is down, bring it up */
- {
- sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
- if(sd < 0) {
- perror("socket");
- exitcode = EXIT_FAILURE;
- goto end;
- }
- strcpy(network.ifr_name, interface);
- ret = ioctl(sd, SIOCGIFFLAGS, &network);
- if(ret == -1){
- perror("ioctl SIOCGIFFLAGS");
- exitcode = EXIT_FAILURE;
- goto end;
- }
- if((network.ifr_flags & IFF_UP) == 0){
- network.ifr_flags |= IFF_UP;
- ret = ioctl(sd, SIOCSIFFLAGS, &network);
- if(ret == -1){
- perror("ioctl SIOCSIFFLAGS");
- exitcode = EXIT_FAILURE;
- goto end;
- }
- }
- close(sd);
- }
-
- uid = getuid();
- gid = getgid();
-
- ret = setuid(uid);
- if (ret == -1){
- perror("setuid");
- }
-
- setgid(gid);
- if (ret == -1){
- perror("setgid");
- }
-
+ free(direntries);
+ fprintf(stderr, "Could not find a network interface\n");
+ exitcode = EXIT_FAILURE;
+ goto end;
+ }
+ }
+
+ /* 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));
+ mc.simple_poll = avahi_simple_poll_new();
+ if(mc.simple_poll == NULL){
+ fprintf(stderr, "Avahi: Failed to create simple poll object.\n");
+ exitcode = EX_UNAVAILABLE;
+ goto end;
+ }
+
+ sigemptyset(&sigterm_action.sa_mask);
+ ret = sigaddset(&sigterm_action.sa_mask, SIGINT);
+ if(ret == -1){
+ perror("sigaddset");
+ exitcode = EX_OSERR;
+ goto end;
+ }
+ ret = sigaddset(&sigterm_action.sa_mask, SIGHUP);
+ if(ret == -1){
+ perror("sigaddset");
+ exitcode = EX_OSERR;
+ goto end;
+ }
+ ret = sigaddset(&sigterm_action.sa_mask, SIGTERM);
+ if(ret == -1){
+ perror("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("sigaction");
+ return EX_OSERR;
+ }
+ if(old_sigterm_action.sa_handler != SIG_IGN){
+ ret = sigaction(SIGINT, &sigterm_action, NULL);
+ if(ret == -1){
+ perror("sigaction");
+ exitcode = EX_OSERR;
+ goto end;
+ }
+ }
+ ret = sigaction(SIGHUP, NULL, &old_sigterm_action);
+ if(ret == -1){
+ perror("sigaction");
+ return EX_OSERR;
+ }
+ if(old_sigterm_action.sa_handler != SIG_IGN){
+ ret = sigaction(SIGHUP, &sigterm_action, NULL);
+ if(ret == -1){
+ perror("sigaction");
+ exitcode = EX_OSERR;
+ goto end;
+ }
+ }
+ ret = sigaction(SIGTERM, NULL, &old_sigterm_action);
+ if(ret == -1){
+ perror("sigaction");
+ return EX_OSERR;
+ }
+ if(old_sigterm_action.sa_handler != SIG_IGN){
+ ret = sigaction(SIGTERM, &sigterm_action, NULL);
+ if(ret == -1){
+ perror("sigaction");
+ exitcode = EX_OSERR;
+ goto end;
+ }
+ }
+
+ /* If the interface is down, bring it up */
+ if(strcmp(interface, "none") != 0){
if_index = (AvahiIfIndex) if_nametoindex(interface);
if(if_index == 0){
fprintf(stderr, "No such interface: \"%s\"\n", interface);
- exit(EXIT_FAILURE);
- }
-
- if(connect_to != NULL){
- /* Connect directly, do not use Zeroconf */
- /* (Mainly meant for debugging) */
- char *address = strrchr(connect_to, ':');
- if(address == NULL){
- fprintf(stderr, "No colon in address\n");
- exitcode = EXIT_FAILURE;
- goto end;
- }
- errno = 0;
- uint16_t port = (uint16_t) strtol(address+1, NULL, 10);
- if(errno){
- perror("Bad port number");
- exitcode = EXIT_FAILURE;
- 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);
-
+ exitcode = EX_UNAVAILABLE;
+ goto end;
+ }
+
+ if(quit_now){
+ goto end;
+ }
+
+ /* Re-raise priviliges */
+ errno = 0;
+ ret = seteuid(0);
+ if(ret == -1){
+ perror("seteuid");
+ }
+
+#ifdef __linux__
+ /* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO
+ messages about the network interface to mess up the prompt */
+ ret = klogctl(8, NULL, 5);
+ bool restore_loglevel = true;
+ if(ret == -1){
+ restore_loglevel = false;
+ perror("klogctl");
+ }
+#endif /* __linux__ */
+
+ sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
+ if(sd < 0){
+ perror("socket");
+ exitcode = EX_OSERR;
+#ifdef __linux__
+ if(restore_loglevel){
+ ret = klogctl(7, NULL, 0);
+ if(ret == -1){
+ perror("klogctl");
+ }
+ }
+#endif /* __linux__ */
+ /* Lower privileges */
+ errno = 0;
+ ret = seteuid(uid);
+ if(ret == -1){
+ perror("seteuid");
+ }
+ goto end;
+ }
+ strcpy(network.ifr_name, interface);
+ ret = ioctl(sd, SIOCGIFFLAGS, &network);
+ if(ret == -1){
+ perror("ioctl SIOCGIFFLAGS");
+#ifdef __linux__
+ if(restore_loglevel){
+ ret = klogctl(7, NULL, 0);
+ if(ret == -1){
+ perror("klogctl");
+ }
+ }
+#endif /* __linux__ */
+ exitcode = EX_OSERR;
+ /* Lower privileges */
+ errno = 0;
+ ret = seteuid(uid);
+ if(ret == -1){
+ perror("seteuid");
+ }
+ goto end;
+ }
+ if((network.ifr_flags & IFF_UP) == 0){
+ network.ifr_flags |= IFF_UP;
+ take_down_interface = true;
+ ret = ioctl(sd, SIOCSIFFLAGS, &network);
+ if(ret == -1){
+ take_down_interface = false;
+ perror("ioctl SIOCSIFFLAGS +IFF_UP");
+ exitcode = EX_OSERR;
+#ifdef __linux__
+ if(restore_loglevel){
+ ret = klogctl(7, NULL, 0);
+ if(ret == -1){
+ perror("klogctl");
+ }
+ }
+#endif /* __linux__ */
+ /* Lower privileges */
+ errno = 0;
+ ret = seteuid(uid);
+ if(ret == -1){
+ perror("seteuid");
+ }
+ goto end;
+ }
+ }
+ /* sleep checking until interface is running */
+ for(int i=0; i < delay * 4; i++){
+ ret = ioctl(sd, SIOCGIFFLAGS, &network);
+ if(ret == -1){
+ perror("ioctl SIOCGIFFLAGS");
+ } else if(network.ifr_flags & IFF_RUNNING){
+ break;
+ }
+ struct timespec sleeptime = { .tv_nsec = 250000000 };
+ ret = nanosleep(&sleeptime, NULL);
+ if(ret == -1 and errno != EINTR){
+ perror("nanosleep");
+ }
+ }
+ if(not take_down_interface){
+ /* We won't need the socket anymore */
+ ret = (int)TEMP_FAILURE_RETRY(close(sd));
+ if(ret == -1){
+ perror("close");
+ }
+ }
+#ifdef __linux__
+ if(restore_loglevel){
+ /* Restores kernel loglevel to default */
+ ret = klogctl(7, NULL, 0);
+ if(ret == -1){
+ perror("klogctl");
+ }
+ }
+#endif /* __linux__ */
+ /* Lower privileges */
+ errno = 0;
+ if(take_down_interface){
+ /* Lower privileges */
+ ret = seteuid(uid);
+ if(ret == -1){
+ perror("seteuid");
+ }
+ } else {
+ /* Lower privileges permanently */
+ ret = setuid(uid);
+ if(ret == -1){
+ perror("setuid");
+ }
+ }
+ }
+
+ if(quit_now){
+ goto end;
+ }
+
+ ret = init_gnutls_global(pubkey, seckey);
+ if(ret == -1){
+ fprintf(stderr, "init_gnutls_global failed\n");
+ exitcode = EX_UNAVAILABLE;
+ goto end;
+ } else {
+ gnutls_initialized = true;
+ }
+
+ if(quit_now){
+ goto end;
+ }
+
+ tempdir_created = true;
+ if(mkdtemp(tempdir) == NULL){
+ tempdir_created = false;
+ perror("mkdtemp");
+ goto end;
+ }
+
+ if(quit_now){
+ goto end;
+ }
+
+ if(not init_gpgme(pubkey, seckey, tempdir)){
+ fprintf(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(stderr, "No colon in address\n");
+ exitcode = EX_USAGE;
+ goto end;
+ }
+
+ if(quit_now){
+ goto end;
+ }
+
+ uint16_t port;
+ errno = 0;
+ tmpmax = strtoimax(address+1, &tmp, 10);
+ if(errno != 0 or tmp == address+1 or *tmp != '\0'
+ or tmpmax != (uint16_t)tmpmax){
+ fprintf(stderr, "Bad port number\n");
+ exitcode = EX_USAGE;
+ goto end;
+ }
+
+ if(quit_now){
+ goto end;
+ }
+
+ port = (uint16_t)tmpmax;
+ *address = '\0';
+ address = connect_to;
+ /* Colon in address indicates IPv6 */
+ int af;
+ if(strchr(address, ':') != NULL){
+ af = AF_INET6;
+ } else {
+ af = AF_INET;
+ }
+
+ if(quit_now){
+ goto end;
+ }
+
+ while(not quit_now){
+ ret = start_mandos_communication(address, port, if_index, af);
+ if(quit_now or ret == 0){
+ break;
+ }
+ sleep(15);
+ };
+
+ 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
+ (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 = 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, NULL);
+ if(sb == NULL){
+ fprintf(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(stderr, "Starting Avahi loop search\n");
+ }
+
+ avahi_simple_poll_loop(mc.simple_poll);
+
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);
- free(pubkeyfilename);
- free(seckeyfilename);
-
- if (gnutls_initalized){
- gnutls_certificate_free_credentials(mc.cred);
- gnutls_global_deinit ();
- }
-
- return exitcode;
+
+ if(debug){
+ fprintf(stderr, "%s exiting\n", argv[0]);
+ }
+
+ /* Cleanup things */
+ if(sb != NULL)
+ avahi_s_service_browser_free(sb);
+
+ if(mc.server != NULL)
+ avahi_server_free(mc.server);
+
+ if(mc.simple_poll != NULL)
+ avahi_simple_poll_free(mc.simple_poll);
+
+ 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);
+ }
+
+ /* Take down the network interface */
+ if(take_down_interface){
+ /* Re-raise priviliges */
+ errno = 0;
+ ret = seteuid(0);
+ if(ret == -1){
+ perror("seteuid");
+ }
+ if(geteuid() == 0){
+ ret = ioctl(sd, SIOCGIFFLAGS, &network);
+ if(ret == -1){
+ perror("ioctl SIOCGIFFLAGS");
+ } else if(network.ifr_flags & IFF_UP) {
+ network.ifr_flags &= ~(short)IFF_UP; /* clear flag */
+ ret = ioctl(sd, SIOCSIFFLAGS, &network);
+ if(ret == -1){
+ perror("ioctl SIOCSIFFLAGS -IFF_UP");
+ }
+ }
+ ret = (int)TEMP_FAILURE_RETRY(close(sd));
+ if(ret == -1){
+ perror("close");
+ }
+ /* Lower privileges permanently */
+ errno = 0;
+ ret = setuid(uid);
+ if(ret == -1){
+ perror("setuid");
+ }
+ }
+ }
+
+ /* Removes the temp directory used by GPGME */
+ if(tempdir_created){
+ DIR *d;
+ struct dirent *direntry;
+ d = opendir(tempdir);
+ if(d == NULL){
+ if(errno != ENOENT){
+ perror("opendir");
+ }
+ } else {
+ while(true){
+ direntry = readdir(d);
+ if(direntry == NULL){
+ break;
+ }
+ /* Skip "." and ".." */
+ if(direntry->d_name[0] == '.'
+ and (direntry->d_name[1] == '\0'
+ or (direntry->d_name[1] == '.'
+ and direntry->d_name[2] == '\0'))){
+ continue;
+ }
+ char *fullname = NULL;
+ ret = asprintf(&fullname, "%s/%s", tempdir,
+ direntry->d_name);
+ if(ret < 0){
+ perror("asprintf");
+ continue;
+ }
+ ret = remove(fullname);
+ if(ret == -1){
+ fprintf(stderr, "remove(\"%s\"): %s\n", fullname,
+ strerror(errno));
+ }
+ free(fullname);
+ }
+ closedir(d);
+ }
+ ret = rmdir(tempdir);
+ if(ret == -1 and errno != ENOENT){
+ perror("rmdir");
+ }
+ }
+
+ if(quit_now){
+ 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("sigaction");
+ }
+ do {
+ ret = raise(signal_received);
+ } while(ret != 0 and errno == EINTR);
+ if(ret != 0){
+ perror("raise");
+ abort();
+ }
+ TEMP_FAILURE_RETRY(pause());
+ }
+
+ return exitcode;
}
=== renamed file 'plugins.d/password-request.xml' => 'plugins.d/mandos-client.xml'
--- plugins.d/password-request.xml 2008-08-18 05:24:20 +0000
+++ plugins.d/mandos-client.xml 2010-09-26 18:32:58 +0000
@@ -1,18 +1,19 @@
-
-
+
-
+
+
+
+%common;
]>
-
+
- &COMMANDNAME;
-
- &COMMANDNAME;
- &VERSION;
+ Mandos Manual
+
+ Mandos
+ &version;
+ &TIMESTAMP;
Björn
@@ -31,33 +32,13 @@
2008
- Teddy Hogeborn & Björn Påhlsson
+ 2009
+ Teddy Hogeborn
+ Björn Påhlsson
-
-
- This manual page is free software: you can redistribute it
- and/or modify it under the terms of the GNU General Public
- License as published by the Free Software Foundation,
- either version 3 of the License, or (at your option) any
- later version.
-
-
-
- This manual page is distributed in the hope that it will
- be useful, but WITHOUT ANY WARRANTY; without even the
- implied warranty of MERCHANTABILITY or FITNESS FOR A
- PARTICULAR PURPOSE. See the GNU General Public License
- for more details.
-
-
-
- You should have received a copy of the GNU General Public
- License along with this program; If not, see
- .
-
-
+
-
+
&COMMANDNAME;
8mandos
@@ -66,252 +47,608 @@
&COMMANDNAME;
- Client for mandos
+ Client for Mandos
-
+
&COMMANDNAME;
- --connectIP
- --keydirKEYDIR
- --interfaceINTERFACE
- --pubkeyPUBKEY
- --seckeySECKEY
- --priorityPRIORITY
- --dh-bitsBITS
- --debug
-
-
- &COMMANDNAME;
- --help
-
-
- &COMMANDNAME;
- --usage
-
-
- &COMMANDNAME;
- --version
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ &COMMANDNAME;
+
+
+
+
+
+
+ &COMMANDNAME;
+
+
+
+ &COMMANDNAME;
+
+
+
+
+
-
+
DESCRIPTION
- &COMMANDNAME; is a mandos plugin that works
- like a client program that through avahi detects mandos servers,
- sets up a gnutls connect and request a encrypted password. Any
- passwords given is automaticly decrypted and passed to
- cryptsetup.
-
-
+ &COMMANDNAME; is a client program that
+ communicates with mandos8
+ to get a password. In slightly more detail, this client program
+ brings up a network interface, uses the interface’s IPv6
+ link-local address to get network connectivity, uses Zeroconf to
+ find servers on the local network, and communicates with servers
+ using TLS with an OpenPGP key to ensure authenticity and
+ confidentiality. This client program keeps running, trying all
+ servers on the network, until it receives a satisfactory reply
+ or a TERM signal is received. If no servers are found, or after
+ all servers have been tried, it waits indefinitely for new
+ servers to appear.
+
+
+ This program is not meant to be run directly; it is really meant
+ to run as a plugin of the Mandos
+ plugin-runner
+ 8mandos, which runs in the
+ initial RAM disk environment because it is
+ specified as a keyscript
in the
+ crypttab5
+ file.
+
+
+
+
+ PURPOSE
+
+ The purpose of this is to enable remote and unattended
+ rebooting of client host computer with an
+ encrypted root file system. See for details.
+
+
OPTIONS
- Commonly not invoked as command lines but from configuration
- file of plugin runner.
+ This program is commonly not invoked from the command line; it
+ is normally started by the Mandos
+ plugin runner, see plugin-runner8mandos
+ . Any command line options this program accepts
+ are therefore normally provided by the plugin runner, and not
+ directly.
-
+
- -c, --connect=
- IP
-
-
- Connect directly to a specified mandos server
-
-
-
-
-
- -d, --keydir=
- KEYDIR
-
-
- Directory where the openpgp keyring is
-
-
-
-
-
- -i, --interface=
- INTERFACE
-
-
- Interface that Avahi will conntect through
-
-
-
-
-
- -p, --pubkey=
- PUBKEY
-
-
- Public openpgp key for gnutls authentication
-
-
-
-
-
- -s, --seckey=
- SECKEY
-
-
- Secret openpgp key for gnutls authentication
-
-
-
-
-
- --priority=PRIORITY
-
-
-
- GNUTLS priority
-
-
-
-
-
- --dh-bits=BITS
-
-
-
- dh-bits to use in gnutls communication
-
-
-
-
-
- --debug
-
-
- Debug mode
-
-
-
-
-
- -?, --help
-
-
- Gives a help message
-
-
-
-
-
- --usage
-
-
- Gives a short usage message
-
-
-
-
-
- -V, --version
-
-
- Prints the program version
-
-
-
+
+
+
+
+ Do not use Zeroconf to locate servers. Connect directly
+ to only one specified Mandos
+ server. Note that an IPv6 address has colon characters in
+ it, so the last colon character is
+ assumed to separate the address from the port number.
+
+
+ This option is normally only useful for testing and
+ debugging.
+
+
+
+
+
+
+
+
+
+ Network interface that will be brought up and scanned for
+ Mandos servers to connect to. The default is the empty
+ string, which will automatically choose an appropriate
+ interface.
+
+
+ If the option is used, this
+ specifies the interface to use to connect to the address
+ given.
+
+
+ Note that since this program will normally run in the
+ initial RAM disk environment, the interface must be an
+ interface which exists at that stage. Thus, the interface
+ can not be a pseudo-interface such as br0
+ or tun0
; such interfaces will not exist
+ until much later in the boot process, and can not be used
+ by this program.
+
+
+ NAME can be the string
+ none
; this will not use
+ any specific interface, and will not bring up an interface
+ on startup. This is not recommended, and only meant for
+ advanced users.
+
+
+
+
+
+
+
+
+
+ OpenPGP public key file name. The default name is
+ /conf/conf.d/mandos/pubkey.txt
.
+
+
+
+
+
+
+
+
+
+ OpenPGP secret key file name. The default name is
+ /conf/conf.d/mandos/seckey.txt
.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sets the number of bits to use for the prime number in the
+ TLS Diffie-Hellman key exchange. Default is 1024.
+
+
+
+
+
+
+
+
+ After bringing the network interface up, the program waits
+ for the interface to arrive in a running
+ state before proceeding. During this time, the kernel log
+ level will be lowered to reduce clutter on the system
+ 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.
+
+
+
+
+
+
+
+
+ Enable debug mode. This will enable a lot of output to
+ standard error about what the program is doing. The
+ program will still perform all other functions normally.
+
+
+ It will also enable debug mode in the Avahi and GnuTLS
+ libraries, making them print large amounts of debugging
+ output.
+
+
+
+
+
+
+
+
+
+ Gives a help message about options and their meanings.
+
+
+
+
+
+
+
+
+ Gives a short usage message.
+
+
+
+
+
+
+
+
+
+ Prints the program version.
+
+
+
-
+
+
+ OVERVIEW
+
+
+ This program is the client part. It is a plugin started by
+ plugin-runner
+ 8mandos which will run in
+ an initial RAM disk environment.
+
+
+ This program could, theoretically, be used as a keyscript in
+ /etc/crypttab, but it would then be
+ impossible to enter a password for the encrypted root disk at
+ the console, since this program does not read from the console
+ at all. This is why a separate plugin runner (
+ plugin-runner
+ 8mandos) is used to run
+ both this program and others in in parallel,
+ one of which will prompt for passwords on
+ the system console.
+
+
+
EXIT STATUS
+ This program will exit with a successful (zero) exit status if a
+ server could be found and the password received from it could be
+ successfully decrypted and output on standard output. The
+ program will exit with a non-zero exit status only if a critical
+ error occurs. Otherwise, it will forever connect to new
+ Mandos servers as they appear, trying
+ to get a decryptable password and print it.
-
+
ENVIRONMENT
+ This program does not use any environment variables, not even
+ the ones provided by cryptsetup8
+ .
-
-
-
+
+
+
FILES
-
-
-
-
-
- BUGS
-
-
-
-
+
+
+ /conf/conf.d/mandos/pubkey.txt
+ /conf/conf.d/mandos/seckey.txt
+
+
+ OpenPGP public and private key files, in ASCII
+ Armor
format. These are the default file names,
+ they can be changed with the and
+ options.
+
+
+
+
+
+
+
+
+
+
+
+
EXAMPLE
+ Note that normally, command line options will not be given
+ directly, but via options for the Mandos plugin-runner
+ 8mandos.
+
+
+ Normal invocation needs no options, if the network interface
+ is eth0
:
+
+
+ &COMMANDNAME;
+
+
+
+
+ Search for Mandos servers (and connect to them) using another
+ interface:
+
+
+
+ &COMMANDNAME; --interface eth1
+
+
+
+
+ Run in debug mode, and use a custom key:
+
+
+
+
+&COMMANDNAME; --debug --pubkey keydir/pubkey.txt --seckey keydir/seckey.txt
+
+
+
+
+
+ Run in debug mode, with a custom key, and do not use Zeroconf
+ to locate a server; connect directly to the IPv6 link-local
+ address fe80::aede:48ff:fe71:f6f2
, port 4711,
+ using interface eth2:
+
+
+
+
+&COMMANDNAME; --debug --pubkey keydir/pubkey.txt --seckey keydir/seckey.txt --connect fe80::aede:48ff:fe71:f6f2:4711 --interface eth2
+
+
+
-
+
SECURITY
+ This program is set-uid to root, but will switch back to the
+ original (and presumably non-privileged) user and group after
+ bringing up the network interface.
+
+
+ To use this program for its intended purpose (see ), the password for the root file system will
+ have to be given out to be stored in a server computer, after
+ having been encrypted using an OpenPGP key. This encrypted data
+ which will be stored in a server can only be decrypted by the
+ OpenPGP key, and the data will only be given out to those
+ clients who can prove they actually have that key. This key,
+ however, is stored unencrypted on the client side in its initial
+ RAM disk image file system. This is normally
+ readable by all, but this is normally fixed during installation
+ of this program; file permissions are set so that no-one is able
+ to read that file.
+
+
+ The only remaining weak point is that someone with physical
+ access to the client hard drive might turn off the client
+ computer, read the OpenPGP keys directly from the hard drive,
+ and communicate with the server. To safeguard against this, the
+ server is supposed to notice the client disappearing and stop
+ giving out the encrypted data. Therefore, it is important to
+ set the timeout and checker interval values tightly on the
+ server. See mandos8.
+
+
+ It will also help if the checker program on the server is
+ configured to request something from the client which can not be
+ spoofed by someone else on the network, unlike unencrypted
+ ICMP echo (ping
) replies.
+
+
+ Note: This makes it completely insecure to
+ have Mandos clients which dual-boot
+ to another operating system which is not
+ trusted to keep the initial RAM disk image
+ confidential.
-
+
SEE ALSO
-
-
- mandos
- 8
-
-
-
- plugin-runner
- 8mandos
-
-
-
- password-prompt
- 8mandos
-
-
-
- Zeroconf
-
-
-
- Avahi
-
-
-
- GnuTLS
-
-
-
-
- GPGME
-
-
-
- RFC 4880: OpenPGP Message
- Format
-
-
-
- RFC 5081: Using OpenPGP Keys for
- Transport Layer Security
-
-
-
- RFC 4291: IP Version 6 Addressing
- Architecture, section 2.5.6, Link-Local IPv6
- Unicast Addresses
-
-
+
+ cryptsetup
+ 8,
+ crypttab
+ 5,
+ mandos
+ 8,
+ password-prompt
+ 8mandos,
+ plugin-runner
+ 8mandos
+
+
+
+
+ Zeroconf
+
+
+
+ Zeroconf is the network protocol standard used for finding
+ Mandos servers on the local network.
+
+
+
+
+
+ Avahi
+
+
+
+ Avahi is the library this program calls to find Zeroconf
+ services.
+
+
+
+
+
+ GnuTLS
+
+
+
+ GnuTLS is the library this client uses to implement TLS for
+ communicating securely with the server, and at the same time
+ send the public OpenPGP key to the server.
+
+
+
+
+
+ GPGME
+
+
+
+ GPGME is the library used to decrypt the OpenPGP data sent
+ by the server.
+
+
+
+
+
+ RFC 4291: IP Version 6 Addressing
+ Architecture
+
+
+
+
+ Section 2.2: Text Representation of
+ Addresses
+
+
+
+ Section 2.5.5.2: IPv4-Mapped IPv6
+ Address
+
+
+
+ Section 2.5.6, Link-Local IPv6 Unicast
+ Addresses
+
+
+ This client uses IPv6 link-local addresses, which are
+ immediately usable since a link-local addresses is
+ automatically assigned to a network interfaces when it
+ is brought up.
+
+
+
+
+
+
+
+
+ RFC 4346: The Transport Layer Security (TLS)
+ Protocol Version 1.1
+
+
+
+ TLS 1.1 is the protocol implemented by GnuTLS.
+
+
+
+
+
+ RFC 4880: OpenPGP Message Format
+
+
+
+ The data received from the server is binary encrypted
+ OpenPGP data.
+
+
+
+
+
+ RFC 5081: Using OpenPGP Keys for Transport Layer
+ Security
+
+
+
+ This is implemented by GnuTLS and used by this program so
+ that OpenPGP keys can be used.
+
+
+
+
-
+
+
+
+
+
+
=== modified file 'plugins.d/password-prompt.c'
--- plugins.d/password-prompt.c 2008-08-16 16:58:31 +0000
+++ plugins.d/password-prompt.c 2010-09-26 18:32:58 +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 © 2007-2008 Teddy Hogeborn & Björn Påhlsson
+ * Password-prompt - Read a password from the terminal and print it
+ *
+ * Copyright © 2008-2010 Teddy Hogeborn
+ * Copyright © 2008-2010 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,8 +19,7 @@
* along with this program. If not, see
* .
*
- * Contact the authors at and
- * .
+ * Contact the authors at .
*/
#define _GNU_SOURCE /* getline() */
@@ -32,35 +32,47 @@
#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 /* EXIT_SUCCESS, EXIT_FAILURE,
- getopt_long, getenv() */
+ getenv() */
#include /* fprintf(), stderr, getline(),
- stdin, feof(), perror(), fputc(),
- stdout, getopt_long */
-#include /* errno, EINVAL */
+ stdin, feof(), fputc()
+ */
+#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 /* strlen, rindex */
#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 */
-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_version = "password-prompt " VERSION;
const char *argp_program_bug_address = "";
-static void termination_handler(__attribute__((unused))int signum){
- quit_now = true;
+static void termination_handler(int signum){
+ if(quit_now){
+ return;
+ }
+ quit_now = 1;
+ signal_received = signum;
}
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;
@@ -73,112 +85,173 @@
struct argp_option options[] = {
{ .name = "prefix", .key = 'p',
.arg = "PREFIX", .flags = 0,
- .doc = "Prefix used before the passprompt", .group = 2 },
+ .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) {
+
+ 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 Passprompt -- Provides a passprompt" };
- ret = argp_parse (&argp, argc, argv, 0, 0, NULL);
- if (ret == ARGP_ERR_UNKNOWN){
- fprintf(stderr, "Unknown error while parsing arguments\n");
- return EXIT_FAILURE;
+ .doc = "Mandos password-prompt -- Read and"
+ " output a password" };
+ ret = argp_parse(&argp, argc, argv,
+ ARGP_IN_ORDER | ARGP_NO_HELP, NULL, NULL);
+ switch(ret){
+ case 0:
+ break;
+ case ENOMEM:
+ default:
+ errno = ret;
+ error(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(debug){
fprintf(stderr, "Storing current terminal attributes\n");
}
- if (tcgetattr(STDIN_FILENO, &t_old) != 0){
- return EXIT_FAILURE;
+ if(tcgetattr(STDIN_FILENO, &t_old) != 0){
+ int e = errno;
+ error(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(0, errno, "sigaddset");
+ return EX_OSERR;
+ }
+ ret = sigaddset(&new_action.sa_mask, SIGHUP);
+ if(ret == -1){
+ error(0, errno, "sigaddset");
+ return EX_OSERR;
+ }
+ ret = sigaddset(&new_action.sa_mask, SIGTERM);
+ if(ret == -1){
+ error(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(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(0, errno, "sigaction");
+ return EX_OSERR;
}
}
ret = sigaction(SIGHUP, NULL, &old_action);
if(ret == -1){
- perror("sigaction");
- return EXIT_FAILURE;
+ error(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(0, errno, "sigaction");
+ return EX_OSERR;
}
}
ret = sigaction(SIGTERM, NULL, &old_action);
if(ret == -1){
- perror("sigaction");
- return EXIT_FAILURE;
+ error(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(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(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");
+ }
status = EXIT_FAILURE;
break;
}
@@ -187,52 +260,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){
- fprintf(stdout, "%s", buffer);
+ sret = getline(&buffer, &n, stdin);
+ if(sret > 0){
status = EXIT_SUCCESS;
+ /* Make n = data size instead of allocated buffer size */
+ n = (size_t)sret;
+ /* Strip final newline */
+ if(n > 0 and buffer[n-1] == '\n'){
+ buffer[n-1] = '\0'; /* not strictly necessary */
+ n--;
+ }
+ size_t written = 0;
+ while(written < n){
+ sret = write(STDOUT_FILENO, buffer + written, n - written);
+ if(sret < 0){
+ int e = errno;
+ error(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(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(0, errno, "getline");
+ switch(e){
+ case EBADF:
+ status = EX_UNAVAILABLE;
+ case EIO:
+ case EINVAL:
+ default:
+ status = EX_IOERR;
+ break;
+ }
break;
}
}
- /* if(ret == 0), then the only sensible thing to do is to retry to
+ /* 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 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");
+ }
}
- if (debug){
+ free(buffer);
+
+ if(debug){
fprintf(stderr, "Restoring terminal attributes\n");
}
- if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_old) != 0){
- perror("tcsetattr+echo");
- }
-
- if (debug){
- fprintf(stderr, "%s is exiting\n", argv[0]);
+ if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_old) != 0){
+ error(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(0, errno, "sigaction");
+ }
+ raise(signal_received);
+ }
+
+ if(debug){
+ fprintf(stderr, "%s is exiting with status %d\n", argv[0],
+ status);
+ }
+ if(status == EXIT_SUCCESS or status == EX_OK){
+ fputc('\n', stderr);
}
return status;
=== modified file 'plugins.d/password-prompt.xml'
--- plugins.d/password-prompt.xml 2008-08-18 05:24:20 +0000
+++ plugins.d/password-prompt.xml 2009-10-30 16:23:43 +0000
@@ -1,18 +1,19 @@
-
-
+
+
+
+%common;
]>
-
+
- &COMMANDNAME;
-
- &COMMANDNAME;
- &VERSION;
+ Mandos Manual
+
+ Mandos
+ &version;
+ &TIMESTAMP;
Björn
@@ -31,33 +32,13 @@
2008
- Teddy Hogeborn & Björn Påhlsson
+ 2009
+ Teddy Hogeborn
+ Björn Påhlsson
-
-
- This manual page is free software: you can redistribute it
- and/or modify it under the terms of the GNU General Public
- License as published by the Free Software Foundation,
- either version 3 of the License, or (at your option) any
- later version.
-
-
-
- This manual page is distributed in the hope that it will
- be useful, but WITHOUT ANY WARRANTY; without even the
- implied warranty of MERCHANTABILITY or FITNESS FOR A
- PARTICULAR PURPOSE. See the GNU General Public License
- for more details.
-
-
-
- You should have received a copy of the GNU General Public
- License along with this program; If not, see
- .
-
-
+
-
+
&COMMANDNAME;
8mandos
@@ -65,146 +46,263 @@
&COMMANDNAME;
-
- Passprompt for luks during boot sequence
-
+ Prompt for a password and output it.
&COMMANDNAME;
- --prefixPREFIX
- --debug
-
-
- &COMMANDNAME;
- --help
-
-
- &COMMANDNAME;
- --usage
-
-
- &COMMANDNAME;
- --version
-
+
+
+ PREFIX
+
+
+
+
+
+ &COMMANDNAME;
+
+
+
+
+
+
+ &COMMANDNAME;
+
+
+
+ &COMMANDNAME;
+
+
+
+
+
-
+
DESCRIPTION
- &COMMANDNAME; is a terminal program that ask for
- passwords during boot sequence. It is a plugin to
- mandos, and is used as a fallback and
- alternative to retriving passwords from a mandos server. During
- boot sequence the user is prompted for the disk password, and
- when a password is given it then gets forwarded to
- LUKS.
+ All &COMMANDNAME; does is prompt for a
+ password and output any given password to standard output.
+
+
+ This program is not very useful on its own. This program is
+ really meant to run as a plugin in the Mandos client-side system, where it is used as a
+ fallback and alternative to retrieving passwords from a
+ Mandos server.
+
+
+ This program is little more than a getpass3
+ wrapper, although actual use of that function is not guaranteed
+ or implied.
OPTIONS
- Commonly not invoked as command lines but from configuration
- file of plugin runner.
+ This program is commonly not invoked from the command line; it
+ is normally started by the Mandos
+ plugin runner, see plugin-runner8mandos
+ . Any command line options this program accepts
+ are therefore normally provided by the plugin runner, and not
+ directly.
-
+
- -p, --prefix=PREFIX
-
-
-
- Prefix used before the passprompt
-
-
-
-
-
- --debug
-
-
- Debug mode
-
-
-
-
-
- -?, --help
-
-
- Gives a help message
-
-
-
-
-
- --usage
-
-
- Gives a short usage message
-
-
-
-
-
- -V, --version
-
-
- Prints the program version
-
-
-
+
+
+
+
+ Prefix string shown before the password prompt.
+
+
+
+
+
+
+
+
+ Enable debug mode. This will enable a lot of output to
+ standard error about what the program is doing. The
+ program will still perform all other functions normally.
+
+
+
+
+
+
+
+
+
+ Gives a help message about options and their meanings.
+
+
+
+
+
+
+
+
+ Gives a short usage message.
+
+
+
+
+
+
+
+
+
+ Prints the program version.
+
+
+
-
+
EXIT STATUS
+ If exit status is 0, the output from the program is the password
+ as it was read. Otherwise, if exit status is other than 0, the
+ program has encountered an error, and any output so far could be
+ corrupt and/or truncated, and should therefore be ignored.
-
+
ENVIRONMENT
-
-
-
-
-
- FILES
-
-
+
+
+ CRYPTTAB_SOURCE
+ CRYPTTAB_NAME
+
+
+ If set, these environment variables will be assumed to
+ contain the source device name and the target device
+ mapper name, respectively, and will be shown as part of
+ the prompt.
+
+
+ These variables will normally be inherited from
+ plugin-runner
+ 8mandos, which will
+ normally have inherited them from
+ /scripts/local-top/cryptroot in the
+ initial RAM disk environment, which will
+ have set them from parsing kernel arguments and
+ /conf/conf.d/cryptroot (also in the
+ initial RAM disk environment), which in turn will have been
+ created when the initial RAM disk image was created by
+ /usr/share/initramfs-tools/hooks/cryptroot, by
+ extracting the information of the root file system from
+ /etc/crypttab.
+
+
+ This behavior is meant to exactly mirror the behavior of
+ askpass, the default password prompter.
+
+
+
+
BUGS
+ None are known at this time.
-
-
+
+
EXAMPLE
+ Note that normally, command line options will not be given
+ directly, but via options for the Mandos plugin-runner
+ 8mandos.
+
+
+ Normal invocation needs no options:
+
+
+ &COMMANDNAME;
+
+
+
+
+ Show a prefix before the prompt; in this case, a host name.
+ It might be useful to be reminded of which host needs a
+ password, in case of KVM switches, etc.
+
+
+
+
+&COMMANDNAME; --prefix=host.example.org:
+
+
+
+
+
+ Run in debug mode.
+
+
+
+ &COMMANDNAME; --debug
+
+
-
+
SECURITY
+ On its own, this program is very simple, and does not exactly
+ present any security risks. The one thing that could be
+ considered worthy of note is this: This program is meant to be
+ run by plugin-runner8mandos
+ , and will, when run standalone, outside, in a
+ normal environment, immediately output on its standard output
+ any presumably secret password it just received. Therefore,
+ when running this program standalone (which should never
+ normally be done), take care not to type in any real secret
+ password by force of habit, since it would then immediately be
+ shown as output.
+
+
+ To further alleviate any risk of being locked out of a system,
+ the plugin-runner
+ 8mandos has a fallback
+ mode which does the same thing as this program, only with less
+ features.
-
+
SEE ALSO
- mandos
- 8,
- plugin-runner
- 8mandos and
- password-request
+ crypttab
+ 5
+ mandos-client
8mandos
+ plugin-runner
+ 8mandos,
-
-
+
+
+
+
+
+
=== added file 'plugins.d/plymouth.c'
--- plugins.d/plymouth.c 1970-01-01 00:00:00 +0000
+++ plugins.d/plymouth.c 2010-09-26 18:32:58 +0000
@@ -0,0 +1,431 @@
+/* -*- coding: utf-8 -*- */
+/*
+ * Usplash - Read a password from usplash and output it
+ *
+ * Copyright © 2010 Teddy Hogeborn
+ * Copyright © 2010 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() */
+#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() */
+
+sig_atomic_t interrupted_by_signal = 0;
+const char plymouth_pid[] = "/dev/.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",
+ "--pid-file="
+ "/dev/.initramfs/"
+ "plymouth.pid",
+ NULL };
+
+static void termination_handler(__attribute__((unused))int signum){
+ if(interrupted_by_signal){
+ return;
+ }
+ interrupted_by_signal = 1;
+}
+
+/* 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;
+}
+
+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(0, errno, "setuid");
+ }
+
+ setsid();
+ ret = chdir("/");
+ if(ret == -1){
+ error(0, errno, "chdir");
+ return false;
+ }
+ ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace our stdout */
+ if(ret == -1){
+ error(0, errno, "dup2");
+ return false;
+ }
+ return true;
+}
+
+bool exec_and_wait(pid_t *pid_return, const char *path,
+ const char **argv, bool interruptable,
+ bool daemonize){
+ int status;
+ int ret;
+ pid_t pid;
+ pid = fork();
+ if(pid == -1){
+ error(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]!=(char *)NULL; i++){
+ tmp = realloc(new_argv, sizeof(const char *) * ((size_t)i + 1));
+ if (tmp == NULL){
+ error(0, errno, "realloc");
+ free(new_argv);
+ _exit(EX_OSERR);
+ }
+ new_argv = (char **)tmp;
+ new_argv[i] = strdup(argv[i]);
+ }
+ new_argv[i] = (char *) NULL;
+
+ execv(path, (char *const *)new_argv);
+ error(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(0, errno, "waitpid");
+ return false;
+ }
+ if(WIFEXITED(status) and (WEXITSTATUS(status) == 0)){
+ return true;
+ }
+ return false;
+}
+
+int is_plymouth(const struct dirent *proc_entry){
+ int ret;
+ {
+ uintmax_t maxvalue;
+ char *tmp;
+ errno = 0;
+ maxvalue = strtoumax(proc_entry->d_name, &tmp, 10);
+
+ if(errno != 0 or *tmp != '\0'
+ or maxvalue != (uintmax_t)((pid_t)maxvalue)){
+ return 0;
+ }
+ }
+ char exe_target[sizeof(plymouth_path)];
+ char *exe_link;
+ ret = asprintf(&exe_link, "/proc/%s/exe", proc_entry->d_name);
+ if(ret == -1){
+ error(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(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(plymouth_path)-1) or
+ (memcmp(plymouth_path, exe_target,
+ sizeof(plymouth_path)-1) != 0)){
+ return 0;
+ }
+ return 1;
+}
+
+pid_t get_pid(void){
+ int ret;
+ FILE *pidfile = fopen(plymouth_pid, "r");
+ uintmax_t maxvalue = 0;
+ if(pidfile != NULL){
+ ret = fscanf(pidfile, "%" SCNuMAX, &maxvalue);
+ if(ret != 1){
+ maxvalue = 0;
+ }
+ fclose(pidfile);
+ }
+ if(maxvalue == 0){
+ struct dirent **direntries;
+ ret = scandir("/proc", &direntries, is_plymouth, alphasort);
+ sscanf(direntries[0]->d_name, "%" SCNuMAX, &maxvalue);
+ }
+ pid_t pid;
+ pid = (pid_t)maxvalue;
+ if((uintmax_t)pid == maxvalue){
+ return pid;
+ }
+
+ return 0;
+}
+
+const char **getargv(pid_t pid){
+ int cl_fd;
+ char *cmdline_filename;
+ ssize_t sret;
+ int ret;
+
+ ret = asprintf(&cmdline_filename, "/proc/%" PRIuMAX "/cmdline",
+ (uintmax_t)pid);
+ if(ret == -1){
+ error(0, errno, "asprintf");
+ return NULL;
+ }
+
+ /* Open /proc//cmdline */
+ cl_fd = open(cmdline_filename, O_RDONLY);
+ free(cmdline_filename);
+ if(cl_fd == -1){
+ error(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(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(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(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(0, errno, "argv = malloc()");
+ free(cmdline);
+ return NULL;
+ }
+ argz_extract(cmdline, cmdline_len, argv); /* Create argv */
+ return (const char **)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(EX_OSERR, errno, "sigaddset");
+ }
+ ret = sigaction(*sig, NULL, &old_action);
+ if(ret == -1){
+ error(EX_OSERR, errno, "sigaction");
+ }
+ if(old_action.sa_handler != SIG_IGN){
+ ret = sigaction(*sig, &new_action, NULL);
+ if(ret == -1){
+ error(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(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 **plymouthd_argv;
+ pid_t pid = get_pid();
+ if(pid == 0){
+ error(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 2010-09-26 18:32:58 +0000
@@ -0,0 +1,278 @@
+
+
+
+
+%common;
+]>
+
+
+
+ Mandos Manual
+
+ Mandos
+ &version;
+ &TIMESTAMP;
+
+
+ Björn
+ Påhlsson
+
+ belorn@fukt.bsnet.se
+
+
+
+ Teddy
+ Hogeborn
+
+ teddy@fukt.bsnet.se
+
+
+
+
+ 2010
+ 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
+
+ crypttab
+ 5,
+ plugin-runner
+ 8mandos,
+ proc
+ 5,
+ plymouth
+ 8
+
+
+
+
+
+
+
+
=== added file 'plugins.d/splashy.c'
--- plugins.d/splashy.c 1970-01-01 00:00:00 +0000
+++ plugins.d/splashy.c 2010-09-26 18:32:58 +0000
@@ -0,0 +1,442 @@
+/* -*- coding: utf-8 -*- */
+/*
+ * Splashy - Read a password from splashy and output it
+ *
+ * Copyright © 2008-2010 Teddy Hogeborn
+ * Copyright © 2008-2010 Björn Påhlsson
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * .
+ *
+ * Contact the authors at .
+ */
+
+#define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), asprintf() */
+#include /* sig_atomic_t, struct sigaction,
+ sigemptyset(), sigaddset(), SIGINT,
+ SIGHUP, SIGTERM, sigaction,
+ SIG_IGN, kill(), SIGKILL */
+#include /* NULL */
+#include /* getenv() */
+#include /* asprintf() */
+#include /* EXIT_FAILURE, free(),
+ EXIT_SUCCESS */
+#include /* pid_t, DIR, struct dirent,
+ ssize_t */
+#include /* opendir(), readdir(), closedir() */
+#include /* intmax_t, strtoimax() */
+#include /* struct stat, lstat(), S_ISLNK */
+#include /* not, or, and */
+#include /* readlink(), fork(), execl(),
+ sleep(), dup2() STDERR_FILENO,
+ STDOUT_FILENO, _exit(),
+ pause() */
+#include /* memcmp() */
+#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 */
+
+sig_atomic_t interrupted_by_signal = 0;
+int signal_received;
+
+static void termination_handler(int signum){
+ if(interrupted_by_signal){
+ return;
+ }
+ interrupted_by_signal = 1;
+ signal_received = signum;
+}
+
+int main(__attribute__((unused))int argc,
+ __attribute__((unused))char **argv){
+ int ret = 0;
+ char *prompt = NULL;
+ DIR *proc_dir = NULL;
+ pid_t splashy_pid = 0;
+ pid_t splashy_command_pid = 0;
+ int exitstatus = EXIT_FAILURE;
+
+ /* Create prompt string */
+ {
+ const char *const cryptsource = getenv("cryptsource");
+ const char *const crypttarget = getenv("crypttarget");
+ const char *const prompt_start = "getpass "
+ "Enter passphrase to unlock the disk";
+
+ if(cryptsource == NULL){
+ if(crypttarget == NULL){
+ ret = asprintf(&prompt, "%s: ", prompt_start);
+ } else {
+ ret = asprintf(&prompt, "%s (%s): ", prompt_start,
+ crypttarget);
+ }
+ } else {
+ if(crypttarget == NULL){
+ ret = asprintf(&prompt, "%s %s: ", prompt_start, cryptsource);
+ } else {
+ ret = asprintf(&prompt, "%s %s (%s): ", prompt_start,
+ cryptsource, crypttarget);
+ }
+ }
+ if(ret == -1){
+ prompt = NULL;
+ exitstatus = EX_OSERR;
+ goto failure;
+ }
+ }
+
+ /* Find splashy process */
+ {
+ const char splashy_name[] = "/sbin/splashy";
+ proc_dir = opendir("/proc");
+ if(proc_dir == NULL){
+ int e = errno;
+ error(0, errno, "opendir");
+ switch(e){
+ case EACCES:
+ case ENOTDIR:
+ case ELOOP:
+ case ENOENT:
+ default:
+ exitstatus = EX_OSFILE;
+ break;
+ case ENAMETOOLONG:
+ case EMFILE:
+ case ENFILE:
+ case ENOMEM:
+ exitstatus = EX_OSERR;
+ break;
+ }
+ goto failure;
+ }
+ for(struct dirent *proc_ent = readdir(proc_dir);
+ proc_ent != NULL;
+ proc_ent = readdir(proc_dir)){
+ pid_t pid;
+ {
+ intmax_t tmpmax;
+ char *tmp;
+ errno = 0;
+ tmpmax = strtoimax(proc_ent->d_name, &tmp, 10);
+ if(errno != 0 or tmp == proc_ent->d_name or *tmp != '\0'
+ or tmpmax != (pid_t)tmpmax){
+ /* Not a process */
+ continue;
+ }
+ pid = (pid_t)tmpmax;
+ }
+ /* Find the executable name by doing readlink() on the
+ /proc//exe link */
+ char exe_target[sizeof(splashy_name)];
+ ssize_t sret;
+ {
+ char *exe_link;
+ ret = asprintf(&exe_link, "/proc/%s/exe", proc_ent->d_name);
+ if(ret == -1){
+ error(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(0, errno, "lstat");
+ free(exe_link);
+ switch(e){
+ case EACCES:
+ case ENOTDIR:
+ case ELOOP:
+ default:
+ exitstatus = EX_OSFILE;
+ break;
+ case ENAMETOOLONG:
+ exitstatus = EX_OSERR;
+ break;
+ }
+ goto failure;
+ }
+ if(not S_ISLNK(exe_stat.st_mode)
+ or exe_stat.st_uid != 0
+ or exe_stat.st_gid != 0){
+ free(exe_link);
+ continue;
+ }
+
+ sret = readlink(exe_link, exe_target, sizeof(exe_target));
+ free(exe_link);
+ }
+ if((sret == ((ssize_t)sizeof(exe_target)-1))
+ and (memcmp(splashy_name, exe_target,
+ sizeof(exe_target)-1) == 0)){
+ splashy_pid = pid;
+ break;
+ }
+ }
+ closedir(proc_dir);
+ proc_dir = NULL;
+ }
+ if(splashy_pid == 0){
+ exitstatus = EX_UNAVAILABLE;
+ goto failure;
+ }
+
+ /* Set up the signal handler */
+ {
+ struct sigaction old_action,
+ new_action = { .sa_handler = termination_handler,
+ .sa_flags = 0 };
+ sigemptyset(&new_action.sa_mask);
+ ret = sigaddset(&new_action.sa_mask, SIGINT);
+ if(ret == -1){
+ error(0, errno, "sigaddset");
+ exitstatus = EX_OSERR;
+ goto failure;
+ }
+ ret = sigaddset(&new_action.sa_mask, SIGHUP);
+ if(ret == -1){
+ error(0, errno, "sigaddset");
+ exitstatus = EX_OSERR;
+ goto failure;
+ }
+ ret = sigaddset(&new_action.sa_mask, SIGTERM);
+ if(ret == -1){
+ error(0, errno, "sigaddset");
+ exitstatus = EX_OSERR;
+ goto failure;
+ }
+ ret = sigaction(SIGINT, NULL, &old_action);
+ if(ret == -1){
+ error(0, errno, "sigaction");
+ exitstatus = EX_OSERR;
+ goto failure;
+ }
+ if(old_action.sa_handler != SIG_IGN){
+ ret = sigaction(SIGINT, &new_action, NULL);
+ if(ret == -1){
+ error(0, errno, "sigaction");
+ exitstatus = EX_OSERR;
+ goto failure;
+ }
+ }
+ ret = sigaction(SIGHUP, NULL, &old_action);
+ if(ret == -1){
+ error(0, errno, "sigaction");
+ exitstatus = EX_OSERR;
+ goto failure;
+ }
+ if(old_action.sa_handler != SIG_IGN){
+ ret = sigaction(SIGHUP, &new_action, NULL);
+ if(ret == -1){
+ error(0, errno, "sigaction");
+ exitstatus = EX_OSERR;
+ goto failure;
+ }
+ }
+ ret = sigaction(SIGTERM, NULL, &old_action);
+ if(ret == -1){
+ error(0, errno, "sigaction");
+ exitstatus = EX_OSERR;
+ goto failure;
+ }
+ if(old_action.sa_handler != SIG_IGN){
+ ret = sigaction(SIGTERM, &new_action, NULL);
+ if(ret == -1){
+ error(0, errno, "sigaction");
+ exitstatus = EX_OSERR;
+ goto failure;
+ }
+ }
+ }
+
+ if(interrupted_by_signal){
+ goto failure;
+ }
+
+ /* Fork off the splashy command to prompt for password */
+ splashy_command_pid = fork();
+ if(splashy_command_pid != 0 and interrupted_by_signal){
+ goto failure;
+ }
+ if(splashy_command_pid == -1){
+ error(0, errno, "fork");
+ exitstatus = EX_OSERR;
+ goto failure;
+ }
+ /* Child */
+ if(splashy_command_pid == 0){
+ if(not interrupted_by_signal){
+ const char splashy_command[] = "/sbin/splashy_update";
+ execl(splashy_command, splashy_command, prompt, (char *)NULL);
+ int e = errno;
+ error(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:
+ case ELIBBAD:
+ case EPERM:
+ _exit(EX_OSFILE);
+ }
+ }
+ free(prompt);
+ _exit(EXIT_FAILURE);
+ }
+
+ /* Parent */
+ free(prompt);
+ prompt = NULL;
+
+ if(interrupted_by_signal){
+ goto failure;
+ }
+
+ /* Wait for command to complete */
+ {
+ int status;
+ do {
+ ret = waitpid(splashy_command_pid, &status, 0);
+ } while(ret == -1 and errno == EINTR
+ and not interrupted_by_signal);
+ if(interrupted_by_signal){
+ goto failure;
+ }
+ if(ret == -1){
+ error(0, errno, "waitpid");
+ if(errno == ECHILD){
+ splashy_command_pid = 0;
+ }
+ } else {
+ /* The child process has exited */
+ splashy_command_pid = 0;
+ if(WIFEXITED(status) and WEXITSTATUS(status) == 0){
+ return EXIT_SUCCESS;
+ }
+ }
+ }
+
+ failure:
+
+ free(prompt);
+
+ if(proc_dir != NULL){
+ TEMP_FAILURE_RETRY(closedir(proc_dir));
+ }
+
+ if(splashy_command_pid != 0){
+ TEMP_FAILURE_RETRY(kill(splashy_command_pid, SIGTERM));
+
+ TEMP_FAILURE_RETRY(kill(splashy_pid, SIGTERM));
+ sleep(2);
+ while(TEMP_FAILURE_RETRY(kill(splashy_pid, 0)) == 0){
+ TEMP_FAILURE_RETRY(kill(splashy_pid, SIGKILL));
+ sleep(1);
+ }
+ pid_t new_splashy_pid = (pid_t)TEMP_FAILURE_RETRY(fork());
+ if(new_splashy_pid == 0){
+ /* Child; will become new splashy process */
+
+ /* Make the effective user ID (root) the only user ID instead of
+ the real user ID (_mandos) */
+ ret = setuid(geteuid());
+ if(ret == -1){
+ error(0, errno, "setuid");
+ }
+
+ setsid();
+ ret = chdir("/");
+ if(ret == -1){
+ error(0, errno, "chdir");
+ }
+/* if(fork() != 0){ */
+/* _exit(EXIT_SUCCESS); */
+/* } */
+ ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace stdout */
+ if(ret == -1){
+ error(0, errno, "dup2");
+ _exit(EX_OSERR);
+ }
+
+ execl("/sbin/splashy", "/sbin/splashy", "boot", (char *)NULL);
+ {
+ int e = errno;
+ error(0, errno, "execl");
+ switch(e){
+ case EACCES:
+ case ENOENT:
+ case ENOEXEC:
+ default:
+ _exit(EX_UNAVAILABLE);
+ case ENAMETOOLONG:
+ case E2BIG:
+ case ENOMEM:
+ _exit(EX_OSERR);
+ case ENOTDIR:
+ case ELOOP:
+ _exit(EX_OSFILE);
+ }
+ }
+ }
+ }
+
+ if(interrupted_by_signal){
+ struct sigaction signal_action;
+ sigemptyset(&signal_action.sa_mask);
+ signal_action.sa_handler = SIG_DFL;
+ ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received,
+ &signal_action, NULL));
+ if(ret == -1){
+ error(0, errno, "sigaction");
+ }
+ do {
+ ret = raise(signal_received);
+ } while(ret != 0 and errno == EINTR);
+ if(ret != 0){
+ error(0, errno, "raise");
+ abort();
+ }
+ TEMP_FAILURE_RETRY(pause());
+ }
+
+ return exitstatus;
+}
=== added file 'plugins.d/splashy.xml'
--- plugins.d/splashy.xml 1970-01-01 00:00:00 +0000
+++ plugins.d/splashy.xml 2009-01-04 21:54:55 +0000
@@ -0,0 +1,283 @@
+
+
+
+
+%common;
+]>
+
+
+
+ Mandos Manual
+
+ Mandos
+ &version;
+ &TIMESTAMP;
+
+
+ Björn
+ Påhlsson
+
+ belorn@fukt.bsnet.se
+
+
+
+ Teddy
+ Hogeborn
+
+ teddy@fukt.bsnet.se
+
+
+
+
+ 2008
+ 2009
+ 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
+
+ crypttab
+ 5,
+ plugin-runner
+ 8mandos,
+ proc
+ 5,
+ splashy
+ 8,
+ splashy_update
+ 8
+
+
+
+
+
+
+
+
=== removed file 'plugins.d/usplash'
--- plugins.d/usplash 2008-08-14 02:24:59 +0000
+++ plugins.d/usplash 1970-01-01 00:00:00 +0000
@@ -1,42 +0,0 @@
-#!/bin/sh -e
-
-# If not on a tty, then get rid of possibly disrupting stderr output
-if ! tty -s; then
- exec 2>/dev/null
-fi
-
-test -x /sbin/usplash
-
-usplash="`pidof usplash -o $$`"
-test -n "$usplash"
-
-# We get some variables from cryptsetup:
-# $cryptsource the device node, like "/dev/sda3"
-# $crypttarget the device mapper name, like "sda3_crypt".
-
-prompt="Enter passphrase to unlock"
-if [ -n "$crypttarget" ]; then
- prompt="$prompt the disk $crypttarget"
-fi
-if [ -n "$cryptsource" ]; then
- prompt="$prompt ($cryptsource)"
-fi
-
-splash_input_password(){
- test -p /dev/.initramfs/usplash_outfifo || return 1
- /sbin/usplash_write "INPUTQUIET $1" || return 1
- cat /dev/.initramfs/usplash_outfifo 2> /dev/null || return 1
-}
-
-# Usplash keeps waiting for input even if some other plugin provided
-# the password, so we must kill it
-trap "kill -TERM $usplash; sleep 2; kill -KILL $usplash;
- kill -TERM $$" TERM HUP
-
-password="`splash_input_password \"$prompt: \" password`"
-
-trap - TERM
-
-/sbin/usplash_write "TIMEOUT 15"
-
-echo -n "$password"
=== added file 'plugins.d/usplash.c'
--- plugins.d/usplash.c 1970-01-01 00:00:00 +0000
+++ plugins.d/usplash.c 2010-09-26 18:32:58 +0000
@@ -0,0 +1,658 @@
+/* -*- coding: utf-8 -*- */
+/*
+ * Usplash - Read a password from usplash and output it
+ *
+ * Copyright © 2008-2010 Teddy Hogeborn
+ * Copyright © 2008-2010 Björn Påhlsson
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * .
+ *
+ * Contact the authors at .
+ */
+
+#define _GNU_SOURCE /* asprintf(), TEMP_FAILURE_RETRY() */
+#include /* sig_atomic_t, struct sigaction,
+ sigemptyset(), sigaddset(), SIGINT,
+ SIGHUP, SIGTERM, sigaction(),
+ SIG_IGN, kill(), SIGKILL */
+#include /* bool, false, true */
+#include /* open(), O_WRONLY, O_RDONLY */
+#include /* and, or, not*/
+#include /* errno, EINTR */
+#include
+#include /* size_t, ssize_t, pid_t, DIR, struct
+ dirent */
+#include /* NULL */
+#include /* strlen(), memcmp() */
+#include /* asprintf()*/
+#include /* close(), write(), readlink(),
+ read(), STDOUT_FILENO, sleep(),
+ fork(), setuid(), geteuid(),
+ setsid(), chdir(), dup2(),
+ STDERR_FILENO, execv() */
+#include /* free(), EXIT_FAILURE, realloc(),
+ EXIT_SUCCESS, malloc(), _exit(),
+ getenv() */
+#include /* opendir(), readdir(), closedir() */
+#include /* intmax_t, strtoimax() */
+#include /* struct stat, lstat(), S_ISLNK */
+#include /* EX_OSERR, EX_UNAVAILABLE */
+#include /* argz_count(), argz_extract() */
+
+sig_atomic_t interrupted_by_signal = 0;
+int signal_received;
+const char usplash_name[] = "/sbin/usplash";
+
+static void termination_handler(int signum){
+ if(interrupted_by_signal){
+ return;
+ }
+ interrupted_by_signal = 1;
+ signal_received = signum;
+}
+
+static bool usplash_write(int *fifo_fd_r,
+ const char *cmd, const char *arg){
+ /*
+ * usplash_write(&fd, "TIMEOUT", "15") will write "TIMEOUT 15\0"
+ * usplash_write(&fd, "PULSATE", NULL) will write "PULSATE\0"
+ * SEE ALSO
+ * usplash_write(8)
+ */
+ int ret;
+ if(*fifo_fd_r == -1){
+ ret = open("/dev/.initramfs/usplash_fifo", O_WRONLY);
+ if(ret == -1){
+ return false;
+ }
+ *fifo_fd_r = ret;
+ }
+
+ const char *cmd_line;
+ size_t cmd_line_len;
+ char *cmd_line_alloc = NULL;
+ if(arg == NULL){
+ cmd_line = cmd;
+ cmd_line_len = strlen(cmd) + 1;
+ } else {
+ do {
+ ret = asprintf(&cmd_line_alloc, "%s %s", cmd, arg);
+ if(ret == -1){
+ int e = errno;
+ TEMP_FAILURE_RETRY(close(*fifo_fd_r));
+ errno = e;
+ return false;
+ }
+ } while(ret == -1);
+ cmd_line = cmd_line_alloc;
+ cmd_line_len = (size_t)ret + 1;
+ }
+
+ size_t written = 0;
+ ssize_t sret = 0;
+ while(written < cmd_line_len){
+ sret = write(*fifo_fd_r, cmd_line + written,
+ cmd_line_len - written);
+ if(sret == -1){
+ int e = errno;
+ TEMP_FAILURE_RETRY(close(*fifo_fd_r));
+ free(cmd_line_alloc);
+ errno = e;
+ return false;
+ }
+ written += (size_t)sret;
+ }
+ free(cmd_line_alloc);
+
+ return true;
+}
+
+/* Create prompt string */
+char *makeprompt(void){
+ int ret = 0;
+ char *prompt;
+ const char *const cryptsource = getenv("cryptsource");
+ const char *const crypttarget = getenv("crypttarget");
+ const char prompt_start[] = "Enter passphrase to unlock the disk";
+
+ if(cryptsource == NULL){
+ if(crypttarget == NULL){
+ ret = asprintf(&prompt, "%s: ", prompt_start);
+ } else {
+ ret = asprintf(&prompt, "%s (%s): ", prompt_start,
+ crypttarget);
+ }
+ } else {
+ if(crypttarget == NULL){
+ ret = asprintf(&prompt, "%s %s: ", prompt_start, cryptsource);
+ } else {
+ ret = asprintf(&prompt, "%s %s (%s): ", prompt_start,
+ cryptsource, crypttarget);
+ }
+ }
+ if(ret == -1){
+ return NULL;
+ }
+ return prompt;
+}
+
+pid_t find_usplash(char **cmdline_r, size_t *cmdline_len_r){
+ int ret = 0;
+ ssize_t sret = 0;
+ char *cmdline = NULL;
+ size_t cmdline_len = 0;
+ DIR *proc_dir = opendir("/proc");
+ if(proc_dir == NULL){
+ error(0, errno, "opendir");
+ return -1;
+ }
+ errno = 0;
+ for(struct dirent *proc_ent = readdir(proc_dir);
+ proc_ent != NULL;
+ proc_ent = readdir(proc_dir)){
+ pid_t pid;
+ {
+ intmax_t tmpmax;
+ char *tmp;
+ tmpmax = strtoimax(proc_ent->d_name, &tmp, 10);
+ if(errno != 0 or tmp == proc_ent->d_name or *tmp != '\0'
+ or tmpmax != (pid_t)tmpmax){
+ /* Not a process */
+ errno = 0;
+ continue;
+ }
+ pid = (pid_t)tmpmax;
+ }
+ /* Find the executable name by doing readlink() on the
+ /proc//exe link */
+ char exe_target[sizeof(usplash_name)];
+ {
+ /* create file name string */
+ char *exe_link;
+ ret = asprintf(&exe_link, "/proc/%s/exe", proc_ent->d_name);
+ if(ret == -1){
+ error(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(0, errno, "lstat");
+ free(exe_link);
+ goto fail_find_usplash;
+ }
+ if(not S_ISLNK(exe_stat.st_mode)
+ or exe_stat.st_uid != 0
+ or exe_stat.st_gid != 0){
+ free(exe_link);
+ continue;
+ }
+
+ sret = readlink(exe_link, exe_target, sizeof(exe_target));
+ free(exe_link);
+ }
+ /* Compare executable name */
+ if((sret != ((ssize_t)sizeof(exe_target)-1))
+ or (memcmp(usplash_name, exe_target,
+ sizeof(exe_target)-1) != 0)){
+ /* Not it */
+ continue;
+ }
+ /* Found usplash */
+ /* Read and save the command line of usplash in "cmdline" */
+ {
+ /* Open /proc//cmdline */
+ int cl_fd;
+ {
+ char *cmdline_filename;
+ ret = asprintf(&cmdline_filename, "/proc/%s/cmdline",
+ proc_ent->d_name);
+ if(ret == -1){
+ error(0, errno, "asprintf");
+ goto fail_find_usplash;
+ }
+ cl_fd = open(cmdline_filename, O_RDONLY);
+ free(cmdline_filename);
+ if(cl_fd == -1){
+ error(0, errno, "open");
+ goto fail_find_usplash;
+ }
+ }
+ size_t cmdline_allocated = 0;
+ char *tmp;
+ const size_t blocksize = 1024;
+ do {
+ /* Allocate more space? */
+ if(cmdline_len + blocksize > cmdline_allocated){
+ tmp = realloc(cmdline, cmdline_allocated + blocksize);
+ if(tmp == NULL){
+ error(0, errno, "realloc");
+ close(cl_fd);
+ goto fail_find_usplash;
+ }
+ cmdline = tmp;
+ cmdline_allocated += blocksize;
+ }
+ /* Read data */
+ sret = read(cl_fd, cmdline + cmdline_len,
+ cmdline_allocated - cmdline_len);
+ if(sret == -1){
+ error(0, errno, "read");
+ close(cl_fd);
+ goto fail_find_usplash;
+ }
+ cmdline_len += (size_t)sret;
+ } while(sret != 0);
+ ret = close(cl_fd);
+ if(ret == -1){
+ error(0, errno, "close");
+ goto fail_find_usplash;
+ }
+ }
+ /* Close directory */
+ ret = closedir(proc_dir);
+ if(ret == -1){
+ error(0, errno, "closedir");
+ goto fail_find_usplash;
+ }
+ /* Success */
+ *cmdline_r = cmdline;
+ *cmdline_len_r = cmdline_len;
+ return pid;
+ }
+
+ fail_find_usplash:
+
+ free(cmdline);
+ if(proc_dir != NULL){
+ int e = errno;
+ closedir(proc_dir);
+ errno = e;
+ }
+ return 0;
+}
+
+int main(__attribute__((unused))int argc,
+ __attribute__((unused))char **argv){
+ int ret = 0;
+ ssize_t sret;
+ int fifo_fd = -1;
+ int outfifo_fd = -1;
+ char *buf = NULL;
+ size_t buf_len = 0;
+ pid_t usplash_pid = -1;
+ bool usplash_accessed = false;
+ int status = EXIT_FAILURE; /* Default failure exit status */
+
+ char *prompt = makeprompt();
+ if(prompt == NULL){
+ status = EX_OSERR;
+ goto failure;
+ }
+
+ /* Find usplash process */
+ char *cmdline = NULL;
+ size_t cmdline_len = 0;
+ usplash_pid = find_usplash(&cmdline, &cmdline_len);
+ if(usplash_pid == 0){
+ status = EX_UNAVAILABLE;
+ goto failure;
+ }
+
+ /* Set up the signal handler */
+ {
+ struct sigaction old_action,
+ new_action = { .sa_handler = termination_handler,
+ .sa_flags = 0 };
+ sigemptyset(&new_action.sa_mask);
+ ret = sigaddset(&new_action.sa_mask, SIGINT);
+ if(ret == -1){
+ error(0, errno, "sigaddset");
+ status = EX_OSERR;
+ goto failure;
+ }
+ ret = sigaddset(&new_action.sa_mask, SIGHUP);
+ if(ret == -1){
+ error(0, errno, "sigaddset");
+ status = EX_OSERR;
+ goto failure;
+ }
+ ret = sigaddset(&new_action.sa_mask, SIGTERM);
+ if(ret == -1){
+ error(0, errno, "sigaddset");
+ status = EX_OSERR;
+ goto failure;
+ }
+ ret = sigaction(SIGINT, NULL, &old_action);
+ if(ret == -1){
+ if(errno != EINTR){
+ error(0, errno, "sigaction");
+ status = EX_OSERR;
+ }
+ goto failure;
+ }
+ if(old_action.sa_handler != SIG_IGN){
+ ret = sigaction(SIGINT, &new_action, NULL);
+ if(ret == -1){
+ if(errno != EINTR){
+ error(0, errno, "sigaction");
+ status = EX_OSERR;
+ }
+ goto failure;
+ }
+ }
+ ret = sigaction(SIGHUP, NULL, &old_action);
+ if(ret == -1){
+ if(errno != EINTR){
+ error(0, errno, "sigaction");
+ status = EX_OSERR;
+ }
+ goto failure;
+ }
+ if(old_action.sa_handler != SIG_IGN){
+ ret = sigaction(SIGHUP, &new_action, NULL);
+ if(ret == -1){
+ if(errno != EINTR){
+ error(0, errno, "sigaction");
+ status = EX_OSERR;
+ }
+ goto failure;
+ }
+ }
+ ret = sigaction(SIGTERM, NULL, &old_action);
+ if(ret == -1){
+ if(errno != EINTR){
+ error(0, errno, "sigaction");
+ status = EX_OSERR;
+ }
+ goto failure;
+ }
+ if(old_action.sa_handler != SIG_IGN){
+ ret = sigaction(SIGTERM, &new_action, NULL);
+ if(ret == -1){
+ if(errno != EINTR){
+ error(0, errno, "sigaction");
+ status = EX_OSERR;
+ }
+ goto failure;
+ }
+ }
+ }
+
+ usplash_accessed = true;
+ /* Write command to FIFO */
+ if(not usplash_write(&fifo_fd, "TIMEOUT", "0")){
+ if(errno != EINTR){
+ error(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(0, errno, "usplash_write");
+ status = EX_OSERR;
+ }
+ goto failure;
+ }
+
+ if(interrupted_by_signal){
+ goto failure;
+ }
+
+ free(prompt);
+ prompt = NULL;
+
+ /* Read reply from usplash */
+ /* Open FIFO */
+ outfifo_fd = open("/dev/.initramfs/usplash_outfifo", O_RDONLY);
+ if(outfifo_fd == -1){
+ if(errno != EINTR){
+ error(0, errno, "open");
+ status = EX_OSERR;
+ }
+ goto failure;
+ }
+
+ if(interrupted_by_signal){
+ goto failure;
+ }
+
+ /* Read from FIFO */
+ size_t buf_allocated = 0;
+ const size_t blocksize = 1024;
+ do {
+ /* Allocate more space */
+ if(buf_len + blocksize > buf_allocated){
+ char *tmp = realloc(buf, buf_allocated + blocksize);
+ if(tmp == NULL){
+ if(errno != EINTR){
+ error(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(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(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(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(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(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(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(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(0, errno, "malloc");
+ return status;
+ }
+ argz_extract(cmdline, cmdline_len, cmdline_argv); /* Create argv */
+
+ /* Kill old usplash */
+ kill(usplash_pid, SIGTERM);
+ sleep(2);
+ while(kill(usplash_pid, 0) == 0){
+ kill(usplash_pid, SIGKILL);
+ sleep(1);
+ }
+
+ pid_t new_usplash_pid = fork();
+ if(new_usplash_pid == 0){
+ /* Child; will become new usplash process */
+
+ /* Make the effective user ID (root) the only user ID instead of
+ the real user ID (_mandos) */
+ ret = setuid(geteuid());
+ if(ret == -1){
+ error(0, errno, "setuid");
+ }
+
+ setsid();
+ ret = chdir("/");
+ if(ret == -1){
+ error(0, errno, "chdir");
+ _exit(EX_OSERR);
+ }
+/* if(fork() != 0){ */
+/* _exit(EXIT_SUCCESS); */
+/* } */
+ ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace our stdout */
+ if(ret == -1){
+ error(0, errno, "dup2");
+ _exit(EX_OSERR);
+ }
+
+ execv(usplash_name, cmdline_argv);
+ if(not interrupted_by_signal){
+ error(0, errno, "execv");
+ }
+ free(cmdline);
+ free(cmdline_argv);
+ _exit(EX_OSERR);
+ }
+ free(cmdline);
+ free(cmdline_argv);
+ sleep(2);
+ if(not usplash_write(&fifo_fd, "PULSATE", NULL)){
+ if(errno != EINTR){
+ error(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(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(0, errno, "sigaction");
+ }
+ do {
+ ret = raise(signal_received);
+ } while(ret != 0 and errno == EINTR);
+ if(ret != 0){
+ error(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 2009-01-04 21:54:55 +0000
@@ -0,0 +1,297 @@
+
+
+
+
+%common;
+]>
+
+
+
+