=== 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
=== modified file 'Makefile'
--- Makefile 2009-10-25 19:16:56 +0000
+++ Makefile 2010-09-23 20:14:08 +0000
@@ -8,13 +8,15 @@
# -Wunreachable-code
#DEBUG=-ggdb3
# For info about _FORTIFY_SOURCE, see
-#
+#
+# and .
FORTIFY=-D_FORTIFY_SOURCE=2 -fstack-protector-all -fPIC
LINK_FORTIFY_LD=-z relro -z now
LINK_FORTIFY=
+
+# If BROKEN_PIE is set, do not build with -pie
ifndef BROKEN_PIE
FORTIFY += -fPIE
-LINK_FORTIFY_LD += -fPIE
LINK_FORTIFY += -pie
endif
#COVERAGE=--coverage
@@ -55,7 +57,7 @@
LDFLAGS=$(COVERAGE) $(LINK_FORTIFY) $(foreach flag,$(LINK_FORTIFY_LD),-Xlinker $(flag))
# Commands to format a DocBook document into a manual page
-DOCBOOKTOMAN=cd $(dir $<); xsltproc --nonet --xinclude \
+DOCBOOKTOMAN=$(strip cd $(dir $<); xsltproc --nonet --xinclude \
--param man.charmap.use.subset 0 \
--param make.year.ranges 1 \
--param make.single.year.ranges 1 \
@@ -63,11 +65,11 @@
--param man.authors.section.enabled 0 \
/usr/share/xml/docbook/stylesheet/nwalsh/manpages/docbook.xsl \
$(notdir $<); \
- $(MANPOST) $(notdir $@)
+ $(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=xsltproc --nonet --xinclude \
+DOCBOOKTOHTML=$(strip xsltproc --nonet --xinclude \
--param make.year.ranges 1 \
--param make.single.year.ranges 1 \
--param man.output.quietly 1 \
@@ -75,20 +77,22 @@
--param citerefentry.link 1 \
--output $@ \
/usr/share/xml/docbook/stylesheet/nwalsh/xhtml/docbook.xsl \
- $<; $(HTMLPOST) $@
+ $<; $(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/usplash plugins.d/splashy plugins.d/askpass-fifo \
+ plugins.d/plymouth
CPROGS=plugin-runner $(PLUGINS)
-PROGS=mandos mandos-keygen mandos-ctl $(CPROGS)
-DOCS=mandos.8 plugin-runner.8mandos mandos-keygen.8 \
+PROGS=mandos mandos-keygen mandos-ctl mandos-monitor $(CPROGS)
+DOCS=mandos.8 mandos-keygen.8 mandos-monitor.8 mandos-ctl.8 \
+ mandos.conf.5 mandos-clients.conf.5 plugin-runner.8mandos \
plugins.d/mandos-client.8mandos \
- plugins.d/password-prompt.8mandos mandos.conf.5 \
- plugins.d/usplash.8mandos plugins.d/splashy.8mandos \
- plugins.d/askpass-fifo.8mandos mandos-clients.conf.5
+ 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))
@@ -129,6 +133,20 @@
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)
@@ -156,39 +174,44 @@
# Update all these files with version number $(version)
common.ent: Makefile
- $(SED) --in-place \
+ $(strip $(SED) --in-place \
--expression='s/^\($$/\1$(version)">/' \
- $@
+ $@)
mandos: Makefile
- $(SED) --in-place \
+ $(strip $(SED) --in-place \
--expression='s/^\(version = "\)[^"]*"$$/\1$(version)"/' \
- $@
+ $@)
mandos-keygen: Makefile
- $(SED) --in-place \
+ $(strip $(SED) --in-place \
--expression='s/^\(VERSION="\)[^"]*"$$/\1$(version)"/' \
- $@
+ $@)
mandos-ctl: Makefile
- $(SED) --in-place \
- --expression='s/^\(version = "\)[^"]*"$$/\1$(version)"/' \
- $@
+ $(strip $(SED) --in-place \
+ --expression='s/^\(version = "\)[^"]*"$$/\1$(version)"/' \
+ $@)
+
+mandos-monitor: Makefile
+ $(strip $(SED) --in-place \
+ --expression='s/^\(version = "\)[^"]*"$$/\1$(version)"/' \
+ $@)
mandos.lsm: Makefile
- $(SED) --in-place \
+ $(strip $(SED) --in-place \
--expression='s/^\(Version:\).*/\1\t$(version)/' \
- $@
- $(SED) --in-place \
+ $@)
+ $(strip $(SED) --in-place \
--expression='s/^\(Entered-date:\).*/\1\t$(shell date --rfc-3339=date --reference=Makefile)/' \
- $@
- $(SED) --in-place \
+ $@)
+ $(strip $(SED) --in-place \
--expression='s/\(mandos_\)[0-9.]\+\(\.orig\.tar\.gz\)/\1$(version)\2/' \
- $@
+ $@)
-plugins.d/mandos-client: plugins.d/mandos-client.o
- $(LINK.o) $(GNUTLS_LIBS) $(AVAHI_LIBS) $(GPGME_LIBS) \
- $(COMMON) $^ $(LOADLIBES) $(LDLIBS) -o $@
+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 \
@@ -207,6 +230,17 @@
# Run the client with a local config and key
run-client: all keydir/seckey.txt keydir/pubkey.txt
+ @echo "###################################################################"
+ @echo "# The following error messages are harmless and can be safely #"
+ @echo "# ignored. The messages are caused by not running as root, but #"
+ @echo "# you should NOT run \"make run-client\" as root unless you also #"
+ @echo "# unpacked and compiled Mandos as root, which is NOT recommended. #"
+ @echo "# From plugin-runner: setuid: Operation not permitted #"
+ @echo "# From askpass-fifo: mkfifo: Permission denied #"
+ @echo "# From mandos-client: setuid: Operation not permitted #"
+ @echo "# seteuid: Operation not permitted #"
+ @echo "# klogctl: Operation not permitted #"
+ @echo "###################################################################"
./plugin-runner --plugin-dir=plugins.d \
--config-file=plugin-runner.conf \
--options-for=mandos-client:--seckey=keydir/seckey.txt,--pubkey=keydir/pubkey.txt \
@@ -219,6 +253,12 @@
# 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
@@ -241,10 +281,16 @@
install-server: doc
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 \
@@ -254,6 +300,10 @@
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 \
@@ -286,6 +336,9 @@
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 \
$(INITRAMFSTOOLS)/hooks/mandos
install --mode=u=rw,go=r initramfs-tools-hook-conf \
@@ -297,16 +350,18 @@
> $(MANDIR)/man8/mandos-keygen.8.gz
gzip --best --to-stdout plugin-runner.8mandos \
> $(MANDIR)/man8/plugin-runner.8mandos.gz
+ gzip --best --to-stdout plugins.d/mandos-client.8mandos \
+ > $(MANDIR)/man8/mandos-client.8mandos.gz
gzip --best --to-stdout plugins.d/password-prompt.8mandos \
> $(MANDIR)/man8/password-prompt.8mandos.gz
- gzip --best --to-stdout plugins.d/mandos-client.8mandos \
- > $(MANDIR)/man8/mandos-client.8mandos.gz
gzip --best --to-stdout plugins.d/usplash.8mandos \
> $(MANDIR)/man8/usplash.8mandos.gz
gzip --best --to-stdout plugins.d/splashy.8mandos \
> $(MANDIR)/man8/splashy.8mandos.gz
gzip --best --to-stdout plugins.d/askpass-fifo.8mandos \
> $(MANDIR)/man8/askpass-fifo.8mandos.gz
+ gzip --best --to-stdout plugins.d/plymouth.8mandos \
+ > $(MANDIR)/man8/plymouth.8mandos.gz
install-client: install-client-nokey
# Post-installation stuff
@@ -318,7 +373,11 @@
uninstall-server:
-rm --force $(PREFIX)/sbin/mandos \
+ $(PREFIX)/sbin/mandos-ctl \
+ $(PREFIX)/sbin/mandos-monitor \
$(MANDIR)/man8/mandos.8.gz \
+ $(MANDIR)/man8/mandos-monitor.8.gz \
+ $(MANDIR)/man8/mandos-ctl.8.gz \
$(MANDIR)/man5/mandos.conf.5.gz \
$(MANDIR)/man5/mandos-clients.conf.5.gz
update-rc.d -f mandos remove
@@ -336,16 +395,18 @@
$(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/usplash.8mandos.gz \
$(MANDIR)/man8/splashy.8mandos.gz \
$(MANDIR)/man8/askpass-fifo.8mandos.gz \
- $(MANDIR)/man8/mandos-client.8mandos.gz
+ $(MANDIR)/man8/plymouth.8mandos.gz \
-rmdir $(PREFIX)/lib/mandos/plugins.d $(CONFDIR)/plugins.d \
$(PREFIX)/lib/mandos $(CONFDIR) $(KEYDIR)
update-initramfs -k all -u
@@ -354,6 +415,7 @@
purge-server: uninstall-server
-rm --force $(CONFDIR)/mandos.conf $(CONFDIR)/clients.conf \
+ $(DESTDIR)/etc/dbus-1/system.d/mandos.conf
$(DESTDIR)/etc/default/mandos \
$(DESTDIR)/etc/init.d/mandos \
$(DESTDIR)/var/run/mandos.pid
=== modified file 'README'
--- README 2009-02-23 11:52:42 +0000
+++ README 2010-09-26 18:32:58 +0000
@@ -99,7 +99,7 @@
Multiple Mandos servers can coexist on a network without any
trouble. They do not clash, and clients will try all available
servers. This means that if just one reboots then the other can
- bring it back up, but if both reboots at the same time they will
+ 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?
@@ -144,7 +144,9 @@
plugins to run, all competing to be the first to find a password and
provide it to the plugin runner.
- Three additional plugins are provided:
+ 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)
@@ -160,8 +162,8 @@
* Copyright
- Copyright © 2008,2009 Teddy Hogeborn
- Copyright © 2008,2009 Björn Påhlsson
+ Copyright © 2008-2010 Teddy Hogeborn
+ Copyright © 2008-2010 Björn Påhlsson
** License:
=== modified file 'TODO'
--- TODO 2009-09-17 11:47:22 +0000
+++ TODO 2010-09-26 18:32:58 +0000
@@ -1,71 +1,109 @@
-*- org -*-
+* Use _attribute_((nonnull)) wherever possible.
+
* mandos-client
** TODO [#B] use scandir(3) instead of readdir(3)
-** TODO [#B] Prefix all debug output with argv[0]
+** 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 " + argv[0]
+** 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 " + argv[0]
+** 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 " + argv[0]
+** 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 " + argv[0]
+** TODO [#B] Prefix all debug output with "Mandos plugin " + program_invocation_short_name
+** TODO [#B] lock stdin (with flock()?)
+
+* TODO [#B] passdev
* plugin-runner
** TODO [#B] use scandir(3) instead of readdir(3)
** TODO [#C] use same file name rules as run-parts(8)
+** kernel command line option for debug info
+** TODO [#B] Use openat()
* mandos (server)
-** TODO [#B] Log level :BUGS:
-** TODO /etc/mandos/clients.d/*.conf
- Watch this directory and add/remove/update clients?
-** TODO config for TXT record
-** TODO [#B] Run-time communication with server :BUGS:
- Probably using D-Bus
-*** Client class
-*** Main server
- + SetLogLevel
- syslogger.setLevel(logging.WARNING)
- + [[http://log.ometer.com/2007-05.html][Best D-Bus practices]]
-** TODO Implement --foreground :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:
+** TODO Date+time on console log messages :BUGS:
Is this the default?
-** TODO DBusServiceObjectUsingSuper
-** Global enable/disable flag
-** By-client countdown on secrets given
-** Fix problem with fsck taking a really long time
+** 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
-** [[file:mandos.xml::XXX][Document D-Bus interface]]
-
-* Provide and install /etc/dbus-1/system.d/mandos.conf
+** 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
-** D-Bus mail loop w/ signal receiver
-** Snack/Newt client data displayer
-*** Client Widgets
+** 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
@@ -75,7 +113,9 @@
For testing decryption before rebooting.
* Makefile
-** Implement DEB_BUILD_OPTIONS
+** 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
@@ -86,5 +126,9 @@
** TODO [#C] /etc/bash_completion.d/mandos
From XML sources directly?
+* 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 2009-01-08 03:54:06 +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
-# above "timeout" 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
@@ -62,4 +71,9 @@
;
;# 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 '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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
=== modified file 'debian/control'
--- debian/control 2009-09-17 11:47:22 +0000
+++ debian/control 2010-09-26 18:32:58 +0000
@@ -15,8 +15,8 @@
Package: mandos
Architecture: all
Depends: ${misc:Depends}, python (>=2.5), python-gnutls, python-dbus,
- python-avahi, python-gobject, avahi-daemon, gnupg (< 2),
- adduser
+ 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
@@ -35,7 +35,8 @@
Package: mandos-client
Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, cryptsetup
+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
=== modified file 'debian/copyright'
--- debian/copyright 2009-01-04 21:54:55 +0000
+++ debian/copyright 2010-09-26 18:32:58 +0000
@@ -5,8 +5,8 @@
Upstream-Source:
Files: *
-Copyright: Copyright © 2008,2009 Teddy Hogeborn
-Copyright: Copyright © 2008,2009 Björn Påhlsson
+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
=== modified file 'debian/mandos-client.README.Debian'
--- debian/mandos-client.README.Debian 2009-09-08 06:28:20 +0000
+++ debian/mandos-client.README.Debian 2010-09-27 17:53:53 +0000
@@ -1,9 +1,11 @@
* Choose the Client Network Interface
- You MUST make sure that the correct network interface is specified
- in the DEVICE setting in the "/etc/initramfs-tools/initramfs.conf"
- file. *If* this is changed, it will be necessary to update the
- initrd image by running the command
+ 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
@@ -74,9 +76,6 @@
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
@@ -84,4 +83,4 @@
work, "--options-for=mandos-client:--connect=:" needs
to be manually added to the file "/etc/mandos/plugin-runner.conf".
- -- Teddy Hogeborn , Tue, 8 Sep 2009 08:25:58 +0200
+ -- Teddy Hogeborn , Mon, 27 Sep 2010 19:53:21 +0200
=== modified file 'debian/mandos.dirs'
--- debian/mandos.dirs 2008-09-17 00:34:09 +0000
+++ debian/mandos.dirs 2010-09-15 17:33:14 +0000
@@ -2,4 +2,5 @@
usr/share/man/man8
etc/init.d
etc/default
+etc/dbus-1/system.d
usr/sbin
=== modified file 'debian/mandos.docs'
--- debian/mandos.docs 2008-10-18 11:17:22 +0000
+++ debian/mandos.docs 2010-09-12 03:00:40 +0000
@@ -1,3 +1,4 @@
NEWS
README
TODO
+DBUS-API
=== modified file 'debian/rules'
--- debian/rules 2009-10-25 19:06:56 +0000
+++ debian/rules 2010-09-09 18:16:14 +0000
@@ -15,13 +15,18 @@
# This has to be exported to make some magic below work.
export DH_OPTIONS
-ifneq (,$(findstring :$(DEB_HOST_ARCH):,:mips:mipsel:))
- BROKEN_PIE := yes
- export BROKEN_PIE
-endif
-ifneq (,$(findstring :$(DEB_BUILD_ARCH):,:mips:mipsel:))
- BROKEN_PIE := yes
- export BROKEN_PIE
+# -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
=== modified file 'debian/watch'
--- debian/watch 2009-01-15 02:52:02 +0000
+++ debian/watch 2010-09-15 17:17:46 +0000
@@ -1,2 +1,2 @@
version=3
-ftp://ftp.fukt.bsnet.se/pub/mandos/mandos[-_]([^\s]+?)(?:\.orig)?\.tar\.(?:gz|bz2|7z)
+ftp://ftp.fukt.bsnet.se/pub/mandos/mandos[-_]([^\s]+?)(?:\.orig)?\.tar\.(?:gz|bz2|7z|xz)
=== modified file 'mandos'
--- mandos 2009-10-25 19:16:56 +0000
+++ mandos 2010-09-27 17:39:03 +0000
@@ -11,8 +11,8 @@
# "AvahiService" class, and some lines in "main".
#
# Everything else is
-# Copyright © 2008,2009 Teddy Hogeborn
-# Copyright © 2008,2009 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
@@ -55,10 +55,12 @@
import logging
import logging.handlers
import pwd
-from contextlib import closing
+import contextlib
import struct
import fcntl
import functools
+import cPickle as pickle
+import multiprocessing
import dbus
import dbus.service
@@ -67,6 +69,8 @@
from dbus.mainloop.glib import DBusGMainLoop
import ctypes
import ctypes.util
+import xml.dom.minidom
+import inspect
try:
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
@@ -79,6 +83,7 @@
version = "1.0.14"
+#logger = logging.getLogger(u'mandos')
logger = logging.Logger(u'mandos')
syslogger = (logging.handlers.SysLogHandler
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
@@ -152,15 +157,20 @@
u" after %i retries, exiting.",
self.rename_count)
raise AvahiServiceError(u"Too many renames")
- self.name = self.server.GetAlternativeServiceName(self.name)
+ self.name = unicode(self.server.GetAlternativeServiceName(self.name))
logger.info(u"Changing Zeroconf service name to %r ...",
- unicode(self.name))
+ 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"""
@@ -174,7 +184,8 @@
self.server.EntryGroupNew()),
avahi.DBUS_INTERFACE_ENTRY_GROUP)
self.group.connect_to_signal('StateChanged',
- self.entry_group_state_changed)
+ self
+ .entry_group_state_changed)
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
self.name, self.type)
self.group.AddService(
@@ -188,7 +199,7 @@
self.group.Commit()
def entry_group_state_changed(self, state, error):
"""Derived from the Avahi example code"""
- logger.debug(u"Avahi state change: %i", state)
+ logger.debug(u"Avahi entry group state change: %i", state)
if state == avahi.ENTRY_GROUP_ESTABLISHED:
logger.debug(u"Zeroconf service established.")
@@ -207,6 +218,7 @@
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()
@@ -228,47 +240,60 @@
"""A representation of a client host served by this server.
Attributes:
- name: string; from the config file, used in log messages and
- D-Bus identifiers
- fingerprint: string (40 or 32 hexadecimal digits); used to
- uniquely identify the client
- secret: bytestring; sent verbatim (over TLS) to client
- host: string; available for use by the checker command
- created: datetime.datetime(); (UTC) object creation
- last_enabled: datetime.datetime(); (UTC)
- enabled: bool()
- last_checked_ok: datetime.datetime(); (UTC) or None
- timeout: datetime.timedelta(); How long from last_checked_ok
- until this client is invalid
- interval: datetime.timedelta(); How often to start a new checker
- disable_hook: If set, called by disable() as disable_hook(self)
+ _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_initiator_tag: a gobject event source tag, or None
- disable_initiator_tag: - '' -
- checker_callback_tag: - '' -
- checker_command: string; External command which is run to check if
- client lives. %() expansions are done at
+ 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
+ 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.
"""
+ 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 _datetime_to_milliseconds(dt):
- "Convert a datetime.datetime() to milliseconds"
- return ((dt.days * 24 * 60 * 60 * 1000)
- + (dt.seconds * 1000)
- + (dt.microseconds // 1000))
+ 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._datetime_to_milliseconds(self.timeout)
+ return self._timedelta_to_milliseconds(self.timeout)
def interval_milliseconds(self):
"Return the 'interval' attribute in milliseconds"
- return self._datetime_to_milliseconds(self.interval)
+ 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
@@ -287,9 +312,9 @@
if u"secret" in config:
self.secret = config[u"secret"].decode(u"base64")
elif u"secfile" in config:
- with closing(open(os.path.expanduser
- (os.path.expandvars
- (config[u"secfile"])))) as secfile:
+ 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"
@@ -297,6 +322,7 @@
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[u"timeout"])
@@ -309,31 +335,49 @@
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))
- # Also start a new checker *right now*.
- self.start_checker()
# Schedule a disable() when 'timeout' has passed
self.disable_initiator_tag = (gobject.timeout_add
(self.timeout_milliseconds(),
self.disable))
self.enabled = True
+ # Also start a new checker *right now*.
+ self.start_checker()
- def disable(self):
+ def disable(self, quiet=True):
"""Disable this client."""
if not getattr(self, "enabled", False):
return False
- logger.info(u"Disabling client %s", self.name)
+ 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
@@ -380,6 +424,9 @@
(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.
@@ -391,12 +438,17 @@
# 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
- if self.checker is not None:
+ 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)
@@ -409,12 +461,14 @@
command = self.checker_command % self.host
except TypeError:
# Escape attributes for the shell
- escaped_attrs = dict((key,
- re.escape(unicode(str(val),
- errors=
- u'replace')))
- for key, val in
- vars(self).iteritems())
+ escaped_attrs = dict(
+ (attr,
+ re.escape(unicode(str(getattr(self, attr, u"")),
+ errors=
+ u'replace')))
+ for attr in
+ self.runtime_expansions)
+
try:
command = self.checker_command % escaped_attrs
except TypeError, error:
@@ -458,44 +512,245 @@
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?"""
- if not getattr(self, u"enabled", False):
- return False
- now = datetime.datetime.utcnow()
- if self.last_checked_ok is None:
- return now < (self.created + self.timeout)
- else:
- return now < (self.last_checked_ok + self.timeout)
-
-
-class ClientDBus(Client, dbus.service.Object):
+
+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/"
- + self.name.replace(u".", u"_")))
- dbus.service.Object.__init__(self, self.bus,
- self.dbus_object_path)
+ (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):
@@ -508,20 +763,20 @@
r = Client.enable(self)
if oldstate != self.enabled:
# Emit D-Bus signals
- self.PropertyChanged(dbus.String(u"enabled"),
+ self.PropertyChanged(dbus.String(u"Enabled"),
dbus.Boolean(True, variant_level=1))
self.PropertyChanged(
- dbus.String(u"last_enabled"),
+ dbus.String(u"LastEnabled"),
self._datetime_to_dbus(self.last_enabled,
variant_level=1))
return r
- def disable(self, signal = True):
+ def disable(self, quiet = False):
oldstate = getattr(self, u"enabled", False)
- r = Client.disable(self)
- if signal and oldstate != self.enabled:
+ r = Client.disable(self, quiet=quiet)
+ if not quiet and oldstate != self.enabled:
# Emit D-Bus signal
- self.PropertyChanged(dbus.String(u"enabled"),
+ self.PropertyChanged(dbus.String(u"Enabled"),
dbus.Boolean(False, variant_level=1))
return r
@@ -530,8 +785,8 @@
self.remove_from_connection()
except LookupError:
pass
- if hasattr(dbus.service.Object, u"__del__"):
- dbus.service.Object.__del__(self, *args, **kwargs)
+ if hasattr(DBusObjectWithProperties, u"__del__"):
+ DBusObjectWithProperties.__del__(self, *args, **kwargs)
Client.__del__(self, *args, **kwargs)
def checker_callback(self, pid, condition, command,
@@ -539,7 +794,7 @@
self.checker_callback_tag = None
self.checker = None
# Emit D-Bus signal
- self.PropertyChanged(dbus.String(u"checker_running"),
+ self.PropertyChanged(dbus.String(u"CheckerRunning"),
dbus.Boolean(False, variant_level=1))
if os.WIFEXITED(condition):
exitstatus = os.WEXITSTATUS(condition)
@@ -560,11 +815,20 @@
r = Client.checked_ok(self, *args, **kwargs)
# Emit D-Bus signal
self.PropertyChanged(
- dbus.String(u"last_checked_ok"),
+ 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:
@@ -578,7 +842,7 @@
# Emit D-Bus signal
self.CheckerStarted(self.current_checker_command)
self.PropertyChanged(
- dbus.String(u"checker_running"),
+ dbus.String(u"CheckerRunning"),
dbus.Boolean(True, variant_level=1))
return r
@@ -587,17 +851,26 @@
r = Client.stop_checker(self, *args, **kwargs)
if (old_checker is not None
and getattr(self, u"checker", None) is None):
- self.PropertyChanged(dbus.String(u"checker_running"),
+ self.PropertyChanged(dbus.String(u"CheckerRunning"),
dbus.Boolean(False, variant_level=1))
return r
-
- ## D-Bus methods & signals
+
+ 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"
- # CheckedOK - method
- @dbus.service.method(_interface)
- def CheckedOK(self):
- return self.checked_ok()
+ ## Signals
# CheckerCompleted - signal
@dbus.service.signal(_interface, signature=u"nxs")
@@ -611,115 +884,44 @@
"D-Bus signal"
pass
- # GetAllProperties - method
- @dbus.service.method(_interface, out_signature=u"a{sv}")
- def GetAllProperties(self):
- "D-Bus method"
- return dbus.Dictionary({
- dbus.String(u"name"):
- dbus.String(self.name, variant_level=1),
- dbus.String(u"fingerprint"):
- dbus.String(self.fingerprint, variant_level=1),
- dbus.String(u"host"):
- dbus.String(self.host, variant_level=1),
- dbus.String(u"created"):
- self._datetime_to_dbus(self.created,
- variant_level=1),
- dbus.String(u"last_enabled"):
- (self._datetime_to_dbus(self.last_enabled,
- variant_level=1)
- if self.last_enabled is not None
- else dbus.Boolean(False, variant_level=1)),
- dbus.String(u"enabled"):
- dbus.Boolean(self.enabled, variant_level=1),
- dbus.String(u"last_checked_ok"):
- (self._datetime_to_dbus(self.last_checked_ok,
- variant_level=1)
- if self.last_checked_ok is not None
- else dbus.Boolean (False, variant_level=1)),
- dbus.String(u"timeout"):
- dbus.UInt64(self.timeout_milliseconds(),
- variant_level=1),
- dbus.String(u"interval"):
- dbus.UInt64(self.interval_milliseconds(),
- variant_level=1),
- dbus.String(u"checker"):
- dbus.String(self.checker_command,
- variant_level=1),
- dbus.String(u"checker_running"):
- dbus.Boolean(self.checker is not None,
- variant_level=1),
- dbus.String(u"object_path"):
- dbus.ObjectPath(self.dbus_object_path,
- variant_level=1)
- }, signature=u"sv")
-
- # IsStillValid - method
- @dbus.service.method(_interface, out_signature=u"b")
- def IsStillValid(self):
- return self.still_valid()
-
# PropertyChanged - signal
@dbus.service.signal(_interface, signature=u"sv")
def PropertyChanged(self, property, value):
"D-Bus signal"
pass
- # ReceivedSecret - signal
+ # GotSecret - signal
@dbus.service.signal(_interface)
- def ReceivedSecret(self):
- "D-Bus signal"
+ 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)
- def Rejected(self):
+ @dbus.service.signal(_interface, signature=u"s")
+ def Rejected(self, reason):
"D-Bus signal"
pass
- # SetChecker - method
- @dbus.service.method(_interface, in_signature=u"s")
- def SetChecker(self, checker):
- "D-Bus setter method"
- self.checker_command = checker
- # Emit D-Bus signal
- self.PropertyChanged(dbus.String(u"checker"),
- dbus.String(self.checker_command,
- variant_level=1))
-
- # SetHost - method
- @dbus.service.method(_interface, in_signature=u"s")
- def SetHost(self, host):
- "D-Bus setter method"
- self.host = host
- # Emit D-Bus signal
- self.PropertyChanged(dbus.String(u"host"),
- dbus.String(self.host, variant_level=1))
-
- # SetInterval - method
- @dbus.service.method(_interface, in_signature=u"t")
- def SetInterval(self, milliseconds):
- self.interval = datetime.timedelta(0, 0, 0, milliseconds)
- # Emit D-Bus signal
- self.PropertyChanged(dbus.String(u"interval"),
- (dbus.UInt64(self.interval_milliseconds(),
- variant_level=1)))
-
- # SetSecret - method
- @dbus.service.method(_interface, in_signature=u"ay",
- byte_arrays=True)
- def SetSecret(self, secret):
- "D-Bus setter method"
- self.secret = str(secret)
-
- # SetTimeout - method
- @dbus.service.method(_interface, in_signature=u"t")
- def SetTimeout(self, milliseconds):
- self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
- # Emit D-Bus signal
- self.PropertyChanged(dbus.String(u"timeout"),
- (dbus.UInt64(self.timeout_milliseconds(),
- variant_level=1)))
+ # 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)
@@ -744,9 +946,221 @@
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 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.
@@ -754,30 +1168,22 @@
Note: This will run in its own forked process."""
def handle(self):
- logger.info(u"TCP connection from: %s",
- unicode(self.client_address))
- logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
- # Open IPC pipe to parent process
- with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
+ 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()))
-
- 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 = u':'.join((u"NONE", u"+VERS-TLS1.1",
# u"+AES-256-CBC", u"+SHA1",
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
@@ -789,7 +1195,19 @@
(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:
@@ -798,39 +1216,103 @@
# established. Just abandon the request.
return
logger.debug(u"Handshake succeeded")
+
+ approval_required = False
try:
- fpr = self.fingerprint(self.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)
+ 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()
- for c in self.server.clients:
- if c.fingerprint == fpr:
- client = c
- break
- else:
- ipc.write(u"NOTFOUND %s %s\n"
- % (fpr, unicode(self.client_address)))
- 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():
- ipc.write(u"INVALID %s\n" % client.name)
- session.bye()
- return
- ipc.write(u"SENDING %s\n" % client.name)
- 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()
+ 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):
@@ -896,24 +1378,39 @@
return hex_fpr
-class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
- """Like socketserver.ForkingMixIn, but also pass a pipe."""
+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
"""
- self.pipe = os.pipe()
- super(ForkingMixInWithPipe,
+ parent_pipe, self.child_pipe = multiprocessing.Pipe()
+
+ super(MultiprocessingMixInWithPipe,
self).process_request(request, client_address)
- os.close(self.pipe[1]) # close write end
- self.add_pipe(self.pipe[0])
- def add_pipe(self, pipe):
+ self.child_pipe.close()
+ self.add_pipe(parent_pipe)
+
+ def add_pipe(self, parent_pipe):
"""Dummy function; override as necessary"""
- os.close(pipe)
-
-
-class IPv6_TCPServer(ForkingMixInWithPipe,
+ pass
+
+class IPv6_TCPServer(MultiprocessingMixInWithPipe,
socketserver.TCPServer, object):
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
@@ -983,9 +1480,6 @@
clients: set of Client objects
gnutls_priority GnuTLS priority string
use_dbus: Boolean; to emit D-Bus signals or not
- 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.
"""
@@ -1007,11 +1501,15 @@
return socketserver.TCPServer.server_activate(self)
def enable(self):
self.enabled = True
- def add_pipe(self, pipe):
+ def add_pipe(self, parent_pipe):
# Call "handle_ipc" for both data and EOF events
- gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
- self.handle_ipc)
- def handle_ipc(self, source, condition, file_objects={}):
+ 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
@@ -1026,60 +1524,58 @@
for cond, name in
condition_names.iteritems()
if cond & condition)
- logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
- conditions_string)
-
- # Turn the pipe file descriptor into a Python file object
- if source not in file_objects:
- file_objects[source] = os.fdopen(source, u"r", 1)
-
- # Read a line from the file object
- cmdline = file_objects[source].readline()
- if not cmdline: # Empty line means end of file
- # close the IPC pipe
- file_objects[source].close()
- del file_objects[source]
-
- # Stop calling this function
- return False
-
- logger.debug(u"IPC command: %r", cmdline)
-
- # Parse and act on command
- cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
-
- if cmd == u"NOTFOUND":
- logger.warning(u"Client not found for fingerprint: %s",
- args)
- if self.use_dbus:
- # Emit D-Bus signal
- mandos_dbus_service.ClientNotFound(args)
- elif cmd == u"INVALID":
- for client in self.clients:
- if client.name == args:
- logger.warning(u"Client %s is invalid", args)
- if self.use_dbus:
- # Emit D-Bus signal
- client.Rejected()
- break
- else:
- logger.error(u"Unknown client %s is invalid", args)
- elif cmd == u"SENDING":
- for client in self.clients:
- if client.name == args:
- logger.info(u"Sending secret to %s", client.name)
- client.checked_ok()
- if self.use_dbus:
- # Emit D-Bus signal
- client.ReceivedSecret()
- break
- else:
- logger.error(u"Sending secret to unknown client %s",
- args)
- else:
- logger.error(u"Unknown IPC command: %r", cmdline)
-
- # Keep calling this function
+ # 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
@@ -1115,9 +1611,9 @@
elif suffix == u"w":
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
else:
- raise ValueError
- except (ValueError, IndexError):
- raise ValueError
+ raise ValueError(u"Unknown suffix %r" % suffix)
+ except (ValueError, IndexError), e:
+ raise ValueError(e.message)
timevalue += delta
return timevalue
@@ -1136,7 +1632,7 @@
def if_nametoindex(interface):
"Get an interface index the hard way, i.e. using fcntl()"
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
- with closing(socket.socket()) as s:
+ with contextlib.closing(socket.socket()) as s:
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
struct.pack(str(u"16s16x"),
interface))
@@ -1162,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,
- u"/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())
@@ -1172,7 +1669,7 @@
def main():
- ######################################################################
+ ##################################################################
# Parsing of options, both command line and config file
parser = optparse.OptionParser(version = "%%prog %s" % version)
@@ -1187,6 +1684,8 @@
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",
@@ -1196,8 +1695,8 @@
help=u"Directory to search for configuration"
u" files")
parser.add_option("--no-dbus", action=u"store_false",
- dest=u"use_dbus",
- help=optparse.SUPPRESS_HELP) # XXX: Not done yet
+ 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]
@@ -1217,6 +1716,7 @@
u"servicename": u"Mandos",
u"use_dbus": u"True",
u"use_ipv6": u"True",
+ u"debuglevel": u"",
}
# Parse config file for server-global settings
@@ -1239,7 +1739,7 @@
# options, if set.
for option in (u"interface", u"address", u"port", u"debug",
u"priority", u"servicename", u"configdir",
- u"use_dbus", u"use_ipv6"):
+ u"use_dbus", u"use_ipv6", u"debuglevel"):
value = getattr(options, option)
if value is not None:
server_settings[option] = value
@@ -1254,14 +1754,10 @@
# For convenience
debug = server_settings[u"debug"]
+ debuglevel = server_settings[u"debuglevel"]
use_dbus = server_settings[u"use_dbus"]
- use_dbus = False # XXX: Not done yet
use_ipv6 = server_settings[u"use_ipv6"]
-
- if not debug:
- syslogger.setLevel(logging.WARNING)
- console.setLevel(logging.WARNING)
-
+
if server_settings[u"servicename"] != u"Mandos":
syslogger.setFormatter(logging.Formatter
(u'Mandos (%s) [%%(process)d]:'
@@ -1273,6 +1769,8 @@
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"],
@@ -1284,16 +1782,18 @@
tcp_server = MandosServer((server_settings[u"address"],
server_settings[u"port"]),
ClientHandler,
- interface=server_settings[u"interface"],
+ interface=(server_settings[u"interface"]
+ or None),
use_ipv6=use_ipv6,
gnutls_priority=
server_settings[u"priority"],
use_dbus=use_dbus)
- pidfilename = u"/var/run/mandos.pid"
- try:
- pidfile = open(pidfilename, u"w")
- except IOError:
- logger.error(u"Could not open file %r", pidfilename)
+ 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
@@ -1316,8 +1816,17 @@
if error[0] != errno.EPERM:
raise error
- # Enable all possible GnuTLS debugging
+ if not debug and not debuglevel:
+ syslogger.setLevel(logging.WARNING)
+ console.setLevel(logging.WARNING)
+ 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)
@@ -1328,6 +1837,16 @@
(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
# From the Avahi example code
@@ -1336,7 +1855,14 @@
bus = dbus.SystemBus()
# End of Avahi example code
if use_dbus:
- bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
+ 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",
@@ -1344,55 +1870,53 @@
if server_settings["interface"]:
service.interface = (if_nametoindex
(str(server_settings[u"interface"])))
+
+ if not debug:
+ # Close all input and output, do double fork, etc.
+ daemon()
+
+ 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(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 debug:
- # Redirect stdin so all checkers get /dev/null
- null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
- os.dup2(null, sys.stdin.fileno())
- if null > 2:
- os.close(null)
- else:
- # No console logging
- logger.removeHandler(console)
- # Close all input and output, do double fork, etc.
- daemon()
-
- try:
- with closing(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
-
- def cleanup():
- "Cleanup function; run on exit"
- service.cleanup()
- while tcp_server.clients:
- client = tcp_server.clients.pop()
- client.disable_hook = None
- client.disable()
-
- atexit.register(cleanup)
-
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())
@@ -1403,13 +1927,13 @@
dbus.service.Object.__init__(self, bus, u"/")
_interface = u"se.bsnet.fukt.Mandos"
- @dbus.service.signal(_interface, signature=u"oa{sv}")
- def ClientAdded(self, objpath, properties):
+ @dbus.service.signal(_interface, signature=u"o")
+ def ClientAdded(self, objpath):
"D-Bus signal"
pass
- @dbus.service.signal(_interface, signature=u"s")
- def ClientNotFound(self, fingerprint):
+ @dbus.service.signal(_interface, signature=u"ss")
+ def ClientNotFound(self, fingerprint, address):
"D-Bus signal"
pass
@@ -1429,7 +1953,7 @@
def GetAllClientsWithProperties(self):
"D-Bus method"
return dbus.Dictionary(
- ((c.dbus_object_path, c.GetAllProperties())
+ ((c.dbus_object_path, c.GetAll(u""))
for c in tcp_server.clients),
signature=u"oa{sv}")
@@ -1441,21 +1965,38 @@
tcp_server.clients.remove(c)
c.remove_from_connection()
# Don't signal anything except ClientRemoved
- c.disable(signal=False)
+ c.disable(quiet=True)
# Emit D-Bus signal
self.ClientRemoved(object_path, c.name)
return
- raise KeyError
+ raise KeyError(object_path)
del _interface
mandos_dbus_service = MandosDBusService()
+ def cleanup():
+ "Cleanup function; run on exit"
+ service.cleanup()
+
+ 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)
+
for client in tcp_server.clients:
if use_dbus:
# Emit D-Bus signal
- mandos_dbus_service.ClientAdded(client.dbus_object_path,
- client.GetAllProperties())
+ mandos_dbus_service.ClientAdded(client.dbus_object_path)
client.enable()
tcp_server.enable()
@@ -1479,6 +2020,7 @@
service.activate()
except dbus.exceptions.DBusException, error:
logger.critical(u"DBusException: %s", error)
+ cleanup()
sys.exit(1)
# End of Avahi example code
@@ -1491,12 +2033,15 @@
main_loop.run()
except AvahiError, error:
logger.critical(u"AvahiError: %s", error)
+ cleanup()
sys.exit(1)
except KeyboardInterrupt:
if debug:
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 2009-09-17 01:21:27 +0000
+++ mandos-clients.conf.xml 2010-09-27 18:57:12 +0000
@@ -3,7 +3,7 @@
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
/etc/mandos/clients.conf">
-
+
%common;
]>
@@ -34,6 +34,7 @@
2008
2009
+ 2010
Teddy Hogeborn
Björn Påhlsson
@@ -63,9 +64,8 @@
>mandos
8, read by it at startup.
The file needs to list all clients that should be able to use
- the service. All clients listed will be regarded as valid, even
- if a client was declared invalid in a previous run of the
- server.
+ the service. 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
@@ -101,54 +101,53 @@
-
-
-
- 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 considered invalid - that is, ineligible
- to get the data this server holds. By default Mandos will
- use 1 hour.
-
-
- The TIME is specified as a
- space-separated number of values, each of which is a
- number and a one-character suffix. The suffix must be one
- of d
, s
, m
,
- h
, and w
for days, seconds,
- minutes, hours, and weeks, respectively. The values are
- added together to give the total time value, so all of
- 330s
,
- 110s 110s 110s
, and
- 5m 30s
will give a value
- of five minutes and thirty seconds.
-
-
-
-
-
-
-
-
- This option is optional.
-
-
- How often to run the checker to confirm that a client is
- still up. Note: a new checker will
- not be started if an old one is still running. The server
- will wait for a checker to complete until the above
- timeout
occurs, at which
- time the client will be marked invalid, and any running
- checker killed. The default interval is 5 minutes.
-
-
- The format of TIME is the same
- as for timeout above.
+
+
+
+ This option is optional.
+
+
+ How long to wait for external approval before resorting to
+ use the value. The
+ default is 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
.
@@ -198,6 +197,70 @@
+
+
+
+ This option is optional, but highly
+ recommended unless the
+ option is modified to a
+ non-standard value without %%(host)s
in it.
+
+
+ Host name for this client. This is not used by the server
+ directly, but can be, and is by default, used by the
+ checker. See the option.
+
+
+
+
+
+
+
+
+ This option is optional.
+
+
+ How often to run the checker to confirm that a client is
+ still up. Note: a new checker will
+ not be started if an old one is still running. The server
+ will wait for a checker to complete until the below
+ timeout
occurs, at which
+ time the client will be disabled, and any running checker
+ killed. The default interval is 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.
+
+
+
+
+
@@ -228,42 +291,30 @@
-
-
-
- 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.
-
-
-
-
-
-
-
-
- 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.
+
+
+ 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.
@@ -306,10 +357,28 @@
%%(foo)s
will be replaced by the value of the attribute
foo of the internal
- Client
object. See the
- source code for details, and let the authors know of any
- attributes that are useful so they may be preserved to any new
- versions of this software.
+ Client
object in the
+ Mandos server. The currently allowed values for
+ foo are:
+ approval_delay
,
+ approval_duration
,
+ created
,
+ enabled
,
+ fingerprint
,
+ host
,
+ interval
,
+ last_approval_request
,
+ last_checked_ok
,
+ last_enabled
,
+ name
,
+ timeout
, and, if using
+ D-Bus, dbus_object_path
.
+ See the source code for details. Currently, none of these attributes
+ except host
are guaranteed
+ to be valid in future versions. Therefore, please
+ let the authors know of any attributes that are useful so they
+ may be preserved to any new versions of this software.
Note that this means that, in order to include an actual
@@ -381,6 +450,8 @@
fingerprint = 3e393aeaefb84c7e89e2f547b3a107558fca3a27
secfile = /etc/mandos/bar-secret
timeout = 15m
+approved_by_default = False
+approval_delay = 30s
=== modified file 'mandos-ctl'
--- mandos-ctl 2009-10-25 19:16:56 +0000
+++ mandos-ctl 2010-09-26 18:32:58 +0000
@@ -1,5 +1,26 @@
#!/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
@@ -8,48 +29,50 @@
import locale
import datetime
import re
+import os
locale.setlocale(locale.LC_ALL, u'')
tablewords = {
- 'name': u'Name',
- 'enabled': u'Enabled',
- 'timeout': u'Timeout',
- 'last_checked_ok': u'Last Successful Check',
- 'created': u'Created',
- 'interval': u'Interval',
- 'host': u'Host',
- 'fingerprint': u'Fingerprint',
- 'checker_running': u'Check Is Running',
- 'last_enabled': u'Last Enabled',
- 'checker': u'Checker',
+ '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', 'last_checked_ok',
- '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"
-bus = dbus.SystemBus()
-mandos_dbus_objc = bus.get_object(busname, server_path)
-mandos_serv = dbus.Interface(mandos_dbus_objc,
- dbus_interface = server_interface)
-mandos_clients = mandos_serv.GetAllClientsWithProperties()
-def datetime_to_milliseconds(dt):
- "Return the 'timeout' attribute in milliseconds"
- return ((dt.days * 24 * 60 * 60 * 1000)
- + (dt.seconds * 1000)
- + (dt.microseconds // 1000))
+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 "%s%02d:%02d:%02d" % (("%dT" % td.days) if td.days else "", # days
- td.seconds // 3600, # hours
- (td.seconds % 3600) // 60, # minutes
- (td.seconds % 60)) # seconds
+ 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):
@@ -92,102 +115,223 @@
timevalue += delta
return timevalue
-def print_clients(clients):
+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 ("timeout", "interval"):
+ 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))
+ max(len(valuetostring(client[key],
+ key))
for client in
clients))
for key in keywords)
+ # Print header line
print format_string % tuple(tablewords[key] for key in keywords)
for client in clients:
print format_string % tuple(valuetostring(client[key], key)
for key in keywords)
-parser = OptionParser(version = "%%prog %s" % version)
-parser.add_option("-a", "--all", action="store_true",
- help="Print all fields")
-parser.add_option("-e", "--enable", action="store_true",
- help="Enable client")
-parser.add_option("-d", "--disable", action="store_true",
- help="disable client")
-parser.add_option("-b", "--bump-timeout", action="store_true",
- help="Bump timeout for client")
-parser.add_option("--start-checker", action="store_true",
- help="Start checker for client")
-parser.add_option("--stop-checker", action="store_true",
- help="Stop checker for client")
-parser.add_option("-V", "--is-valid", action="store_true",
- help="Check if client is still valid")
-parser.add_option("-r", "--remove", action="store_true",
- help="Remove client")
-parser.add_option("-c", "--checker", type="string",
- help="Set checker command for client")
-parser.add_option("-t", "--timeout", type="string",
- help="Set timeout for client")
-parser.add_option("-i", "--interval", type="string",
- help="Set checker interval for client")
-parser.add_option("-H", "--host", type="string",
- help="Set host for client")
-parser.add_option("-s", "--secret", type="string",
- help="Set password blob (file) for client")
-options, client_names = parser.parse_args()
-
-# Compile list of clients to process
-clients=[]
-for name in client_names:
- for path, client in mandos_clients.iteritems():
- if client['name'] == name:
- client_objc = bus.get_object(busname, path)
- clients.append(dbus.Interface(client_objc,
- dbus_interface
- = client_interface))
- break
- else:
- print >> sys.stderr, "Client not found on server: %r" % name
- sys.exit(1)
-
-if not clients and mandos_clients.values():
- keywords = defaultkeywords
- if options.all:
- keywords = ('name', 'enabled', 'timeout', 'last_checked_ok',
- 'created', 'interval', 'host', 'fingerprint',
- 'checker_running', 'last_enabled', 'checker')
- print_clients(mandos_clients.values())
-
-# Process each client in the list by all selected options
-for client in clients:
- if options.remove:
- mandos_serv.RemoveClient(client.__dbus_object_path__)
- if options.enable:
- client.Enable()
- if options.disable:
- client.Disable()
- if options.bump_timeout:
- client.BumpTimeout()
- if options.start_checker:
- client.StartChecker()
- if options.stop_checker:
- client.StopChecker()
- if options.is_valid:
- sys.exit(0 if client.IsStillValid() else 1)
- if options.checker:
- client.SetChecker(options.checker)
- if options.host:
- client.SetHost(options.host)
- if options.interval:
- client.SetInterval(datetime_to_milliseconds
- (string_to_delta(options.interval)))
- if options.timeout:
- client.SetTimeout(datetime_to_milliseconds
- (string_to_delta(options.timeout)))
- if options.secret:
- client.SetSecret(dbus.ByteArray(open(options.secret, 'rb').read()))
+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 2009-10-25 19:16:56 +0000
+++ mandos-keygen 2010-09-26 18:32:58 +0000
@@ -2,8 +2,8 @@
#
# Mandos key generator - create a new OpenPGP key for a Mandos client
#
-# Copyright © 2008,2009 Teddy Hogeborn
-# Copyright © 2008,2009 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
@@ -217,12 +217,27 @@
%commit
EOF
+ if tty --quiet; then
+ cat <<-EOF
+ Note: Due to entropy requirements, key generation could take
+ anything from a few minutes to SEVERAL HOURS. Please be
+ patient and/or supply the system with more entropy if needed.
+ EOF
+ echo -n "Started: "
+ date
+ fi
+
# Generate a new key in the key rings
gpg --quiet --batch --no-tty --no-options --enable-dsa2 \
--homedir "$RINGDIR" --trust-model always \
--gen-key "$BATCHFILE"
rm --force "$BATCHFILE"
+ if tty --quiet; then
+ echo -n "Finished: "
+ date
+ fi
+
# Backup any old key files
if cp --backup=numbered --force "$SECKEYFILE" "$SECKEYFILE" \
2>/dev/null; then
@@ -278,12 +293,13 @@
stty -echo
echo -n "Enter passphrase: " >&2
first="$(head --lines=1 | tr --delete '\n')"
- echo -n -e "\nRepeat passphrase: " >&2
+ echo >&2
+ echo -n "Repeat passphrase: " >&2
second="$(head --lines=1 | tr --delete '\n')"
echo >&2
stty echo
if [ "$first" != "$second" ]; then
- echo -e "Passphrase mismatch" >&2
+ echo "Passphrase mismatch" >&2
touch "$RINGDIR"/mismatch
else
echo -n "$first"
=== added file 'mandos-monitor'
--- mandos-monitor 1970-01-01 00:00:00 +0000
+++ mandos-monitor 2010-09-27 16:27:13 +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.lsm'
--- mandos.lsm 2009-10-25 19:16:56 +0000
+++ mandos.lsm 2010-09-14 18:22:03 +0000
@@ -1,7 +1,7 @@
Begin4
Title: Mandos
Version: 1.0.14
-Entered-date: 2009-10-25
+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.
=== modified file 'mandos.xml'
--- mandos.xml 2009-09-17 11:47:22 +0000
+++ mandos.xml 2010-09-27 17:39:03 +0000
@@ -2,7 +2,7 @@
-
+
%common;
]>
@@ -33,6 +33,7 @@
2008
2009
+ 2010
Teddy Hogeborn
Björn Påhlsson
@@ -86,6 +87,11 @@
+
+
+
+
@@ -192,6 +198,24 @@
+
+
+
+ Set the debugging log level.
+ LEVEL is a string, one of
+ CRITICAL
,
+ ERROR
,
+ WARNING
,
+ INFO
, or
+ DEBUG
, in order of
+ increasing verbosity. The default level is
+ WARNING
.
+
+
+
+
+
@@ -233,6 +257,16 @@
+
+
+
+
+ See also .
+
+
+
+
+
@@ -326,6 +360,26 @@
+
+ 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
@@ -336,6 +390,16 @@
+
+ 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
@@ -396,8 +460,8 @@
/var/run/mandos.pid
- The file containing the process id of
- &COMMANDNAME;.
+ The file containing the process id of the
+ &COMMANDNAME; process started last.
@@ -431,15 +495,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.
@@ -527,19 +585,18 @@
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
=== modified file 'plugin-runner.c'
--- plugin-runner.c 2009-09-10 06:28:14 +0000
+++ plugin-runner.c 2010-09-26 21:27:28 +0000
@@ -2,8 +2,8 @@
/*
* Mandos plugin runner - Run Mandos plugins
*
- * Copyright © 2008,2009 Teddy Hogeborn
- * Copyright © 2008,2009 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
@@ -23,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, fileno(), fprintf(),
+#include /* fileno(), fprintf(),
stderr, STDOUT_FILENO */
-#include /* DIR, opendir(), stat(), struct
+#include /* DIR, fdopendir(), stat(), struct
stat, waitpid(), WIFEXITED(),
WEXITSTATUS(), wait(), pid_t,
uid_t, gid_t, getuid(), getgid(),
@@ -42,7 +42,7 @@
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(),
@@ -54,7 +54,7 @@
#include /* fcntl(), F_GETFD, F_SETFD,
FD_CLOEXEC */
#include /* strsep, strlen(), asprintf(),
- strsignal() */
+ strsignal(), strcmp(), strncmp() */
#include /* errno */
#include /* struct argp_option, struct
argp_state, struct argp,
@@ -68,6 +68,10 @@
*/
#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
@@ -102,7 +106,7 @@
/* Gets an existing plugin based on name,
or if none is found, creates a new one */
static plugin *getplugin(char *name){
- /* Check for exiting plugin with that name */
+ /* 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))){
@@ -123,7 +127,9 @@
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;
}
}
@@ -137,8 +143,10 @@
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;
@@ -148,9 +156,11 @@
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;
@@ -258,7 +268,7 @@
/* No child processes */
break;
}
- perror("waitpid");
+ error(0, errno, "waitpid");
}
/* A child exited, find it in process_list */
@@ -349,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;
}
@@ -394,119 +404,137 @@
.doc = "Group ID the plugins will run as", .group = 3 },
{ .name = "debug", .key = 132,
.doc = "Debug mode", .group = 4 },
+ /*
+ * These reproduce what we would get without ARGP_NO_HELP
+ */
+ { .name = "help", .key = '?',
+ .doc = "Give this help list", .group = -1 },
+ { .name = "usage", .key = -3,
+ .doc = "Give a short usage message", .group = -1 },
+ { .name = "version", .key = 'V',
+ .doc = "Print program version", .group = -1 },
{ .name = NULL }
};
- error_t parse_opt(int key, char *arg, __attribute__((unused))
- struct argp_state *state){
+ 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 */
- if(arg != NULL){
+ {
char *plugin_option;
while((plugin_option = strsep(&arg, ",")) != NULL){
- if(plugin_option[0] == '\0'){
- continue;
- }
if(not add_argument(getplugin(NULL), plugin_option)){
- perror("add_argument");
- return ARGP_ERR_UNKNOWN;
+ break;
}
}
}
break;
case 'G': /* --global-env */
- if(arg == NULL){
- break;
- }
- if(not add_environment(getplugin(NULL), arg, true)){
- perror("add_environment");
- }
+ add_environment(getplugin(NULL), arg, true);
break;
case 'o': /* --options-for */
- if(arg != NULL){
- char *plugin_name = strsep(&arg, ":");
- if(plugin_name[0] == '\0'){
- break;
- }
- char *plugin_option;
- while((plugin_option = strsep(&arg, ",")) != NULL){
- if(not add_argument(getplugin(plugin_name), plugin_option)){
- perror("add_argument");
- return ARGP_ERR_UNKNOWN;
+ {
+ char *option_list = strchr(arg, ':');
+ if(option_list == NULL){
+ argp_error(state, "No colon in \"%s\"", arg);
+ errno = EINVAL;
+ break;
+ }
+ *option_list = '\0';
+ option_list++;
+ if(arg[0] == '\0'){
+ argp_error(state, "Empty plugin name");
+ errno = EINVAL;
+ break;
+ }
+ char *option;
+ while((option = strsep(&option_list, ",")) != NULL){
+ if(not add_argument(getplugin(arg), option)){
+ break;
}
}
}
break;
case 'E': /* --env-for */
- if(arg == NULL){
- break;
- }
{
char *envdef = strchr(arg, ':');
if(envdef == NULL){
+ argp_error(state, "No colon in \"%s\"", arg);
+ errno = EINVAL;
break;
}
*envdef = '\0';
- if(not add_environment(getplugin(arg), envdef+1, true)){
- perror("add_environment");
+ envdef++;
+ if(arg[0] == '\0'){
+ argp_error(state, "Empty plugin name");
+ errno = EINVAL;
+ break;
}
+ add_environment(getplugin(arg), envdef, true);
}
break;
case 'd': /* --disable */
- if(arg != NULL){
+ {
plugin *p = getplugin(arg);
- if(p == NULL){
- return ARGP_ERR_UNKNOWN;
+ if(p != NULL){
+ p->disabled = true;
}
- p->disabled = true;
}
break;
case 'e': /* --enable */
- if(arg != NULL){
+ {
plugin *p = getplugin(arg);
- if(p == NULL){
- return ARGP_ERR_UNKNOWN;
+ if(p != NULL){
+ p->disabled = false;
}
- p->disabled = false;
}
break;
case 128: /* --plugin-dir */
free(plugindir);
plugindir = strdup(arg);
- if(plugindir == NULL){
- perror("strdup");
- }
break;
case 129: /* --config-file */
/* This is already done by parse_opt_config_file() */
break;
case 130: /* --userid */
- errno = 0;
tmpmax = strtoimax(arg, &tmp, 10);
if(errno != 0 or tmp == arg or *tmp != '\0'
or tmpmax != (uid_t)tmpmax){
- fprintf(stderr, "Bad user ID number: \"%s\", using %"
- PRIdMAX "\n", arg, (intmax_t)uid);
- } else {
- uid = (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 */
- errno = 0;
tmpmax = strtoimax(arg, &tmp, 10);
if(errno != 0 or tmp == arg or *tmp != '\0'
or tmpmax != (gid_t)tmpmax){
- fprintf(stderr, "Bad group ID number: \"%s\", using %"
- PRIdMAX "\n", arg, (intmax_t)gid);
- } else {
- gid = (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;
+ /*
+ * 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.
@@ -515,16 +543,13 @@
/* Cryptsetup always passes an argument, which is an empty
string if "none" was specified in /etc/crypttab. So if
argument was empty, we ignore it silently. */
- if(arg[0] != '\0'){
- fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
+ if(arg[0] == '\0'){
+ break;
}
- break;
- case ARGP_KEY_END:
- break;
default:
return ARGP_ERR_UNKNOWN;
}
- return 0;
+ return errno; /* Set to 0 at start */
}
/* This option parser is the same as parse_opt() above, except it
@@ -532,6 +557,7 @@
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 */
@@ -544,20 +570,19 @@
case 129: /* --config-file */
free(argfile);
argfile = strdup(arg);
- if(argfile == NULL){
- perror("strdup");
- }
break;
case 130: /* --userid */
case 131: /* --groupid */
case 132: /* --debug */
+ case '?': /* --help */
+ case -3: /* --usage */
+ case 'V': /* --version */
case ARGP_KEY_ARG:
- case ARGP_KEY_END:
break;
default:
return ARGP_ERR_UNKNOWN;
}
- return 0;
+ return errno;
}
struct argp argp = { .options = options,
@@ -567,10 +592,20 @@
/* Parse using parse_opt_config_file() in order to get the custom
config file location, if any. */
- ret = argp_parse(&argp, argc, argv, ARGP_IN_ORDER, 0, NULL);
- if(ret == ARGP_ERR_UNKNOWN){
- fprintf(stderr, "Unknown error while parsing arguments\n");
- exitstatus = EXIT_FAILURE;
+ ret = argp_parse(&argp, argc, argv,
+ ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP,
+ NULL, NULL);
+ switch(ret){
+ case 0:
+ break;
+ case ENOMEM:
+ default:
+ errno = ret;
+ error(0, errno, "argp_parse");
+ exitstatus = EX_OSERR;
+ goto fallback;
+ case EINVAL:
+ exitstatus = EX_USAGE;
goto fallback;
}
@@ -593,8 +628,8 @@
custom_argc = 1;
custom_argv = malloc(sizeof(char*) * 2);
if(custom_argv == NULL){
- perror("malloc");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "malloc");
+ exitstatus = EX_OSERR;
goto fallback;
}
custom_argv[0] = argv[0];
@@ -616,8 +651,8 @@
}
new_arg = strdup(p);
if(new_arg == NULL){
- perror("strdup");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "strdup");
+ exitstatus = EX_OSERR;
free(org_line);
goto fallback;
}
@@ -626,8 +661,8 @@
custom_argv = realloc(custom_argv, sizeof(char *)
* ((unsigned int) custom_argc + 1));
if(custom_argv == NULL){
- perror("realloc");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "realloc");
+ exitstatus = EX_OSERR;
free(org_line);
goto fallback;
}
@@ -639,8 +674,8 @@
ret = fclose(conffp);
} while(ret == EOF and errno == EINTR);
if(ret == EOF){
- perror("fclose");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "fclose");
+ exitstatus = EX_IOERR;
goto fallback;
}
free(org_line);
@@ -648,29 +683,48 @@
/* 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;
+ error(0, errno, "fopen");
+ exitstatus = EX_OSERR;
goto fallback;
}
}
- /* If there was any arguments from configuration file,
- pass them to parser as command arguments */
+ /* If there were any arguments from the configuration file, pass
+ them to parser as command line arguments */
if(custom_argv != NULL){
- ret = argp_parse(&argp, custom_argc, custom_argv, ARGP_IN_ORDER,
- 0, NULL);
- if(ret == ARGP_ERR_UNKNOWN){
- fprintf(stderr, "Unknown error while parsing arguments\n");
- exitstatus = EXIT_FAILURE;
+ ret = argp_parse(&argp, custom_argc, custom_argv,
+ ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP,
+ NULL, NULL);
+ switch(ret){
+ case 0:
+ break;
+ case ENOMEM:
+ default:
+ errno = ret;
+ error(0, errno, "argp_parse");
+ exitstatus = EX_OSERR;
+ goto fallback;
+ case EINVAL:
+ exitstatus = EX_CONFIG;
goto fallback;
}
}
/* Parse actual command line arguments, to let them override the
config file */
- ret = argp_parse(&argp, argc, argv, ARGP_IN_ORDER, 0, NULL);
- if(ret == ARGP_ERR_UNKNOWN){
- fprintf(stderr, "Unknown error while parsing arguments\n");
- exitstatus = EXIT_FAILURE;
+ ret = argp_parse(&argp, argc, argv,
+ ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP,
+ NULL, NULL);
+ switch(ret){
+ case 0:
+ break;
+ case ENOMEM:
+ default:
+ errno = ret;
+ error(0, errno, "argp_parse");
+ exitstatus = EX_OSERR;
+ goto fallback;
+ case EINVAL:
+ exitstatus = EX_USAGE;
goto fallback;
}
@@ -691,35 +745,56 @@
/* Strip permissions down to nobody */
setgid(gid);
if(ret == -1){
- perror("setgid");
+ error(0, errno, "setgid");
}
ret = setuid(uid);
if(ret == -1){
- perror("setuid");
- }
-
- if(plugindir == NULL){
- dir = opendir(PDIR);
- } else {
- dir = opendir(plugindir);
- }
-
- if(dir == NULL){
- perror("Could not open plugin dir");
- exitstatus = EXIT_FAILURE;
- goto fallback;
- }
-
- /* Set the FD_CLOEXEC flag on the directory, if possible */
+ 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;
}
}
@@ -734,8 +809,8 @@
/* All directory entries have been processed */
if(dirst == NULL){
if(errno == EBADF){
- perror("readdir");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "readdir");
+ exitstatus = EX_IOERR;
goto fallback;
}
break;
@@ -771,7 +846,7 @@
for(const char **suf = bad_suffixes; *suf != NULL; suf++){
size_t suf_len = strlen(*suf);
if((d_name_len >= suf_len)
- and (strcmp((dirst->d_name)+d_name_len-suf_len, *suf)
+ and (strcmp((dirst->d_name) + d_name_len-suf_len, *suf)
== 0)){
if(debug){
fprintf(stderr, "Ignoring plugin dir entry \"%s\""
@@ -797,13 +872,13 @@
dirst->d_name));
}
if(ret < 0){
- perror("asprintf");
+ error(0, errno, "asprintf");
continue;
}
ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st));
if(ret == -1){
- perror("stat");
+ error(0, errno, "stat");
free(filename);
continue;
}
@@ -821,7 +896,7 @@
plugin *p = getplugin(dirst->d_name);
if(p == NULL){
- perror("getplugin");
+ error(0, errno, "getplugin");
free(filename);
continue;
}
@@ -839,13 +914,13 @@
if(g != NULL){
for(char **a = g->argv + 1; *a != NULL; a++){
if(not add_argument(p, *a)){
- perror("add_argument");
+ error(0, errno, "add_argument");
}
}
/* Add global environment variables */
for(char **e = g->environ; *e != NULL; e++){
if(not add_environment(p, *e, false)){
- perror("add_environment");
+ error(0, errno, "add_environment");
}
}
}
@@ -856,7 +931,7 @@
if(p->environ[0] != NULL){
for(char **e = environ; *e != NULL; e++){
if(not add_environment(p, *e, false)){
- perror("add_environment");
+ error(0, errno, "add_environment");
}
}
}
@@ -864,21 +939,21 @@
int pipefd[2];
ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd));
if(ret == -1){
- perror("pipe");
- exitstatus = EXIT_FAILURE;
+ 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 */
@@ -886,8 +961,8 @@
&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 */
@@ -896,27 +971,27 @@
pid = fork();
} while(pid == -1 and errno == EINTR);
if(pid == -1){
- perror("fork");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "fork");
+ exitstatus = EX_OSERR;
goto fallback;
}
if(pid == 0){
/* this is the child process */
ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
if(ret < 0){
- perror("sigaction");
- _exit(EXIT_FAILURE);
+ error(0, errno, "sigaction");
+ _exit(EX_OSERR);
}
ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
if(ret < 0){
- perror("sigprocmask");
- _exit(EXIT_FAILURE);
+ error(0, errno, "sigprocmask");
+ _exit(EX_OSERR);
}
ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
if(ret == -1){
- perror("dup2");
- _exit(EXIT_FAILURE);
+ error(0, errno, "dup2");
+ _exit(EX_OSERR);
}
if(dirfd(dir) < 0){
@@ -926,13 +1001,13 @@
}
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 */
@@ -943,14 +1018,14 @@
free(filename);
plugin *new_plugin = getplugin(dirst->d_name);
if(new_plugin == NULL){
- perror("getplugin");
+ 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;
}
@@ -963,12 +1038,13 @@
&sigchld_action.sa_mask,
NULL));
if(ret < 0){
- perror("sigprocmask");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "sigprocmask");
+ exitstatus = EX_OSERR;
goto fallback;
}
- FD_SET(new_plugin->fd, &rfds_all);
+ FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
+ -Wconversion */
if(maxfd < new_plugin->fd){
maxfd = new_plugin->fd;
@@ -995,8 +1071,8 @@
fd_set rfds = rfds_all;
int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
if(select_ret == -1 and errno != EINTR){
- perror("select");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "select");
+ exitstatus = EX_OSERR;
goto fallback;
}
/* OK, now either a process completed, or something can be read
@@ -1028,7 +1104,8 @@
}
/* Remove the plugin */
- FD_CLR(proc->fd, &rfds_all);
+ FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
+ -Wconversion */
/* Block signal while modifying process_list */
ret = (int)TEMP_FAILURE_RETRY(sigprocmask
@@ -1036,8 +1113,8 @@
&sigchld_action.sa_mask,
NULL));
if(ret < 0){
- perror("sigprocmask");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "sigprocmask");
+ exitstatus = EX_OSERR;
goto fallback;
}
@@ -1050,8 +1127,8 @@
(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;
}
@@ -1067,14 +1144,16 @@
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;
@@ -1084,8 +1163,8 @@
proc->buffer = realloc(proc->buffer, proc->buffer_size
+ (size_t) BUFFER_SIZE);
if(proc->buffer == NULL){
- perror("malloc");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "malloc");
+ exitstatus = EX_OSERR;
goto fallback;
}
proc->buffer_size += BUFFER_SIZE;
@@ -1112,7 +1191,8 @@
fallback:
- if(plugin_list == NULL or exitstatus != EXIT_SUCCESS){
+ if(plugin_list == NULL or (exitstatus != EXIT_SUCCESS
+ and exitstatus != EX_OK)){
/* Fallback if all plugins failed, none are found or an error
occured */
bool bret;
@@ -1126,16 +1206,16 @@
}
bret = print_out_password(passwordbuffer, len);
if(not bret){
- perror("print_out_password");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "print_out_password");
+ exitstatus = EX_IOERR;
}
}
/* Restore old signal handler */
ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
if(ret == -1){
- perror("sigaction");
- exitstatus = EXIT_FAILURE;
+ error(0, errno, "sigaction");
+ exitstatus = EX_OSERR;
}
if(custom_argv != NULL){
@@ -1156,7 +1236,7 @@
ret = kill(p->pid, SIGTERM);
if(ret == -1 and errno != ESRCH){
/* Set-uid proccesses might not get closed */
- perror("kill");
+ error(0, errno, "kill");
}
}
}
@@ -1166,7 +1246,7 @@
ret = wait(NULL);
} while(ret >= 0);
if(errno != ECHILD){
- perror("wait");
+ error(0, errno, "wait");
}
free_plugin_list();
=== modified file 'plugins.d/askpass-fifo.c'
--- plugins.d/askpass-fifo.c 2009-09-16 23:28:39 +0000
+++ plugins.d/askpass-fifo.c 2010-09-26 18:32:58 +0000
@@ -2,8 +2,8 @@
/*
* Askpass-FIFO - Read a password from a FIFO and output it
*
- * Copyright © 2008,2009 Teddy Hogeborn
- * Copyright © 2008,2009 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
@@ -26,13 +26,19 @@
#include /* ssize_t */
#include /* mkfifo(), S_IRUSR, S_IWUSR */
#include /* and */
-#include /* errno, EEXIST */
-#include /* perror() */
+#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,
@@ -43,16 +49,46 @@
/* Create FIFO */
const char passfifo[] = "/lib/cryptsetup/passfifo";
ret = mkfifo(passfifo, S_IRUSR | S_IWUSR);
- if(ret == -1 and errno != EEXIST){
- perror("mkfifo");
- return EXIT_FAILURE;
+ 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){
- perror("open");
- return EXIT_FAILURE;
+ 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 */
@@ -65,18 +101,30 @@
if(buf_len + blocksize > buf_allocated){
char *tmp = realloc(buf, buf_allocated + blocksize);
if(tmp == NULL){
- perror("realloc");
+ error(0, errno, "realloc");
free(buf);
- return EXIT_FAILURE;
+ return EX_OSERR;
}
buf = tmp;
buf_allocated += blocksize;
}
sret = read(fifo_fd, buf + buf_len, buf_allocated - buf_len);
if(sret == -1){
- perror("read");
+ int e = errno;
free(buf);
- return EXIT_FAILURE;
+ errno = e;
+ error(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);
@@ -90,13 +138,37 @@
while(written < buf_len){
sret = write(STDOUT_FILENO, buf + written, buf_len - written);
if(sret == -1){
- perror("write");
+ int e = errno;
free(buf);
- return EXIT_FAILURE;
+ errno = e;
+ error(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;
}
=== modified file 'plugins.d/mandos-client.c'
--- plugins.d/mandos-client.c 2009-09-17 11:22:28 +0000
+++ plugins.d/mandos-client.c 2010-09-26 21:27:28 +0000
@@ -9,8 +9,8 @@
* "browse_callback", and parts of "main".
*
* Everything else is
- * Copyright © 2008,2009 Teddy Hogeborn
- * Copyright © 2008,2009 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
@@ -43,8 +43,8 @@
stdout, ferror(), remove() */
#include /* uint16_t, uint32_t */
#include /* NULL, size_t, ssize_t */
-#include /* free(), EXIT_SUCCESS, EXIT_FAILURE,
- srand(), strtof(), abort() */
+#include /* free(), EXIT_SUCCESS, srand(),
+ strtof(), abort() */
#include /* bool, false, true */
#include /* memset(), strcmp(), strlen(),
strerror(), asprintf(), strcpy() */
@@ -63,7 +63,7 @@
strtoimax() */
#include /* assert() */
#include /* perror(), errno */
-#include /* nanosleep(), time() */
+#include /* nanosleep(), time(), sleep() */
#include /* ioctl, ifreq, SIOCGIFFLAGS, IFF_UP,
SIOCSIFFLAGS, if_indextoname(),
if_nametoindex(), IF_NAMESIZE */
@@ -82,6 +82,8 @@
#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() */
@@ -125,6 +127,8 @@
static const char mandos_protocol_version[] = "1";
const char *argp_program_version = "mandos-client " VERSION;
const char *argp_program_bug_address = "";
+static const char sys_class_net[] = "/sys/class/net";
+char *connect_to = NULL;
/* Used for passing in values through the Avahi callback functions */
typedef struct {
@@ -552,7 +556,10 @@
gnutls_session_t session;
int pf; /* Protocol family */
+ errno = 0;
+
if(quit_now){
+ errno = EINTR;
return -1;
}
@@ -565,6 +572,7 @@
break;
default:
fprintf(stderr, "Bad address family: %d\n", af);
+ errno = EINVAL;
return -1;
}
@@ -580,11 +588,14 @@
tcp_sd = socket(pf, SOCK_STREAM, 0);
if(tcp_sd < 0){
+ int e = errno;
perror("socket");
+ errno = e;
goto mandos_end;
}
if(quit_now){
+ errno = EINTR;
goto mandos_end;
}
@@ -597,11 +608,15 @@
ret = inet_pton(af, ip, &to.in.sin_addr);
}
if(ret < 0 ){
+ int e = errno;
perror("inet_pton");
+ errno = e;
goto mandos_end;
}
if(ret == 0){
+ int e = errno;
fprintf(stderr, "Bad address: %s\n", ip);
+ errno = e;
goto mandos_end;
}
if(af == AF_INET6){
@@ -615,6 +630,7 @@
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 */
@@ -627,6 +643,7 @@
}
if(quit_now){
+ errno = EINTR;
goto mandos_end;
}
@@ -663,6 +680,7 @@
}
if(quit_now){
+ errno = EINTR;
goto mandos_end;
}
@@ -672,11 +690,16 @@
ret = connect(tcp_sd, &to.in, sizeof(to)); /* IPv4 */
}
if(ret < 0){
- perror("connect");
+ 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;
}
@@ -687,7 +710,9 @@
ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written,
out_size - written));
if(ret == -1){
+ int e = errno;
perror("write");
+ errno = e;
goto mandos_end;
}
written += (size_t)ret;
@@ -703,6 +728,7 @@
}
if(quit_now){
+ errno = EINTR;
goto mandos_end;
}
}
@@ -712,18 +738,21 @@
}
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);
@@ -733,6 +762,7 @@
fprintf(stderr, "*** GnuTLS Handshake failed ***\n");
gnutls_perror(ret);
}
+ errno = EPROTO;
goto mandos_end;
}
@@ -746,17 +776,21 @@
while(true){
if(quit_now){
+ errno = EINTR;
goto mandos_end;
}
buffer_capacity = incbuffer(&buffer, buffer_length,
buffer_capacity);
if(buffer_capacity == 0){
+ int e = errno;
perror("incbuffer");
+ errno = e;
goto mandos_end;
}
if(quit_now){
+ errno = EINTR;
goto mandos_end;
}
@@ -775,12 +809,14 @@
ret = gnutls_handshake(session);
if(quit_now){
+ errno = EINTR;
goto mandos_end;
}
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
if(ret < 0){
fprintf(stderr, "*** GnuTLS Re-handshake failed ***\n");
gnutls_perror(ret);
+ errno = EPROTO;
goto mandos_end;
}
break;
@@ -788,6 +824,7 @@
fprintf(stderr, "Unknown error while reading data from"
" encrypted session with Mandos server\n");
gnutls_bye(session, GNUTLS_SHUT_RDWR);
+ errno = EIO;
goto mandos_end;
}
} else {
@@ -800,12 +837,14 @@
}
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);
@@ -820,6 +859,7 @@
written = 0;
while(written < (size_t) decrypted_buffer_size){
if(quit_now){
+ errno = EINTR;
goto mandos_end;
}
@@ -827,10 +867,12 @@
(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));
}
+ errno = e;
goto mandos_end;
}
written += (size_t)ret;
@@ -842,17 +884,25 @@
/* Shutdown procedure */
mandos_end:
- free(decrypted_buffer);
- free(buffer);
- if(tcp_sd >= 0){
- ret = (int)TEMP_FAILURE_RETRY(close(tcp_sd));
- }
- if(ret == -1){
- perror("close");
- }
- gnutls_deinit(session);
- if(quit_now){
- retval = -1;
+ {
+ 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;
}
@@ -974,6 +1024,101 @@
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;
+ 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;
@@ -981,13 +1126,12 @@
intmax_t tmpmax;
char *tmp;
int exitcode = EXIT_SUCCESS;
- const char *interface = "eth0";
+ const char *interface = "";
struct ifreq network;
int sd = -1;
bool take_down_interface = false;
uid_t uid;
gid_t gid;
- char *connect_to = NULL;
char tempdir[] = "/tmp/mandosXXXXXX";
bool tempdir_created = false;
AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
@@ -1056,11 +1200,21 @@
.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;
@@ -1082,8 +1236,7 @@
tmpmax = strtoimax(arg, &tmp, 10);
if(errno != 0 or tmp == arg or *tmp != '\0'
or tmpmax != (typeof(mc.dh_bits))tmpmax){
- fprintf(stderr, "Bad number of DH bits\n");
- exit(EXIT_FAILURE);
+ argp_error(state, "Bad number of DH bits");
}
mc.dh_bits = (typeof(mc.dh_bits))tmpmax;
break;
@@ -1094,28 +1247,46 @@
errno = 0;
delay = strtof(arg, &tmp);
if(errno != 0 or tmp == arg or *tmp != '\0'){
- fprintf(stderr, "Bad delay\n");
- exit(EXIT_FAILURE);
+ argp_error(state, "Bad delay");
}
break;
- case ARGP_KEY_ARG:
- argp_usage(state);
- 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 client -- Get and decrypt"
" passwords from a Mandos server" };
- ret = argp_parse(&argp, argc, argv, 0, 0, NULL);
- if(ret == ARGP_ERR_UNKNOWN){
- fprintf(stderr, "Unknown error while parsing arguments\n");
- exitcode = EXIT_FAILURE;
+ 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;
}
}
@@ -1123,6 +1294,31 @@
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;
+ }
+ free(direntries);
+ } else {
+ 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 */
@@ -1131,7 +1327,7 @@
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;
+ exitcode = EX_UNAVAILABLE;
goto end;
}
@@ -1139,19 +1335,19 @@
ret = sigaddset(&sigterm_action.sa_mask, SIGINT);
if(ret == -1){
perror("sigaddset");
- exitcode = EXIT_FAILURE;
+ exitcode = EX_OSERR;
goto end;
}
ret = sigaddset(&sigterm_action.sa_mask, SIGHUP);
if(ret == -1){
perror("sigaddset");
- exitcode = EXIT_FAILURE;
+ exitcode = EX_OSERR;
goto end;
}
ret = sigaddset(&sigterm_action.sa_mask, SIGTERM);
if(ret == -1){
perror("sigaddset");
- exitcode = EXIT_FAILURE;
+ exitcode = EX_OSERR;
goto end;
}
/* Need to check if the handler is SIG_IGN before handling:
@@ -1161,49 +1357,49 @@
ret = sigaction(SIGINT, NULL, &old_sigterm_action);
if(ret == -1){
perror("sigaction");
- return EXIT_FAILURE;
+ return EX_OSERR;
}
if(old_sigterm_action.sa_handler != SIG_IGN){
ret = sigaction(SIGINT, &sigterm_action, NULL);
if(ret == -1){
perror("sigaction");
- exitcode = EXIT_FAILURE;
+ exitcode = EX_OSERR;
goto end;
}
}
ret = sigaction(SIGHUP, NULL, &old_sigterm_action);
if(ret == -1){
perror("sigaction");
- return EXIT_FAILURE;
+ return EX_OSERR;
}
if(old_sigterm_action.sa_handler != SIG_IGN){
ret = sigaction(SIGHUP, &sigterm_action, NULL);
if(ret == -1){
perror("sigaction");
- exitcode = EXIT_FAILURE;
+ exitcode = EX_OSERR;
goto end;
}
}
ret = sigaction(SIGTERM, NULL, &old_sigterm_action);
if(ret == -1){
perror("sigaction");
- return EXIT_FAILURE;
+ return EX_OSERR;
}
if(old_sigterm_action.sa_handler != SIG_IGN){
ret = sigaction(SIGTERM, &sigterm_action, NULL);
if(ret == -1){
perror("sigaction");
- exitcode = EXIT_FAILURE;
+ exitcode = EX_OSERR;
goto end;
}
}
/* If the interface is down, bring it up */
- if(interface[0] != '\0'){
+ if(strcmp(interface, "none") != 0){
if_index = (AvahiIfIndex) if_nametoindex(interface);
if(if_index == 0){
fprintf(stderr, "No such interface: \"%s\"\n", interface);
- exitcode = EXIT_FAILURE;
+ exitcode = EX_UNAVAILABLE;
goto end;
}
@@ -1220,7 +1416,7 @@
#ifdef __linux__
/* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO
- messages to mess up the prompt */
+ messages about the network interface to mess up the prompt */
ret = klogctl(8, NULL, 5);
bool restore_loglevel = true;
if(ret == -1){
@@ -1232,7 +1428,7 @@
sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
if(sd < 0){
perror("socket");
- exitcode = EXIT_FAILURE;
+ exitcode = EX_OSERR;
#ifdef __linux__
if(restore_loglevel){
ret = klogctl(7, NULL, 0);
@@ -1261,7 +1457,7 @@
}
}
#endif /* __linux__ */
- exitcode = EXIT_FAILURE;
+ exitcode = EX_OSERR;
/* Lower privileges */
errno = 0;
ret = seteuid(uid);
@@ -1276,8 +1472,8 @@
ret = ioctl(sd, SIOCSIFFLAGS, &network);
if(ret == -1){
take_down_interface = false;
- perror("ioctl SIOCSIFFLAGS");
- exitcode = EXIT_FAILURE;
+ perror("ioctl SIOCSIFFLAGS +IFF_UP");
+ exitcode = EX_OSERR;
#ifdef __linux__
if(restore_loglevel){
ret = klogctl(7, NULL, 0);
@@ -1349,7 +1545,7 @@
ret = init_gnutls_global(pubkey, seckey);
if(ret == -1){
fprintf(stderr, "init_gnutls_global failed\n");
- exitcode = EXIT_FAILURE;
+ exitcode = EX_UNAVAILABLE;
goto end;
} else {
gnutls_initialized = true;
@@ -1372,7 +1568,7 @@
if(not init_gpgme(pubkey, seckey, tempdir)){
fprintf(stderr, "init_gpgme failed\n");
- exitcode = EXIT_FAILURE;
+ exitcode = EX_UNAVAILABLE;
goto end;
} else {
gpgme_initialized = true;
@@ -1388,7 +1584,7 @@
char *address = strrchr(connect_to, ':');
if(address == NULL){
fprintf(stderr, "No colon in address\n");
- exitcode = EXIT_FAILURE;
+ exitcode = EX_USAGE;
goto end;
}
@@ -1402,7 +1598,7 @@
if(errno != 0 or tmp == address+1 or *tmp != '\0'
or tmpmax != (uint16_t)tmpmax){
fprintf(stderr, "Bad port number\n");
- exitcode = EXIT_FAILURE;
+ exitcode = EX_USAGE;
goto end;
}
@@ -1424,13 +1620,19 @@
if(quit_now){
goto end;
}
-
- ret = start_mandos_communication(address, port, if_index, af);
- if(ret < 0){
- exitcode = EXIT_FAILURE;
- } else {
+
+ 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;
}
@@ -1460,7 +1662,7 @@
if(mc.server == NULL){
fprintf(stderr, "Failed to create Avahi server: %s\n",
avahi_strerror(error));
- exitcode = EXIT_FAILURE;
+ exitcode = EX_UNAVAILABLE;
goto end;
}
@@ -1475,7 +1677,7 @@
if(sb == NULL){
fprintf(stderr, "Failed to create service browser: %s\n",
avahi_strerror(avahi_server_errno(mc.server)));
- exitcode = EXIT_FAILURE;
+ exitcode = EX_UNAVAILABLE;
goto end;
}
@@ -1530,10 +1732,10 @@
if(ret == -1){
perror("ioctl SIOCGIFFLAGS");
} else if(network.ifr_flags & IFF_UP) {
- network.ifr_flags &= ~IFF_UP; /* clear flag */
+ network.ifr_flags &= ~(short)IFF_UP; /* clear flag */
ret = ioctl(sd, SIOCSIFFLAGS, &network);
if(ret == -1){
- perror("ioctl SIOCSIFFLAGS");
+ perror("ioctl SIOCSIFFLAGS -IFF_UP");
}
}
ret = (int)TEMP_FAILURE_RETRY(close(sd));
=== modified file 'plugins.d/mandos-client.xml'
--- plugins.d/mandos-client.xml 2009-02-09 02:01:13 +0000
+++ plugins.d/mandos-client.xml 2010-09-26 18:32:58 +0000
@@ -2,7 +2,7 @@
-
+
%common;
]>
@@ -202,8 +202,9 @@
Network interface that will be brought up and scanned for
- Mandos servers to connect to. The default is
- eth0
.
+ Mandos servers to connect to. The default is the empty
+ string, which will automatically choose an appropriate
+ interface.
If the option is used, this
@@ -220,10 +221,11 @@
by this program.
- NAME can be the empty string;
- this will not use any specific interface, and will not
- bring up an interface on startup. This is not
- recommended, and only meant for advanced users.
+ 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.
=== modified file 'plugins.d/password-prompt.c'
--- plugins.d/password-prompt.c 2009-09-07 07:48:59 +0000
+++ plugins.d/password-prompt.c 2010-09-26 18:32:58 +0000
@@ -2,8 +2,8 @@
/*
* Password-prompt - Read a password from the terminal and print it
*
- * Copyright © 2008,2009 Teddy Hogeborn
- * Copyright © 2008,2009 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
@@ -37,19 +37,24 @@
#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 sig_atomic_t quit_now = 0;
int signal_received;
@@ -66,7 +71,8 @@
}
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;
@@ -82,10 +88,20 @@
.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){
+ errno = 0;
switch (key){
case 'p':
prefix = arg;
@@ -93,25 +109,42 @@
case 128:
debug = true;
break;
- case ARGP_KEY_ARG:
- argp_usage(state);
- break;
- case ARGP_KEY_END:
+ /*
+ * These reproduce what we would get without ARGP_NO_HELP
+ */
+ case '?': /* --help */
+ argp_state_help(state, state->out_stream,
+ (ARGP_HELP_STD_HELP | ARGP_HELP_EXIT_ERR)
+ & ~(unsigned int)ARGP_HELP_EXIT_OK);
+ case -3: /* --usage */
+ argp_state_help(state, state->out_stream,
+ ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR);
+ case 'V': /* --version */
+ fprintf(state->out_stream, "%s\n", argp_program_version);
+ exit(argp_err_exit_status);
break;
default:
return ARGP_ERR_UNKNOWN;
}
- return 0;
+ return errno;
}
struct argp argp = { .options = options, .parser = parse_opt,
.args_doc = "",
.doc = "Mandos password-prompt -- Read and"
" output a password" };
- ret = argp_parse(&argp, argc, argv, 0, 0, NULL);
- if(ret == ARGP_ERR_UNKNOWN){
- fprintf(stderr, "Unknown error while parsing arguments\n");
- return EXIT_FAILURE;
+ ret = argp_parse(&argp, argc, argv,
+ ARGP_IN_ORDER | ARGP_NO_HELP, NULL, NULL);
+ switch(ret){
+ case 0:
+ break;
+ case ENOMEM:
+ default:
+ errno = ret;
+ error(0, errno, "argp_parse");
+ return EX_OSERR;
+ case EINVAL:
+ return EX_USAGE;
}
}
@@ -123,25 +156,32 @@
}
if(tcgetattr(STDIN_FILENO, &t_old) != 0){
- perror("tcgetattr");
- return EXIT_FAILURE;
+ 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);
ret = sigaddset(&new_action.sa_mask, SIGINT);
if(ret == -1){
- perror("sigaddset");
- return EXIT_FAILURE;
+ error(0, errno, "sigaddset");
+ return EX_OSERR;
}
ret = sigaddset(&new_action.sa_mask, SIGHUP);
if(ret == -1){
- perror("sigaddset");
- return EXIT_FAILURE;
+ error(0, errno, "sigaddset");
+ return EX_OSERR;
}
ret = sigaddset(&new_action.sa_mask, SIGTERM);
if(ret == -1){
- perror("sigaddset");
- return EXIT_FAILURE;
+ error(0, errno, "sigaddset");
+ return EX_OSERR;
}
/* Need to check if the handler is SIG_IGN before handling:
| [[info:libc:Initial Signal Actions]] |
@@ -149,38 +189,38 @@
*/
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){
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){
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){
ret = sigaction(SIGTERM, &new_action, NULL);
if(ret == -1){
- perror("sigaction");
- return EXIT_FAILURE;
+ error(0, errno, "sigaction");
+ return EX_OSERR;
}
}
@@ -190,12 +230,20 @@
}
t_new = t_old;
- t_new.c_lflag &= ~ECHO;
+ t_new.c_lflag &= ~(tcflag_t)ECHO;
if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_new) != 0){
- perror("tcsetattr-echo");
- return EXIT_FAILURE;
+ 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){
fprintf(stderr, "Waiting for input from stdin \n");
}
@@ -212,55 +260,101 @@
fprintf(stderr, "%s ", prefix);
}
{
- const char *cryptsource = getenv("cryptsource");
- const char *crypttarget = getenv("crypttarget");
- const char *const prompt
- = "Enter passphrase to unlock the disk";
+ const char *cryptsource = getenv("CRYPTTAB_SOURCE");
+ const char *crypttarget = getenv("CRYPTTAB_NAME");
+ /* Before cryptsetup 1.1.0~rc2 */
+ if(cryptsource == NULL){
+ cryptsource = getenv("cryptsource");
+ }
+ if(crypttarget == NULL){
+ crypttarget = getenv("crypttarget");
+ }
+ const char *const prompt1 = "Unlocking the disk";
+ const char *const prompt2 = "Enter passphrase";
if(cryptsource == NULL){
if(crypttarget == NULL){
- fprintf(stderr, "%s: ", prompt);
+ fprintf(stderr, "%s to unlock the disk: ", prompt2);
} else {
- fprintf(stderr, "%s (%s): ", prompt, crypttarget);
+ fprintf(stderr, "%s (%s)\n%s: ", prompt1, crypttarget,
+ prompt2);
}
} else {
if(crypttarget == NULL){
- fprintf(stderr, "%s %s: ", prompt, cryptsource);
+ fprintf(stderr, "%s %s\n%s: ", prompt1, cryptsource,
+ prompt2);
} else {
- fprintf(stderr, "%s %s (%s): ", prompt, cryptsource,
- crypttarget);
+ fprintf(stderr, "%s %s (%s)\n%s: ", prompt1, cryptsource,
+ crypttarget, prompt2);
}
}
}
- ret = getline(&buffer, &n, stdin);
- if(ret > 0){
+ sret = getline(&buffer, &n, stdin);
+ if(sret > 0){
status = EXIT_SUCCESS;
/* Make n = data size instead of allocated buffer size */
- n = (size_t)ret;
+ n = (size_t)sret;
/* Strip final newline */
- if(n>0 and buffer[n-1] == '\n'){
+ if(n > 0 and buffer[n-1] == '\n'){
buffer[n-1] = '\0'; /* not strictly necessary */
n--;
}
size_t written = 0;
while(written < n){
- ret = write(STDOUT_FILENO, buffer + written, n - written);
- if(ret < 0){
- perror("write");
- status = EXIT_FAILURE;
- break;
- }
- written += (size_t)ret;
+ sret = write(STDOUT_FILENO, buffer + written, n - written);
+ if(sret < 0){
+ int e = errno;
+ error(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(sret < 0){
+ int e = errno;
if(errno != EINTR and not feof(stdin)){
- perror("getline");
- status = EXIT_FAILURE;
+ 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){
@@ -276,7 +370,7 @@
fprintf(stderr, "Restoring terminal attributes\n");
}
if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_old) != 0){
- perror("tcsetattr+echo");
+ error(0, errno, "tcsetattr+echo");
}
if(quit_now){
@@ -284,7 +378,7 @@
old_action.sa_handler = SIG_DFL;
ret = sigaction(signal_received, &old_action, NULL);
if(ret == -1){
- perror("sigaction");
+ error(0, errno, "sigaction");
}
raise(signal_received);
}
@@ -293,7 +387,7 @@
fprintf(stderr, "%s is exiting with status %d\n", argv[0],
status);
}
- if(status == EXIT_SUCCESS){
+ if(status == EXIT_SUCCESS or status == EX_OK){
fputc('\n', stderr);
}
=== modified file 'plugins.d/password-prompt.xml'
--- plugins.d/password-prompt.xml 2009-01-04 21:54:55 +0000
+++ plugins.d/password-prompt.xml 2009-10-30 16:23:43 +0000
@@ -2,7 +2,7 @@
-
+
%common;
]>
@@ -183,8 +183,8 @@
ENVIRONMENT
- cryptsource
- crypttarget
+ CRYPTTAB_SOURCE
+ CRYPTTAB_NAME
If set, these environment variables will be assumed to
=== 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
+
+
+
+
+
+
+
+
=== modified file 'plugins.d/splashy.c'
--- plugins.d/splashy.c 2009-09-16 23:28:39 +0000
+++ plugins.d/splashy.c 2010-09-26 18:32:58 +0000
@@ -2,8 +2,8 @@
/*
* Splashy - Read a password from splashy and output it
*
- * Copyright © 2008,2009 Teddy Hogeborn
- * Copyright © 2008,2009 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
@@ -29,7 +29,7 @@
SIG_IGN, kill(), SIGKILL */
#include /* NULL */
#include /* getenv() */
-#include /* asprintf(), perror() */
+#include /* asprintf() */
#include /* EXIT_FAILURE, free(),
EXIT_SUCCESS */
#include /* pid_t, DIR, struct dirent,
@@ -43,9 +43,17 @@
STDOUT_FILENO, _exit(),
pause() */
#include /* memcmp() */
-#include /* errno */
+#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;
@@ -65,6 +73,7 @@
DIR *proc_dir = NULL;
pid_t splashy_pid = 0;
pid_t splashy_command_pid = 0;
+ int exitstatus = EXIT_FAILURE;
/* Create prompt string */
{
@@ -90,6 +99,7 @@
}
if(ret == -1){
prompt = NULL;
+ exitstatus = EX_OSERR;
goto failure;
}
}
@@ -99,7 +109,23 @@
const char splashy_name[] = "/sbin/splashy";
proc_dir = opendir("/proc");
if(proc_dir == NULL){
- perror("opendir");
+ 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);
@@ -126,7 +152,8 @@
char *exe_link;
ret = asprintf(&exe_link, "/proc/%s/exe", proc_ent->d_name);
if(ret == -1){
- perror("asprintf");
+ error(0, errno, "asprintf");
+ exitstatus = EX_OSERR;
goto failure;
}
@@ -138,8 +165,20 @@
free(exe_link);
continue;
}
- perror("lstat");
+ 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)
@@ -163,6 +202,7 @@
proc_dir = NULL;
}
if(splashy_pid == 0){
+ exitstatus = EX_UNAVAILABLE;
goto failure;
}
@@ -174,52 +214,61 @@
sigemptyset(&new_action.sa_mask);
ret = sigaddset(&new_action.sa_mask, SIGINT);
if(ret == -1){
- perror("sigaddset");
+ error(0, errno, "sigaddset");
+ exitstatus = EX_OSERR;
goto failure;
}
ret = sigaddset(&new_action.sa_mask, SIGHUP);
if(ret == -1){
- perror("sigaddset");
+ error(0, errno, "sigaddset");
+ exitstatus = EX_OSERR;
goto failure;
}
ret = sigaddset(&new_action.sa_mask, SIGTERM);
if(ret == -1){
- perror("sigaddset");
+ error(0, errno, "sigaddset");
+ exitstatus = EX_OSERR;
goto failure;
}
ret = sigaction(SIGINT, NULL, &old_action);
if(ret == -1){
- perror("sigaction");
+ 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){
- perror("sigaction");
+ error(0, errno, "sigaction");
+ exitstatus = EX_OSERR;
goto failure;
}
}
ret = sigaction(SIGHUP, NULL, &old_action);
if(ret == -1){
- perror("sigaction");
+ 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){
- perror("sigaction");
+ error(0, errno, "sigaction");
+ exitstatus = EX_OSERR;
goto failure;
}
}
ret = sigaction(SIGTERM, NULL, &old_action);
if(ret == -1){
- perror("sigaction");
+ 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){
- perror("sigaction");
+ error(0, errno, "sigaction");
+ exitstatus = EX_OSERR;
goto failure;
}
}
@@ -235,7 +284,8 @@
goto failure;
}
if(splashy_command_pid == -1){
- perror("fork");
+ error(0, errno, "fork");
+ exitstatus = EX_OSERR;
goto failure;
}
/* Child */
@@ -243,7 +293,31 @@
if(not interrupted_by_signal){
const char splashy_command[] = "/sbin/splashy_update";
execl(splashy_command, splashy_command, prompt, (char *)NULL);
- perror("execl");
+ 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);
@@ -268,7 +342,7 @@
goto failure;
}
if(ret == -1){
- perror("waitpid");
+ error(0, errno, "waitpid");
if(errno == ECHILD){
splashy_command_pid = 0;
}
@@ -306,26 +380,42 @@
the real user ID (_mandos) */
ret = setuid(geteuid());
if(ret == -1){
- perror("setuid");
+ error(0, errno, "setuid");
}
setsid();
ret = chdir("/");
if(ret == -1){
- perror("chdir");
+ error(0, errno, "chdir");
}
/* if(fork() != 0){ */
/* _exit(EXIT_SUCCESS); */
/* } */
ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace stdout */
if(ret == -1){
- perror("dup2");
- _exit(EXIT_FAILURE);
+ error(0, errno, "dup2");
+ _exit(EX_OSERR);
}
-
+
execl("/sbin/splashy", "/sbin/splashy", "boot", (char *)NULL);
- perror("execl");
- _exit(EXIT_FAILURE);
+ {
+ 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);
+ }
+ }
}
}
@@ -336,17 +426,17 @@
ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received,
&signal_action, NULL));
if(ret == -1){
- perror("sigaction");
+ error(0, errno, "sigaction");
}
do {
ret = raise(signal_received);
} while(ret != 0 and errno == EINTR);
if(ret != 0){
- perror("raise");
+ error(0, errno, "raise");
abort();
}
TEMP_FAILURE_RETRY(pause());
}
- return EXIT_FAILURE;
+ return exitstatus;
}
=== modified file 'plugins.d/usplash.c'
--- plugins.d/usplash.c 2009-09-17 04:16:32 +0000
+++ plugins.d/usplash.c 2010-09-26 18:32:58 +0000
@@ -2,8 +2,8 @@
/*
* Usplash - Read a password from usplash and output it
*
- * Copyright © 2008,2009 Teddy Hogeborn
- * Copyright © 2008,2009 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
@@ -31,22 +31,25 @@
#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(), perror() */
+#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() */
-#include /* getenv() */
+ 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;
@@ -152,7 +155,7 @@
size_t cmdline_len = 0;
DIR *proc_dir = opendir("/proc");
if(proc_dir == NULL){
- perror("opendir");
+ error(0, errno, "opendir");
return -1;
}
errno = 0;
@@ -180,7 +183,7 @@
char *exe_link;
ret = asprintf(&exe_link, "/proc/%s/exe", proc_ent->d_name);
if(ret == -1){
- perror("asprintf");
+ error(0, errno, "asprintf");
goto fail_find_usplash;
}
@@ -192,7 +195,7 @@
free(exe_link);
continue;
}
- perror("lstat");
+ error(0, errno, "lstat");
free(exe_link);
goto fail_find_usplash;
}
@@ -223,13 +226,13 @@
ret = asprintf(&cmdline_filename, "/proc/%s/cmdline",
proc_ent->d_name);
if(ret == -1){
- perror("asprintf");
+ error(0, errno, "asprintf");
goto fail_find_usplash;
}
cl_fd = open(cmdline_filename, O_RDONLY);
free(cmdline_filename);
if(cl_fd == -1){
- perror("open");
+ error(0, errno, "open");
goto fail_find_usplash;
}
}
@@ -241,7 +244,7 @@
if(cmdline_len + blocksize > cmdline_allocated){
tmp = realloc(cmdline, cmdline_allocated + blocksize);
if(tmp == NULL){
- perror("realloc");
+ error(0, errno, "realloc");
close(cl_fd);
goto fail_find_usplash;
}
@@ -252,7 +255,7 @@
sret = read(cl_fd, cmdline + cmdline_len,
cmdline_allocated - cmdline_len);
if(sret == -1){
- perror("read");
+ error(0, errno, "read");
close(cl_fd);
goto fail_find_usplash;
}
@@ -260,14 +263,14 @@
} while(sret != 0);
ret = close(cl_fd);
if(ret == -1){
- perror("close");
+ error(0, errno, "close");
goto fail_find_usplash;
}
}
/* Close directory */
ret = closedir(proc_dir);
if(ret == -1){
- perror("closedir");
+ error(0, errno, "closedir");
goto fail_find_usplash;
}
/* Success */
@@ -297,9 +300,11 @@
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;
}
@@ -308,6 +313,7 @@
size_t cmdline_len = 0;
usplash_pid = find_usplash(&cmdline, &cmdline_len);
if(usplash_pid == 0){
+ status = EX_UNAVAILABLE;
goto failure;
}
@@ -319,23 +325,27 @@
sigemptyset(&new_action.sa_mask);
ret = sigaddset(&new_action.sa_mask, SIGINT);
if(ret == -1){
- perror("sigaddset");
+ error(0, errno, "sigaddset");
+ status = EX_OSERR;
goto failure;
}
ret = sigaddset(&new_action.sa_mask, SIGHUP);
if(ret == -1){
- perror("sigaddset");
+ error(0, errno, "sigaddset");
+ status = EX_OSERR;
goto failure;
}
ret = sigaddset(&new_action.sa_mask, SIGTERM);
if(ret == -1){
- perror("sigaddset");
+ error(0, errno, "sigaddset");
+ status = EX_OSERR;
goto failure;
}
ret = sigaction(SIGINT, NULL, &old_action);
if(ret == -1){
if(errno != EINTR){
- perror("sigaction");
+ error(0, errno, "sigaction");
+ status = EX_OSERR;
}
goto failure;
}
@@ -343,7 +353,8 @@
ret = sigaction(SIGINT, &new_action, NULL);
if(ret == -1){
if(errno != EINTR){
- perror("sigaction");
+ error(0, errno, "sigaction");
+ status = EX_OSERR;
}
goto failure;
}
@@ -351,7 +362,8 @@
ret = sigaction(SIGHUP, NULL, &old_action);
if(ret == -1){
if(errno != EINTR){
- perror("sigaction");
+ error(0, errno, "sigaction");
+ status = EX_OSERR;
}
goto failure;
}
@@ -359,7 +371,8 @@
ret = sigaction(SIGHUP, &new_action, NULL);
if(ret == -1){
if(errno != EINTR){
- perror("sigaction");
+ error(0, errno, "sigaction");
+ status = EX_OSERR;
}
goto failure;
}
@@ -367,7 +380,8 @@
ret = sigaction(SIGTERM, NULL, &old_action);
if(ret == -1){
if(errno != EINTR){
- perror("sigaction");
+ error(0, errno, "sigaction");
+ status = EX_OSERR;
}
goto failure;
}
@@ -375,7 +389,8 @@
ret = sigaction(SIGTERM, &new_action, NULL);
if(ret == -1){
if(errno != EINTR){
- perror("sigaction");
+ error(0, errno, "sigaction");
+ status = EX_OSERR;
}
goto failure;
}
@@ -386,7 +401,8 @@
/* Write command to FIFO */
if(not usplash_write(&fifo_fd, "TIMEOUT", "0")){
if(errno != EINTR){
- perror("usplash_write");
+ error(0, errno, "usplash_write");
+ status = EX_OSERR;
}
goto failure;
}
@@ -397,7 +413,8 @@
if(not usplash_write(&fifo_fd, "INPUTQUIET", prompt)){
if(errno != EINTR){
- perror("usplash_write");
+ error(0, errno, "usplash_write");
+ status = EX_OSERR;
}
goto failure;
}
@@ -414,7 +431,8 @@
outfifo_fd = open("/dev/.initramfs/usplash_outfifo", O_RDONLY);
if(outfifo_fd == -1){
if(errno != EINTR){
- perror("open");
+ error(0, errno, "open");
+ status = EX_OSERR;
}
goto failure;
}
@@ -432,7 +450,8 @@
char *tmp = realloc(buf, buf_allocated + blocksize);
if(tmp == NULL){
if(errno != EINTR){
- perror("realloc");
+ error(0, errno, "realloc");
+ status = EX_OSERR;
}
goto failure;
}
@@ -443,7 +462,8 @@
buf_allocated - buf_len);
if(sret == -1){
if(errno != EINTR){
- perror("read");
+ error(0, errno, "read");
+ status = EX_OSERR;
}
TEMP_FAILURE_RETRY(close(outfifo_fd));
goto failure;
@@ -457,7 +477,8 @@
ret = close(outfifo_fd);
if(ret == -1){
if(errno != EINTR){
- perror("close");
+ error(0, errno, "close");
+ status = EX_OSERR;
}
goto failure;
}
@@ -469,7 +490,8 @@
if(not usplash_write(&fifo_fd, "TIMEOUT", "15")){
if(errno != EINTR){
- perror("usplash_write");
+ error(0, errno, "usplash_write");
+ status = EX_OSERR;
}
goto failure;
}
@@ -481,7 +503,8 @@
ret = close(fifo_fd);
if(ret == -1){
if(errno != EINTR){
- perror("close");
+ error(0, errno, "close");
+ status = EX_OSERR;
}
goto failure;
}
@@ -494,7 +517,8 @@
sret = write(STDOUT_FILENO, buf + written, buf_len - written);
if(sret == -1){
if(errno != EINTR){
- perror("write");
+ error(0, errno, "write");
+ status = EX_OSERR;
}
goto failure;
}
@@ -523,14 +547,14 @@
/* If usplash was never accessed, we can stop now */
if(not usplash_accessed){
- return EXIT_FAILURE;
+ return status;
}
/* Close FIFO */
if(fifo_fd != -1){
ret = (int)TEMP_FAILURE_RETRY(close(fifo_fd));
if(ret == -1 and errno != EINTR){
- perror("close");
+ error(0, errno, "close");
}
fifo_fd = -1;
}
@@ -539,31 +563,19 @@
if(outfifo_fd != -1){
ret = (int)TEMP_FAILURE_RETRY(close(outfifo_fd));
if(ret == -1){
- perror("close");
- }
- }
-
- /* Create argc and argv for new usplash*/
- int cmdline_argc = 0;
- char **cmdline_argv = malloc(sizeof(char *));
- {
- size_t position = 0;
- while(position < cmdline_len){
- char **tmp = realloc(cmdline_argv,
- (sizeof(char *)
- * (size_t)(cmdline_argc + 2)));
- if(tmp == NULL){
- perror("realloc");
- free(cmdline_argv);
- return EXIT_FAILURE;
- }
- cmdline_argv = tmp;
- cmdline_argv[cmdline_argc] = cmdline + position;
- cmdline_argc++;
- position += strlen(cmdline + position) + 1;
- }
- cmdline_argv[cmdline_argc] = NULL;
- }
+ 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);
@@ -580,34 +592,38 @@
the real user ID (_mandos) */
ret = setuid(geteuid());
if(ret == -1){
- perror("setuid");
+ 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){
- perror("dup2");
- _exit(EXIT_FAILURE);
+ error(0, errno, "dup2");
+ _exit(EX_OSERR);
}
execv(usplash_name, cmdline_argv);
if(not interrupted_by_signal){
- perror("execv");
+ error(0, errno, "execv");
}
free(cmdline);
free(cmdline_argv);
- _exit(EXIT_FAILURE);
+ _exit(EX_OSERR);
}
free(cmdline);
free(cmdline_argv);
sleep(2);
if(not usplash_write(&fifo_fd, "PULSATE", NULL)){
if(errno != EINTR){
- perror("usplash_write");
+ error(0, errno, "usplash_write");
}
}
@@ -615,7 +631,7 @@
if(fifo_fd != -1){
ret = (int)TEMP_FAILURE_RETRY(close(fifo_fd));
if(ret == -1 and errno != EINTR){
- perror("close");
+ error(0, errno, "close");
}
fifo_fd = -1;
}
@@ -626,17 +642,17 @@
ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received,
&signal_action, NULL));
if(ret == -1){
- perror("sigaction");
+ error(0, errno, "sigaction");
}
do {
ret = raise(signal_received);
} while(ret != 0 and errno == EINTR);
if(ret != 0){
- perror("raise");
+ error(0, errno, "raise");
abort();
}
TEMP_FAILURE_RETRY(pause());
}
- return EXIT_FAILURE;
+ return status;
}