=== added directory '.bzr-builddeb' === added file '.bzr-builddeb/default.conf' --- .bzr-builddeb/default.conf 1970-01-01 00:00:00 +0000 +++ .bzr-builddeb/default.conf 2008-09-17 00:34:09 +0000 @@ -0,0 +1,2 @@ +[BUILDDEB] +split = True === modified file '.bzrignore' --- .bzrignore 2008-08-27 01:18:25 +0000 +++ .bzrignore 2008-10-03 09:32:30 +0000 @@ -1,8 +1,14 @@ *.5 *.8 *.8mandos +confdir +debian/po/messages.mo +debian/po/templates.pot +keydir +man plugin-runner +plugins.d/askpass-fifo +plugins.d/mandos-client plugins.d/password-prompt -plugins.d/password-request -confdir -keydir +plugins.d/splashy +plugins.d/usplash === added file 'INSTALL' --- INSTALL 1970-01-01 00:00:00 +0000 +++ INSTALL 2009-02-15 09:28:06 +0000 @@ -0,0 +1,133 @@ +-*- org -*- + +* Prerequisites + +** Operating System + + Debian 5.0 "lenny" or Ubuntu 8.04 "Hardy Heron". + + This is mostly for the support scripts which make sure that the + client is installed and started in the initial RAM disk environment + and that the initrd.img file is automatically made unreadable. The + server and client programs themselves *could* be run in other + distributions, but they *are* specific to GNU/Linux systems, and + are not intended to be portable to other Unixes. + +** Libraries + + The following libraries and packages are needed. (It is possible + that it might work with older versions of some of these, but these + versions are confirmed to work. Newer versions are almost + certainly OK.) + +*** Documentation + These are required to build the manual pages for both the server + and client: + + + DocBook 4.5 http://www.docbook.org/ + Note: DocBook 5.0 is not compatible. + + DocBook XSL stylesheets 1.71.0 + http://wiki.docbook.org/topic/DocBookXslStylesheets + + Package names: + docbook docbook-xsl + + To build just the documentation, run the command "make doc". Then + the manual page "mandos.8", for example, can be read by running + "man -l mandos.8". + +*** Mandos Server + + GnuTLS 2.4 http://www.gnu.org/software/gnutls/ + + Avahi 0.6.16 http://www.avahi.org/ + + Python 2.5 http://www.python.org/ + + Python-GnuTLS 1.1.5 http://pypi.python.org/pypi/python-gnutls/ + + dbus-python 0.82.4 http://dbus.freedesktop.org/doc/dbus-python/ + + python-ctypes 1.0.0 http://pypi.python.org/pypi/ctypes + + PyGObject 2.14.2 http://library.gnome.org/devel/pygobject/ + + Strongly recommended: + + fping 2.4b2-to-ipv6 http://www.fping.com/ + + Package names: + python-gnutls avahi-daemon python python-avahi python-dbus + python-ctypes python-gobject + +*** Mandos Client + + initramfs-tools 0.85i + http://packages.qa.debian.org/i/initramfs-tools.html + + GnuTLS 2.4 http://www.gnu.org/software/gnutls/ + + Avahi 0.6.16 http://www.avahi.org/ + + GnuPG 1.4.9 http://www.gnupg.org/ + + GPGME 1.1.6 http://www.gnupg.org/related_software/gpgme/ + + Package names: + initramfs-tools libgnutls-dev libavahi-core-dev gnupg + libgpgme11-dev + +* Installing the Mandos server + + 1. Do "make doc". + + 2. On the computer to run as a Mandos server, run the following + command: + For Debian: su -c 'make install-server' + For Ubuntu: sudo make install-server + + (This creates a configuration without any clients configured; you + need an actually configured client to do that; see below.) + +* Installing the Mandos client. + + 1. Do "make all doc". + + 2. On the computer to run as a Mandos client, run the following + command: + For Debian: su -c 'make install-client' + For Ubuntu: sudo make install-client + + This will also create an OpenPGP key, which will take some time + and entropy, so be patient. + + 3. Run the following command: + For Debian: su -c 'mandos-keygen --password' + For Ubuntu: sudo mandos-keygen --password + + When prompted, enter the password/passphrase for the encrypted + root file system on this client computer. The command will + output a section of text, starting with a [section header]. Copy + and append this to the file "/etc/mandos/clients.conf" *on the + server computer*. + + 4. Configure the client to use the correct network interface. The + default is "eth0", and if this needs to be adjusted, it will be + necessary to edit /etc/mandos/plugin-runner.conf to uncomment and + change the line there. If that file is changed, the initrd.img + file must be updated, possibly using the following command: + + # update-initramfs -k all -u + + 5. On the server computer, start the server by running the command + For Debian: su -c 'invoke-rc.d mandos start' + For Ubuntu: sudo invoke-rc.d mandos start + + At this point, it is possible to verify that the correct password + will be received by the client by running the command: + + # /usr/lib/mandos/plugins.d/mandos-client \ + --pubkey=/etc/keys/mandos/pubkey.txt \ + --seckey=/etc/keys/mandos/seckey.txt; echo + + This command should retrieve the password from the server, + decrypt it, and output it to standard output. + + After this, the client computer should be able to reboot without + needing a password entered on the console, as long as it does not + take more than an hour to reboot. + +* Further customizations + + You may want to tighten or loosen the timeouts in the server + configuration files; see mandos.conf(5) and mandos-clients.conf(5). + If IPsec is not used, it is suggested that a more cryptographically + secure checker program is used and configured, since without IPsec + ping packets can be faked. === modified file 'Makefile' --- Makefile 2008-09-05 18:19:52 +0000 +++ Makefile 2009-05-23 05:59:52 +0000 @@ -1,40 +1,55 @@ WARN=-O -Wall -Wformat=2 -Winit-self -Wmissing-include-dirs \ -Wswitch-default -Wswitch-enum -Wunused-parameter \ - -Wstrict-aliasing=2 -Wextra -Wfloat-equal -Wundef -Wshadow \ + -Wstrict-aliasing=1 -Wextra -Wfloat-equal -Wundef -Wshadow \ -Wunsafe-loop-optimizations -Wpointer-arith \ -Wbad-function-cast -Wcast-qual -Wcast-align -Wwrite-strings \ -Wconversion -Wstrict-prototypes -Wold-style-definition \ - -Wpacked -Wnested-externs -Wunreachable-code -Winline \ - -Wvolatile-register-var -DEBUG=-ggdb3 + -Wpacked -Wnested-externs -Winline -Wvolatile-register-var +# -Wunreachable-code +#DEBUG=-ggdb3 # For info about _FORTIFY_SOURCE, see # -FORTIFY=-D_FORTIFY_SOURCE=2 # -fstack-protector-all +FORTIFY=-D_FORTIFY_SOURCE=2 -fstack-protector-all -fPIC -fPIE +LINK_FORTIFY_LD=-z relro -fPIE +LINK_FORTIFY=-pie #COVERAGE=--coverage OPTIMIZE=-Os LANGUAGE=-std=gnu99 -# PREFIX=/usr/local +htmldir=man +version=1.0.11 +SED=sed + +## Use these settings for a traditional /usr/local install +# PREFIX=$(DESTDIR)/usr/local +# CONFDIR=$(DESTDIR)/etc/mandos +# KEYDIR=$(DESTDIR)/etc/mandos/keys +# MANDIR=$(PREFIX)/man +# INITRAMFSTOOLS=$(DESTDIR)/etc/initramfs-tools +## + +## These settings are for a package-type install PREFIX=$(DESTDIR)/usr -# CONFDIR=/usr/local/lib/mandos CONFDIR=$(DESTDIR)/etc/mandos -# KEYDIR=/usr/local/lib/mandos/keys KEYDIR=$(DESTDIR)/etc/keys/mandos -# MANDIR=/usr/local/man -MANDIR=$(DESTDIR)/usr/share/man +MANDIR=$(PREFIX)/share/man +INITRAMFSTOOLS=$(DESTDIR)/usr/share/initramfs-tools +## -GNUTLS_CFLAGS=$(shell libgnutls-config --cflags) -GNUTLS_LIBS=$(shell libgnutls-config --libs) +GNUTLS_CFLAGS=$(shell pkg-config --cflags-only-I gnutls) +GNUTLS_LIBS=$(shell pkg-config --libs gnutls) AVAHI_CFLAGS=$(shell pkg-config --cflags-only-I avahi-core) AVAHI_LIBS=$(shell pkg-config --libs avahi-core) -GPGME_CFLAGS=$(shell gpgme-config --cflags) -GPGME_LIBS=$(shell gpgme-config --libs) +GPGME_CFLAGS=$(shell gpgme-config --cflags; getconf LFS_CFLAGS) +GPGME_LIBS=$(shell gpgme-config --libs; getconf LFS_LIBS; \ + getconf LFS_LDFLAGS) # Do not change these two CFLAGS=$(WARN) $(DEBUG) $(FORTIFY) $(COVERAGE) $(OPTIMIZE) \ - $(LANGUAGE) $(GNUTLS_CFLAGS) $(AVAHI_CFLAGS) $(GPGME_CFLAGS) -LDFLAGS=$(COVERAGE) + $(LANGUAGE) $(GNUTLS_CFLAGS) $(AVAHI_CFLAGS) $(GPGME_CFLAGS) \ + -DVERSION='"$(version)"' +LDFLAGS=$(COVERAGE) $(LINK_FORTIFY) $(foreach flag,$(LINK_FORTIFY_LD),-Xlinker $(flag)) -# Commands to format a DocBook refentry document into a manual page +# Commands to format a DocBook document into a manual page DOCBOOKTOMAN=cd $(dir $<); xsltproc --nonet --xinclude \ --param man.charmap.use.subset 0 \ --param make.year.ranges 1 \ @@ -44,72 +59,153 @@ /usr/share/xml/docbook/stylesheet/nwalsh/manpages/docbook.xsl \ $(notdir $<); \ $(MANPOST) $(notdir $@) -# DocBook-to-man post-processing to fix a \n escape bug -MANPOST=sed --in-place --expression='s,\\\\en,\\en,g;s,\\n,\\en,g' - -PLUGINS=plugins.d/password-prompt plugins.d/password-request -PROGS=plugin-runner $(PLUGINS) +# 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 \ + --param make.year.ranges 1 \ + --param make.single.year.ranges 1 \ + --param man.output.quietly 1 \ + --param man.authors.section.enabled 0 \ + --param citerefentry.link 1 \ + --output $@ \ + /usr/share/xml/docbook/stylesheet/nwalsh/xhtml/docbook.xsl \ + $<; $(HTMLPOST) $@ +# Fix citerefentry links +HTMLPOST=$(SED) --in-place \ + --expression='s/\(\)\([^<]*\)\(<\/span>(\)\([^)]*\)\()<\/span><\/a>\)/\1\3.\5\2\3\4\5\6/g' + +PLUGINS=plugins.d/password-prompt plugins.d/mandos-client \ + plugins.d/usplash plugins.d/splashy plugins.d/askpass-fifo +CPROGS=plugin-runner $(PLUGINS) +PROGS=mandos mandos-keygen mandos-ctl $(CPROGS) DOCS=mandos.8 plugin-runner.8mandos mandos-keygen.8 \ - plugins.d/password-request.8mandos \ + plugins.d/mandos-client.8mandos \ plugins.d/password-prompt.8mandos mandos.conf.5 \ - mandos-clients.conf.5 - -objects=$(addsuffix .o,$(PROGS)) - -all: $(PROGS) + plugins.d/usplash.8mandos plugins.d/splashy.8mandos \ + plugins.d/askpass-fifo.8mandos mandos-clients.conf.5 + +htmldocs=$(addsuffix .xhtml,$(DOCS)) + +objects=$(addsuffix .o,$(CPROGS)) + +all: $(PROGS) mandos.lsm doc: $(DOCS) -%.5: %.xml legalnotice.xml - $(DOCBOOKTOMAN) - -%.8: %.xml legalnotice.xml - $(DOCBOOKTOMAN) - -%.8mandos: %.xml legalnotice.xml - $(DOCBOOKTOMAN) - -mandos.8: mandos.xml mandos-options.xml overview.xml legalnotice.xml - $(DOCBOOKTOMAN) - -mandos-keygen.8: mandos-keygen.xml overview.xml legalnotice.xml - $(DOCBOOKTOMAN) - -mandos.conf.5: mandos.conf.xml mandos-options.xml legalnotice.xml - $(DOCBOOKTOMAN) - -plugin-runner.8mandos: plugin-runner.xml overview.xml legalnotice.xml - $(DOCBOOKTOMAN) - -plugins.d/password-request.8mandos: plugins.d/password-request.xml \ - mandos-options.xml \ - overview.xml legalnotice.xml - $(DOCBOOKTOMAN) - -plugins.d/password-request: plugins.d/password-request.o +html: $(htmldocs) + +%.5: %.xml common.ent legalnotice.xml + $(DOCBOOKTOMAN) +%.5.xhtml: %.xml common.ent legalnotice.xml + $(DOCBOOKTOHTML) + +%.8: %.xml common.ent legalnotice.xml + $(DOCBOOKTOMAN) +%.8.xhtml: %.xml common.ent legalnotice.xml + $(DOCBOOKTOHTML) + +%.8mandos: %.xml common.ent legalnotice.xml + $(DOCBOOKTOMAN) +%.8mandos.xhtml: %.xml common.ent legalnotice.xml + $(DOCBOOKTOHTML) + +mandos.8: mandos.xml common.ent mandos-options.xml overview.xml \ + legalnotice.xml + $(DOCBOOKTOMAN) +mandos.8.xhtml: mandos.xml common.ent mandos-options.xml \ + overview.xml legalnotice.xml + $(DOCBOOKTOHTML) + +mandos-keygen.8: mandos-keygen.xml common.ent overview.xml \ + legalnotice.xml + $(DOCBOOKTOMAN) +mandos-keygen.8.xhtml: mandos-keygen.xml common.ent overview.xml \ + legalnotice.xml + $(DOCBOOKTOHTML) + +mandos.conf.5: mandos.conf.xml common.ent mandos-options.xml \ + legalnotice.xml + $(DOCBOOKTOMAN) +mandos.conf.5.xhtml: mandos.conf.xml common.ent mandos-options.xml \ + legalnotice.xml + $(DOCBOOKTOHTML) + +plugin-runner.8mandos: plugin-runner.xml common.ent overview.xml \ + legalnotice.xml + $(DOCBOOKTOMAN) +plugin-runner.8mandos.xhtml: plugin-runner.xml common.ent \ + overview.xml legalnotice.xml + $(DOCBOOKTOHTML) + +plugins.d/mandos-client.8mandos: plugins.d/mandos-client.xml \ + common.ent \ + mandos-options.xml \ + overview.xml legalnotice.xml + $(DOCBOOKTOMAN) +plugins.d/mandos-client.8mandos.xhtml: plugins.d/mandos-client.xml \ + common.ent \ + mandos-options.xml \ + overview.xml legalnotice.xml + $(DOCBOOKTOHTML) + +# Update all these files with version number $(version) +common.ent: Makefile + $(SED) --in-place \ + --expression='s/^\($$/\1$(version)">/' \ + $@ + +mandos: Makefile + $(SED) --in-place \ + --expression='s/^\(version = "\)[^"]*"$$/\1$(version)"/' \ + $@ + +mandos-keygen: Makefile + $(SED) --in-place \ + --expression='s/^\(VERSION="\)[^"]*"$$/\1$(version)"/' \ + $@ + +mandos-ctl: Makefile + $(SED) --in-place \ + --expression='s/^\(version = "\)[^"]*"$$/\1$(version)"/' \ + $@ + +mandos.lsm: Makefile + $(SED) --in-place \ + --expression='s/^\(Version:\).*/\1\t$(version)/' \ + $@ + $(SED) --in-place \ + --expression='s/^\(Entered-date:\).*/\1\t$(shell date --rfc-3339=date --reference=Makefile)/' \ + $@ + $(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 $@ -.PHONY : all doc clean distclean run-client run-server install \ +.PHONY : all doc html clean distclean run-client run-server install \ install-server install-client uninstall uninstall-server \ uninstall-client purge purge-server purge-client clean: - -rm --force $(PROGS) $(objects) $(DOCS) core + -rm --force $(CPROGS) $(objects) $(htmldocs) $(DOCS) core distclean: clean mostlyclean: clean maintainer-clean: clean -rm --force --recursive keydir confdir -check: +check: all ./mandos --check # Run the client with a local config and key run-client: all keydir/seckey.txt keydir/pubkey.txt ./plugin-runner --plugin-dir=plugins.d \ --config-file=plugin-runner.conf \ - --options-for=password-request:--seckey=keydir/seckey.txt,--pubkey=keydir/pubkey.txt + --options-for=mandos-client:--seckey=keydir/seckey.txt,--pubkey=keydir/pubkey.txt \ + $(CLIENTARGS) # Used by run-client keydir/seckey.txt keydir/pubkey.txt: mandos-keygen @@ -118,7 +214,7 @@ # Run the server with a local config run-server: confdir/mandos.conf confdir/clients.conf - ./mandos --debug --configdir=confdir + ./mandos --debug --no-dbus --configdir=confdir $(SERVERARGS) # Used by run-server confdir/mandos.conf: mandos.conf @@ -126,23 +222,31 @@ install --mode=u=rw,go=r $^ $@ confdir/clients.conf: clients.conf keydir/seckey.txt install --directory confdir - install --mode=u=rw,g=r $< $@ + install --mode=u=rw $< $@ # Add a client password ./mandos-keygen --dir keydir --password >> $@ -install: install-server install-client +install: install-server install-client-nokey + +install-html: html + install --directory $(htmldir) + install --mode=u=rw,go=r --target-directory=$(htmldir) \ + $(htmldocs) install-server: doc - install --directory $(CONFDIR) $(MANDIR)/man5 \ - $(MANDIR)/man8 + install --directory $(CONFDIR) install --mode=u=rwx,go=rx mandos $(PREFIX)/sbin/mandos install --mode=u=rw,go=r --target-directory=$(CONFDIR) \ mandos.conf - install --mode=u=rw,g=r --target-directory=$(CONFDIR) \ + install --mode=u=rw --target-directory=$(CONFDIR) \ clients.conf - install --mode=u=rwx,go=rx init.d-mandos /etc/init.d/mandos - install --mode=u=rw,go=r default-mandos /etc/default/mandos - update-rc.d mandos defaults + install --mode=u=rwx,go=rx init.d-mandos \ + $(DESTDIR)/etc/init.d/mandos + install --mode=u=rw,go=r default-mandos \ + $(DESTDIR)/etc/default/mandos + if [ -z $(DESTDIR) ]; then \ + update-rc.d mandos defaults 25 15;\ + fi gzip --best --to-stdout mandos.8 \ > $(MANDIR)/man8/mandos.8.gz gzip --best --to-stdout mandos.conf.5 \ @@ -150,16 +254,14 @@ gzip --best --to-stdout mandos-clients.conf.5 \ > $(MANDIR)/man5/mandos-clients.conf.5.gz -install-client: all doc /usr/share/initramfs-tools/hooks/. - install --directory $(PREFIX)/lib/mandos $(CONFDIR) \ - $(MANDIR)/man8 - install --directory --mode=u=rwx $(KEYDIR) - install --directory --mode=u=rwx \ +install-client-nokey: all doc + install --directory $(PREFIX)/lib/mandos $(CONFDIR) + install --directory --mode=u=rwx $(KEYDIR) \ $(PREFIX)/lib/mandos/plugins.d - if [ "$(CONFDIR)/plugins.d" \ - != "$(PREFIX)/lib/mandos/plugins.d" ]; then \ - install --directory "$(CONFDIR)/plugins.d"; \ - fi + if [ "$(CONFDIR)" != "$(PREFIX)/lib/mandos" ]; then \ + install --mode=u=rwx \ + --directory "$(CONFDIR)/plugins.d"; \ + fi install --mode=u=rwx,go=rx \ --target-directory=$(PREFIX)/lib/mandos plugin-runner install --mode=u=rwx,go=rx --target-directory=$(PREFIX)/sbin \ @@ -169,16 +271,22 @@ plugins.d/password-prompt install --mode=u=rwxs,go=rx \ --target-directory=$(PREFIX)/lib/mandos/plugins.d \ - plugins.d/password-request - install --mode=u=rwx,go=rx \ + plugins.d/mandos-client + install --mode=u=rwxs,go=rx \ --target-directory=$(PREFIX)/lib/mandos/plugins.d \ plugins.d/usplash + install --mode=u=rwxs,go=rx \ + --target-directory=$(PREFIX)/lib/mandos/plugins.d \ + plugins.d/splashy + install --mode=u=rwxs,go=rx \ + --target-directory=$(PREFIX)/lib/mandos/plugins.d \ + plugins.d/askpass-fifo install initramfs-tools-hook \ - /usr/share/initramfs-tools/hooks/mandos - install initramfs-tools-hook-conf \ - /usr/share/initramfs-tools/conf-hooks.d/mandos + $(INITRAMFSTOOLS)/hooks/mandos + install --mode=u=rw,go=r initramfs-tools-hook-conf \ + $(INITRAMFSTOOLS)/conf-hooks.d/mandos install initramfs-tools-script \ - /usr/share/initramfs-tools/scripts/local-top/mandos + $(INITRAMFSTOOLS)/scripts/init-premount/mandos install --mode=u=rw,go=r plugin-runner.conf $(CONFDIR) gzip --best --to-stdout mandos-keygen.8 \ > $(MANDIR)/man8/mandos-keygen.8.gz @@ -186,8 +294,17 @@ > $(MANDIR)/man8/plugin-runner.8mandos.gz gzip --best --to-stdout plugins.d/password-prompt.8mandos \ > $(MANDIR)/man8/password-prompt.8mandos.gz - gzip --best --to-stdout plugins.d/password-request.8mandos \ - > $(MANDIR)/man8/password-request.8mandos.gz + 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 + +install-client: install-client-nokey +# Post-installation stuff -$(PREFIX)/sbin/mandos-keygen --dir "$(KEYDIR)" update-initramfs -k all -u echo "Now run mandos-keygen --password --dir $(KEYDIR)" @@ -206,26 +323,35 @@ # Refuse to uninstall client if /etc/crypttab is explicitly configured # to use it. ! grep --regexp='^ *[^ #].*keyscript=[^,=]*/mandos/' \ - /etc/crypttab + $(DESTDIR)/etc/crypttab -rm --force $(PREFIX)/sbin/mandos-keygen \ $(PREFIX)/lib/mandos/plugin-runner \ $(PREFIX)/lib/mandos/plugins.d/password-prompt \ - $(PREFIX)/lib/mandos/plugins.d/password-request \ - /usr/share/initramfs-tools/hooks/mandos \ - /usr/share/initramfs-tools/conf-hooks.d/mandos \ + $(PREFIX)/lib/mandos/plugins.d/mandos-client \ + $(PREFIX)/lib/mandos/plugins.d/usplash \ + $(PREFIX)/lib/mandos/plugins.d/splashy \ + $(PREFIX)/lib/mandos/plugins.d/askpass-fifo \ + $(INITRAMFSTOOLS)/hooks/mandos \ + $(INITRAMFSTOOLS)/conf-hooks.d/mandos \ + $(INITRAMFSTOOLS)/scripts/init-premount/mandos \ $(MANDIR)/man8/plugin-runner.8mandos.gz \ $(MANDIR)/man8/mandos-keygen.8.gz \ $(MANDIR)/man8/password-prompt.8mandos.gz \ - $(MANDIR)/man8/password-request.8mandos.gz + $(MANDIR)/man8/usplash.8mandos.gz \ + $(MANDIR)/man8/splashy.8mandos.gz \ + $(MANDIR)/man8/askpass-fifo.8mandos.gz \ + $(MANDIR)/man8/mandos-client.8mandos.gz -rmdir $(PREFIX)/lib/mandos/plugins.d $(CONFDIR)/plugins.d \ - $(PREFIX)/lib/mandos $(CONFDIR) + $(PREFIX)/lib/mandos $(CONFDIR) $(KEYDIR) update-initramfs -k all -u purge: purge-server purge-client purge-server: uninstall-server -rm --force $(CONFDIR)/mandos.conf $(CONFDIR)/clients.conf \ - /etc/default/mandos /etc/init.d/mandos + $(DESTDIR)/etc/default/mandos \ + $(DESTDIR)/etc/init.d/mandos \ + $(DESTDIR)/var/run/mandos.pid -rmdir $(CONFDIR) purge-client: uninstall-client === added file 'NEWS' --- NEWS 1970-01-01 00:00:00 +0000 +++ NEWS 2009-05-23 05:59:52 +0000 @@ -0,0 +1,102 @@ +This NEWS file records noteworthy changes, very tersely. +See the manual for detailed information. + +Version 1.0.11 (2009-05-23) +* Client +** Bug fix: Use "pkg-config" instead of old "libgnutls-config". + +Version 1.0.10 (2009-05-17) +* Client +** Security bug fix: Fix permissions on initrd.img-*.bak files when + upgrading from older versions. + +Version 1.0.9 (2009-05-17) +* Client +** Security bug fix: Fix permissions on initrd.img file when + installing new linux-image-* packages calling mkinitramfs-kpkg (all + version lower than 2.6.28-1-* does this). + +Version 1.0.8 (2009-02-25) +* Client +** Bug fix: Fix missing quote characters in initramfs-tools-hook. + +Version 1.0.7 (2009-02-24) +* Client +** Bug fix: Do not depend on GNU awk. + +Version 1.0.6 (2009-02-13) +* Server +** Fix bug where server would stop responding, with a zombie checker +** Support for disabling IPv6 (only for advanced users) +** Fix bug which made server not change group ID + +* Client +** Bug fix: Fix permission for /lib64 (on relevant architechtures). +** Add support for IPv4 addresses. +** Add support in mandos-client for not bringing up a network + interface by specifying an empty string to "--interface". +** Make password prompt on boot not be mangled by kernel log messages + about network interface. +** Get network interface from initramfs.conf and/or from kernel + command line. +** If set by "ip=" kernel command line, configure network on boot. +** Support connecting directly using "mandos=connect" kernel command. + line option, provided network is configured using "ip=". +** Fix bug which made plugin-runner and mandos-client not change group + ID. +** Fix bug where the "--options-for" option of plugin-runner would + truncate the value at the first colon character. +** Fix bug where plugin-runner would not go to fallback if all plugins + failed. +** Fix bug where mandos-client would not clean temporary directory on + a signal or on certain file systems. +** Bug fix: remove bashism in /bin/sh script "mandos-keygen". + +Version 1.0.5 (2009-01-17) +* Client +** Fix small memory leak in plugin-runner. + +Version 1.0.4 (2009-01-15) +* Server +** Only find matched user/group pairs when searching for suitable + nonprivileged user/group to switch to. + +* Client +** New kernel parameter "mandos=off" makes client not run at boot. +** Fix linking errors and compilation warnings on AMD64. +** Parse numbers in command line options better. +** The splashy and usplash plugins are more robust while traversing + /proc, and will not abort if a process suddenly disappears. + +Version 1.0.3 (2009-01-06) +* Server +** Now tries to change to user and group "_mandos" before falling back + to trying the old values "mandos", "nobody:nogroup", and "65534". +** Now does not abort on startup even if no clients are defined in + clients.conf. + +* Client +** Plugins named "*.dpkg-bak" are now ignored. +** Hopefully fixed compilation failure on some architectures where the + C compiler does not recognize the "-z" option as a linker option. + +Version 1.0.2 (2008-10-17) +* mandos-keygen now signs the encrypted key blobs. This signature is + not currently verified by mandos-client, but this may change in the + future. + +Version 1.0.1 (2008-10-07) +* Server +** Expand environment variables and ~user in clients.conf's "secfile" + The "secfile" option in /etc/mandos/clients.conf now expands + "~user/foo" and "$ENVVAR" strings. + +* Client (plugin-runner, plugins, etc.) +** Manual pages for the usplash, splashy, and askpass-fifo plugins. + All plugins now have man pages. +** More secure compilation and linking flags. + All programs are now compiled with "-fstack-protector-all -fPIE + -pie", and linked using "-z relro -pie" for additional security. + +* There is now a "NEWS" file (this one), giving a history of + noteworthy changes. === modified file 'README' --- README 2008-09-05 07:11:24 +0000 +++ README 2009-02-23 11:52:42 +0000 @@ -3,15 +3,15 @@ * Mandos - Have your cake and eat it too! - You know how it is. You've heard of it happening. The Man comes - and takes away your servers, your friends' servers, the servers of + You know how it is. You’ve heard of it happening. The Man comes + and takes away your servers, your friends’ servers, the servers of everybody in the same hosting facility. The servers of their - neighbors, and their neighbors' friends. The servers of people who - owe them money. And like *that*, they're gone. And you doubt - you'll ever see them again. + neighbors, and their neighbors’ friends. The servers of people who + owe them money. And like *that*, they’re gone. And you doubt + you’ll ever see them again. That is why your servers have encrypted root file systems. However, - there's a downside. There's no going around it: rebooting is a + there’s a downside. There’s no going around it: rebooting is a pain. Dragging out that rarely-used keyboard and screen and unraveling cables behind your servers to plug them in to type in that password is messy, especially if you have many servers. There @@ -23,7 +23,7 @@ have to be on hand and manually type in the password at boot time. Otherwise the server just sits there, waiting for a password. - Wouldn't it be great if you could have the security of encrypted + Wouldn’t it be great if you could have the security of encrypted root file systems and still have servers that could boot up automatically if there was a short power outage while you were asleep? That you could reboot at will, without having someone run @@ -49,13 +49,13 @@ Now, of course the initial RAM disk image is not on the encrypted root file system, so anyone who had physical access could take the - server offline and read the disk with their own tools to get the - authentication keys used by a client. *But*, by then the Mandos - server should notice that the original server has been offline for - too long, and will no longer give out the encrypted key. The timing - here is the only real weak point, and the method, frequency and - timeout of the server’s checking can be adjusted to any desired - level of paranoia + Mandos client computer offline and read the disk with their own + tools to get the authentication keys used by a client. *But*, by + then the Mandos server should notice that the original server has + been offline for too long, and will no longer give out the encrypted + key. The timing here is the only real weak point, and the method, + frequency and timeout of the server’s checking can be adjusted to + any desired level of paranoia (The encrypted keys on the Mandos server is on its normal file system, so those are safe, provided the root file system of *that* @@ -69,12 +69,12 @@ to do. An attacker would have to physically disassemble the client computer, extract the key from the initial RAM disk image, and then connect to a *still online* Mandos server to get the encrypted key, - all *before* the Mandos server timeout kicks in and the Mandos - server refuses to give out the key to anyone. + and do all this *before* the Mandos server timeout kicks in and the + Mandos server refuses to give out the key to anyone. - Now, as the typical SOP seems to be to barge in and turn off and - grab *all* computers, to maybe look at them months later, this is - not likely. If someone does that, the whole system *will* lock + Now, as the typical procedure seems to be to barge in and turn off + and grab *all* computers, to maybe look at them months later, this + is not likely. If someone does that, the whole system *will* lock itself up completely, since Mandos servers are no longer running. For sophisticated attackers who *could* do the clever thing, *and* @@ -129,3 +129,52 @@ focused, and competent attacks, but against the early morning knock on your door and the sudden absence of all the servers in your server room. Which it does nicely. + +* The Plugin System + In the early designs, the mandos-client(8mandos) program (which + retrieves a password from the Mandos server) also prompted for a + password on the terminal, in case a Mandos server could not be + found. Other ways of retrieving a password could easily be + envisoned, but this multiplicity of purpose was seen to be too + complex to be a viable way to continue. Instead, the original + program was separated into mandos-client(8mandos) and + password-prompt(8mandos), and a plugin-runner(8mandos) exist to run + them both in parallel, allowing the first successful plugin to + provide the password. This opened up for any number of additional + plugins to run, all competing to be the first to find a password and + provide it to the plugin runner. + + Three additional plugins are provided: + * usplash(8mandos) + This prompts for a password when using usplash(8). + * splashy(8mandos) + This prompts for a password when using splashy(8). + * askpass-fifo(8mandos) + To provide compatibility with the "askpass" program from + cryptsetup, this plugin listens to the same FIFO as askpass would + do. + + More plugins can easily be written and added by the system + administrator; see the section called "WRITING PLUGINS" in + plugin-runner(8mandos) to learn the plugin requirements. + +* Copyright + + Copyright © 2008,2009 Teddy Hogeborn + Copyright © 2008,2009 Björn Påhlsson + +** License: + + This program is free software: you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see + . === modified file 'TODO' --- TODO 2008-09-05 07:11:24 +0000 +++ TODO 2009-09-05 01:46:42 +0000 @@ -1,85 +1,82 @@ -*- org -*- +* mandos-client +** TODO [#A] Clean up /tmp directory and take down interface on signal :test: +** TODO [#B] use scandir(3) instead of readdir(3) +** TODO [#B] Prefix all debug output with argv[0] + +* splashy +** TODO [#A] Re-raise signal received when exiting due to handled signal. +** TODO [#B] use scandir(3) instead of readdir(3) +** TODO [#B] Prefix all debug output with argv[0] + +* usplash +** TODO [#A] Re-raise signal received when exiting due to handled signal. +** TODO [#B] use scandir(3) instead of readdir(3) +** TODO [#B] Prefix all debug output with argv[0] + +* password-prompt +** TODO [#B] Prefix all debug output with argv[0] + * plugin-runner - -* password-request -** [#B] Temporarily lower kernel log level - for less printouts during sucessfull boot. -** IPv4 support -** use strsep instead of strtok? -** Do not depend on GnuPG key rings on disk - This would mean creating new GnuPG key rings with GPGME by - importing the key files from scratch on every program start. -** Keydir move: /etc/mandos -> /etc/keys/mandos - Must create in preinst if not pre-depending on cryptsetup - -* password-prompt +** TODO [#A] Do not handle ignored signals +** TODO [#B] use scandir(3) instead of readdir(3) +** TODO [#C] use same file name rules as run-parts(8) * mandos (server) -** [#A] /etc/init.d/mandos-server :teddy: -** [#B] Log level :bugs: -** /etc/mandos/clients.d/*.conf +** TODO [#B] Log level :BUGS: +** TODO /etc/mandos/clients.d/*.conf Watch this directory and add/remove/update clients? -** config for TXT record -** [#B] Run-time communication with server :bugs: +** TODO config for TXT record +** TODO [#B] Run-time communication with server :BUGS: Probably using D-Bus - See also [[*Mandos-tools]] -** Implement --foreground :bugs: - [[info:standards:Option%20Table][Table of Long Options]] -** Implement --socket - [[info:standards:Option%20Table][Table of Long Options]] -** Date+time on console log messages :bugs: +*** Client class +*** Main server + + SetLogLevel + syslogger.setLevel(logging.WARNING) + + [[http://log.ometer.com/2007-05.html][Best D-Bus practices]] +** TODO Implement --foreground :BUGS: + [[info:standards:Option%20Table][Table of Long Options]] +** TODO Implement --socket + [[info:standards:Option%20Table][Table of Long Options]] +** TODO Date+time on console log messages :BUGS: Is this the default? -** delete hook when clients fall out by timeout - -* Mandos-tools/utilities - All of this probably using D-Bus -** List clients -** Disable client -** Enable client -** Reboot timer - -* Man pages -** Use xinclude for common sections - Like authors, etc. - - -* Installer -** Client-side -*** Update initrd.img after installation - This seems to use some kind of "trigger" system - [[file:/usr/share/doc/dpkg/triggers.txt.gz]] - dpkg-trigger(1), deb-triggers(5) -*** mandos-keygen -**** "--passfile" option - Using the "secfile" option instead of "secret" -**** [#A] "--test" option - For testing decryption before rebooting. -** Server-side -*** [#A] Create mandos user and group for server -*** [#A] Create /var/run/mandos directory with perm and ownership -*** [#A] install rc.d script and do update-rc.d - between config files and man pages - - -* [#A] Package +** TODO Split IPv6_TCPServer into a generic and Mandos-specific class +** TODO move handle_ipc out of IPv6_TCPServer +** TODO DBusServiceObjectUsingSuper +** Global enable/disable flag +** By-client countdown on secrets given + +* mandos.xml +** [[file:mandos.xml::XXX][Document D-Bus interface]] + +* Provide and install /etc/dbus-1/system.d/mandos.conf + +* mandos-ctl +*** Handle "no D-Bus server" and/or "no Mandos server found" better +*** [#B] --dump option + +* mandos-name +** D-Bus mail loop w/ signal receiver +** Urwid/Newt client data displayer +*** Urwid scaffolding +*** Client Widgets +*** Properties popup + +* mandos-keygen +** TODO Loop until passwords match when run interactively +** TODO "--secfile" option + Using the "secfile" option instead of "secret" +** TODO [#B] "--test" option + For testing decryption before rebooting. + +* Package ** /usr/share/initramfs-tools/hooks/mandos -*** Do not install in initrd.img if configured not to. - Use "/etc/initramfs-tools/conf.d/mandos"? Definitely a debconf - question. -** /etc/bash_completion.d/mandos +*** TODO [#C] use same file name rules as run-parts(8) +*** TODO [#C] Do not install in initrd.img if configured not to. + Use "/etc/initramfs-tools/hooksconf.d/mandos"? +** TODO [#C] /etc/bash_completion.d/mandos From XML sources directly? -** unperish -** bzr-builddeb - -* INSTALL file - -* Web site - -* Mailing list - -* Announce project on news - [[news:comp.os.linux.announce]] #+STARTUP: showall === modified file 'clients.conf' --- clients.conf 2008-08-27 01:18:25 +0000 +++ clients.conf 2009-01-08 03:54:06 +0000 @@ -14,7 +14,7 @@ ;interval = 5m # What command to run as "the checker". -;checker = fping -q -- %(host)s +;checker = fping -q -- %%(host)s ;#### @@ -55,7 +55,7 @@ ;fingerprint = 3e393aeaefb84c7e89e2f547b3a107558fca3a27 ; ;# If "secret" is not specified, a file can be read for the data. -;;secfile = /etc/mandos/bar-secret.txt.asc +;secfile = /etc/mandos/bar-secret.bin ; ;# An IP address for host is also fine, if the checker accepts it. ;host = 192.0.2.3 === added file 'common.ent' --- common.ent 1970-01-01 00:00:00 +0000 +++ common.ent 2009-05-23 05:59:52 +0000 @@ -0,0 +1,3 @@ + + + === added directory 'debian' === added file 'debian/changelog' --- debian/changelog 1970-01-01 00:00:00 +0000 +++ debian/changelog 2009-05-23 05:59:52 +0000 @@ -0,0 +1,124 @@ +mandos (1.0.11-1) unstable; urgency=low + + * debian/control (Standards-Version): Changed to "3.8.1". + * Makefile (GNUTLS_CFLAGS, GNUTLS_CFLAGS): Use "pkg-config" instead of + the old "libgnutls-config" script. (Closes: #529836) + + -- Teddy Hogeborn Sat, 23 May 2009 07:12:20 +0200 + +mandos (1.0.10-1) unstable; urgency=low + + * New upstream release. + * debian/mandos-client.postinst (update_initramfs): Fix permissions of + old initrd.img-*.bak files. + + -- Teddy Hogeborn Sun, 17 May 2009 04:56:35 +0200 + +mandos (1.0.9-1) unstable; urgency=low + + * New upstream release. + + -- Teddy Hogeborn Sun, 17 May 2009 02:59:45 +0200 + +mandos (1.0.8-1) unstable; urgency=low + + * New upstream release. + + -- Teddy Hogeborn Wed, 25 Feb 2009 02:26:57 +0100 + +mandos (1.0.7-1) unstable; urgency=low + + * New upstream release. + + -- Teddy Hogeborn Tue, 24 Feb 2009 12:58:06 +0100 + +mandos (1.0.6-1) unstable; urgency=low + + * New upstream release. + * debian/mandos-client.postinst: Converted to Bourne shell. Also + minor message change. + * debian/mandos-client.postrm: Minor message change. + * debian/mandos.postinst: Converted to Bourne shell. Also minor + message change. + * debian/mandos.prerm: Minor message change. + * debian/rules (install-indep): Removed "--no-start" from + dh_installinit. + * debian/mandos-client.lintian-overrides: Remove obsolete override for + unbreakable line in plugin-runner manual page. + * debian/control (mandos/Depends): Added "python-gobject". + * debian/mandos-client.dirs: Change + "usr/share/initramfs-tools/scripts/local-top" to + "usr/share/initramfs-tools/scripts/init-premount". + * debian/mandos-client.README.Debian: Add reference to initramfs.conf + and nfsroot.txt. New section about the new non-local connection + feature. + + -- Teddy Hogeborn Fri, 13 Feb 2009 09:27:25 +0100 + +mandos (1.0.5-1) unstable; urgency=low + + * New upstream release. + + -- Teddy Hogeborn Sat, 17 Jan 2009 02:26:00 +0100 + +mandos (1.0.4-1) unstable; urgency=low + + * New upstream release. + * debian/watch: New file. + * debian/mandos-client.README.Debian: Document new "mandos=off" kernel + parameter. + + -- Teddy Hogeborn Thu, 15 Jan 2009 05:49:22 +0100 + +mandos (1.0.3-2) unstable; urgency=low + + * Removed some now-unused debconf files. + * Changed postinst scripts to not source debconf/confmodule. + * Removed po-debconf from build-depends. + + -- Teddy Hogeborn Tue, 06 Jan 2009 21:28:20 +0100 + +mandos (1.0.3-1) unstable; urgency=low + + * New upstream release. + * Add -Xlinker to linker flags to fix FTBFS for some architectures. + Thanks to Thiemo Seufer for the report and + fix. (Closes: #509398) + * Remove debconf use altogether, thereby stopping debconf abuse. Thanks + to Christian Perrier . (Closes: #509653) + * Add NEWS file to /usr/share/doc directories. + * Use and create "_mandos" user+group. Rename old user+group created by + older versions of this package. + * Fix manual pages by adding build-depend on "docbook-xml". + + -- Teddy Hogeborn Tue, 06 Jan 2009 01:21:20 +0100 + +mandos (1.0.2-1) unstable; urgency=low + + * New upstream release. + * debian/copyright: Rewritten to conform to + . + + -- Teddy Hogeborn Fri, 17 Oct 2008 20:42:12 +0200 + +mandos (1.0.1-1) unstable; urgency=low + + * New upstream release. + * Separate /usr/share/doc/mandos-client/README.Debian into sections with + headlines. Add instructions on how to test the server and verify the + password. + + -- Teddy Hogeborn Tue, 07 Oct 2008 23:07:23 +0200 + +mandos (1.0-2) unstable; urgency=low + + * Added comments in debian/*.lintian-overrides files. Added Debian + revison number to version number. + + -- Teddy Hogeborn Wed, 01 Oct 2008 17:23:35 +0200 + +mandos (1.0-1) unstable; urgency=low + + * Initial Release. (Closes: #500727). + + -- Teddy Hogeborn Tue, 30 Sep 2008 21:58:43 +0200 === added file 'debian/compat' --- debian/compat 1970-01-01 00:00:00 +0000 +++ debian/compat 2008-09-17 00:34:09 +0000 @@ -0,0 +1,1 @@ +7 === added file 'debian/control' --- debian/control 1970-01-01 00:00:00 +0000 +++ debian/control 2009-05-18 17:57:13 +0000 @@ -0,0 +1,53 @@ +Source: mandos +Section: admin +Priority: extra +Maintainer: Mandos Maintainers +Uploaders: Teddy Hogeborn , + Björn Påhlsson +Build-Depends: debhelper (>= 7), docbook-xml, docbook-xsl, + libavahi-core-dev, libgpgme11-dev, libgnutls-dev, xsltproc, + pkg-config +Standards-Version: 3.8.1 +Vcs-Bzr: http://ftp.fukt.bsnet.se/pub/mandos/trunk +Vcs-Browser: http://bzr.fukt.bsnet.se/loggerhead/mandos/trunk/files +Homepage: http://www.fukt.bsnet.se/mandos + +Package: mandos +Architecture: all +Depends: ${misc:Depends}, python (>=2.5), python-gnutls, python-dbus, + python-avahi, python-gobject, avahi-daemon, gnupg (< 2), + adduser +Recommends: fping +Description: a server giving encrypted passwords to Mandos clients + This is the server part of the Mandos system, which allows + computers to have encrypted root file systems and at the + same time be capable of remote and/or unattended reboots. + . + The computers run a small client program in the initial RAM + disk environment which will communicate with a server over a + network. All network communication is encrypted using TLS. + The clients are identified by the server using an OpenPGP + key; each client has one unique to it. The server sends the + clients an encrypted password. The encrypted password is + decrypted by the clients using the same OpenPGP key, and the + password is then used to unlock the root file system, + whereupon the computers can continue booting normally. + +Package: mandos-client +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, cryptsetup +Enhances: cryptsetup +Description: do unattended reboots with an encrypted root file system + This is the client part of the Mandos system, which allows + computers to have encrypted root file systems and at the + same time be capable of remote and/or unattended reboots. + . + The computers run a small client program in the initial RAM + disk environment which will communicate with a server over a + network. All network communication is encrypted using TLS. + The clients are identified by the server using an OpenPGP + key; each client has one unique to it. The server sends the + clients an encrypted password. The encrypted password is + decrypted by the clients using the same OpenPGP key, and the + password is then used to unlock the root file system, + whereupon the computers can continue booting normally. === added file 'debian/copyright' --- debian/copyright 1970-01-01 00:00:00 +0000 +++ debian/copyright 2009-01-04 21:54:55 +0000 @@ -0,0 +1,26 @@ +Format-Specification: + http://wiki.debian.org/Proposals/CopyrightFormat?action=recall&rev=233 +Upstream-Name: Mandos +Upstream-Maintainer: Mandos Maintainers +Upstream-Source: + +Files: * +Copyright: Copyright © 2008,2009 Teddy Hogeborn +Copyright: Copyright © 2008,2009 Björn Påhlsson +License: GPL-3+ + This program is free software: you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + . + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see + . + . + On Debian systems, the complete text of the GNU General Public + License can be found in "/usr/share/common-licenses/GPL". === added file 'debian/mandos-client.README.Debian' --- debian/mandos-client.README.Debian 1970-01-01 00:00:00 +0000 +++ debian/mandos-client.README.Debian 2009-04-14 14:56:46 +0000 @@ -0,0 +1,77 @@ +* Configure The Server + + A client key has been automatically created in /etc/keys/mandos. + The next step is to run "mandos-keygen --password" to get a config + file section. This should be appended to /etc/mandos/clients.conf + on the Mandos server. + +* Use the Correct Network Interface + + 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 doing "update-initramfs -k all -u". This setting can be + overridden at boot time on the Linux kernel command line using the + sixth colon-separated field of the "ip=" option; for exact syntax, + see the file "Documentation/nfsroot.txt" in the Linux source tree. + + Note that since this is used in the initial RAM disk environment, + the network interface must exist at that stage. Thus, the interface + can *not* be a pseudo-interface such as "br0" or "tun0"; instead, a + real interface (such as "eth0") must be used. + +* Test the Server + + After the server has been started and this client's key added, it is + possible to verify that the correct password will be received by + this client by running the command, on the client: + + # /usr/lib/mandos/plugins.d/mandos-client \ + --pubkey=/etc/keys/mandos/pubkey.txt \ + --seckey=/etc/keys/mandos/seckey.txt; echo + + This command should retrieve the password from the server, decrypt + it, and output it to standard output. There it can be verified to + be the correct password, before rebooting. + +* User-Supplied Plugins + + Any plugins found in /etc/mandos/plugins.d will override and add to + the normal Mandos plugins. When adding or changing plugins, do not + forget to update the initital RAM disk image: + + # update-initramfs -k all -u + +* Do *NOT* Edit /etc/crypttab + + It is NOT necessary to edit /etc/crypttab to specify + /usr/lib/mandos/plugin-runner as a keyscript for the root file + system; if no keyscript is given for the root file system, the + Mandos client will be the new default way for getting a password for + the root file system when booting. + +* Emergency Escape + + If it ever should be necessary, the Mandos client can be temporarily + prevented from running at startup by passing the parameter + "mandos=off" to the kernel. + +* Non-local Connection (Not Using ZeroConf) + + If the "ip=" kernel command line option is used to specify a + complete IP address and device name, as noted above, it then becomes + possible to specify a specific IP address and port to connect to, + instead of using ZeroConf. The syntax for doing this is + "mandos=connect::". + + Warning: this will cause the client to make exactly one attempt at + connecting, and then fail if it does not succeed. + + For very advanced users, it it possible to specify simply + "mandos=connect" on the kernel command line to make the system only + set up the network (using the data in the "ip=" option) and not pass + any extra "--connect" options to mandos-client at boot. For this to + work, "--options-for=mandos-client:--connect=
:" needs + to be manually added to the file "/etc/mandos/plugin-runner.conf". + + -- Teddy Hogeborn , Tue, 14 Apr 2009 16:51:18 +0200 === added file 'debian/mandos-client.dirs' --- debian/mandos-client.dirs 1970-01-01 00:00:00 +0000 +++ debian/mandos-client.dirs 2009-02-07 04:50:39 +0000 @@ -0,0 +1,5 @@ +usr/share/man/man8 +usr/sbin +usr/share/initramfs-tools/hooks +usr/share/initramfs-tools/conf-hooks.d +usr/share/initramfs-tools/scripts/init-premount === added file 'debian/mandos-client.docs' --- debian/mandos-client.docs 1970-01-01 00:00:00 +0000 +++ debian/mandos-client.docs 2008-10-18 11:17:22 +0000 @@ -0,0 +1,3 @@ +NEWS +README +TODO === added file 'debian/mandos-client.links' --- debian/mandos-client.links 1970-01-01 00:00:00 +0000 +++ debian/mandos-client.links 2008-09-19 13:50:22 +0000 @@ -0,0 +1,1 @@ +usr/share/man/man8/plugin-runner.8mandos.gz usr/share/man/man5/plugin-runner.conf.5mandos.gz === added file 'debian/mandos-client.lintian-overrides' --- debian/mandos-client.lintian-overrides 1970-01-01 00:00:00 +0000 +++ debian/mandos-client.lintian-overrides 2009-01-18 06:41:57 +0000 @@ -0,0 +1,27 @@ +# This directory contains secret client key files. +# +mandos-client binary: non-standard-dir-perm etc/keys/mandos/ 0700 != 0755 + +# The directory /usr/lib/mandos/plugins.d contains setuid binaries +# which are not meant to be run outside an initial RAM disk +# environment (except for test purposes). It would be insecure to +# allow anyone to run them. +# +mandos-client binary: non-standard-dir-perm usr/lib/mandos/plugins.d/ 0700 != 0755 + +# These binaries must be setuid root, since they need root powers, but +# are started by plugin-runner(8mandos), which runs all plugins as +# user/group "_mandos". These binaries are not run in a running +# system, but in an initial RAM disk environment. Here they are +# protected from non-root access by the directory permissions, above. +# +mandos-client binary: setuid-binary usr/lib/mandos/plugins.d/mandos-client 4755 root/root +mandos-client binary: setuid-binary usr/lib/mandos/plugins.d/askpass-fifo 4755 root/root +mandos-client binary: setuid-binary usr/lib/mandos/plugins.d/splashy 4755 root/root +mandos-client binary: setuid-binary usr/lib/mandos/plugins.d/usplash 4755 root/root + +# The directory /etc/mandos/plugins.d can be used by local system +# administrators to place plugins in, overriding and complementing +# /usr/lib/mandos/plugins.d, and must be likewise protected. +# +mandos-client binary: non-standard-dir-perm etc/mandos/plugins.d/ 0700 != 0755 === added file 'debian/mandos-client.postinst' --- debian/mandos-client.postinst 1970-01-01 00:00:00 +0000 +++ debian/mandos-client.postinst 2009-05-24 23:36:15 +0000 @@ -0,0 +1,81 @@ +#!/bin/sh -e +# This script can be called in the following ways: +# +# After the package was installed: +# configure +# +# +# If prerm fails during upgrade or fails on failed upgrade: +# abort-upgrade +# +# If prerm fails during deconfiguration of a package: +# abort-deconfigure in-favour +# removing +# +# If prerm fails during replacement due to conflict: +# abort-remove in-favour + +# Update the initial RAM file system image +update_initramfs() +{ + if [ -x /usr/sbin/update-initramfs ]; then + update-initramfs -u -k all + fi + + if dpkg --compare-versions "$2" lt-nl "1.0.10-1"; then + # Make old initrd.img files unreadable too, in case they were + # created with mandos-client 1.0.8 or older. + find /boot -maxdepth 1 -type f -name "initrd.img-*.bak" \ + -print0 | xargs --null --no-run-if-empty chmod o-r + fi +} + +# Add user and group +add_mandos_user(){ + # Rename old "mandos" user and group + if dpkg --compare-versions "$2" lt "1.0.3-1"; then + case "`getent passwd mandos`" in + *:Mandos\ password\ system,,,:/nonexistent:/bin/false) + usermod --login _mandos mandos + groupmod --new-name _mandos mandos + return + ;; + esac + fi + # Create new user and group + if ! getent passwd _mandos >/dev/null; then + adduser --system --force-badname --quiet --home /nonexistent \ + --no-create-home --group --disabled-password \ + --gecos "Mandos password system" _mandos + fi +} + +# Create client key pair +create_key(){ + if [ -r /etc/keys/mandos/pubkey.txt \ + -a -r /etc/keys/mandos/seckey.txt ]; then + return 0 + fi + if [ -x /usr/sbin/mandos-keygen ]; then + mandos-keygen + fi +} + +case "$1" in + configure) + add_mandos_user "$@" + create_key "$@" + update_initramfs "$@" + ;; + abort-upgrade|abort-deconfigure|abort-remove) + ;; + + *) + echo "$0 called with unknown argument '$1'" 1>&2 + exit 1 + ;; +esac + +#DEBHELPER# + +exit 0 === added file 'debian/mandos-client.postrm' --- debian/mandos-client.postrm 1970-01-01 00:00:00 +0000 +++ debian/mandos-client.postrm 2009-01-18 00:16:57 +0000 @@ -0,0 +1,60 @@ +#!/bin/sh -e +# This script can be called in the following ways: +# +# After the package was removed: +# remove +# +# After the package was purged: +# purge +# +# After the package was upgraded: +# upgrade +# if that fails: +# failed-upgrade +# +# +# After all of the packages files have been replaced: +# disappear +# +# +# If preinst fails during install: +# abort-install +# +# If preinst fails during upgrade of removed package: +# abort-install +# +# If preinst fails during upgrade: +# abort-upgrade + + +# Update the initial RAM file system image +update_initramfs() +{ + if [ -x /usr/sbin/update-initramfs ]; then + update-initramfs -u -k all + fi +} + +case "$1" in + remove) + update_initramfs + ;; + + purge) + shred --remove /etc/keys/mandos/seckey.txt 2>/dev/null || : + rm --force /etc/mandos/plugin-runner.conf \ + /etc/keys/mandos/pubkey.txt \ + /etc/keys/mandos/seckey.txt 2>/dev/null + ;; + upgrade|failed-upgrade|disappear|abort-install|abort-upgrade) + ;; + + *) + echo "$0 called with unknown argument '$1'" 1>&2 + exit 1 + ;; +esac + +#DEBHELPER# + +exit 0 === added file 'debian/mandos.README.Debian' --- debian/mandos.README.Debian 1970-01-01 00:00:00 +0000 +++ debian/mandos.README.Debian 2009-01-04 22:15:01 +0000 @@ -0,0 +1,7 @@ +The Mandos server is useless without at least one configured client in +/etc/mandos/clients.conf. To create one, install the "mandos-client" +package on a client computer, and run "mandos-keygen --password" there +to get a config file stanza. Append that to /etc/mandos/clients.conf +on the Mandos server. + + -- Teddy Hogeborn , Sun, 4 Jan 2009 22:59:22 +0100 === added file 'debian/mandos.dirs' --- debian/mandos.dirs 1970-01-01 00:00:00 +0000 +++ debian/mandos.dirs 2008-09-17 00:34:09 +0000 @@ -0,0 +1,5 @@ +usr/share/man/man5 +usr/share/man/man8 +etc/init.d +etc/default +usr/sbin === added file 'debian/mandos.docs' --- debian/mandos.docs 1970-01-01 00:00:00 +0000 +++ debian/mandos.docs 2008-10-18 11:17:22 +0000 @@ -0,0 +1,3 @@ +NEWS +README +TODO === added file 'debian/mandos.lintian-overrides' --- debian/mandos.lintian-overrides 1970-01-01 00:00:00 +0000 +++ debian/mandos.lintian-overrides 2008-10-01 15:29:01 +0000 @@ -0,0 +1,4 @@ +# This config file will normally have encrypted secret client keys in +# it, so it must be kept unreadable for non-root users. +# +mandos binary: non-standard-file-perm etc/mandos/clients.conf 0600 != 0644 === added file 'debian/mandos.postinst' --- debian/mandos.postinst 1970-01-01 00:00:00 +0000 +++ debian/mandos.postinst 2009-05-24 23:28:04 +0000 @@ -0,0 +1,49 @@ +#!/bin/sh -e +# This script can be called in the following ways: +# +# After the package was installed: +# configure +# +# +# If prerm fails during upgrade or fails on failed upgrade: +# abort-upgrade +# +# If prerm fails during deconfiguration of a package: +# abort-deconfigure in-favour +# removing +# +# If prerm fails during replacement due to conflict: +# abort-remove in-favour + +case "$1" in + configure) + # Rename old "mandos" user and group + if dpkg --compare-versions "$2" lt "1.0.3-1"; then + case "`getent passwd mandos`" in + *:Mandos\ password\ system,,,:/nonexistent:/bin/false) + usermod --login _mandos mandos + groupmod --new-name _mandos mandos + ;; + esac + fi + # Create new user and group + if ! getent passwd _mandos >/dev/null; then + adduser --system --force-badname --quiet \ + --home /nonexistent --no-create-home --group \ + --disabled-password --gecos "Mandos password system" \ + _mandos + fi + ;; + + abort-upgrade|abort-deconfigure|abort-remove) + ;; + + *) + echo "$0 called with unknown argument '$1'" 1>&2 + exit 1 + ;; +esac + +#DEBHELPER# + +exit 0 === added file 'debian/mandos.prerm' --- debian/mandos.prerm 1970-01-01 00:00:00 +0000 +++ debian/mandos.prerm 2009-01-18 00:16:57 +0000 @@ -0,0 +1,38 @@ +#! /bin/sh +# prerm script for mandos +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * 'remove' +# * 'upgrade' +# * 'failed-upgrade' +# * 'remove' 'in-favour' +# * 'deconfigure' 'in-favour' +# 'removing' +# +# for details, see /usr/share/doc/packaging-manual/ + +case "$1" in + remove|deconfigure) + if [ -x /etc/init.d/mandos ]; then + if [ -x /usr/sbin/invoke-rc.d ]; then + invoke-rc.d mandos stop + else + /etc/init.d/mandos stop + fi + fi + ;; + upgrade|failed-upgrade) + ;; + *) + echo "prerm called with unknown argument '$1'" >&2 + exit 0 + ;; +esac + +#DEBHELPER# + +exit 0 === added directory 'debian/po' === added file 'debian/rules' --- debian/rules 1970-01-01 00:00:00 +0000 +++ debian/rules 2009-01-18 00:18:50 +0000 @@ -0,0 +1,92 @@ +#!/usr/bin/make -f +# Sample debian/rules that uses debhelper. +# +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. +# +# Modified to make a template file for a multi-binary package with separated +# build-arch and build-indep targets by Bill Allombert 2001 + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +# This has to be exported to make some magic below work. +export DH_OPTIONS + +configure: configure-stamp +configure-stamp: + dh_testdir + touch configure-stamp + +build: build-arch build-indep + +build-arch: build-arch-stamp +build-arch-stamp: configure-stamp + dh_auto_build -- all doc + touch $@ + +build-indep: build-indep-stamp +build-indep-stamp: configure-stamp + dh_auto_build -- doc + touch $@ + +clean: + dh_testdir + dh_testroot + rm -f build-arch-stamp build-indep-stamp configure-stamp + dh_auto_clean + dh_clean + +install: install-indep install-arch +install-indep: + dh_testdir + dh_testroot + dh_prep + dh_installdirs --indep + $(MAKE) DESTDIR=$(CURDIR)/debian/mandos install-server + dh_lintian + dh_installinit --onlyscripts \ + --update-rcd-params="defaults 25 15" + dh_install --indep + +install-arch: + dh_testdir + dh_testroot + dh_prep + dh_installdirs --same-arch + $(MAKE) DESTDIR=$(CURDIR)/debian/mandos-client install-client-nokey + dh_lintian + dh_install --same-arch + +binary-common: + dh_testdir + dh_testroot + dh_installchangelogs + dh_installdocs + dh_link + dh_strip + dh_compress + dh_fixperms --exclude etc/keys/mandos \ + --exclude etc/mandos/clients.conf \ + --exclude etc/mandos/plugins.d \ + --exclude usr/lib/mandos/plugins.d + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +# Build architecture independant packages using the common target. +binary-indep: build-indep install-indep + $(MAKE) -f debian/rules DH_OPTIONS=--indep binary-common + +# Build architecture dependant packages using the common target. +binary-arch: build-arch install-arch + $(MAKE) -f debian/rules DH_OPTIONS=--same-arch binary-common + +binary: binary-arch binary-indep + +.PHONY: build clean binary-indep binary-arch binary install \ + install-indep install-arch configure === added file 'debian/watch' --- debian/watch 1970-01-01 00:00:00 +0000 +++ debian/watch 2009-01-15 02:52:02 +0000 @@ -0,0 +1,2 @@ +version=3 +ftp://ftp.fukt.bsnet.se/pub/mandos/mandos[-_]([^\s]+?)(?:\.orig)?\.tar\.(?:gz|bz2|7z) === modified file 'default-mandos' --- default-mandos 2008-09-05 08:38:30 +0000 +++ default-mandos 2008-09-17 00:34:09 +0000 @@ -2,3 +2,6 @@ # "/etc/mandos". # #CONFIGDIR=/etc/mandos + +# Additional options that are passed to the Daemon. +DAEMON_ARGS="" === modified file 'init.d-mandos' --- init.d-mandos 2008-09-05 16:24:33 +0000 +++ init.d-mandos 2008-09-21 12:04:02 +0000 @@ -1,7 +1,7 @@ #! /bin/sh ### BEGIN INIT INFO # Provides: mandos -# Required-Start: $remote_fs +# Required-Start: $remote_fs avahi-daemon # Required-Stop: $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 === modified file 'initramfs-tools-hook' --- initramfs-tools-hook 2008-09-05 07:11:24 +0000 +++ initramfs-tools-hook 2009-04-17 08:26:17 +0000 @@ -40,7 +40,7 @@ exit 1 fi -for d in /etc/keys/mandos /usr/local/lib/mandos/keys; do +for d in /etc/keys/mandos /etc/mandos/keys; do if [ -d "$d" ]; then keydir="$d" break @@ -51,6 +51,14 @@ exit 1 fi +set `{ getent passwd _mandos \ + || getent passwd nobody \ + || echo ::65534:65534:::; } \ + | cut --delimiter=: --fields=3,4 --only-delimited \ + --output-delimiter=" "` +mandos_user="$1" +mandos_group="$2" + # The Mandos network client uses the network auto_add_modules net # The Mandos network client uses IPv6 @@ -62,8 +70,10 @@ PLUGINDIR="${MANDOSDIR}/plugins.d" # Make directories -mkdir --parents "${DESTDIR}${CONFDIR}" -mkdir --parents "${DESTDIR}${PLUGINDIR}" +install --directory --mode=u=rwx,go=rx "${DESTDIR}${CONFDIR}" \ + "${DESTDIR}${MANDOSDIR}" +install --owner=${mandos_user} --group=${mandos_group} --directory \ + --mode=u=rwx "${DESTDIR}${PLUGINDIR}" # Copy the Mandos plugin runner copy_exec "$prefix"/lib/mandos/plugin-runner "${MANDOSDIR}" @@ -78,9 +88,11 @@ continue fi case "$base" in - *~|.*|\#*\#|*.dpkg-old|*.dpkg-new|*.dpkg-divert) : ;; - "*") :;; - *) copy_exec "$file" "${PLUGINDIR}";; + *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) + : ;; + */"*") + echo "W: Mandos client plugin directory is empty." >&2 ;; + *) copy_exec "$file" "${PLUGINDIR}" ;; esac done @@ -88,24 +100,46 @@ for file in /etc/mandos/plugins.d/*; do base="`basename \"$file\"`" case "$base" in - *~|.*|\#*\#|*.dpkg-old|*.dpkg-new|*.dpkg-divert) : ;; - "*") :;; - *) copy_exec "$file" "${PLUGINDIR}";; + *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) + : ;; + */"*") : ;; + *) copy_exec "$file" "${PLUGINDIR}" ;; esac done # GPGME needs /usr/bin/gpg -if ! [ -e "${DESTDIR}/usr/bin/gpg" ] \ - && [ -n "`ls \"${DESTDIR}\"/usr/lib/libgpgme.so* 2>/dev/null`" ]; then +if [ ! -e "${DESTDIR}/usr/bin/gpg" \ + -a -n "`ls \"${DESTDIR}\"/usr/lib/libgpgme.so* \ + 2>/dev/null`" ]; then copy_exec /usr/bin/gpg fi -# Config files and key files -for file in /etc/mandos/* "$keydir"/*; do - if [ -d "$file" ]; then - continue - fi - cp --archive --sparse=always "$file" "${DESTDIR}${CONFDIR}" +# Config files +for file in /etc/mandos/*; do + if [ -d "$file" ]; then + continue + fi + cp --archive --sparse=always "$file" "${DESTDIR}${CONFDIR}" +done + +if [ ${mandos_user} != 65534 ]; then + sed --in-place --expression="1i--userid=${mandos_user}" \ + "${DESTDIR}${CONFDIR}/plugin-runner.conf" +fi + +if [ ${mandos_group} != 65534 ]; then + sed --in-place --expression="1i--groupid=${mandos_group}" \ + "${DESTDIR}${CONFDIR}/plugin-runner.conf" +fi + +# Key files +for file in "$keydir"/*; do + if [ -d "$file" ]; then + continue + fi + cp --archive --sparse=always "$file" "${DESTDIR}${CONFDIR}" + chown ${mandos_user}:${mandos_group} \ + "${DESTDIR}${CONFDIR}/`basename \"$file\"`" done # /lib/mandos/plugin-runner will drop priviliges, but needs access to @@ -120,7 +154,7 @@ # condition. This umask is set by "initramfs-tools-hook-conf", # installed as "/usr/share/initramfs-tools/conf-hooks.d/mandos".) # -for full in "${PLUGINDIR}" "${CONFDIR}"; do +for full in "${MANDOSDIR}" "${CONFDIR}"; do while [ "$full" != "/" ]; do chmod a+rX "${DESTDIR}$full" full="`dirname \"$full\"`" @@ -134,7 +168,9 @@ chmod a+rX "${DESTDIR}$dir" fi done -for dir in /lib /usr/lib; do - find "${DESTDIR}$dir" \! -perm /u+rw,g+r -prune -o -print0 \ - | xargs --null --no-run-if-empty chmod a+rX +for dir in "${DESTDIR}"/lib* "${DESTDIR}"/usr/lib*; do + if [ -d "$dir" ]; then + find "$dir" \! -perm -u+rw,g+r -prune -or -print0 \ + | xargs --null --no-run-if-empty chmod a+rX + fi done === modified file 'initramfs-tools-hook-conf' --- initramfs-tools-hook-conf 2008-08-12 19:22:34 +0000 +++ initramfs-tools-hook-conf 2009-05-17 00:50:09 +0000 @@ -1,1 +1,13 @@ +# -*- shell-script -*- + +# if mkinitramfs is started by mkinitramfs-kpkg, mkinitramfs-kpkg has +# already touched the initrd file with umask 022 before we had a +# chance to affect it. We cannot allow a readable initrd file, +# therefore we must fix this now. +if [ -e "${outfile}" ] \ + && [ `stat --format=%s "${outfile}"` -eq 0 ]; then + rm "${outfile}" + (umask 027; touch "${outfile}") +fi + UMASK=027 === modified file 'initramfs-tools-script' --- initramfs-tools-script 2008-08-14 02:24:59 +0000 +++ initramfs-tools-script 2009-02-09 02:01:13 +0000 @@ -6,25 +6,103 @@ # # This script should be installed as -# "/usr/share/initramfs-tools/scripts/local-top/mandos" which will -# eventually be "/scripts/local-top/mandos" in the initrd.img file. +# "/usr/share/initramfs-tools/scripts/init-premount/mandos" which will +# eventually be "/scripts/init-premount/mandos" in the initrd.img +# file. -# No initramfs pre-requirements; we must instead run BEFORE cryptroot. -# This is not a problem, since cryptroot forces itself to run LAST. -PREREQ="" +# No initramfs pre-requirements. +PREREQ="udev" prereqs() { - echo "$PREREQ" + echo "$PREREQ" } case $1 in prereqs) - prereqs - exit 0 - ;; -esac - -test -w /conf/conf.d/cryptroot + prereqs + exit 0 + ;; +esac + +. /scripts/functions + +for param in `cat /proc/cmdline`; do + case "$param" in + ip=*) IPOPTS="${param#ip=}" ;; + mandos=*) + # Split option line on commas + old_ifs="$IFS" + IFS="$IFS," + for mpar in ${param#mandos=}; do + IFS="$old_ifs" + case "$mpar" in + off) exit 0 ;; + connect) connect="" ;; + connect:*) connect="${mpar#connect:}" ;; + *) log_warning_msg "$0: Bad option ${mpar}" ;; + esac + done + unset mpar + IFS="$old_ifs" + unset old_ifs + ;; + esac +done +unset param + +chmod a=rwxt /tmp + +test -r /conf/conf.d/cryptroot +test -w /conf/conf.d + +# Get DEVICE from /conf/initramfs.conf and other files +. /conf/initramfs.conf +for conf in /conf/conf.d/*; do + [ -f ${conf} ] && . ${conf} +done +if [ -e /conf/param.conf ]; then + . /conf/param.conf +fi + +# Override DEVICE from sixth field of ip= kernel option, if passed +case "$IPOPTS" in + *:*:*:*:*:*) # At least six fields + # Remove the first five fields + device="${IPOPTS#*:*:*:*:*:}" + # Remove all fields except the first one + DEVICE="${device%%:*}" + ;; +esac + +# Add device setting (if any) to plugin-runner.conf +if [ "${DEVICE+set}" = set ]; then + # Did we get the device from an ip= option? + if [ "${device+set}" = set ]; then + # Let ip= option override local config; append: + cat <<-EOF >>/conf/conf.d/mandos/plugin-runner.conf + + --options-for=mandos-client:--interface=${DEVICE} +EOF + else + # Prepend device setting so any later options would override: + sed -i -e \ + '1i--options-for=mandos-client:--interface='"${DEVICE}" \ + /conf/conf.d/mandos/plugin-runner.conf + fi +fi +unset device + +# If we are connecting directly, run "configure_networking" (from +# /scripts/functions); it needs IPOPTS and DEVICE +if [ "${connect+set}" = set ]; then + configure_networking + if [ -n "$connect" ]; then + cat <<-EOF >>/conf/conf.d/mandos/plugin-runner.conf + + --options-for=mandos-client:--connect=${connect} +EOF + fi +fi # Do not replace cryptroot file unless we need to. replace_cryptroot=no === modified file 'legalnotice.xml' --- legalnotice.xml 2008-08-31 15:06:39 +0000 +++ legalnotice.xml 2008-09-06 17:24:58 +0000 @@ -4,21 +4,24 @@ This manual page is free software: you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. + modify it under the terms of the GNU General + Public License as published by the Free Software Foundation, + either version 3 of the License, or (at your option) any later + version. This manual page is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. + 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 . + You should have received a copy of the GNU + General Public License along with this program. If not, see + http://www.gnu.org/licenses/. === modified file 'mandos' --- mandos 2008-09-05 18:19:52 +0000 +++ mandos 2009-08-30 03:10:29 +0000 @@ -6,12 +6,13 @@ # This program is partly derived from an example program for an Avahi # service publisher, downloaded from # . This includes the -# methods "add" and "remove" in the "AvahiService" class, the -# "server_state_changed" and "entry_group_state_changed" functions, -# and some lines in "main". +# methods "add", "remove", "server_state_changed", +# "entry_group_state_changed", "cleanup", and "activate" in the +# "AvahiService" class, and some lines in "main". # # Everything else is -# Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson +# Copyright © 2008,2009 Teddy Hogeborn +# Copyright © 2008,2009 Björn Påhlsson # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -30,12 +31,11 @@ # Contact the authors at . # -from __future__ import division +from __future__ import division, with_statement, absolute_import -import SocketServer +import SocketServer as socketserver import socket -import select -from optparse import OptionParser +import optparse import datetime import errno import gnutls.crypto @@ -44,45 +44,62 @@ import gnutls.library.functions import gnutls.library.constants import gnutls.library.types -import ConfigParser +import ConfigParser as configparser import sys import re import os import signal -from sets import Set import subprocess import atexit import stat import logging import logging.handlers import pwd +from contextlib import closing +import struct +import fcntl +import functools import dbus +import dbus.service import gobject import avahi from dbus.mainloop.glib import DBusGMainLoop import ctypes - -version = "1.0" - -logger = logging.Logger('mandos') -syslogger = logging.handlers.SysLogHandler\ - (facility = logging.handlers.SysLogHandler.LOG_DAEMON, - address = "/dev/log") -syslogger.setFormatter(logging.Formatter\ - ('Mandos: %(levelname)s: %(message)s')) +import ctypes.util + +try: + SO_BINDTODEVICE = socket.SO_BINDTODEVICE +except AttributeError: + try: + from IN import SO_BINDTODEVICE + except ImportError: + SO_BINDTODEVICE = None + + +version = "1.0.11" + +logger = logging.Logger(u'mandos') +syslogger = (logging.handlers.SysLogHandler + (facility = logging.handlers.SysLogHandler.LOG_DAEMON, + address = "/dev/log")) +syslogger.setFormatter(logging.Formatter + (u'Mandos [%(process)d]: %(levelname)s:' + u' %(message)s')) logger.addHandler(syslogger) console = logging.StreamHandler() -console.setFormatter(logging.Formatter('%(name)s: %(levelname)s:' - ' %(message)s')) +console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:' + u' %(levelname)s:' + u' %(message)s')) logger.addHandler(console) class AvahiError(Exception): - def __init__(self, value): + def __init__(self, value, *args, **kwargs): self.value = value - def __str__(self): - return repr(self.value) + super(AvahiError, self).__init__(value, *args, **kwargs) + def __unicode__(self): + return unicode(repr(self.value)) class AvahiServiceError(AvahiError): pass @@ -93,11 +110,12 @@ class AvahiService(object): """An Avahi (Zeroconf) service. + Attributes: interface: integer; avahi.IF_UNSPEC or an interface index. Used to optionally bind to the specified interface. - name: string; Example: 'Mandos' - type: string; Example: '_mandos._tcp'. + name: string; Example: u'Mandos' + type: string; Example: u'_mandos._tcp'. See port: integer; what port to announce TXT: list of strings; TXT record for the service @@ -106,213 +124,265 @@ max_renames: integer; maximum number of renames rename_count: integer; counter so we only rename after collisions a sensible number of times + group: D-Bus Entry Group + server: D-Bus Server + bus: dbus.SystemBus() """ def __init__(self, interface = avahi.IF_UNSPEC, name = None, - type = None, port = None, TXT = None, domain = "", - host = "", max_renames = 32768): + servicetype = None, port = None, TXT = None, + domain = u"", host = u"", max_renames = 32768, + protocol = avahi.PROTO_UNSPEC, bus = None): self.interface = interface self.name = name - self.type = type + self.type = servicetype self.port = port - if TXT is None: - self.TXT = [] - else: - self.TXT = TXT + self.TXT = TXT if TXT is not None else [] self.domain = domain self.host = host self.rename_count = 0 self.max_renames = max_renames + self.protocol = protocol + self.group = None # our entry group + self.server = None + self.bus = bus def rename(self): """Derived from the Avahi example code""" if self.rename_count >= self.max_renames: logger.critical(u"No suitable Zeroconf service name found" u" after %i retries, exiting.", - rename_count) - raise AvahiServiceError("Too many renames") - self.name = server.GetAlternativeServiceName(self.name) + self.rename_count) + raise AvahiServiceError(u"Too many renames") + self.name = self.server.GetAlternativeServiceName(self.name) logger.info(u"Changing Zeroconf service name to %r ...", - str(self.name)) - syslogger.setFormatter(logging.Formatter\ - ('Mandos (%s): %%(levelname)s:' - ' %%(message)s' % self.name)) + unicode(self.name)) + syslogger.setFormatter(logging.Formatter + (u'Mandos (%s) [%%(process)d]:' + u' %%(levelname)s: %%(message)s' + % self.name)) self.remove() self.add() self.rename_count += 1 def remove(self): """Derived from the Avahi example code""" - if group is not None: - group.Reset() + if self.group is not None: + self.group.Reset() def add(self): """Derived from the Avahi example code""" - global group - if group is None: - group = dbus.Interface\ - (bus.get_object(avahi.DBUS_NAME, - server.EntryGroupNew()), - avahi.DBUS_INTERFACE_ENTRY_GROUP) - group.connect_to_signal('StateChanged', - entry_group_state_changed) + if self.group is None: + self.group = dbus.Interface( + self.bus.get_object(avahi.DBUS_NAME, + self.server.EntryGroupNew()), + avahi.DBUS_INTERFACE_ENTRY_GROUP) + self.group.connect_to_signal('StateChanged', + self.entry_group_state_changed) logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...", - service.name, service.type) - group.AddService( - self.interface, # interface - avahi.PROTO_INET6, # protocol - dbus.UInt32(0), # flags - self.name, self.type, - self.domain, self.host, - dbus.UInt16(self.port), - avahi.string_array_to_txt_array(self.TXT)) - group.Commit() - -# From the Avahi example code: -group = None # our entry group -# End of Avahi example code + self.name, self.type) + self.group.AddService( + self.interface, + self.protocol, + dbus.UInt32(0), # flags + self.name, self.type, + self.domain, self.host, + dbus.UInt16(self.port), + avahi.string_array_to_txt_array(self.TXT)) + self.group.Commit() + def entry_group_state_changed(self, state, error): + """Derived from the Avahi example code""" + logger.debug(u"Avahi state change: %i", state) + + if state == avahi.ENTRY_GROUP_ESTABLISHED: + logger.debug(u"Zeroconf service established.") + elif state == avahi.ENTRY_GROUP_COLLISION: + logger.warning(u"Zeroconf service name collision.") + self.rename() + elif state == avahi.ENTRY_GROUP_FAILURE: + logger.critical(u"Avahi: Error in group state changed %s", + unicode(error)) + raise AvahiGroupError(u"State changed: %s" + % unicode(error)) + def cleanup(self): + """Derived from the Avahi example code""" + if self.group is not None: + self.group.Free() + self.group = None + def server_state_changed(self, state): + """Derived from the Avahi example code""" + if state == avahi.SERVER_COLLISION: + logger.error(u"Zeroconf server name collision") + self.remove() + elif state == avahi.SERVER_RUNNING: + self.add() + def activate(self): + """Derived from the Avahi example code""" + if self.server is None: + self.server = dbus.Interface( + self.bus.get_object(avahi.DBUS_NAME, + avahi.DBUS_PATH_SERVER), + avahi.DBUS_INTERFACE_SERVER) + self.server.connect_to_signal(u"StateChanged", + self.server_state_changed) + self.server_state_changed(self.server.GetState()) class Client(object): """A representation of a client host served by this server. + Attributes: - name: string; from the config file, used in log messages + 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(); object creation, not client host - last_checked_ok: datetime.datetime() or None if not yet checked OK - timeout: datetime.timedelta(); How long from last_checked_ok - until this client is invalid - interval: datetime.timedelta(); How often to start a new checker - stop_hook: If set, called by stop() as stop_hook(self) - checker: subprocess.Popen(); a running checker process used - to see if the client lives. - 'None' if no process is running. + 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) + checker: subprocess.Popen(); a running checker process used + to see if the client lives. + 'None' if no process is running. checker_initiator_tag: a gobject event source tag, or None - stop_initiator_tag: - '' - + disable_initiator_tag: - '' - checker_callback_tag: - '' - checker_command: string; External command which is run to check if client lives. %() expansions are done at runtime with vars(self) as dict, so that for instance %(name)s can be used in the command. - Private attibutes: - _timeout: Real variable for 'timeout' - _interval: Real variable for 'interval' - _timeout_milliseconds: Used when calling gobject.timeout_add() - _interval_milliseconds: - '' - + current_checker_command: string; current running checker_command """ - def _set_timeout(self, timeout): - "Setter function for 'timeout' attribute" - self._timeout = timeout - self._timeout_milliseconds = ((self.timeout.days - * 24 * 60 * 60 * 1000) - + (self.timeout.seconds * 1000) - + (self.timeout.microseconds - // 1000)) - timeout = property(lambda self: self._timeout, - _set_timeout) - del _set_timeout - def _set_interval(self, interval): - "Setter function for 'interval' attribute" - self._interval = interval - self._interval_milliseconds = ((self.interval.days - * 24 * 60 * 60 * 1000) - + (self.interval.seconds - * 1000) - + (self.interval.microseconds - // 1000)) - interval = property(lambda self: self._interval, - _set_interval) - del _set_interval - def __init__(self, name = None, stop_hook=None, config={}): + + @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 timeout_milliseconds(self): + "Return the 'timeout' attribute in milliseconds" + return self._datetime_to_milliseconds(self.timeout) + + def interval_milliseconds(self): + "Return the 'interval' attribute in milliseconds" + return self._datetime_to_milliseconds(self.interval) + + def __init__(self, name = None, disable_hook=None, config=None): """Note: the 'checker' key in 'config' sets the 'checker_command' attribute and *not* the 'checker' attribute.""" self.name = name + if config is None: + config = {} logger.debug(u"Creating client %r", self.name) # Uppercase and remove spaces from fingerprint for later # comparison purposes with return value from the fingerprint() # function - self.fingerprint = config["fingerprint"].upper()\ - .replace(u" ", u"") + self.fingerprint = (config[u"fingerprint"].upper() + .replace(u" ", u"")) logger.debug(u" Fingerprint: %s", self.fingerprint) - if "secret" in config: - self.secret = config["secret"].decode(u"base64") - elif "secfile" in config: - sf = open(config["secfile"]) - self.secret = sf.read() - sf.close() + if u"secret" in config: + self.secret = config[u"secret"].decode(u"base64") + elif u"secfile" in config: + with closing(open(os.path.expanduser + (os.path.expandvars + (config[u"secfile"])))) as secfile: + self.secret = secfile.read() else: raise TypeError(u"No secret or secfile for client %s" % self.name) - self.host = config.get("host", "") - self.created = datetime.datetime.now() + self.host = config.get(u"host", u"") + self.created = datetime.datetime.utcnow() + self.enabled = False + self.last_enabled = None self.last_checked_ok = None - self.timeout = string_to_delta(config["timeout"]) - self.interval = string_to_delta(config["interval"]) - self.stop_hook = stop_hook + self.timeout = string_to_delta(config[u"timeout"]) + self.interval = string_to_delta(config[u"interval"]) + self.disable_hook = disable_hook self.checker = None self.checker_initiator_tag = None - self.stop_initiator_tag = None + self.disable_initiator_tag = None self.checker_callback_tag = None - self.check_command = config["checker"] - def start(self): + self.checker_command = config[u"checker"] + self.current_checker_command = None + self.last_connect = None + + def enable(self): """Start this client's checker and timeout hooks""" + if getattr(self, u"enabled", False): + # Already enabled + return + self.last_enabled = datetime.datetime.utcnow() # Schedule a new checker to be started an 'interval' from now, # and every interval from then on. - self.checker_initiator_tag = gobject.timeout_add\ - (self._interval_milliseconds, - self.start_checker) + self.checker_initiator_tag = (gobject.timeout_add + (self.interval_milliseconds(), + self.start_checker)) # Also start a new checker *right now*. self.start_checker() - # Schedule a stop() when 'timeout' has passed - self.stop_initiator_tag = gobject.timeout_add\ - (self._timeout_milliseconds, - self.stop) - def stop(self): - """Stop this client. - The possibility that a client might be restarted is left open, - but not currently used.""" - # If this client doesn't have a secret, it is already stopped. - if hasattr(self, "secret") and self.secret: - logger.info(u"Stopping client %s", self.name) - self.secret = None - else: + # Schedule a disable() when 'timeout' has passed + self.disable_initiator_tag = (gobject.timeout_add + (self.timeout_milliseconds(), + self.disable)) + self.enabled = True + + def disable(self): + """Disable this client.""" + if not getattr(self, "enabled", False): return False - if getattr(self, "stop_initiator_tag", False): - gobject.source_remove(self.stop_initiator_tag) - self.stop_initiator_tag = None - if getattr(self, "checker_initiator_tag", False): + logger.info(u"Disabling client %s", self.name) + if getattr(self, u"disable_initiator_tag", False): + gobject.source_remove(self.disable_initiator_tag) + self.disable_initiator_tag = None + if getattr(self, u"checker_initiator_tag", False): gobject.source_remove(self.checker_initiator_tag) self.checker_initiator_tag = None self.stop_checker() - if self.stop_hook: - self.stop_hook(self) + if self.disable_hook: + self.disable_hook(self) + self.enabled = False # Do not run this again if called by a gobject.timeout_add return False + def __del__(self): - self.stop_hook = None - self.stop() - def checker_callback(self, pid, condition): + self.disable_hook = None + self.disable() + + def checker_callback(self, pid, condition, command): """The checker has completed, so take appropriate actions.""" - now = datetime.datetime.now() self.checker_callback_tag = None self.checker = None - if os.WIFEXITED(condition) \ - and (os.WEXITSTATUS(condition) == 0): - logger.info(u"Checker for %(name)s succeeded", - vars(self)) - self.last_checked_ok = now - gobject.source_remove(self.stop_initiator_tag) - self.stop_initiator_tag = gobject.timeout_add\ - (self._timeout_milliseconds, - self.stop) - elif not os.WIFEXITED(condition): + if os.WIFEXITED(condition): + exitstatus = os.WEXITSTATUS(condition) + if exitstatus == 0: + logger.info(u"Checker for %(name)s succeeded", + vars(self)) + self.checked_ok() + else: + logger.info(u"Checker for %(name)s failed", + vars(self)) + else: logger.warning(u"Checker for %(name)s crashed?", vars(self)) - else: - logger.info(u"Checker for %(name)s failed", - vars(self)) + + def checked_ok(self): + """Bump up the timeout for this client. + + This should only be called when the client has been seen, + alive and well. + """ + self.last_checked_ok = datetime.datetime.utcnow() + gobject.source_remove(self.disable_initiator_tag) + self.disable_initiator_tag = (gobject.timeout_add + (self.timeout_milliseconds(), + self.disable)) + def start_checker(self): """Start a new checker subprocess if one is not running. + If a checker already exists, leave it running and do nothing.""" # The reason for not killing a running checker is that if we @@ -323,21 +393,35 @@ # 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. + + # If a checker exists, make sure it is not a zombie + if self.checker is not None: + pid, status = os.waitpid(self.checker.pid, os.WNOHANG) + if pid: + logger.warning(u"Checker was a zombie") + gobject.source_remove(self.checker_callback_tag) + self.checker_callback(pid, status, + self.current_checker_command) + # Start a new checker if needed if self.checker is None: try: - # In case check_command has exactly one % operator - command = self.check_command % self.host + # In case checker_command has exactly one % operator + command = self.checker_command % self.host except TypeError: # Escape attributes for the shell - escaped_attrs = dict((key, re.escape(str(val))) + escaped_attrs = dict((key, + re.escape(unicode(str(val), + errors= + u'replace'))) for key, val in vars(self).iteritems()) try: - command = self.check_command % escaped_attrs + command = self.checker_command % escaped_attrs except TypeError, error: logger.error(u'Could not format string "%s":' - u' %s', self.check_command, error) + u' %s', self.checker_command, error) return True # Try again later + self.current_checker_command = command try: logger.info(u"Starting checker %r for %s", command, self.name) @@ -347,21 +431,29 @@ # always replaced by /dev/null.) self.checker = subprocess.Popen(command, close_fds=True, - shell=True, cwd="/") - self.checker_callback_tag = gobject.child_watch_add\ - (self.checker.pid, - self.checker_callback) + shell=True, cwd=u"/") + self.checker_callback_tag = (gobject.child_watch_add + (self.checker.pid, + self.checker_callback, + data=command)) + # The checker may have completed before the gobject + # watch was added. Check for this. + pid, status = os.waitpid(self.checker.pid, os.WNOHANG) + if pid: + gobject.source_remove(self.checker_callback_tag) + self.checker_callback(pid, status, command) except OSError, error: logger.error(u"Failed to start subprocess: %s", error) # Re-run this periodically if run by gobject.timeout_add return True + def stop_checker(self): """Force the checker process, if any, to stop.""" if self.checker_callback_tag: gobject.source_remove(self.checker_callback_tag) self.checker_callback_tag = None - if getattr(self, "checker", None) is None: + if getattr(self, u"checker", None) is None: return logger.debug(u"Stopping checker for %(name)s", vars(self)) try: @@ -373,226 +465,645 @@ if error.errno != errno.ESRCH: # No such process raise self.checker = None + def still_valid(self): """Has the timeout not yet passed for this client?""" - now = datetime.datetime.now() + if 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) -def peer_certificate(session): - "Return the peer's OpenPGP certificate as a bytestring" - # If not an OpenPGP certificate... - if gnutls.library.functions.gnutls_certificate_type_get\ - (session._c_object) \ - != gnutls.library.constants.GNUTLS_CRT_OPENPGP: - # ...do the normal thing - return session.peer_certificate - list_size = ctypes.c_uint() - cert_list = gnutls.library.functions.gnutls_certificate_get_peers\ - (session._c_object, ctypes.byref(list_size)) - if list_size.value == 0: - return None - cert = cert_list[0] - return ctypes.string_at(cert.data, cert.size) - - -def fingerprint(openpgp): - "Convert an OpenPGP bytestring to a hexdigit fingerprint string" - # New GnuTLS "datum" with the OpenPGP public key - datum = gnutls.library.types.gnutls_datum_t\ - (ctypes.cast(ctypes.c_char_p(openpgp), - ctypes.POINTER(ctypes.c_ubyte)), - ctypes.c_uint(len(openpgp))) - # New empty GnuTLS certificate - crt = gnutls.library.types.gnutls_openpgp_crt_t() - gnutls.library.functions.gnutls_openpgp_crt_init\ - (ctypes.byref(crt)) - # Import the OpenPGP public key into the certificate - gnutls.library.functions.gnutls_openpgp_crt_import\ - (crt, ctypes.byref(datum), - gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW) - # Verify the self signature in the key - crtverify = ctypes.c_uint(); - gnutls.library.functions.gnutls_openpgp_crt_verify_self\ - (crt, 0, ctypes.byref(crtverify)) - if crtverify.value != 0: - gnutls.library.functions.gnutls_openpgp_crt_deinit(crt) - raise gnutls.errors.CertificateSecurityError("Verify failed") - # New buffer for the fingerprint - buffer = ctypes.create_string_buffer(20) - buffer_length = ctypes.c_size_t() - # Get the fingerprint from the certificate into the buffer - gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\ - (crt, ctypes.byref(buffer), ctypes.byref(buffer_length)) - # Deinit the certificate - gnutls.library.functions.gnutls_openpgp_crt_deinit(crt) - # Convert the buffer to a Python bytestring - fpr = ctypes.string_at(buffer, buffer_length.value) - # Convert the bytestring to hexadecimal notation - hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr) - return hex_fpr - - -class tcp_handler(SocketServer.BaseRequestHandler, object): - """A TCP request handler class. - Instantiated by IPv6_TCPServer for each request to handle it. +class ClientDBus(Client, dbus.service.Object): + """A Client class using D-Bus + + Attributes: + dbus_object_path: dbus.ObjectPath + bus: dbus.SystemBus() + """ + # dbus.service.Object doesn't use super(), so we can't either. + + def __init__(self, bus = None, *args, **kwargs): + self.bus = bus + Client.__init__(self, *args, **kwargs) + # Only now, when this client is initialized, can it show up on + # the D-Bus + self.dbus_object_path = (dbus.ObjectPath + (u"/clients/" + + self.name.replace(u".", u"_"))) + dbus.service.Object.__init__(self, self.bus, + self.dbus_object_path) + + @staticmethod + def _datetime_to_dbus(dt, variant_level=0): + """Convert a UTC datetime.datetime() to a D-Bus type.""" + return dbus.String(dt.isoformat(), + variant_level=variant_level) + + def enable(self): + oldstate = getattr(self, u"enabled", False) + r = Client.enable(self) + if oldstate != self.enabled: + # Emit D-Bus signals + self.PropertyChanged(dbus.String(u"enabled"), + dbus.Boolean(True, variant_level=1)) + self.PropertyChanged( + dbus.String(u"last_enabled"), + self._datetime_to_dbus(self.last_enabled, + variant_level=1)) + return r + + def disable(self, signal = True): + oldstate = getattr(self, u"enabled", False) + r = Client.disable(self) + if signal and oldstate != self.enabled: + # Emit D-Bus signal + self.PropertyChanged(dbus.String(u"enabled"), + dbus.Boolean(False, variant_level=1)) + return r + + def __del__(self, *args, **kwargs): + try: + self.remove_from_connection() + except LookupError: + pass + if hasattr(dbus.service.Object, u"__del__"): + dbus.service.Object.__del__(self, *args, **kwargs) + Client.__del__(self, *args, **kwargs) + + def checker_callback(self, pid, condition, command, + *args, **kwargs): + self.checker_callback_tag = None + self.checker = None + # Emit D-Bus signal + self.PropertyChanged(dbus.String(u"checker_running"), + dbus.Boolean(False, variant_level=1)) + if os.WIFEXITED(condition): + exitstatus = os.WEXITSTATUS(condition) + # Emit D-Bus signal + self.CheckerCompleted(dbus.Int16(exitstatus), + dbus.Int64(condition), + dbus.String(command)) + else: + # Emit D-Bus signal + self.CheckerCompleted(dbus.Int16(-1), + dbus.Int64(condition), + dbus.String(command)) + + return Client.checker_callback(self, pid, condition, command, + *args, **kwargs) + + def checked_ok(self, *args, **kwargs): + r = Client.checked_ok(self, *args, **kwargs) + # Emit D-Bus signal + self.PropertyChanged( + dbus.String(u"last_checked_ok"), + (self._datetime_to_dbus(self.last_checked_ok, + variant_level=1))) + return r + + def start_checker(self, *args, **kwargs): + old_checker = self.checker + if self.checker is not None: + old_checker_pid = self.checker.pid + else: + old_checker_pid = None + r = Client.start_checker(self, *args, **kwargs) + # Only if new checker process was started + if (self.checker is not None + and old_checker_pid != self.checker.pid): + # Emit D-Bus signal + self.CheckerStarted(self.current_checker_command) + self.PropertyChanged( + dbus.String(u"checker_running"), + dbus.Boolean(True, variant_level=1)) + return r + + def stop_checker(self, *args, **kwargs): + old_checker = getattr(self, u"checker", None) + r = Client.stop_checker(self, *args, **kwargs) + if (old_checker is not None + and getattr(self, u"checker", None) is None): + self.PropertyChanged(dbus.String(u"checker_running"), + dbus.Boolean(False, variant_level=1)) + return r + + ## D-Bus methods & signals + _interface = u"se.bsnet.fukt.Mandos.Client" + + # CheckedOK - method + @dbus.service.method(_interface) + def CheckedOK(self): + return self.checked_ok() + + # CheckerCompleted - signal + @dbus.service.signal(_interface, signature=u"nxs") + def CheckerCompleted(self, exitcode, waitstatus, command): + "D-Bus signal" + pass + + # CheckerStarted - signal + @dbus.service.signal(_interface, signature=u"s") + def CheckerStarted(self, command): + "D-Bus signal" + pass + + # 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 + @dbus.service.signal(_interface) + def ReceivedSecret(self): + "D-Bus signal" + pass + + # Rejected - signal + @dbus.service.signal(_interface) + def Rejected(self): + "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))) + + # Enable - method + @dbus.service.method(_interface) + def Enable(self): + "D-Bus method" + self.enable() + + # StartChecker - method + @dbus.service.method(_interface) + def StartChecker(self): + "D-Bus method" + self.start_checker() + + # Disable - method + @dbus.service.method(_interface) + def Disable(self): + "D-Bus method" + self.disable() + + # StopChecker - method + @dbus.service.method(_interface) + def StopChecker(self): + self.stop_checker() + + del _interface + + +class ClientHandler(socketserver.BaseRequestHandler, object): + """A class to handle client connections. + + Instantiated once for each connection to handle it. Note: This will run in its own forked process.""" def handle(self): logger.info(u"TCP connection from: %s", - unicode(self.client_address)) - session = gnutls.connection.ClientSession\ - (self.request, gnutls.connection.X509Credentials()) - - line = self.request.makefile().readline() - logger.debug(u"Protocol version: %r", line) - try: - if int(line.strip().split()[0]) > 1: - raise RuntimeError - except (ValueError, IndexError, RuntimeError), error: - logger.error(u"Unknown protocol version: %s", error) - return - - # Note: gnutls.connection.X509Credentials is really a generic - # GnuTLS certificate credentials object so long as no X.509 - # keys are added to it. Therefore, we can use it here despite - # using OpenPGP certificates. - - #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC", - # "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP", - # "+DHE-DSS")) - priority = "NORMAL" # Fallback default, since this - # MUST be set. - if self.server.settings["priority"]: - priority = self.server.settings["priority"] - gnutls.library.functions.gnutls_priority_set_direct\ - (session._c_object, priority, None); - - try: - session.handshake() - except gnutls.errors.GNUTLSError, error: - logger.warning(u"Handshake failed: %s", error) - # Do not run session.bye() here: the session is not - # established. Just abandon the request. - return - try: - fpr = fingerprint(peer_certificate(session)) - except (TypeError, gnutls.errors.GNUTLSError), error: - logger.warning(u"Bad certificate: %s", error) - session.bye() - return - logger.debug(u"Fingerprint: %s", fpr) - client = None - for c in self.server.clients: - if c.fingerprint == fpr: - client = c - break - if not client: - logger.warning(u"Client not found for fingerprint: %s", - fpr) - session.bye() - return - # Have to check if client.still_valid(), since it is possible - # that the client timed out while establishing the GnuTLS - # session. - if not client.still_valid(): - logger.warning(u"Client %(name)s is invalid", - vars(client)) - session.bye() - return - sent_size = 0 - while sent_size < len(client.secret): - sent = session.send(client.secret[sent_size:]) - logger.debug(u"Sent: %d, remaining: %d", - sent, len(client.secret) - - (sent_size + sent)) - sent_size += sent - session.bye() - - -class IPv6_TCPServer(SocketServer.ForkingTCPServer, object): - """IPv6 TCP server. Accepts 'None' as address and/or port. + 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: + 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", + # u"+DHE-DSS")) + # Use a fallback default, since this MUST be set. + priority = self.server.gnutls_priority + if priority is None: + priority = u"NORMAL" + (gnutls.library.functions + .gnutls_priority_set_direct(session._c_object, + priority, None)) + + try: + session.handshake() + except gnutls.errors.GNUTLSError, error: + logger.warning(u"Handshake failed: %s", error) + # Do not run session.bye() here: the session is not + # established. Just abandon the request. + return + logger.debug(u"Handshake succeeded") + 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) + + 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() + + @staticmethod + def peer_certificate(session): + "Return the peer's OpenPGP certificate as a bytestring" + # If not an OpenPGP certificate... + if (gnutls.library.functions + .gnutls_certificate_type_get(session._c_object) + != gnutls.library.constants.GNUTLS_CRT_OPENPGP): + # ...do the normal thing + return session.peer_certificate + list_size = ctypes.c_uint(1) + cert_list = (gnutls.library.functions + .gnutls_certificate_get_peers + (session._c_object, ctypes.byref(list_size))) + if not bool(cert_list) and list_size.value != 0: + raise gnutls.errors.GNUTLSError(u"error getting peer" + u" certificate") + if list_size.value == 0: + return None + cert = cert_list[0] + return ctypes.string_at(cert.data, cert.size) + + @staticmethod + def fingerprint(openpgp): + "Convert an OpenPGP bytestring to a hexdigit fingerprint" + # New GnuTLS "datum" with the OpenPGP public key + datum = (gnutls.library.types + .gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp), + ctypes.POINTER + (ctypes.c_ubyte)), + ctypes.c_uint(len(openpgp)))) + # New empty GnuTLS certificate + crt = gnutls.library.types.gnutls_openpgp_crt_t() + (gnutls.library.functions + .gnutls_openpgp_crt_init(ctypes.byref(crt))) + # Import the OpenPGP public key into the certificate + (gnutls.library.functions + .gnutls_openpgp_crt_import(crt, ctypes.byref(datum), + gnutls.library.constants + .GNUTLS_OPENPGP_FMT_RAW)) + # Verify the self signature in the key + crtverify = ctypes.c_uint() + (gnutls.library.functions + .gnutls_openpgp_crt_verify_self(crt, 0, + ctypes.byref(crtverify))) + if crtverify.value != 0: + gnutls.library.functions.gnutls_openpgp_crt_deinit(crt) + raise (gnutls.errors.CertificateSecurityError + (u"Verify failed")) + # New buffer for the fingerprint + buf = ctypes.create_string_buffer(20) + buf_len = ctypes.c_size_t() + # Get the fingerprint from the certificate into the buffer + (gnutls.library.functions + .gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf), + ctypes.byref(buf_len))) + # Deinit the certificate + gnutls.library.functions.gnutls_openpgp_crt_deinit(crt) + # Convert the buffer to a Python bytestring + fpr = ctypes.string_at(buf, buf_len.value) + # Convert the bytestring to hexadecimal notation + hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr) + return hex_fpr + + +class ForkingMixInWithPipe(socketserver.ForkingMixIn, object): + """Like socketserver.ForkingMixIn, but also pass a pipe.""" + 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, + 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): + """Dummy function; override as necessary""" + os.close(pipe) + + +class IPv6_TCPServer(ForkingMixInWithPipe, + socketserver.TCPServer, object): + """IPv6-capable TCP server. Accepts 'None' as address and/or port + Attributes: - settings: Server settings - clients: Set() of Client objects enabled: Boolean; whether this server is activated yet + interface: None or a network interface name (string) + use_ipv6: Boolean; to use IPv6 or not """ - address_family = socket.AF_INET6 - def __init__(self, *args, **kwargs): - if "settings" in kwargs: - self.settings = kwargs["settings"] - del kwargs["settings"] - if "clients" in kwargs: - self.clients = kwargs["clients"] - del kwargs["clients"] - self.enabled = False - return super(type(self), self).__init__(*args, **kwargs) + def __init__(self, server_address, RequestHandlerClass, + interface=None, use_ipv6=True): + self.interface = interface + if use_ipv6: + self.address_family = socket.AF_INET6 + socketserver.TCPServer.__init__(self, server_address, + RequestHandlerClass) def server_bind(self): """This overrides the normal server_bind() function to bind to an interface if one was specified, and also NOT to bind to an address or port if they were not specified.""" - if self.settings["interface"]: - # 25 is from /usr/include/asm-i486/socket.h - SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25) - try: - self.socket.setsockopt(socket.SOL_SOCKET, - SO_BINDTODEVICE, - self.settings["interface"]) - except socket.error, error: - if error[0] == errno.EPERM: - logger.error(u"No permission to" - u" bind to interface %s", - self.settings["interface"]) - else: - raise error + if self.interface is not None: + if SO_BINDTODEVICE is None: + logger.error(u"SO_BINDTODEVICE does not exist;" + u" cannot bind to interface %s", + self.interface) + else: + try: + self.socket.setsockopt(socket.SOL_SOCKET, + SO_BINDTODEVICE, + str(self.interface + + u'\0')) + except socket.error, error: + if error[0] == errno.EPERM: + logger.error(u"No permission to" + u" bind to interface %s", + self.interface) + elif error[0] == errno.ENOPROTOOPT: + logger.error(u"SO_BINDTODEVICE not available;" + u" cannot bind to interface %s", + self.interface) + else: + raise # Only bind(2) the socket if we really need to. if self.server_address[0] or self.server_address[1]: if not self.server_address[0]: - in6addr_any = "::" - self.server_address = (in6addr_any, + if self.address_family == socket.AF_INET6: + any_address = u"::" # in6addr_any + else: + any_address = socket.INADDR_ANY + self.server_address = (any_address, self.server_address[1]) elif not self.server_address[1]: self.server_address = (self.server_address[0], 0) -# if self.settings["interface"]: +# if self.interface: # self.server_address = (self.server_address[0], # 0, # port # 0, # flowinfo # if_nametoindex -# (self.settings -# ["interface"])) - return super(type(self), self).server_bind() +# (self.interface)) + return socketserver.TCPServer.server_bind(self) + + +class MandosServer(IPv6_TCPServer): + """Mandos server. + + Attributes: + clients: set of Client objects + gnutls_priority GnuTLS priority string + use_dbus: Boolean; to emit D-Bus signals or not + clients: set of Client objects + gnutls_priority GnuTLS priority string + use_dbus: Boolean; to emit D-Bus signals or not + + Assumes a gobject.MainLoop event loop. + """ + def __init__(self, server_address, RequestHandlerClass, + interface=None, use_ipv6=True, clients=None, + gnutls_priority=None, use_dbus=True): + self.enabled = False + self.clients = clients + if self.clients is None: + self.clients = set() + self.use_dbus = use_dbus + self.gnutls_priority = gnutls_priority + IPv6_TCPServer.__init__(self, server_address, + RequestHandlerClass, + interface = interface, + use_ipv6 = use_ipv6) def server_activate(self): if self.enabled: - return super(type(self), self).server_activate() + return socketserver.TCPServer.server_activate(self) def enable(self): self.enabled = True + def add_pipe(self, 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={}): + condition_names = { + gobject.IO_IN: u"IN", # There is data to read. + gobject.IO_OUT: u"OUT", # Data can be written (without + # blocking). + gobject.IO_PRI: u"PRI", # There is urgent data to read. + gobject.IO_ERR: u"ERR", # Error condition. + gobject.IO_HUP: u"HUP" # Hung up (the connection has been + # broken, usually for pipes and + # sockets). + } + conditions_string = ' | '.join(name + for cond, name in + condition_names.iteritems() + if cond & condition) + 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 + return True def string_to_delta(interval): """Parse a string and return a datetime.timedelta - - >>> string_to_delta('7d') + + >>> string_to_delta(u'7d') datetime.timedelta(7) - >>> string_to_delta('60s') + >>> string_to_delta(u'60s') datetime.timedelta(0, 60) - >>> string_to_delta('60m') + >>> string_to_delta(u'60m') datetime.timedelta(0, 3600) - >>> string_to_delta('24h') + >>> string_to_delta(u'24h') datetime.timedelta(1) >>> string_to_delta(u'1w') datetime.timedelta(7) - >>> string_to_delta('5m 30s') + >>> string_to_delta(u'5m 30s') datetime.timedelta(0, 330) """ timevalue = datetime.timedelta(0) for s in interval.split(): try: - suffix=unicode(s[-1]) - value=int(s[:-1]) + suffix = unicode(s[-1]) + value = int(s[:-1]) if suffix == u"d": delta = datetime.timedelta(value) elif suffix == u"s": @@ -611,62 +1122,39 @@ return timevalue -def server_state_changed(state): - """Derived from the Avahi example code""" - if state == avahi.SERVER_COLLISION: - logger.error(u"Zeroconf server name collision") - service.remove() - elif state == avahi.SERVER_RUNNING: - service.add() - - -def entry_group_state_changed(state, error): - """Derived from the Avahi example code""" - logger.debug(u"Avahi state change: %i", state) - - if state == avahi.ENTRY_GROUP_ESTABLISHED: - logger.debug(u"Zeroconf service established.") - elif state == avahi.ENTRY_GROUP_COLLISION: - logger.warning(u"Zeroconf service name collision.") - service.rename() - elif state == avahi.ENTRY_GROUP_FAILURE: - logger.critical(u"Avahi: Error in group state changed %s", - unicode(error)) - raise AvahiGroupError("State changed: %s", str(error)) - def if_nametoindex(interface): - """Call the C function if_nametoindex(), or equivalent""" + """Call the C function if_nametoindex(), or equivalent + + Note: This function cannot accept a unicode string.""" global if_nametoindex try: - if "ctypes.util" not in sys.modules: - import ctypes.util - if_nametoindex = ctypes.cdll.LoadLibrary\ - (ctypes.util.find_library("c")).if_nametoindex + if_nametoindex = (ctypes.cdll.LoadLibrary + (ctypes.util.find_library(u"c")) + .if_nametoindex) except (OSError, AttributeError): - if "struct" not in sys.modules: - import struct - if "fcntl" not in sys.modules: - import fcntl + logger.warning(u"Doing if_nametoindex the hard way") def if_nametoindex(interface): "Get an interface index the hard way, i.e. using fcntl()" SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h - s = socket.socket() - ifreq = fcntl.ioctl(s, SIOCGIFINDEX, - struct.pack("16s16x", interface)) - s.close() - interface_index = struct.unpack("I", ifreq[16:20])[0] + with closing(socket.socket()) as s: + ifreq = fcntl.ioctl(s, SIOCGIFINDEX, + struct.pack(str(u"16s16x"), + interface)) + interface_index = struct.unpack(str(u"I"), + ifreq[16:20])[0] return interface_index return if_nametoindex(interface) def daemon(nochdir = False, noclose = False): """See daemon(3). Standard BSD Unix function. + This should really exist as os.daemon, but it doesn't (yet).""" if os.fork(): sys.exit() os.setsid() if not nochdir: - os.chdir("/") + os.chdir(u"/") if os.fork(): sys.exit() if not noclose: @@ -674,7 +1162,7 @@ null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR) if not stat.S_ISCHR(os.fstat(null).st_mode): raise OSError(errno.ENODEV, - "/dev/null not a character device") + u"/dev/null not a character device") os.dup2(null, sys.stdin.fileno()) os.dup2(null, sys.stdout.fileno()) os.dup2(null, sys.stderr.fileno()) @@ -683,30 +1171,36 @@ def main(): - global main_loop_started - main_loop_started = False - - parser = OptionParser(version = "%%prog %s" % version) - parser.add_option("-i", "--interface", type="string", - metavar="IF", help="Bind to interface IF") - parser.add_option("-a", "--address", type="string", - help="Address to listen for requests on") - parser.add_option("-p", "--port", type="int", - help="Port number to receive requests on") - parser.add_option("--check", action="store_true", default=False, - help="Run self-test") - parser.add_option("--debug", action="store_true", - help="Debug mode; run in foreground and log to" - " terminal") - parser.add_option("--priority", type="string", help="GnuTLS" - " priority string (see GnuTLS documentation)") - parser.add_option("--servicename", type="string", metavar="NAME", - help="Zeroconf service name") - parser.add_option("--configdir", type="string", - default="/etc/mandos", metavar="DIR", - help="Directory to search for configuration" - " files") - (options, args) = parser.parse_args() + + ###################################################################### + # Parsing of options, both command line and config file + + parser = optparse.OptionParser(version = "%%prog %s" % version) + parser.add_option("-i", u"--interface", type=u"string", + metavar="IF", help=u"Bind to interface IF") + parser.add_option("-a", u"--address", type=u"string", + help=u"Address to listen for requests on") + parser.add_option("-p", u"--port", type=u"int", + help=u"Port number to receive requests on") + parser.add_option("--check", action=u"store_true", + help=u"Run self-test") + parser.add_option("--debug", action=u"store_true", + help=u"Debug mode; run in foreground and log to" + u" terminal") + parser.add_option("--priority", type=u"string", help=u"GnuTLS" + u" priority string (see GnuTLS documentation)") + parser.add_option("--servicename", type=u"string", + metavar=u"NAME", help=u"Zeroconf service name") + parser.add_option("--configdir", type=u"string", + default=u"/etc/mandos", metavar=u"DIR", + help=u"Directory to search for configuration" + u" files") + parser.add_option("--no-dbus", action=u"store_false", + dest=u"use_dbus", help=u"Do not provide D-Bus" + u" system bus interface") + parser.add_option("--no-ipv6", action=u"store_false", + dest=u"use_ipv6", help=u"Do not use IPv6") + options = parser.parse_args()[0] if options.check: import doctest @@ -714,127 +1208,151 @@ sys.exit() # Default values for config file for server-global settings - server_defaults = { "interface": "", - "address": "", - "port": "", - "debug": "False", - "priority": - "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP", - "servicename": "Mandos", + server_defaults = { u"interface": u"", + u"address": u"", + u"port": u"", + u"debug": u"False", + u"priority": + u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP", + u"servicename": u"Mandos", + u"use_dbus": u"True", + u"use_ipv6": u"True", } # Parse config file for server-global settings - server_config = ConfigParser.SafeConfigParser(server_defaults) + server_config = configparser.SafeConfigParser(server_defaults) del server_defaults - server_config.read(os.path.join(options.configdir, "mandos.conf")) + server_config.read(os.path.join(options.configdir, + u"mandos.conf")) # Convert the SafeConfigParser object to a dict server_settings = server_config.defaults() - # Use getboolean on the boolean config option - server_settings["debug"] = server_config.getboolean\ - ("DEFAULT", "debug") + # Use the appropriate methods on the non-string config options + for option in (u"debug", u"use_dbus", u"use_ipv6"): + server_settings[option] = server_config.getboolean(u"DEFAULT", + option) + if server_settings["port"]: + server_settings["port"] = server_config.getint(u"DEFAULT", + u"port") del server_config # Override the settings from the config file with command line # options, if set. - for option in ("interface", "address", "port", "debug", - "priority", "servicename", "configdir"): + for option in (u"interface", u"address", u"port", u"debug", + u"priority", u"servicename", u"configdir", + u"use_dbus", u"use_ipv6"): value = getattr(options, option) if value is not None: server_settings[option] = value del options + # Force all strings to be unicode + for option in server_settings.keys(): + if type(server_settings[option]) is str: + server_settings[option] = unicode(server_settings[option]) # Now we have our good server settings in "server_settings" - debug = server_settings["debug"] + ################################################################## + + # For convenience + debug = server_settings[u"debug"] + use_dbus = server_settings[u"use_dbus"] + use_ipv6 = server_settings[u"use_ipv6"] if not debug: syslogger.setLevel(logging.WARNING) console.setLevel(logging.WARNING) - if server_settings["servicename"] != "Mandos": - syslogger.setFormatter(logging.Formatter\ - ('Mandos (%s): %%(levelname)s:' - ' %%(message)s' - % server_settings["servicename"])) + if server_settings[u"servicename"] != u"Mandos": + syslogger.setFormatter(logging.Formatter + (u'Mandos (%s) [%%(process)d]:' + u' %%(levelname)s: %%(message)s' + % server_settings[u"servicename"])) # Parse config file with clients - client_defaults = { "timeout": "1h", - "interval": "5m", - "checker": "fping -q -- %(host)s", - "host": "", + client_defaults = { u"timeout": u"1h", + u"interval": u"5m", + u"checker": u"fping -q -- %%(host)s", + u"host": u"", } - client_config = ConfigParser.SafeConfigParser(client_defaults) - client_config.read(os.path.join(server_settings["configdir"], - "clients.conf")) - - clients = Set() - tcp_server = IPv6_TCPServer((server_settings["address"], - server_settings["port"]), - tcp_handler, - settings=server_settings, - clients=clients) - pidfilename = "/var/run/mandos.pid" - pidfile = None - try: - pidfile = open(pidfilename, "w") - except IOError, error: - logger.error("Could not open file %r", pidfilename) - - uid = 65534 - gid = 65534 - try: - uid = pwd.getpwnam("mandos").pw_uid - except KeyError: - try: - uid = pwd.getpwnam("nobody").pw_uid - except KeyError: - pass - try: - gid = pwd.getpwnam("mandos").pw_gid - except KeyError: - try: - gid = pwd.getpwnam("nogroup").pw_gid - except KeyError: - pass - try: + client_config = configparser.SafeConfigParser(client_defaults) + client_config.read(os.path.join(server_settings[u"configdir"], + u"clients.conf")) + + global mandos_dbus_service + mandos_dbus_service = None + + tcp_server = MandosServer((server_settings[u"address"], + server_settings[u"port"]), + ClientHandler, + interface=server_settings[u"interface"], + 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) + + try: + uid = pwd.getpwnam(u"_mandos").pw_uid + gid = pwd.getpwnam(u"_mandos").pw_gid + except KeyError: + try: + uid = pwd.getpwnam(u"mandos").pw_uid + gid = pwd.getpwnam(u"mandos").pw_gid + except KeyError: + try: + uid = pwd.getpwnam(u"nobody").pw_uid + gid = pwd.getpwnam(u"nobody").pw_gid + except KeyError: + uid = 65534 + gid = 65534 + try: + os.setgid(gid) os.setuid(uid) - os.setgid(gid) except OSError, error: if error[0] != errno.EPERM: raise error - global service - service = AvahiService(name = server_settings["servicename"], - type = "_mandos._tcp", ); - if server_settings["interface"]: - service.interface = if_nametoindex\ - (server_settings["interface"]) + # Enable all possible GnuTLS debugging + if debug: + # "Use a log level over 10 to enable all debugging options." + # - GnuTLS manual + gnutls.library.functions.gnutls_global_set_log_level(11) + + @gnutls.library.types.gnutls_log_func + def debug_gnutls(level, string): + logger.debug(u"GnuTLS: %s", string[:-1]) + + (gnutls.library.functions + .gnutls_global_set_log_function(debug_gnutls)) global main_loop - global bus - global server # From the Avahi example code DBusGMainLoop(set_as_default=True ) main_loop = gobject.MainLoop() bus = dbus.SystemBus() - server = dbus.Interface(bus.get_object(avahi.DBUS_NAME, - avahi.DBUS_PATH_SERVER), - avahi.DBUS_INTERFACE_SERVER) # End of Avahi example code - - def remove_from_clients(client): - clients.remove(client) - if not clients: - logger.critical(u"No clients left, exiting") - sys.exit() - - clients.update(Set(Client(name = section, - stop_hook = remove_from_clients, - config - = dict(client_config.items(section))) - for section in client_config.sections())) - if not clients: - logger.critical(u"No clients defined") - sys.exit(1) + if use_dbus: + bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus) + protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET + service = AvahiService(name = server_settings[u"servicename"], + servicetype = u"_mandos._tcp", + protocol = protocol, bus = bus) + if server_settings["interface"]: + service.interface = (if_nametoindex + (str(server_settings[u"interface"]))) + + client_class = Client + if use_dbus: + client_class = functools.partial(ClientDBus, bus = bus) + tcp_server.clients.update(set( + client_class(name = section, + config= dict(client_config.items(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 @@ -848,30 +1366,27 @@ # Close all input and output, do double fork, etc. daemon() - if pidfile is not None: - pid = os.getpid() - try: + try: + with closing(pidfile): + pid = os.getpid() pidfile.write(str(pid) + "\n") - pidfile.close() - except IOError, err: - logger.error(u"Could not write %s file with PID %d", - pidfilename, os.getpid()) - del pidfile + 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" - global group - # From the Avahi example code - if not group is None: - group.Free() - group = None - # End of Avahi example code + service.cleanup() - while clients: - client = clients.pop() - client.stop_hook = None - client.stop() + while tcp_server.clients: + client = tcp_server.clients.pop() + client.disable_hook = None + client.disable() atexit.register(cleanup) @@ -880,24 +1395,87 @@ signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit()) signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit()) - for client in clients: - client.start() + if use_dbus: + class MandosDBusService(dbus.service.Object): + """A D-Bus proxy object""" + def __init__(self): + dbus.service.Object.__init__(self, bus, u"/") + _interface = u"se.bsnet.fukt.Mandos" + + @dbus.service.signal(_interface, signature=u"oa{sv}") + def ClientAdded(self, objpath, properties): + "D-Bus signal" + pass + + @dbus.service.signal(_interface, signature=u"s") + def ClientNotFound(self, fingerprint): + "D-Bus signal" + pass + + @dbus.service.signal(_interface, signature=u"os") + def ClientRemoved(self, objpath, name): + "D-Bus signal" + pass + + @dbus.service.method(_interface, out_signature=u"ao") + def GetAllClients(self): + "D-Bus method" + return dbus.Array(c.dbus_object_path + for c in tcp_server.clients) + + @dbus.service.method(_interface, + out_signature=u"a{oa{sv}}") + def GetAllClientsWithProperties(self): + "D-Bus method" + return dbus.Dictionary( + ((c.dbus_object_path, c.GetAllProperties()) + for c in tcp_server.clients), + signature=u"oa{sv}") + + @dbus.service.method(_interface, in_signature=u"o") + def RemoveClient(self, object_path): + "D-Bus method" + for c in tcp_server.clients: + if c.dbus_object_path == object_path: + tcp_server.clients.remove(c) + c.remove_from_connection() + # Don't signal anything except ClientRemoved + c.disable(signal=False) + # Emit D-Bus signal + self.ClientRemoved(object_path, c.name) + return + raise KeyError + + del _interface + + mandos_dbus_service = MandosDBusService() + + for client in tcp_server.clients: + if use_dbus: + # Emit D-Bus signal + mandos_dbus_service.ClientAdded(client.dbus_object_path, + client.GetAllProperties()) + client.enable() tcp_server.enable() tcp_server.server_activate() # Find out what port we got service.port = tcp_server.socket.getsockname()[1] - logger.info(u"Now listening on address %r, port %d, flowinfo %d," - u" scope_id %d" % tcp_server.socket.getsockname()) + if use_ipv6: + logger.info(u"Now listening on address %r, port %d," + " flowinfo %d, scope_id %d" + % tcp_server.socket.getsockname()) + else: # IPv4 + logger.info(u"Now listening on address %r, port %d" + % tcp_server.socket.getsockname()) #service.interface = tcp_server.socket.getsockname()[3] try: # From the Avahi example code - server.connect_to_signal("StateChanged", server_state_changed) try: - server_state_changed(server.GetState()) + service.activate() except dbus.exceptions.DBusException, error: logger.critical(u"DBusException: %s", error) sys.exit(1) @@ -905,18 +1483,19 @@ gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN, lambda *args, **kwargs: - tcp_server.handle_request\ - (*args[2:], **kwargs) or True) + (tcp_server.handle_request + (*args[2:], **kwargs) or True)) logger.debug(u"Starting main loop") - main_loop_started = True main_loop.run() except AvahiError, error: - logger.critical(u"AvahiError: %s" + unicode(error)) + logger.critical(u"AvahiError: %s", error) sys.exit(1) except KeyboardInterrupt: if debug: - print + print >> sys.stderr + logger.debug(u"Server received KeyboardInterrupt") + logger.debug(u"Server exiting") if __name__ == '__main__': main() === modified file 'mandos-clients.conf.xml' --- mandos-clients.conf.xml 2008-09-04 13:36:59 +0000 +++ mandos-clients.conf.xml 2009-02-15 09:09:27 +0000 @@ -1,10 +1,11 @@ /etc/mandos/clients.conf"> - + + +%common; ]> @@ -12,7 +13,7 @@ Mandos Manual Mandos - &VERSION; + &version; &TIMESTAMP; @@ -32,12 +33,13 @@ 2008 + 2009 Teddy Hogeborn Björn Påhlsson - + &CONFNAME; 5 @@ -49,11 +51,11 @@ Configuration file for the Mandos server - + &CONFPATH; - + DESCRIPTION @@ -95,9 +97,9 @@ Unknown options are ignored. The used options are as follows: - + - + @@ -126,7 +128,7 @@ - + @@ -149,7 +151,7 @@ - + @@ -168,7 +170,7 @@ PATH will be searched. The default value for the checker command is fping %(host)s. + >-- %%(host)s. In addition to normal start time expansion, this option @@ -223,7 +225,7 @@ - + @@ -239,9 +241,14 @@ 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. + - + @@ -250,7 +257,7 @@ This option is optional, but highly recommended unless the option is modified to a - non-standard value without %(host)s in it. + non-standard value without %%(host)s in it. Host name for this client. This is not used by the server @@ -313,7 +320,7 @@ mode is needed to expose an error of this kind. - + @@ -344,7 +351,7 @@ [DEFAULT] timeout = 1h interval = 5m -checker = fping -q -- %(host)s +checker = fping -q -- %%(host)s # Client "foo" [foo] @@ -373,7 +380,6 @@ fingerprint = 3e393aeaefb84c7e89e2f547b3a107558fca3a27 secfile = /etc/mandos/bar-secret timeout = 15m - === added file 'mandos-ctl' --- mandos-ctl 1970-01-01 00:00:00 +0000 +++ mandos-ctl 2009-08-30 03:10:29 +0000 @@ -0,0 +1,193 @@ +#!/usr/bin/python +# -*- mode: python; coding: utf-8 -*- + +from __future__ import division +import sys +import dbus +from optparse import OptionParser +import locale +import datetime +import re + +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', + } +defaultkeywords = ('name', 'enabled', 'timeout', 'last_checked_ok', + 'checker') +domain = 'se.bsnet.fukt' +busname = domain + '.Mandos' +server_path = '/' +server_interface = domain + '.Mandos' +client_interface = domain + '.Mandos.Client' +version = "1.0.11" +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 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 + + +def string_to_delta(interval): + """Parse a string and return a datetime.timedelta + + >>> string_to_delta('7d') + datetime.timedelta(7) + >>> string_to_delta('60s') + datetime.timedelta(0, 60) + >>> string_to_delta('60m') + datetime.timedelta(0, 3600) + >>> string_to_delta('24h') + datetime.timedelta(1) + >>> string_to_delta(u'1w') + datetime.timedelta(7) + >>> string_to_delta('5m 30s') + datetime.timedelta(0, 330) + """ + timevalue = datetime.timedelta(0) + regexp = re.compile("\d+[dsmhw]") + + for s in regexp.findall(interval): + try: + suffix = unicode(s[-1]) + value = int(s[:-1]) + if suffix == u"d": + delta = datetime.timedelta(value) + elif suffix == u"s": + delta = datetime.timedelta(0, value) + elif suffix == u"m": + delta = datetime.timedelta(0, 0, 0, 0, value) + elif suffix == u"h": + delta = datetime.timedelta(0, 0, 0, 0, 0, value) + elif suffix == u"w": + delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value) + else: + raise ValueError + except (ValueError, IndexError): + raise ValueError + timevalue += delta + return timevalue + +def print_clients(clients): + def valuetostring(value, keyword): + if type(value) is dbus.Boolean: + return u"Yes" if value else u"No" + if keyword in ("timeout", "interval"): + return milliseconds_to_string(value) + return unicode(value) + + format_string = u' '.join(u'%%-%ds' % + max(len(tablewords[key]), + max(len(valuetostring(client[key], key)) + for client in + clients)) + for key in keywords) + print 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())) + === modified file 'mandos-keygen' --- mandos-keygen 2008-09-05 16:24:33 +0000 +++ mandos-keygen 2009-05-23 05:59:52 +0000 @@ -2,7 +2,8 @@ # # Mandos key generator - create a new OpenPGP key for a Mandos client # -# Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson +# Copyright © 2008,2009 Teddy Hogeborn +# Copyright © 2008,2009 Björn Påhlsson # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,14 +21,14 @@ # Contact the authors at . # -VERSION="1.0" +VERSION="1.0.11" KEYDIR="/etc/keys/mandos" KEYTYPE=DSA KEYLENGTH=2048 SUBKEYTYPE=ELG-E SUBKEYLENGTH=2048 -KEYNAME="`hostname --fqdn`" +KEYNAME="`hostname --fqdn 2>/dev/null || hostname`" KEYEMAIL="" KEYCOMMENT="Mandos client key" KEYEXPIRE=0 @@ -35,9 +36,13 @@ KEYCOMMENT_ORIG="$KEYCOMMENT" mode=keygen +if [ ! -d "$KEYDIR" ]; then + KEYDIR="/etc/mandos/keys" +fi + # Parse options -TEMP=`getopt --options vhd:t:l:n:e:c:x:f \ - --longoptions version,help,password,dir:,type:,length:,subtype:,sublength:,name:,email:,comment:,expire:,force \ +TEMP=`getopt --options vhpF:d:t:l:s:L:n:e:c:x:f \ + --longoptions version,help,password,passfile:,dir:,type:,length:,subtype:,sublength:,name:,email:,comment:,expire:,force \ --name "$0" -- "$@"` help(){ @@ -49,6 +54,7 @@ $basename [ OPTIONS ] Encrypted password creation: $basename { -p | --password } [ --name NAME ] [ --dir DIR] + $basename { -F | --passfile } FILE [ --name NAME ] [ --dir DIR] Key creation options: -v, --version Show program's version number and exit @@ -70,10 +76,14 @@ -x TIME, --expire TIME Key expire time. Default is no expiration. See gpg(1) for syntax. - -f, --force Force overwriting old keys. + -f, --force Force overwriting old key files. Password creation options: - -p, --password Create an encrypted password using the keys in + -p, --password Create an encrypted password using the key in + the key directory. All options other than + --dir and --name are ignored. + -F FILE, --passfile FILE + Encrypt a password from FILE using the key in the key directory. All options other than --dir and --name are ignored. EOF @@ -83,6 +93,7 @@ while :; do case "$1" in -p|--password) mode=password; shift;; + -F|--passfile) mode=password; PASSFILE="$2"; shift 2;; -d|--dir) KEYDIR="$2"; shift 2;; -t|--type) KEYTYPE="$2"; shift 2;; -s|--subtype) SUBKEYTYPE="$2"; shift 2;; @@ -136,7 +147,7 @@ echo "Invalid key length" >&2 exit 1 fi - + if [ -z "$KEYEXPIRE" ]; then echo "Empty key expiration" >&2 exit 1 @@ -148,8 +159,8 @@ [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|*) FORCE=0;; esac - if { [ -e "$SECKEYFILE" ] || [ -e "$PUBKEYFILE" ]; } \ - && [ "$FORCE" -eq 0 ]; then + if [ \( -e "$SECKEYFILE" -o -e "$PUBKEYFILE" \) \ + -a "$FORCE" -eq 0 ]; then echo "Refusing to overwrite old key files; use --force" >&2 exit 1 fi @@ -161,7 +172,7 @@ if [ -n "$KEYEMAIL" ]; then KEYEMAILLINE="Name-Email: $KEYEMAIL" fi - + # Create temporary gpg batch file BATCHFILE="`mktemp -t mandos-keygen-batch.XXXXXXXXXX`" fi @@ -184,7 +195,7 @@ stty echo; \ " EXIT -umask 027 +umask 077 if [ "$mode" = keygen ]; then # Create batch file for GnuPG @@ -231,7 +242,7 @@ FILECOMMENT="$FILECOMMENT <$KEYEMAIL>" fi - # Export keys from key rings to key files + # Export key from key rings to key files gpg --quiet --batch --no-tty --no-options --enable-dsa2 \ --homedir "$RINGDIR" --armor --export-options export-minimal \ --comment "$FILECOMMENT" --output "$SECKEYFILE" \ @@ -242,7 +253,7 @@ fi if [ "$mode" = password ]; then - # Import keys into temporary key rings + # Import key into temporary key rings gpg --quiet --batch --no-tty --no-options --enable-dsa2 \ --homedir "$RINGDIR" --trust-model always --armor \ --import "$SECKEYFILE" @@ -254,29 +265,45 @@ FINGERPRINT="`gpg --quiet --batch --no-tty --no-options \ --enable-dsa2 --homedir \"$RINGDIR\" --trust-model always \ --fingerprint --with-colons \ - | sed -n -e '/^fpr:/{s/^fpr:.*:\\([0-9A-Z]*\\):\$/\\1/p;q}'`" + | sed --quiet \ + --expression='/^fpr:/{s/^fpr:.*:\\([0-9A-Z]*\\):\$/\\1/p;q}'`" test -n "$FINGERPRINT" FILECOMMENT="Encrypted password for a Mandos client" - stty -echo - echo -n "Enter passphrase: " >&2 - sed -e '1q' \ - | gpg --quiet --batch --no-tty --no-options --enable-dsa2 \ + if [ -n "$PASSFILE" ]; then + cat "$PASSFILE" + else + stty -echo + echo -n "Enter passphrase: " >&2 + first="$(head --lines=1 | tr --delete '\n')" + echo -n -e "\nRepeat passphrase: " >&2 + second="$(head --lines=1 | tr --delete '\n')" + echo >&2 + stty echo + if [ "$first" != "$second" ]; then + echo -e "Passphrase mismatch" >&2 + touch "$RINGDIR"/mismatch + else + echo -n "$first" + fi + fi | gpg --quiet --batch --no-tty --no-options --enable-dsa2 \ --homedir "$RINGDIR" --trust-model always --armor --encrypt \ - --recipient "$FINGERPRINT" --comment "$FILECOMMENT" \ + --sign --recipient "$FINGERPRINT" --comment "$FILECOMMENT" \ > "$SECFILE" - echo >&2 - stty echo + if [ -e "$RINGDIR"/mismatch ]; then + rm --force "$RINGDIR"/mismatch + exit 1 + fi cat <<-EOF [$KEYNAME] host = $KEYNAME fingerprint = $FINGERPRINT secret = -EOF - sed -n -e ' + EOF + sed --quiet --expression=' /^-----BEGIN PGP MESSAGE-----$/,/^-----END PGP MESSAGE-----$/{ /^$/,${ # Remove 24-bit Radix-64 checksum === modified file 'mandos-keygen.xml' --- mandos-keygen.xml 2008-09-03 19:13:50 +0000 +++ mandos-keygen.xml 2009-01-04 21:54:55 +0000 @@ -1,9 +1,10 @@ - + + +%common; ]> @@ -11,7 +12,7 @@ Mandos Manual Mandos - &VERSION; + &version; &TIMESTAMP; @@ -31,12 +32,13 @@ 2008 + 2009 Teddy Hogeborn Björn Påhlsson - + &COMMANDNAME; 8 @@ -48,7 +50,7 @@ Generate key and password for Mandos client and server. - + &COMMANDNAME; @@ -122,6 +124,10 @@ + + + FILE @@ -159,7 +165,7 @@ &COMMANDNAME; is a program to generate the OpenPGP key used by - password-request + mandos-client 8mandos. The key is normally written to /etc/mandos for later installation into the initrd image, but this, and most other things, can be changed @@ -167,8 +173,9 @@ This program can also be used with the - option to generate a ready-made - section for clients.conf (see + or + options to generate a ready-made section for + clients.conf (see mandos-clients.conf 5). @@ -197,7 +204,7 @@ - + @@ -210,7 +217,7 @@ - + @@ -222,7 +229,7 @@ - + @@ -234,7 +241,7 @@ - + @@ -247,7 +254,7 @@ - + @@ -259,7 +266,7 @@ - + @@ -271,7 +278,7 @@ - + @@ -284,7 +291,7 @@ - + @@ -298,7 +305,7 @@ - + @@ -326,9 +333,21 @@ + + + + + + The same as , but read from + FILE, not the terminal. + + + - + OVERVIEW @@ -338,7 +357,7 @@ clients.conf on the server. - + EXIT STATUS @@ -364,7 +383,7 @@ - + FILES Use the option to change where @@ -401,13 +420,13 @@ - + - + EXAMPLE @@ -454,7 +473,7 @@ - + SECURITY @@ -469,7 +488,7 @@ 8. - + SEE ALSO @@ -479,7 +498,7 @@ 5, mandos 8, - password-request + mandos-client 8mandos === modified file 'mandos-options.xml' --- mandos-options.xml 2008-09-03 05:04:40 +0000 +++ mandos-options.xml 2009-02-13 05:38:21 +0000 @@ -6,7 +6,7 @@ This file is used by both mandos(8) and mandos.conf(5), since these options can be used both on the command line and in the config file. -It is also used for some texts by password-request(8mandos). +It is also used for some texts by mandos-client(8mandos). -->
@@ -26,10 +26,12 @@ specified IPv6 address. If a link-local address is specified, an interface should be set, since a link-local address is only valid on a single interface. By default, the server will listen to all - available addresses. If set, this must be an IPv6 address; an - IPv4 address can only be specified using IPv4-mapped IPv6 address - syntax: ::FFFF:192.0.2.3. + available addresses. If set, this must normally be an IPv6 + address; an IPv4 address can only be specified using IPv4-mapped + IPv6 address syntax: ::FFFF:192.0.2.3. (Only if IPv6 usage is + disabled (see below) must this be an IPv4 + address.) @@ -58,12 +60,30 @@ Zeroconf service name. The default is Mandos. This only needs to be - changed this if it, for some reason, is necessary to run more than - one server on the same host, which would not + changed if for some reason is would be necessary to run more than + one server on the same host. This would not normally be useful. If there are name collisions on the same network, the newer server will automatically rename itself to Mandos #2, and so on; therefore, this option is not needed in that case. + + This option controls whether the server will provide a D-Bus + system bus interface. The default is to provide such an + interface. + + + + This option controls whether the server will use IPv6 sockets and + addresses. The default is to use IPv6. This option should + never normally be turned off, even in + IPv4-only environments. This is because + mandos-client + 8mandos will normally use + IPv6 link-local addresses, and will not be able to find or connect + to the server if this option is turned off. Only + advanced users should consider changing this option. + +
=== modified file 'mandos.conf' --- mandos.conf 2008-08-18 23:55:28 +0000 +++ mandos.conf 2009-02-13 05:38:21 +0000 @@ -36,3 +36,9 @@ # If there are name collisions on the same *network*, the server will # rename itself to "Mandos #2", etc. ;servicename = Mandos + +# Whether to provide a D-Bus system bus interface or not +;use_dbus = True + +# Whether to use IPv6. (Changing this is NOT recommended.) +;use_ipv6 = True === modified file 'mandos.conf.xml' --- mandos.conf.xml 2008-09-04 13:36:59 +0000 +++ mandos.conf.xml 2009-02-25 01:14:29 +0000 @@ -1,10 +1,11 @@ /etc/mandos/mandos.conf"> - + + +%common; ]> @@ -12,7 +13,7 @@ Mandos Manual Mandos - &VERSION; + &version; &TIMESTAMP; @@ -32,12 +33,13 @@ 2008 + 2009 Teddy Hogeborn Björn Påhlsson - + &CONFNAME; 5 @@ -49,11 +51,11 @@ Configuration file for the Mandos server - + &CONFPATH; - + DESCRIPTION @@ -71,7 +73,7 @@ # or ; are ignored and may be used to provide comments. - + OPTIONS @@ -84,7 +86,7 @@ - + @@ -92,7 +94,7 @@ - + @@ -100,7 +102,7 @@ - + - + @@ -119,7 +121,7 @@ - + @@ -129,6 +131,28 @@ + + + + + + + + + + + + + + @@ -166,11 +190,13 @@ [DEFAULT] # A configuration example interface = eth0 -address = 2001:db8:f983:bd0b:30de:ae4a:71f2:f672 +address = fe80::aede:48ff:fe71:f6f2 port = 1025 debug = true priority = SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP servicename = Daena +use_dbus = False +use_ipv6 = True
@@ -185,7 +211,7 @@ mandos-clients.conf 5 - + @@ -211,7 +237,7 @@ The clients use IPv6 link-local addresses, which are immediately usable since a link-local addresses is - automatically assigned to a network interfaces when it + automatically assigned to a network interface when it is brought up. === added file 'mandos.lsm' --- mandos.lsm 1970-01-01 00:00:00 +0000 +++ mandos.lsm 2009-05-23 05:59:52 +0000 @@ -0,0 +1,22 @@ +Begin4 +Title: Mandos +Version: 1.0.11 +Entered-date: 2009-05-23 +Description: The Mandos system allows computers to have encrypted +root file systems and at the same time be capable of remote and/or +unattended reboots. +Keywords: boot, encryption, luks, cryptsetup, network, openpgp, +tls, dm-crypt +Author: teddy@fukt.bsnet.se (Teddy Hogeborn), + belorn@fukt.bsnet.se (Björn Påhlsson) +Maintained-by: teddy@fukt.bsnet.se (Teddy Hogeborn), + belorn@fukt.bsnet.se (Björn Påhlsson) +Primary-site: http://www.fukt.bsnet.se/mandos + 99K mandos_1.0.11.orig.tar.gz +Alternate-site: ftp://ftp.fukt.bsnet.se/pub/mandos + 99K mandos_1.0.11.orig.tar.gz +Platforms: Requires GCC, GNU libC, Avahi, GnuPG, Python 2.5, and +various other libraries. While made for Debian GNU/Linux, it is +probably portable to other distributions, but not other Unixes. +Copying-policy: GNU General Public License version 3.0 or later +End === modified file 'mandos.xml' --- mandos.xml 2008-09-05 16:24:33 +0000 +++ mandos.xml 2009-02-25 01:47:45 +0000 @@ -1,17 +1,18 @@ - + + +%common; ]> - + Mandos Manual Mandos - &VERSION; + &version; &TIMESTAMP; @@ -31,12 +32,13 @@ 2008 + 2009 Teddy Hogeborn Björn Påhlsson - + &COMMANDNAME; 8 @@ -48,7 +50,7 @@ Gives encrypted passwords to authenticated Mandos clients - + &COMMANDNAME; @@ -83,6 +85,10 @@ DIRECTORY + + + + &COMMANDNAME; @@ -100,7 +106,7 @@ - + DESCRIPTION @@ -186,7 +192,7 @@ - + @@ -194,7 +200,7 @@ - + @@ -203,7 +209,7 @@ xpointer="servicename"/> - + @@ -218,7 +224,7 @@ - + @@ -227,9 +233,26 @@ + + + + + + + See also . + + + + + + + + + + - + OVERVIEW @@ -239,7 +262,7 @@ RAM disk environment. - + NETWORK PROTOCOL @@ -297,7 +320,7 @@ - + CHECKING @@ -311,7 +334,7 @@ 5. - + LOGGING @@ -321,7 +344,17 @@ and also show them on the console. - + + + D-BUS INTERFACE + + The server will by default provide a D-Bus system bus interface. + This interface will only be accessible by the root user or a + Mandos-specific user, if such a user exists. + + + + EXIT STATUS @@ -329,7 +362,7 @@ critical error is encountered. - + ENVIRONMENT @@ -349,8 +382,8 @@ - - + + FILES Use the option to change where @@ -420,7 +453,7 @@ 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 @@ -434,7 +467,7 @@ Debug mode is conflated with running in the foreground. - The console log messages does not show a time stamp. + The console log messages do not show a time stamp. This server does not check the expire time of clients’ OpenPGP @@ -479,10 +512,10 @@ - + SECURITY - + SERVER Running this &COMMANDNAME; server program @@ -491,7 +524,7 @@ soon after startup. - + CLIENTS The server only gives out its stored data to clients which @@ -504,7 +537,7 @@ mandos-clients.conf 5) must be made non-readable by anyone - except the user running the server. + except the user starting the server (usually root). As detailed in , the status of all @@ -529,12 +562,12 @@ For more details on client-side security, see - password-request + mandos-client 8mandos. - + SEE ALSO @@ -543,7 +576,7 @@ 5, mandos.conf 5, - password-request + mandos-client 8mandos, sh1 === modified file 'overview.xml' --- overview.xml 2008-09-01 08:29:23 +0000 +++ overview.xml 2008-09-13 15:36:18 +0000 @@ -6,10 +6,12 @@ encrypted root file systems and at the same time be capable of remote and/or unattended reboots. The computers run a small client program in the initial RAM disk environment which - will communicate with a server over a network. The clients are - identified by the server using a OpenPGP key; each client has one - unique to it. The server sends the clients an encrypted password. - The encrypted password is decrypted by the clients using the same - OpenPGP key, and the password is then used to unlock the root file - system, whereupon the computers can continue booting normally. + will communicate with a server over a network. All network + communication is encrypted using TLS. The + clients are identified by the server using an OpenPGP key; each + client has one unique to it. The server sends the clients an + encrypted password. The encrypted password is decrypted by the + clients using the same OpenPGP key, and the password is then used to + unlock the root file system, whereupon the computers can continue + booting normally. === modified file 'plugin-runner.c' --- plugin-runner.c 2008-09-04 19:59:05 +0000 +++ plugin-runner.c 2009-09-05 17:39:34 +0000 @@ -1,8 +1,9 @@ -/* -*- coding: utf-8 -*- */ +/* -*- coding: utf-8; mode: c; mode: orgtbl -*- */ /* * Mandos plugin runner - Run Mandos plugins * - * Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson + * Copyright © 2008,2009 Teddy Hogeborn + * Copyright © 2008,2009 Björn Påhlsson * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -27,8 +28,8 @@ #include /* malloc(), exit(), EXIT_FAILURE, EXIT_SUCCESS, realloc() */ #include /* bool, true, false */ -#include /* perror, popen(), fileno(), - fprintf(), stderr, STDOUT_FILENO */ +#include /* perror, fileno(), fprintf(), + stderr, STDOUT_FILENO */ #include /* DIR, opendir(), stat(), struct stat, waitpid(), WIFEXITED(), WEXITSTATUS(), wait(), pid_t, @@ -37,7 +38,8 @@ #include /* fd_set, select(), FD_ZERO(), FD_SET(), FD_ISSET(), FD_CLR */ #include /* wait(), waitpid(), WIFEXITED(), - WEXITSTATUS() */ + WEXITSTATUS(), WTERMSIG(), + WCOREDUMP() */ #include /* struct stat, stat(), S_ISREG() */ #include /* and, or, not */ #include /* DIR, struct dirent, opendir(), @@ -46,12 +48,13 @@ fcntl(), setuid(), setgid(), F_GETFD, F_SETFD, FD_CLOEXEC, access(), pipe(), fork(), close() - dup2, STDOUT_FILENO, _exit(), + dup2(), STDOUT_FILENO, _exit(), execv(), write(), read(), close() */ #include /* fcntl(), F_GETFD, F_SETFD, FD_CLOEXEC */ -#include /* strsep, strlen(), asprintf() */ +#include /* strsep, strlen(), asprintf(), + strsignal() */ #include /* errno */ #include /* struct argp_option, struct argp_state, struct argp, @@ -61,19 +64,19 @@ #include /* struct sigaction, sigemptyset(), sigaddset(), sigaction(), sigprocmask(), SIG_BLOCK, SIGCHLD, - SIG_UNBLOCK, kill() */ + SIG_UNBLOCK, kill(), sig_atomic_t + */ #include /* errno, EBADF */ +#include /* intmax_t, PRIdMAX, strtoimax() */ #define BUFFER_SIZE 256 #define PDIR "/lib/mandos/plugins.d" #define AFILE "/conf/conf.d/mandos/plugin-runner.conf" -const char *argp_program_version = "plugin-runner 1.0"; +const char *argp_program_version = "plugin-runner " VERSION; const char *argp_program_bug_address = ""; -struct plugin; - typedef struct plugin{ char *name; /* can be NULL or any plugin name */ char **argv; @@ -81,7 +84,7 @@ char **environ; int envc; bool disabled; - + /* Variables used for running processes*/ pid_t pid; int fd; @@ -89,8 +92,8 @@ size_t buffer_size; size_t buffer_length; bool eof; - volatile bool completed; - volatile int status; + volatile sig_atomic_t completed; + int status; struct plugin *next; } plugin; @@ -100,32 +103,33 @@ or if none is found, creates a new one */ static plugin *getplugin(char *name){ /* Check for exiting plugin with that name */ - for (plugin *p = plugin_list; p != NULL; p = p->next){ - if ((p->name == name) - or (p->name and name and (strcmp(p->name, name) == 0))){ + for(plugin *p = plugin_list; p != NULL; p = p->next){ + if((p->name == name) + or (p->name and name and (strcmp(p->name, name) == 0))){ return p; } } /* Create a new plugin */ plugin *new_plugin = malloc(sizeof(plugin)); - if (new_plugin == NULL){ + if(new_plugin == NULL){ return NULL; } char *copy_name = NULL; if(name != NULL){ copy_name = strdup(name); if(copy_name == NULL){ + free(new_plugin); return NULL; } } - *new_plugin = (plugin) { .name = copy_name, - .argc = 1, - .disabled = false, - .next = plugin_list }; + *new_plugin = (plugin){ .name = copy_name, + .argc = 1, + .disabled = false, + .next = plugin_list }; new_plugin->argv = malloc(sizeof(char *) * 2); - if (new_plugin->argv == NULL){ + if(new_plugin->argv == NULL){ free(copy_name); free(new_plugin); return NULL; @@ -206,10 +210,9 @@ /* * Based on the example in the GNU LibC manual chapter 13.13 "File * Descriptor Flags". - * *Note File Descriptor Flags:(libc)Descriptor Flags. + | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] | */ -static int set_cloexec_flag(int fd) -{ +static int set_cloexec_flag(int fd){ int ret = fcntl(fd, F_GETFD, 0); /* If reading the flags failed, return error indication now. */ if(ret < 0){ @@ -222,7 +225,8 @@ /* Mark processes as completed when they exit, and save their exit status. */ -void handle_sigchld(__attribute__((unused)) int sig){ +static void handle_sigchld(__attribute__((unused)) int sig){ + int old_errno = errno; while(true){ plugin *proc = plugin_list; int status; @@ -232,11 +236,11 @@ break; } if(pid == -1){ - if (errno != ECHILD){ - perror("waitpid"); + if(errno == ECHILD){ + /* No child processes */ + break; } - /* No child processes */ - break; + perror("waitpid"); } /* A child exited, find it in process_list */ @@ -248,16 +252,14 @@ continue; } proc->status = status; - proc->completed = true; + proc->completed = 1; } + errno = old_errno; } /* Prints out a password to stdout */ -bool print_out_password(const char *buffer, size_t length){ +static bool print_out_password(const char *buffer, size_t length){ ssize_t ret; - if(length>0 and buffer[length-1] == '\n'){ - length--; - } for(size_t written = 0; written < length; written += (size_t)ret){ ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buffer + written, length - written)); @@ -280,7 +282,7 @@ } free(plugin_node->environ); free(plugin_node->buffer); - + /* Removes the plugin from the singly-linked list */ if(plugin_node == plugin_list){ /* First one - simple */ @@ -314,6 +316,7 @@ struct stat st; fd_set rfds_all; int ret, maxfd = 0; + ssize_t sret; uid_t uid = 65534; gid_t gid = 65534; bool debug = false; @@ -376,17 +379,19 @@ { .name = NULL } }; - error_t parse_opt (int key, char *arg, __attribute__((unused)) - struct argp_state *state) { - switch (key) { + error_t parse_opt(int key, char *arg, __attribute__((unused)) + struct argp_state *state){ + switch(key){ + char *tmp; + intmax_t tmpmax; case 'g': /* --global-options */ - if (arg != NULL){ - char *p; - while((p = strsep(&arg, ",")) != NULL){ - if(p[0] == '\0'){ + if(arg != NULL){ + char *plugin_option; + while((plugin_option = strsep(&arg, ",")) != NULL){ + if(plugin_option[0] == '\0'){ continue; } - if(not add_argument(getplugin(NULL), p)){ + if(not add_argument(getplugin(NULL), plugin_option)){ perror("add_argument"); return ARGP_ERR_UNKNOWN; } @@ -397,32 +402,19 @@ if(arg == NULL){ break; } - { - char *envdef = strdup(arg); - if(envdef == NULL){ - break; - } - if(not add_environment(getplugin(NULL), envdef, true)){ - perror("add_environment"); - } + if(not add_environment(getplugin(NULL), arg, true)){ + perror("add_environment"); } break; case 'o': /* --options-for */ - if (arg != NULL){ - char *p_name = strsep(&arg, ":"); - if(p_name[0] == '\0' or arg == NULL){ - break; - } - char *opt = strsep(&arg, ":"); - if(opt[0] == '\0' or opt == NULL){ - break; - } - char *p; - while((p = strsep(&opt, ",")) != NULL){ - if(p[0] == '\0'){ - continue; - } - if(not add_argument(getplugin(p_name), p)){ + 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; } @@ -438,18 +430,14 @@ if(envdef == NULL){ break; } - char *p_name = strndup(arg, (size_t) (envdef-arg)); - if(p_name == NULL){ - break; - } - envdef++; - if(not add_environment(getplugin(p_name), envdef, true)){ + *envdef = '\0'; + if(not add_environment(getplugin(arg), envdef+1, true)){ perror("add_environment"); } } break; case 'd': /* --disable */ - if (arg != NULL){ + if(arg != NULL){ plugin *p = getplugin(arg); if(p == NULL){ return ARGP_ERR_UNKNOWN; @@ -458,7 +446,7 @@ } break; case 'e': /* --enable */ - if (arg != NULL){ + if(arg != NULL){ plugin *p = getplugin(arg); if(p == NULL){ return ARGP_ERR_UNKNOWN; @@ -467,25 +455,51 @@ } break; case 128: /* --plugin-dir */ + free(plugindir); plugindir = strdup(arg); if(plugindir == NULL){ perror("strdup"); - } + } break; case 129: /* --config-file */ /* This is already done by parse_opt_config_file() */ break; case 130: /* --userid */ - uid = (uid_t)strtol(arg, NULL, 10); + 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; + } break; case 131: /* --groupid */ - gid = (gid_t)strtol(arg, NULL, 10); + 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; + } break; case 132: /* --debug */ debug = true; break; +/* + * When adding more options before this line, remember to also add a + * "case" to the "parse_opt_config_file" function below. + */ case ARGP_KEY_ARG: - fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg); + /* Cryptsetup always passes an argument, which is an empty + string if "none" was specified in /etc/crypttab. So if + argument was empty, we ignore it silently. */ + if(arg[0] != '\0'){ + fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg); + } break; case ARGP_KEY_END: break; @@ -497,10 +511,10 @@ /* This option parser is the same as parse_opt() above, except it ignores everything but the --config-file option. */ - error_t parse_opt_config_file (int key, char *arg, - __attribute__((unused)) - struct argp_state *state) { - switch (key) { + error_t parse_opt_config_file(int key, char *arg, + __attribute__((unused)) + struct argp_state *state){ + switch(key){ case 'g': /* --global-options */ case 'G': /* --global-env */ case 'o': /* --options-for */ @@ -510,11 +524,12 @@ case 128: /* --plugin-dir */ break; case 129: /* --config-file */ + free(argfile); argfile = strdup(arg); if(argfile == NULL){ perror("strdup"); } - break; + break; case 130: /* --userid */ case 131: /* --groupid */ case 132: /* --debug */ @@ -532,10 +547,10 @@ .args_doc = "", .doc = "Mandos plugin runner -- Run plugins" }; - /* Parse using the parse_opt_config_file in order to get the custom + /* Parse using parse_opt_config_file() in order to get the custom config file location, if any. */ - ret = argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, NULL); - if (ret == ARGP_ERR_UNKNOWN){ + 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; goto fallback; @@ -545,19 +560,18 @@ argp.parser = parse_opt; /* Open the configfile if available */ - if (argfile == NULL){ + if(argfile == NULL){ conffp = fopen(AFILE, "r"); } else { conffp = fopen(argfile, "r"); - } + } if(conffp != NULL){ char *org_line = NULL; char *p, *arg, *new_arg, *line; size_t size = 0; - ssize_t sret; const char whitespace_delims[] = " \r\t\f\v\n"; const char comment_delim[] = "#"; - + custom_argc = 1; custom_argv = malloc(sizeof(char*) * 2); if(custom_argv == NULL){ @@ -567,7 +581,7 @@ } custom_argv[0] = argv[0]; custom_argv[1] = NULL; - + /* for each line in the config file, strip whitespace and ignore commented text */ while(true){ @@ -575,7 +589,7 @@ if(sret == -1){ break; } - + line = org_line; arg = strsep(&line, comment_delim); while((p = strsep(&arg, whitespace_delims)) != NULL){ @@ -600,14 +614,22 @@ goto fallback; } custom_argv[custom_argc-1] = new_arg; - custom_argv[custom_argc] = NULL; + custom_argv[custom_argc] = NULL; } } + do{ + ret = fclose(conffp); + }while(ret == EOF and errno == EINTR); + if(ret == EOF){ + perror("fclose"); + exitstatus = EXIT_FAILURE; + goto fallback; + } free(org_line); } else { /* Check for harmful errors and go to fallback. Other errors might not affect opening plugins */ - if (errno == EMFILE or errno == ENFILE or errno == ENOMEM){ + if(errno == EMFILE or errno == ENFILE or errno == ENOMEM){ perror("fopen"); exitstatus = EXIT_FAILURE; goto fallback; @@ -616,9 +638,9 @@ /* If there was any arguments from configuration file, pass them to parser as command arguments */ if(custom_argv != NULL){ - ret = argp_parse (&argp, custom_argc, custom_argv, ARGP_IN_ORDER, - 0, NULL); - if (ret == ARGP_ERR_UNKNOWN){ + 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; goto fallback; @@ -627,8 +649,8 @@ /* 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){ + 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; goto fallback; @@ -641,7 +663,7 @@ for(char **a = p->argv; *a != NULL; a++){ fprintf(stderr, "\tArg: %s\n", *a); } - fprintf(stderr, "...and %u environment variables\n", p->envc); + fprintf(stderr, "...and %d environment variables\n", p->envc); for(char **a = p->environ; *a != NULL; a++){ fprintf(stderr, "\t%s\n", *a); } @@ -649,16 +671,16 @@ } /* Strip permissions down to nobody */ + setgid(gid); + if(ret == -1){ + perror("setgid"); + } ret = setuid(uid); - if (ret == -1){ + if(ret == -1){ perror("setuid"); - } - setgid(gid); - if (ret == -1){ - perror("setgid"); } - if (plugindir == NULL){ + if(plugindir == NULL){ dir = opendir(PDIR); } else { dir = opendir(plugindir); @@ -691,7 +713,7 @@ /* All directory entries have been processed */ if(dirst == NULL){ - if (errno == EBADF){ + if(errno == EBADF){ perror("readdir"); exitstatus = EXIT_FAILURE; goto fallback; @@ -709,6 +731,7 @@ const char const *bad_suffixes[] = { "~", "#", ".dpkg-new", ".dpkg-old", + ".dpkg-bak", ".dpkg-divert", NULL }; for(const char **pre = bad_prefixes; *pre != NULL; pre++){ size_t pre_len = strlen(*pre); @@ -743,23 +766,27 @@ continue; } } - + char *filename; - ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name); + if(plugindir == NULL){ + ret = asprintf(&filename, PDIR "/%s", dirst->d_name); + } else { + ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name); + } if(ret < 0){ perror("asprintf"); continue; } ret = stat(filename, &st); - if (ret == -1){ + if(ret == -1){ perror("stat"); free(filename); continue; } - + /* Ignore non-executable files */ - if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){ + if(not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){ if(debug){ fprintf(stderr, "Ignoring plugin dir entry \"%s\"" " with bad type or mode\n", filename); @@ -812,7 +839,7 @@ int pipefd[2]; ret = pipe(pipefd); - if (ret == -1){ + if(ret == -1){ perror("pipe"); exitstatus = EXIT_FAILURE; goto fallback; @@ -831,7 +858,7 @@ goto fallback; } /* Block SIGCHLD until process is safely in process list */ - ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL); + ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL); if(ret < 0){ perror("sigprocmask"); exitstatus = EXIT_FAILURE; @@ -851,12 +878,12 @@ perror("sigaction"); _exit(EXIT_FAILURE); } - ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL); + ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL); if(ret < 0){ perror("sigprocmask"); _exit(EXIT_FAILURE); } - + ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */ if(ret == -1){ perror("dup2"); @@ -885,9 +912,9 @@ close(pipefd[1]); /* Close unused write end of pipe */ free(filename); plugin *new_plugin = getplugin(dirst->d_name); - if (new_plugin == NULL){ + if(new_plugin == NULL){ perror("getplugin"); - ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL); + ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL); if(ret < 0){ perror("sigprocmask"); } @@ -900,7 +927,7 @@ /* Unblock SIGCHLD so signal handler can be run if this process has already completed */ - ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL); + ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL); if(ret < 0){ perror("sigprocmask"); exitstatus = EXIT_FAILURE; @@ -909,15 +936,15 @@ FD_SET(new_plugin->fd, &rfds_all); - if (maxfd < new_plugin->fd){ + if(maxfd < new_plugin->fd){ maxfd = new_plugin->fd; } - } closedir(dir); dir = NULL; - + free_plugin(getplugin(NULL)); + for(plugin *p = plugin_list; p != NULL; p = p->next){ if(p->pid != 0){ break; @@ -928,44 +955,47 @@ free_plugin_list(); } } - + /* Main loop while running plugins exist */ while(plugin_list){ fd_set rfds = rfds_all; int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL); - if (select_ret == -1){ + if(select_ret == -1){ perror("select"); exitstatus = EXIT_FAILURE; goto fallback; } /* OK, now either a process completed, or something can be read from one of them */ - for(plugin *proc = plugin_list; proc != NULL; proc = proc->next){ + for(plugin *proc = plugin_list; proc != NULL;){ /* Is this process completely done? */ - if(proc->eof and proc->completed){ + if(proc->completed and proc->eof){ /* Only accept the plugin output if it exited cleanly */ if(not WIFEXITED(proc->status) or WEXITSTATUS(proc->status) != 0){ /* Bad exit by plugin */ - + if(debug){ if(WIFEXITED(proc->status)){ - fprintf(stderr, "Plugin %u exited with status %d\n", - (unsigned int) (proc->pid), + fprintf(stderr, "Plugin %s [%" PRIdMAX "] exited with" + " status %d\n", proc->name, + (intmax_t) (proc->pid), WEXITSTATUS(proc->status)); - } else if(WIFSIGNALED(proc->status)) { - fprintf(stderr, "Plugin %u killed by signal %d\n", - (unsigned int) (proc->pid), - WTERMSIG(proc->status)); + } else if(WIFSIGNALED(proc->status)){ + fprintf(stderr, "Plugin %s [%" PRIdMAX "] killed by" + " signal %d: %s\n", proc->name, + (intmax_t) (proc->pid), + WTERMSIG(proc->status), + strsignal(WTERMSIG(proc->status))); } else if(WCOREDUMP(proc->status)){ - fprintf(stderr, "Plugin %d dumped core\n", - (unsigned int) (proc->pid)); + fprintf(stderr, "Plugin %s [%" PRIdMAX "] dumped" + " core\n", proc->name, (intmax_t) (proc->pid)); } } /* Remove the plugin */ FD_CLR(proc->fd, &rfds_all); - + /* Block signal while modifying process_list */ ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL); if(ret < 0){ @@ -973,10 +1003,14 @@ exitstatus = EXIT_FAILURE; goto fallback; } + + plugin *next_plugin = proc->next; free_plugin(proc); + proc = next_plugin; + /* We are done modifying process list, so unblock signal */ - ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, - NULL); + ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, + NULL); if(ret < 0){ perror("sigprocmask"); exitstatus = EXIT_FAILURE; @@ -986,11 +1020,12 @@ if(plugin_list == NULL){ break; } + continue; } /* This process exited nicely, so print its buffer */ - + bool bret = print_out_password(proc->buffer, proc->buffer_length); if(not bret){ @@ -1003,13 +1038,14 @@ /* This process has not completed. Does it have any output? */ if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ /* This process had nothing to say at this time */ + proc = proc->next; continue; } /* Before reading, make the process' data buffer large enough */ if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){ proc->buffer = realloc(proc->buffer, proc->buffer_size + (size_t) BUFFER_SIZE); - if (proc->buffer == NULL){ + if(proc->buffer == NULL){ perror("malloc"); exitstatus = EXIT_FAILURE; goto fallback; @@ -1017,22 +1053,23 @@ proc->buffer_size += BUFFER_SIZE; } /* Read from the process */ - ret = read(proc->fd, proc->buffer + proc->buffer_length, - BUFFER_SIZE); - if(ret < 0){ + sret = read(proc->fd, proc->buffer + proc->buffer_length, + BUFFER_SIZE); + if(sret < 0){ /* Read error from this process; ignore the error */ + proc = proc->next; continue; } - if(ret == 0){ + if(sret == 0){ /* got EOF */ proc->eof = true; } else { - proc->buffer_length += (size_t) ret; + proc->buffer_length += (size_t) sret; } } } - - + + fallback: if(plugin_list == NULL or exitstatus != EXIT_SUCCESS){ @@ -1041,7 +1078,13 @@ bool bret; fprintf(stderr, "Going to fallback mode using getpass(3)\n"); char *passwordbuffer = getpass("Password: "); - bret = print_out_password(passwordbuffer, strlen(passwordbuffer)); + size_t len = strlen(passwordbuffer); + /* Strip trailing newline */ + if(len > 0 and passwordbuffer[len-1] == '\n'){ + passwordbuffer[len-1] = '\0'; /* not strictly necessary */ + len--; + } + bret = print_out_password(passwordbuffer, len); if(not bret){ perror("print_out_password"); exitstatus = EXIT_FAILURE; @@ -1054,7 +1097,7 @@ perror("sigaction"); exitstatus = EXIT_FAILURE; } - + if(custom_argv != NULL){ for(char **arg = custom_argv+1; *arg != NULL; arg++){ free(*arg); @@ -1066,7 +1109,7 @@ closedir(dir); } - /* Free the process list and kill the processes */ + /* Kill the processes */ for(plugin *p = plugin_list; p != NULL; p = p->next){ if(p->pid != 0){ close(p->fd); @@ -1085,7 +1128,7 @@ if(errno != ECHILD){ perror("wait"); } - + free_plugin_list(); free(plugindir); === modified file 'plugin-runner.conf' --- plugin-runner.conf 2008-09-05 07:11:24 +0000 +++ plugin-runner.conf 2009-04-17 08:26:17 +0000 @@ -1,9 +1,10 @@ -## This is the configuration file for plugin-runner. It should be -## installed as "/etc/mandos/plugin-runner.conf", which will be copied -## to "/conf/conf.d/mandos/plugin-runner.conf" in the initrd.img file. -## -## The default network interface for password-request(8mandos) is -## "eth0". Uncomment this line and change it if necessary. -## +## This is the configuration file for plugin-runner(8mandos). This +## file should be installed as "/etc/mandos/plugin-runner.conf", and +## will be copied to "/conf/conf.d/mandos/plugin-runner.conf" in the +## initrd.img file. +## +## After editing this file, the initrd image file must be updated for +## the changes to take effect! -#--options-for=password-request:--interface=eth0 +## Example: +#--options-for=mandos-client:--debug === modified file 'plugin-runner.xml' --- plugin-runner.xml 2008-09-05 18:19:52 +0000 +++ plugin-runner.xml 2009-01-18 06:41:57 +0000 @@ -1,9 +1,10 @@ - + + +%common; ]> @@ -11,7 +12,7 @@ Mandos Manual Mandos - &VERSION; + &version; &TIMESTAMP; @@ -31,12 +32,13 @@ 2008 + 2009 Teddy Hogeborn Björn Påhlsson - + &COMMANDNAME; 8mandos @@ -48,16 +50,16 @@ Run Mandos plugins, pass data from first to succeed. - + &COMMANDNAME; @@ -170,10 +172,10 @@ @@ -247,7 +249,7 @@ - + @@ -261,7 +263,7 @@ - + @@ -276,7 +278,7 @@ - + @@ -289,7 +291,7 @@ - + @@ -302,7 +304,7 @@ - + @@ -365,7 +367,7 @@ - + @@ -377,7 +379,7 @@ - + OVERVIEW @@ -403,7 +405,7 @@ code will make this plugin-runner output the password from that plugin, stop any other plugins, and exit. - + WRITING PLUGINS @@ -416,6 +418,11 @@ console. + If the password is a single-line, manually entered passprase, + a final trailing newline character should + not be printed. + + The plugin will run in the initial RAM disk environment, so care must be taken not to depend on any files or running services not available there. @@ -566,13 +573,13 @@ Run plugins from a different directory, read a different configuration file, and add two options to the - password-request + mandos-client 8mandos plugin: -&COMMANDNAME; --config-file=/etc/mandos/plugin-runner.conf --plugin-dir /usr/lib/mandos/plugins.d --options-for=password-request:--pubkey=/etc/keys/mandos/pubkey.txt,--seckey=/etc/keys/mandos/seckey.txt +cd /etc/keys/mandos; &COMMANDNAME; --config-file=/etc/mandos/plugin-runner.conf --plugin-dir /usr/lib/mandos/plugins.d --options-for=mandos-client:--pubkey=pubkey.txt,--seckey=seckey.txt @@ -620,7 +627,7 @@ 8, password-prompt 8mandos, - password-request + mandos-client 8mandos === added file 'plugins.d/askpass-fifo.c' --- plugins.d/askpass-fifo.c 1970-01-01 00:00:00 +0000 +++ plugins.d/askpass-fifo.c 2009-08-30 03:10:29 +0000 @@ -0,0 +1,105 @@ +/* -*- coding: utf-8 -*- */ +/* + * Askpass-FIFO - Read a password from a FIFO and output it + * + * Copyright © 2008,2009 Teddy Hogeborn + * Copyright © 2008,2009 Björn Påhlsson + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * 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 and + * . + */ + +#define _GNU_SOURCE /* TEMP_FAILURE_RETRY() */ +#include /* ssize_t */ +#include /* mkfifo(), S_IRUSR, S_IWUSR */ +#include /* and */ +#include /* errno, EEXIST */ +#include /* perror() */ +#include /* EXIT_FAILURE, NULL, size_t, free(), + realloc(), EXIT_SUCCESS */ +#include /* open(), O_RDONLY */ +#include /* read(), close(), write(), + STDOUT_FILENO */ + + +int main(__attribute__((unused))int argc, + __attribute__((unused))char **argv){ + int ret = 0; + ssize_t sret; + + /* Create FIFO */ + const char passfifo[] = "/lib/cryptsetup/passfifo"; + ret = (int)TEMP_FAILURE_RETRY(mkfifo(passfifo, S_IRUSR | S_IWUSR)); + if(ret == -1 and errno != EEXIST){ + perror("mkfifo"); + return EXIT_FAILURE; + } + + /* Open FIFO */ + int fifo_fd = (int)TEMP_FAILURE_RETRY(open(passfifo, O_RDONLY)); + if(fifo_fd == -1){ + perror("open"); + return EXIT_FAILURE; + } + + /* Read from FIFO */ + char *buf = NULL; + size_t buf_len = 0; + { + size_t buf_allocated = 0; + const size_t blocksize = 1024; + do{ + if(buf_len + blocksize > buf_allocated){ + char *tmp = realloc(buf, buf_allocated + blocksize); + if(tmp == NULL){ + perror("realloc"); + free(buf); + return EXIT_FAILURE; + } + buf = tmp; + buf_allocated += blocksize; + } + sret = TEMP_FAILURE_RETRY(read(fifo_fd, buf + buf_len, + buf_allocated - buf_len)); + if(sret == -1){ + perror("read"); + free(buf); + return EXIT_FAILURE; + } + buf_len += (size_t)sret; + }while(sret != 0); + } + + /* Close FIFO */ + TEMP_FAILURE_RETRY(close(fifo_fd)); + + /* Print password to stdout */ + size_t written = 0; + while(written < buf_len){ + sret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buf + written, + buf_len - written)); + if(sret == -1){ + perror("write"); + free(buf); + return EXIT_FAILURE; + } + written += (size_t)sret; + } + free(buf); + + return EXIT_SUCCESS; +} === added file 'plugins.d/askpass-fifo.xml' --- plugins.d/askpass-fifo.xml 1970-01-01 00:00:00 +0000 +++ plugins.d/askpass-fifo.xml 2009-01-04 21:54:55 +0000 @@ -0,0 +1,162 @@ + + + + +%common; +]> + + + + Mandos Manual + + Mandos + &version; + &TIMESTAMP; + + + Björn + Påhlsson +
+ belorn@fukt.bsnet.se +
+
+ + Teddy + Hogeborn +
+ teddy@fukt.bsnet.se +
+
+
+ + 2008 + 2009 + Teddy Hogeborn + Björn Påhlsson + + +
+ + + &COMMANDNAME; + 8mandos + + + + &COMMANDNAME; + Mandos plugin to get a password from a + FIFO. + + + + + &COMMANDNAME; + + + + + DESCRIPTION + + This program reads a password from a FIFO and + outputs it to standard output. + + + This program is not very useful on its own. This program is + really meant to run as a plugin in the Mandos client-side system, where it is used as a + fallback and alternative to retrieving passwords from a + Mandos server. + + + This program is meant to be imitate a feature of the + askpass program, so that programs written to + interface with it can keep working under the + Mandos system. + + + + + OPTIONS + + This program takes no options. + + + + + EXIT STATUS + + If exit status is 0, the output from the program is the password + as it was read. Otherwise, if exit status is other than 0, the + program was interrupted or encountered an error, and any output + so far could be corrupt and/or truncated, and should therefore + be ignored. + + + + + FILES + + + /lib/cryptsetup/passfifo + + + This is the FIFO where this program + will read the password. If it does not exist, it will be + created. + + + + + + + + EXAMPLE + + Note that normally, this program will not be invoked directly, + but instead started by the Mandos plugin-runner8mandos + . + + + + This program takes no options. + + + &COMMANDNAME; + + + + + + SECURITY + + The only thing that could be considered worthy of note is + this: This program is meant to be run by + plugin-runner8mandos, and will, when run + standalone, outside, in a normal environment, immediately output + on its standard output any presumably secret password it just + received. Therefore, when running this program standalone + (which should never normally be done), take care not to type in + any real secret password by force of habit, since it would then + immediately be shown as output. + + + + + SEE ALSO + + fifo + 7, + plugin-runner + 8mandos + + +
+ + + + + === renamed file 'plugins.d/password-request.c' => 'plugins.d/mandos-client.c' --- plugins.d/password-request.c 2008-09-03 19:04:05 +0000 +++ plugins.d/mandos-client.c 2009-09-05 01:46:42 +0000 @@ -1,6 +1,6 @@ /* -*- coding: utf-8 -*- */ /* - * Mandos client - get and decrypt data from a Mandos server + * Mandos-client - get and decrypt data from a Mandos server * * This program is partly derived from an example program for an Avahi * service browser, downloaded from @@ -9,7 +9,8 @@ * "browse_callback", and parts of "main". * * Everything else is - * Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson + * Copyright © 2008,2009 Teddy Hogeborn + * Copyright © 2008,2009 Björn Påhlsson * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -29,48 +30,62 @@ */ /* Needed by GPGME, specifically gpgme_data_seek() */ +#ifndef _LARGEFILE_SOURCE #define _LARGEFILE_SOURCE +#endif +#ifndef _FILE_OFFSET_BITS #define _FILE_OFFSET_BITS 64 +#endif #define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), asprintf() */ #include /* fprintf(), stderr, fwrite(), - stdout, ferror() */ + stdout, ferror(), remove() */ #include /* uint16_t, uint32_t */ #include /* NULL, size_t, ssize_t */ #include /* free(), EXIT_SUCCESS, EXIT_FAILURE, - srand() */ -#include /* bool, true */ + srand(), strtof() */ +#include /* bool, false, true */ #include /* memset(), strcmp(), strlen(), strerror(), asprintf(), strcpy() */ -#include /* ioctl */ +#include /* ioctl */ #include /* socket(), inet_pton(), sockaddr, sockaddr_in6, PF_INET6, - SOCK_STREAM, INET6_ADDRSTRLEN, - uid_t, gid_t, open(), opendir(), DIR */ + SOCK_STREAM, uid_t, gid_t, open(), + opendir(), DIR */ #include /* open() */ #include /* socket(), struct sockaddr_in6, - struct in6_addr, inet_pton(), - connect() */ + inet_pton(), connect() */ #include /* open() */ -#include /* opendir(), struct dirent, readdir() */ -#include /* PRIu16 */ +#include /* opendir(), struct dirent, readdir() + */ +#include /* PRIu16, PRIdMAX, intmax_t, + strtoimax() */ #include /* assert() */ #include /* perror(), errno */ -#include /* time() */ +#include /* nanosleep(), time() */ #include /* ioctl, ifreq, SIOCGIFFLAGS, IFF_UP, SIOCSIFFLAGS, if_indextoname(), if_nametoindex(), IF_NAMESIZE */ -#include +#include /* IN6_IS_ADDR_LINKLOCAL, + INET_ADDRSTRLEN, INET6_ADDRSTRLEN + */ #include /* close(), SEEK_SET, off_t, write(), getuid(), getgid(), setuid(), setgid() */ #include /* inet_pton(), htons */ -#include /* not, and */ +#include /* not, or, and */ #include /* struct argp_option, error_t, struct argp_state, struct argp, argp_parse(), ARGP_KEY_ARG, ARGP_KEY_END, ARGP_ERR_UNKNOWN */ +#include /* sigemptyset(), sigaddset(), + sigaction(), SIGTERM, sig_atomic_t, + raise() */ + +#ifdef __linux__ +#include /* klogctl() */ +#endif /* __linux__ */ /* Avahi */ /* All Avahi types, constants and functions @@ -89,7 +104,8 @@ gnutls_* init_gnutls_session(), GNUTLS_* */ -#include /* gnutls_certificate_set_openpgp_key_file(), +#include + /* gnutls_certificate_set_openpgp_key_file(), GNUTLS_OPENPGP_FMT_BASE64 */ /* GPGME */ @@ -101,17 +117,13 @@ #define BUFFER_SIZE 256 -/* - #define PATHDIR "/conf/conf.d/mandos" -*/ - #define PATHDIR "/conf/conf.d/mandos" #define SECKEY "seckey.txt" -#define PUBKEY "pupkey.txt" +#define PUBKEY "pubkey.txt" bool debug = false; static const char mandos_protocol_version[] = "1"; -const char *argp_program_version = "password-request 1.0"; +const char *argp_program_version = "mandos-client " VERSION; const char *argp_program_bug_address = ""; /* Used for passing in values through the Avahi callback functions */ @@ -125,16 +137,21 @@ gpgme_ctx_t ctx; } mandos_context; +/* global context so signal handler can reach it*/ +mandos_context mc = { .simple_poll = NULL, .server = NULL, + .dh_bits = 1024, .priority = "SECURE256" + ":!CTYPE-X.509:+CTYPE-OPENPGP" }; + /* - * Make room in "buffer" for at least BUFFER_SIZE additional bytes. - * "buffer_capacity" is how much is currently allocated, + * Make additional room in "buffer" for at least BUFFER_SIZE more + * bytes. "buffer_capacity" is how much is currently allocated, * "buffer_length" is how much is already used. */ -size_t adjustbuffer(char **buffer, size_t buffer_length, +size_t incbuffer(char **buffer, size_t buffer_length, size_t buffer_capacity){ - if (buffer_length + BUFFER_SIZE > buffer_capacity){ + if(buffer_length + BUFFER_SIZE > buffer_capacity){ *buffer = realloc(*buffer, buffer_capacity + BUFFER_SIZE); - if (buffer == NULL){ + if(buffer == NULL){ return 0; } buffer_capacity += BUFFER_SIZE; @@ -145,41 +162,41 @@ /* * Initialize GPGME. */ -static bool init_gpgme(mandos_context *mc, const char *seckey, +static bool init_gpgme(const char *seckey, const char *pubkey, const char *tempdir){ int ret; gpgme_error_t rc; gpgme_engine_info_t engine_info; - + /* - * Helper function to insert pub and seckey to the enigne keyring. + * Helper function to insert pub and seckey to the engine keyring. */ bool import_key(const char *filename){ int fd; gpgme_data_t pgp_data; - fd = TEMP_FAILURE_RETRY(open(filename, O_RDONLY)); + fd = (int)TEMP_FAILURE_RETRY(open(filename, O_RDONLY)); if(fd == -1){ perror("open"); return false; } rc = gpgme_data_new_from_fd(&pgp_data, fd); - if (rc != GPG_ERR_NO_ERROR){ + if(rc != GPG_ERR_NO_ERROR){ fprintf(stderr, "bad gpgme_data_new_from_fd: %s: %s\n", gpgme_strsource(rc), gpgme_strerror(rc)); return false; } - - rc = gpgme_op_import(mc->ctx, pgp_data); - if (rc != GPG_ERR_NO_ERROR){ + + rc = gpgme_op_import(mc.ctx, pgp_data); + if(rc != GPG_ERR_NO_ERROR){ fprintf(stderr, "bad gpgme_op_import: %s: %s\n", gpgme_strsource(rc), gpgme_strerror(rc)); return false; } - - ret = TEMP_FAILURE_RETRY(close(fd)); + + ret = (int)TEMP_FAILURE_RETRY(close(fd)); if(ret == -1){ perror("close"); } @@ -187,22 +204,22 @@ return true; } - if (debug){ - fprintf(stderr, "Initialize gpgme\n"); + if(debug){ + fprintf(stderr, "Initializing GPGME\n"); } - + /* Init GPGME */ gpgme_check_version(NULL); rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP); - if (rc != GPG_ERR_NO_ERROR){ + if(rc != GPG_ERR_NO_ERROR){ fprintf(stderr, "bad gpgme_engine_check_version: %s: %s\n", gpgme_strsource(rc), gpgme_strerror(rc)); return false; } - + /* Set GPGME home directory for the OpenPGP engine only */ - rc = gpgme_get_engine_info (&engine_info); - if (rc != GPG_ERR_NO_ERROR){ + rc = gpgme_get_engine_info(&engine_info); + if(rc != GPG_ERR_NO_ERROR){ fprintf(stderr, "bad gpgme_get_engine_info: %s: %s\n", gpgme_strsource(rc), gpgme_strerror(rc)); return false; @@ -219,44 +236,43 @@ fprintf(stderr, "Could not set GPGME home dir to %s\n", tempdir); return false; } - + /* Create new GPGME "context" */ - rc = gpgme_new(&(mc->ctx)); - if (rc != GPG_ERR_NO_ERROR){ + rc = gpgme_new(&(mc.ctx)); + if(rc != GPG_ERR_NO_ERROR){ fprintf(stderr, "bad gpgme_new: %s: %s\n", gpgme_strsource(rc), gpgme_strerror(rc)); return false; } - if (not import_key(pubkey) or not import_key(seckey)){ + if(not import_key(pubkey) or not import_key(seckey)){ return false; } - return true; + return true; } /* * Decrypt OpenPGP data. * Returns -1 on error */ -static ssize_t pgp_packet_decrypt (const mandos_context *mc, - const char *cryptotext, - size_t crypto_size, - char **plaintext){ +static ssize_t pgp_packet_decrypt(const char *cryptotext, + size_t crypto_size, + char **plaintext){ gpgme_data_t dh_crypto, dh_plain; gpgme_error_t rc; ssize_t ret; size_t plaintext_capacity = 0; ssize_t plaintext_length = 0; - if (debug){ + if(debug){ fprintf(stderr, "Trying to decrypt OpenPGP data\n"); } /* Create new GPGME data buffer from memory cryptotext */ rc = gpgme_data_new_from_mem(&dh_crypto, cryptotext, crypto_size, 0); - if (rc != GPG_ERR_NO_ERROR){ + if(rc != GPG_ERR_NO_ERROR){ fprintf(stderr, "bad gpgme_data_new_from_mem: %s: %s\n", gpgme_strsource(rc), gpgme_strerror(rc)); return -1; @@ -264,7 +280,7 @@ /* Create new empty GPGME data buffer for the plaintext */ rc = gpgme_data_new(&dh_plain); - if (rc != GPG_ERR_NO_ERROR){ + if(rc != GPG_ERR_NO_ERROR){ fprintf(stderr, "bad gpgme_data_new: %s: %s\n", gpgme_strsource(rc), gpgme_strerror(rc)); gpgme_data_release(dh_crypto); @@ -273,15 +289,15 @@ /* Decrypt data from the cryptotext data buffer to the plaintext data buffer */ - rc = gpgme_op_decrypt(mc->ctx, dh_crypto, dh_plain); - if (rc != GPG_ERR_NO_ERROR){ + rc = gpgme_op_decrypt(mc.ctx, dh_crypto, dh_plain); + if(rc != GPG_ERR_NO_ERROR){ fprintf(stderr, "bad gpgme_op_decrypt: %s: %s\n", gpgme_strsource(rc), gpgme_strerror(rc)); plaintext_length = -1; - if (debug){ + if(debug){ gpgme_decrypt_result_t result; - result = gpgme_op_decrypt_result(mc->ctx); - if (result == NULL){ + result = gpgme_op_decrypt_result(mc.ctx); + if(result == NULL){ fprintf(stderr, "gpgme_op_decrypt_result failed\n"); } else { fprintf(stderr, "Unsupported algorithm: %s\n", @@ -293,16 +309,14 @@ } gpgme_recipient_t recipient; recipient = result->recipients; - if(recipient){ - while(recipient != NULL){ - fprintf(stderr, "Public key algorithm: %s\n", - gpgme_pubkey_algo_name(recipient->pubkey_algo)); - fprintf(stderr, "Key ID: %s\n", recipient->keyid); - fprintf(stderr, "Secret key available: %s\n", - recipient->status == GPG_ERR_NO_SECKEY - ? "No" : "Yes"); - recipient = recipient->next; - } + while(recipient != NULL){ + fprintf(stderr, "Public key algorithm: %s\n", + gpgme_pubkey_algo_name(recipient->pubkey_algo)); + fprintf(stderr, "Key ID: %s\n", recipient->keyid); + fprintf(stderr, "Secret key available: %s\n", + recipient->status == GPG_ERR_NO_SECKEY + ? "No" : "Yes"); + recipient = recipient->next; } } } @@ -314,19 +328,19 @@ } /* Seek back to the beginning of the GPGME plaintext data buffer */ - if (gpgme_data_seek(dh_plain, (off_t) 0, SEEK_SET) == -1){ - perror("pgpme_data_seek"); + if(gpgme_data_seek(dh_plain, (off_t)0, SEEK_SET) == -1){ + perror("gpgme_data_seek"); plaintext_length = -1; goto decrypt_end; } *plaintext = NULL; while(true){ - plaintext_capacity = adjustbuffer(plaintext, + plaintext_capacity = incbuffer(plaintext, (size_t)plaintext_length, plaintext_capacity); - if (plaintext_capacity == 0){ - perror("adjustbuffer"); + if(plaintext_capacity == 0){ + perror("incbuffer"); plaintext_length = -1; goto decrypt_end; } @@ -334,7 +348,7 @@ ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length, BUFFER_SIZE); /* Print the data, if any */ - if (ret == 0){ + if(ret == 0){ /* EOF */ break; } @@ -364,9 +378,10 @@ return plaintext_length; } -static const char * safer_gnutls_strerror (int value) { - const char *ret = gnutls_strerror (value); /* Spurious warning */ - if (ret == NULL) +static const char * safer_gnutls_strerror(int value){ + const char *ret = gnutls_strerror(value); /* Spurious warning from + -Wunreachable-code */ + if(ret == NULL) ret = "(unknown)"; return ret; } @@ -377,8 +392,7 @@ fprintf(stderr, "GnuTLS: %s", string); } -static int init_gnutls_global(mandos_context *mc, - const char *pubkeyfilename, +static int init_gnutls_global(const char *pubkeyfilename, const char *seckeyfilename){ int ret; @@ -387,13 +401,13 @@ } ret = gnutls_global_init(); - if (ret != GNUTLS_E_SUCCESS) { - fprintf (stderr, "GnuTLS global_init: %s\n", - safer_gnutls_strerror(ret)); + if(ret != GNUTLS_E_SUCCESS){ + fprintf(stderr, "GnuTLS global_init: %s\n", + safer_gnutls_strerror(ret)); return -1; } - if (debug){ + if(debug){ /* "Use a log level over 10 to enable all debugging options." * - GnuTLS manual */ @@ -402,12 +416,14 @@ } /* OpenPGP credentials */ - gnutls_certificate_allocate_credentials(&mc->cred); - if (ret != GNUTLS_E_SUCCESS){ - fprintf (stderr, "GnuTLS memory error: %s\n", /* Spurious - warning */ - safer_gnutls_strerror(ret)); - gnutls_global_deinit (); + gnutls_certificate_allocate_credentials(&mc.cred); + if(ret != GNUTLS_E_SUCCESS){ + fprintf(stderr, "GnuTLS memory error: %s\n", /* Spurious warning + from + -Wunreachable-code + */ + safer_gnutls_strerror(ret)); + gnutls_global_deinit(); return -1; } @@ -418,9 +434,9 @@ } ret = gnutls_certificate_set_openpgp_key_file - (mc->cred, pubkeyfilename, seckeyfilename, + (mc.cred, pubkeyfilename, seckeyfilename, GNUTLS_OPENPGP_FMT_BASE64); - if (ret != GNUTLS_E_SUCCESS) { + if(ret != GNUTLS_E_SUCCESS){ fprintf(stderr, "Error[%d] while reading the OpenPGP key pair ('%s'," " '%s')\n", ret, pubkeyfilename, seckeyfilename); @@ -430,66 +446,66 @@ } /* GnuTLS server initialization */ - ret = gnutls_dh_params_init(&mc->dh_params); - if (ret != GNUTLS_E_SUCCESS) { - fprintf (stderr, "Error in GnuTLS DH parameter initialization:" - " %s\n", safer_gnutls_strerror(ret)); + ret = gnutls_dh_params_init(&mc.dh_params); + if(ret != GNUTLS_E_SUCCESS){ + fprintf(stderr, "Error in GnuTLS DH parameter initialization:" + " %s\n", safer_gnutls_strerror(ret)); goto globalfail; } - ret = gnutls_dh_params_generate2(mc->dh_params, mc->dh_bits); - if (ret != GNUTLS_E_SUCCESS) { - fprintf (stderr, "Error in GnuTLS prime generation: %s\n", - safer_gnutls_strerror(ret)); + ret = gnutls_dh_params_generate2(mc.dh_params, mc.dh_bits); + if(ret != GNUTLS_E_SUCCESS){ + fprintf(stderr, "Error in GnuTLS prime generation: %s\n", + safer_gnutls_strerror(ret)); goto globalfail; } - gnutls_certificate_set_dh_params(mc->cred, mc->dh_params); + gnutls_certificate_set_dh_params(mc.cred, mc.dh_params); return 0; globalfail: - gnutls_certificate_free_credentials(mc->cred); + gnutls_certificate_free_credentials(mc.cred); gnutls_global_deinit(); + gnutls_dh_params_deinit(mc.dh_params); return -1; } -static int init_gnutls_session(mandos_context *mc, - gnutls_session_t *session){ +static int init_gnutls_session(gnutls_session_t *session){ int ret; /* GnuTLS session creation */ ret = gnutls_init(session, GNUTLS_SERVER); - if (ret != GNUTLS_E_SUCCESS){ + if(ret != GNUTLS_E_SUCCESS){ fprintf(stderr, "Error in GnuTLS session initialization: %s\n", safer_gnutls_strerror(ret)); } { const char *err; - ret = gnutls_priority_set_direct(*session, mc->priority, &err); - if (ret != GNUTLS_E_SUCCESS) { + ret = gnutls_priority_set_direct(*session, mc.priority, &err); + if(ret != GNUTLS_E_SUCCESS){ fprintf(stderr, "Syntax error at: %s\n", err); fprintf(stderr, "GnuTLS error: %s\n", safer_gnutls_strerror(ret)); - gnutls_deinit (*session); + gnutls_deinit(*session); return -1; } } ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE, - mc->cred); - if (ret != GNUTLS_E_SUCCESS) { + mc.cred); + if(ret != GNUTLS_E_SUCCESS){ fprintf(stderr, "Error setting GnuTLS credentials: %s\n", safer_gnutls_strerror(ret)); - gnutls_deinit (*session); + gnutls_deinit(*session); return -1; } /* ignore client certificate if any. */ - gnutls_certificate_server_set_request (*session, - GNUTLS_CERT_IGNORE); + gnutls_certificate_server_set_request(*session, + GNUTLS_CERT_IGNORE); - gnutls_dh_set_prime_bits (*session, mc->dh_bits); + gnutls_dh_set_prime_bits(*session, mc.dh_bits); return 0; } @@ -498,12 +514,19 @@ static void empty_log(__attribute__((unused)) AvahiLogLevel level, __attribute__((unused)) const char *txt){} +sig_atomic_t quit_now = 0; +int signal_received = 0; + /* Called when a Mandos server is found */ static int start_mandos_communication(const char *ip, uint16_t port, AvahiIfIndex if_index, - mandos_context *mc){ - int ret, tcp_sd; - union { struct sockaddr in; struct sockaddr_in6 in6; } to; + int af){ + int ret, tcp_sd = -1; + ssize_t sret; + union { + struct sockaddr_in in; + struct sockaddr_in6 in6; + } to; char *buffer = NULL; char *decrypted_buffer; size_t buffer_length = 0; @@ -511,56 +534,115 @@ ssize_t decrypted_buffer_size; size_t written; int retval = 0; - char interface[IF_NAMESIZE]; gnutls_session_t session; - - ret = init_gnutls_session (mc, &session); - if (ret != 0){ + int pf; /* Protocol family */ + + if(quit_now){ + return -1; + } + + switch(af){ + case AF_INET6: + pf = PF_INET6; + break; + case AF_INET: + pf = PF_INET; + break; + default: + fprintf(stderr, "Bad address family: %d\n", af); + return -1; + } + + ret = init_gnutls_session(&session); + if(ret != 0){ return -1; } if(debug){ - fprintf(stderr, "Setting up a tcp connection to %s, port %" PRIu16 + fprintf(stderr, "Setting up a TCP connection to %s, port %" PRIu16 "\n", ip, port); } - tcp_sd = socket(PF_INET6, SOCK_STREAM, 0); - if(tcp_sd < 0) { + tcp_sd = socket(pf, SOCK_STREAM, 0); + if(tcp_sd < 0){ perror("socket"); - return -1; + retval = -1; + goto mandos_end; } - if(debug){ - if(if_indextoname((unsigned int)if_index, interface) == NULL){ - perror("if_indextoname"); - return -1; - } - fprintf(stderr, "Binding to interface %s\n", interface); + if(quit_now){ + goto mandos_end; } memset(&to, 0, sizeof(to)); - to.in6.sin6_family = AF_INET6; - /* It would be nice to have a way to detect if we were passed an - IPv4 address here. Now we assume an IPv6 address. */ - ret = inet_pton(AF_INET6, ip, &to.in6.sin6_addr); - if (ret < 0 ){ + if(af == AF_INET6){ + to.in6.sin6_family = (sa_family_t)af; + ret = inet_pton(af, ip, &to.in6.sin6_addr); + } else { /* IPv4 */ + to.in.sin_family = (sa_family_t)af; + ret = inet_pton(af, ip, &to.in.sin_addr); + } + if(ret < 0 ){ perror("inet_pton"); - return -1; + retval = -1; + goto mandos_end; } if(ret == 0){ fprintf(stderr, "Bad address: %s\n", ip); - return -1; - } - to.in6.sin6_port = htons(port); /* Spurious warning */ + retval = -1; + goto mandos_end; + } + if(af == AF_INET6){ + to.in6.sin6_port = htons(port); /* Spurious warnings from + -Wconversion and + -Wunreachable-code */ + + if(IN6_IS_ADDR_LINKLOCAL /* Spurious warnings from */ + (&to.in6.sin6_addr)){ /* -Wstrict-aliasing=2 or lower and + -Wunreachable-code*/ + if(if_index == AVAHI_IF_UNSPEC){ + fprintf(stderr, "An IPv6 link-local address is incomplete" + " without a network interface\n"); + retval = -1; + goto mandos_end; + } + /* Set the network interface number as scope */ + to.in6.sin6_scope_id = (uint32_t)if_index; + } + } else { + to.in.sin_port = htons(port); /* Spurious warnings from + -Wconversion and + -Wunreachable-code */ + } - to.in6.sin6_scope_id = (uint32_t)if_index; + if(quit_now){ + goto mandos_end; + } if(debug){ - fprintf(stderr, "Connection to: %s, port %" PRIu16 "\n", ip, - port); - char addrstr[INET6_ADDRSTRLEN] = ""; - if(inet_ntop(to.in6.sin6_family, &(to.in6.sin6_addr), addrstr, - sizeof(addrstr)) == NULL){ + if(af == AF_INET6 and if_index != AVAHI_IF_UNSPEC){ + char interface[IF_NAMESIZE]; + if(if_indextoname((unsigned int)if_index, interface) == NULL){ + perror("if_indextoname"); + } else { + fprintf(stderr, "Connection to: %s%%%s, port %" PRIu16 "\n", + ip, interface, port); + } + } else { + fprintf(stderr, "Connection to: %s, port %" PRIu16 "\n", ip, + port); + } + char addrstr[(INET_ADDRSTRLEN > INET6_ADDRSTRLEN) ? + INET_ADDRSTRLEN : INET6_ADDRSTRLEN] = ""; + const char *pcret; + if(af == AF_INET6){ + pcret = inet_ntop(af, &(to.in6.sin6_addr), addrstr, + sizeof(addrstr)); + } else { + pcret = inet_ntop(af, &(to.in.sin_addr), addrstr, + sizeof(addrstr)); + } + if(pcret == NULL){ perror("inet_ntop"); } else { if(strcmp(addrstr, ip) != 0){ @@ -569,19 +651,32 @@ } } - ret = connect(tcp_sd, &to.in, sizeof(to)); - if (ret < 0){ + if(quit_now){ + goto mandos_end; + } + + if(af == AF_INET6){ + ret = connect(tcp_sd, &to.in6, sizeof(to)); + } else { + ret = connect(tcp_sd, &to.in, sizeof(to)); /* IPv4 */ + } + if(ret < 0){ perror("connect"); - return -1; + retval = -1; + goto mandos_end; + } + + if(quit_now){ + goto mandos_end; } const char *out = mandos_protocol_version; written = 0; - while (true){ + while(true){ size_t out_size = strlen(out); - ret = TEMP_FAILURE_RETRY(write(tcp_sd, out + written, + ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written, out_size - written)); - if (ret == -1){ + if(ret == -1){ perror("write"); retval = -1; goto mandos_end; @@ -590,29 +685,44 @@ if(written < out_size){ continue; } else { - if (out == mandos_protocol_version){ + if(out == mandos_protocol_version){ written = 0; out = "\r\n"; } else { break; } } + + if(quit_now){ + goto mandos_end; + } } if(debug){ fprintf(stderr, "Establishing TLS session with %s\n", ip); } - gnutls_transport_set_ptr (session, (gnutls_transport_ptr_t) tcp_sd); + if(quit_now){ + goto mandos_end; + } + + gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) tcp_sd); + + if(quit_now){ + goto mandos_end; + } do{ - ret = gnutls_handshake (session); + ret = gnutls_handshake(session); + if(quit_now){ + goto mandos_end; + } } while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED); - if (ret != GNUTLS_E_SUCCESS){ + if(ret != GNUTLS_E_SUCCESS){ if(debug){ fprintf(stderr, "*** GnuTLS Handshake failed ***\n"); - gnutls_perror (ret); + gnutls_perror(ret); } retval = -1; goto mandos_end; @@ -621,36 +731,49 @@ /* Read OpenPGP packet that contains the wanted password */ if(debug){ - fprintf(stderr, "Retrieving pgp encrypted password from %s\n", + fprintf(stderr, "Retrieving OpenPGP encrypted password from %s\n", ip); } while(true){ - buffer_capacity = adjustbuffer(&buffer, buffer_length, + + if(quit_now){ + goto mandos_end; + } + + buffer_capacity = incbuffer(&buffer, buffer_length, buffer_capacity); - if (buffer_capacity == 0){ - perror("adjustbuffer"); + if(buffer_capacity == 0){ + perror("incbuffer"); retval = -1; goto mandos_end; } - ret = gnutls_record_recv(session, buffer+buffer_length, - BUFFER_SIZE); - if (ret == 0){ + if(quit_now){ + goto mandos_end; + } + + sret = gnutls_record_recv(session, buffer+buffer_length, + BUFFER_SIZE); + if(sret == 0){ break; } - if (ret < 0){ - switch(ret){ + if(sret < 0){ + switch(sret){ case GNUTLS_E_INTERRUPTED: case GNUTLS_E_AGAIN: break; case GNUTLS_E_REHANDSHAKE: do{ - ret = gnutls_handshake (session); + ret = gnutls_handshake(session); + + if(quit_now){ + goto mandos_end; + } } while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED); - if (ret < 0){ + if(ret < 0){ fprintf(stderr, "*** GnuTLS Re-handshake failed ***\n"); - gnutls_perror (ret); + gnutls_perror(ret); retval = -1; goto mandos_end; } @@ -659,11 +782,11 @@ fprintf(stderr, "Unknown error while reading data from" " encrypted session with Mandos server\n"); retval = -1; - gnutls_bye (session, GNUTLS_SHUT_RDWR); + gnutls_bye(session, GNUTLS_SHUT_RDWR); goto mandos_end; } } else { - buffer_length += (size_t) ret; + buffer_length += (size_t) sret; } } @@ -671,18 +794,30 @@ fprintf(stderr, "Closing TLS session\n"); } - gnutls_bye (session, GNUTLS_SHUT_RDWR); - - if (buffer_length > 0){ - decrypted_buffer_size = pgp_packet_decrypt(mc, buffer, + if(quit_now){ + goto mandos_end; + } + + gnutls_bye(session, GNUTLS_SHUT_RDWR); + + if(quit_now){ + goto mandos_end; + } + + if(buffer_length > 0){ + decrypted_buffer_size = pgp_packet_decrypt(buffer, buffer_length, &decrypted_buffer); - if (decrypted_buffer_size >= 0){ + if(decrypted_buffer_size >= 0){ written = 0; while(written < (size_t) decrypted_buffer_size){ - ret = (int)fwrite (decrypted_buffer + written, 1, - (size_t)decrypted_buffer_size - written, - stdout); + if(quit_now){ + goto mandos_end; + } + + ret = (int)fwrite(decrypted_buffer + written, 1, + (size_t)decrypted_buffer_size - written, + stdout); if(ret == 0 and ferror(stdout)){ if(debug){ fprintf(stderr, "Error writing encrypted data: %s\n", @@ -705,17 +840,22 @@ mandos_end: free(buffer); - ret = TEMP_FAILURE_RETRY(close(tcp_sd)); + if(tcp_sd >= 0){ + ret = (int)TEMP_FAILURE_RETRY(close(tcp_sd)); + } if(ret == -1){ perror("close"); } - gnutls_deinit (session); + gnutls_deinit(session); + if(quit_now){ + retval = -1; + } return retval; } static void resolve_callback(AvahiSServiceResolver *r, AvahiIfIndex interface, - AVAHI_GCC_UNUSED AvahiProtocol protocol, + AvahiProtocol proto, AvahiResolverEvent event, const char *name, const char *type, @@ -726,19 +866,22 @@ AVAHI_GCC_UNUSED AvahiStringList *txt, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, - void* userdata) { - mandos_context *mc = userdata; + AVAHI_GCC_UNUSED void* userdata){ assert(r); /* Called whenever a service has been resolved successfully or timed out */ - switch (event) { + if(quit_now){ + return; + } + + switch(event){ default: case AVAHI_RESOLVER_FAILURE: fprintf(stderr, "(Avahi Resolver) Failed to resolve service '%s'" " of type '%s' in domain '%s': %s\n", name, type, domain, - avahi_strerror(avahi_server_errno(mc->server))); + avahi_strerror(avahi_server_errno(mc.server))); break; case AVAHI_RESOLVER_FOUND: @@ -747,41 +890,45 @@ avahi_address_snprint(ip, sizeof(ip), address); if(debug){ fprintf(stderr, "Mandos server \"%s\" found on %s (%s, %" - PRIu16 ") on port %d\n", name, host_name, ip, - interface, port); + PRIdMAX ") on port %" PRIu16 "\n", name, host_name, + ip, (intmax_t)interface, port); } - int ret = start_mandos_communication(ip, port, interface, mc); - if (ret == 0){ - avahi_simple_poll_quit(mc->simple_poll); + int ret = start_mandos_communication(ip, port, interface, + avahi_proto_to_af(proto)); + if(ret == 0){ + avahi_simple_poll_quit(mc.simple_poll); } } } avahi_s_service_resolver_free(r); } -static void browse_callback( AvahiSServiceBrowser *b, - AvahiIfIndex interface, - AvahiProtocol protocol, - AvahiBrowserEvent event, - const char *name, - const char *type, - const char *domain, - AVAHI_GCC_UNUSED AvahiLookupResultFlags - flags, - void* userdata) { - mandos_context *mc = userdata; +static void browse_callback(AvahiSServiceBrowser *b, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *name, + const char *type, + const char *domain, + AVAHI_GCC_UNUSED AvahiLookupResultFlags + flags, + AVAHI_GCC_UNUSED void* userdata){ assert(b); /* Called whenever a new services becomes available on the LAN or is removed from the LAN */ - switch (event) { + if(quit_now){ + return; + } + + switch(event){ default: case AVAHI_BROWSER_FAILURE: fprintf(stderr, "(Avahi browser) %s\n", - avahi_strerror(avahi_server_errno(mc->server))); - avahi_simple_poll_quit(mc->simple_poll); + avahi_strerror(avahi_server_errno(mc.server))); + avahi_simple_poll_quit(mc.simple_poll); return; case AVAHI_BROWSER_NEW: @@ -790,12 +937,11 @@ the callback function is called the Avahi server will free the resolver for us. */ - if (!(avahi_s_service_resolver_new(mc->server, interface, - protocol, name, type, domain, - AVAHI_PROTO_INET6, 0, - resolve_callback, mc))) + if(avahi_s_service_resolver_new(mc.server, interface, protocol, + name, type, domain, protocol, 0, + resolve_callback, NULL) == NULL) fprintf(stderr, "Avahi: Failed to resolve service '%s': %s\n", - name, avahi_strerror(avahi_server_errno(mc->server))); + name, avahi_strerror(avahi_server_errno(mc.server))); break; case AVAHI_BROWSER_REMOVE: @@ -810,335 +956,591 @@ } } +/* stop main loop after sigterm has been called */ +static void handle_sigterm(int sig){ + if(quit_now){ + return; + } + quit_now = 1; + signal_received = sig; + int old_errno = errno; + if(mc.simple_poll != NULL){ + avahi_simple_poll_quit(mc.simple_poll); + } + errno = old_errno; +} + int main(int argc, char *argv[]){ - AvahiSServiceBrowser *sb = NULL; - int error; - int ret; - int exitcode = EXIT_SUCCESS; - const char *interface = "eth0"; - struct ifreq network; - int sd; - uid_t uid; - gid_t gid; - char *connect_to = NULL; - char tempdir[] = "/tmp/mandosXXXXXX"; - AvahiIfIndex if_index = AVAHI_IF_UNSPEC; - const char *seckey = PATHDIR "/" SECKEY; - const char *pubkey = PATHDIR "/" PUBKEY; - - mandos_context mc = { .simple_poll = NULL, .server = NULL, - .dh_bits = 1024, .priority = "SECURE256" - ":!CTYPE-X.509:+CTYPE-OPENPGP" }; - bool gnutls_initalized = false; - bool pgpme_initalized = false; - - { - struct argp_option options[] = { - { .name = "debug", .key = 128, - .doc = "Debug mode", .group = 3 }, - { .name = "connect", .key = 'c', - .arg = "ADDRESS:PORT", - .doc = "Connect directly to a specific Mandos server", - .group = 1 }, - { .name = "interface", .key = 'i', - .arg = "NAME", - .doc = "Interface that will be used to search for Mandos" - " servers", - .group = 1 }, - { .name = "seckey", .key = 's', - .arg = "FILE", - .doc = "OpenPGP secret key file base name", - .group = 1 }, - { .name = "pubkey", .key = 'p', - .arg = "FILE", - .doc = "OpenPGP public key file base name", - .group = 2 }, - { .name = "dh-bits", .key = 129, - .arg = "BITS", - .doc = "Bit length of the prime number used in the" - " Diffie-Hellman key exchange", - .group = 2 }, - { .name = "priority", .key = 130, - .arg = "STRING", - .doc = "GnuTLS priority string for the TLS handshake", - .group = 1 }, - { .name = NULL } - }; - - error_t parse_opt (int key, char *arg, - struct argp_state *state) { - /* Get the INPUT argument from `argp_parse', which we know is - a pointer to our plugin list pointer. */ - switch (key) { - case 128: /* --debug */ - debug = true; - break; - case 'c': /* --connect */ - connect_to = arg; - break; - case 'i': /* --interface */ - interface = arg; - break; - case 's': /* --seckey */ - seckey = arg; - break; - case 'p': /* --pubkey */ - pubkey = arg; - break; - case 129: /* --dh-bits */ - errno = 0; - mc.dh_bits = (unsigned int) strtol(arg, NULL, 10); - if (errno){ - perror("strtol"); - exit(EXIT_FAILURE); + AvahiSServiceBrowser *sb = NULL; + int error; + int ret; + intmax_t tmpmax; + char *tmp; + int exitcode = EXIT_SUCCESS; + const char *interface = "eth0"; + 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; + const char *seckey = PATHDIR "/" SECKEY; + const char *pubkey = PATHDIR "/" PUBKEY; + + bool gnutls_initialized = false; + bool gpgme_initialized = false; + float delay = 2.5f; + + struct sigaction old_sigterm_action; + struct sigaction sigterm_action = { .sa_handler = handle_sigterm }; + + { + struct argp_option options[] = { + { .name = "debug", .key = 128, + .doc = "Debug mode", .group = 3 }, + { .name = "connect", .key = 'c', + .arg = "ADDRESS:PORT", + .doc = "Connect directly to a specific Mandos server", + .group = 1 }, + { .name = "interface", .key = 'i', + .arg = "NAME", + .doc = "Network interface that will be used to search for" + " Mandos servers", + .group = 1 }, + { .name = "seckey", .key = 's', + .arg = "FILE", + .doc = "OpenPGP secret key file base name", + .group = 1 }, + { .name = "pubkey", .key = 'p', + .arg = "FILE", + .doc = "OpenPGP public key file base name", + .group = 2 }, + { .name = "dh-bits", .key = 129, + .arg = "BITS", + .doc = "Bit length of the prime number used in the" + " Diffie-Hellman key exchange", + .group = 2 }, + { .name = "priority", .key = 130, + .arg = "STRING", + .doc = "GnuTLS priority string for the TLS handshake", + .group = 1 }, + { .name = "delay", .key = 131, + .arg = "SECONDS", + .doc = "Maximum delay to wait for interface startup", + .group = 2 }, + { .name = NULL } + }; + + error_t parse_opt(int key, char *arg, + struct argp_state *state){ + switch(key){ + case 128: /* --debug */ + debug = true; + break; + case 'c': /* --connect */ + connect_to = arg; + break; + case 'i': /* --interface */ + interface = arg; + break; + case 's': /* --seckey */ + seckey = arg; + break; + case 'p': /* --pubkey */ + pubkey = arg; + break; + case 129: /* --dh-bits */ + errno = 0; + tmpmax = strtoimax(arg, &tmp, 10); + if(errno != 0 or tmp == arg or *tmp != '\0' + or tmpmax != (typeof(mc.dh_bits))tmpmax){ + fprintf(stderr, "Bad number of DH bits\n"); + exit(EXIT_FAILURE); + } + mc.dh_bits = (typeof(mc.dh_bits))tmpmax; + break; + case 130: /* --priority */ + mc.priority = arg; + break; + case 131: /* --delay */ + errno = 0; + delay = strtof(arg, &tmp); + if(errno != 0 or tmp == arg or *tmp != '\0'){ + fprintf(stderr, "Bad delay\n"); + exit(EXIT_FAILURE); + } + break; + case ARGP_KEY_ARG: + argp_usage(state); + case ARGP_KEY_END: + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; + } + + struct argp argp = { .options = options, .parser = parse_opt, + .args_doc = "", + .doc = "Mandos client -- Get and decrypt" + " passwords from a Mandos server" }; + ret = argp_parse(&argp, argc, argv, 0, 0, NULL); + if(ret == ARGP_ERR_UNKNOWN){ + fprintf(stderr, "Unknown error while parsing arguments\n"); + exitcode = EXIT_FAILURE; + goto end; + } + } + + if(not debug){ + avahi_set_log_function(empty_log); + } + + /* Initialize Avahi early so avahi_simple_poll_quit() can be called + from the signal handler */ + /* Initialize the pseudo-RNG for Avahi */ + srand((unsigned int) time(NULL)); + mc.simple_poll = avahi_simple_poll_new(); + if(mc.simple_poll == NULL){ + fprintf(stderr, "Avahi: Failed to create simple poll object.\n"); + exitcode = EXIT_FAILURE; + goto end; + } + + sigemptyset(&sigterm_action.sa_mask); + ret = sigaddset(&sigterm_action.sa_mask, SIGINT); + if(ret == -1){ + perror("sigaddset"); + exitcode = EXIT_FAILURE; + goto end; + } + ret = sigaddset(&sigterm_action.sa_mask, SIGHUP); + if(ret == -1){ + perror("sigaddset"); + exitcode = EXIT_FAILURE; + goto end; + } + ret = sigaddset(&sigterm_action.sa_mask, SIGTERM); + if(ret == -1){ + perror("sigaddset"); + exitcode = EXIT_FAILURE; + goto end; + } + /* Need to check if the handler is SIG_IGN before handling: + | [[info:libc:Initial Signal Actions]] | + | [[info:libc:Basic Signal Handling]] | + */ + ret = sigaction(SIGINT, NULL, &old_sigterm_action); + if(ret == -1){ + perror("sigaction"); + return EXIT_FAILURE; + } + if(old_sigterm_action.sa_handler != SIG_IGN){ + ret = sigaction(SIGINT, &sigterm_action, NULL); + if(ret == -1){ + perror("sigaction"); + exitcode = EXIT_FAILURE; + goto end; + } + } + ret = sigaction(SIGHUP, NULL, &old_sigterm_action); + if(ret == -1){ + perror("sigaction"); + return EXIT_FAILURE; + } + if(old_sigterm_action.sa_handler != SIG_IGN){ + ret = sigaction(SIGHUP, &sigterm_action, NULL); + if(ret == -1){ + perror("sigaction"); + exitcode = EXIT_FAILURE; + goto end; + } + } + ret = sigaction(SIGTERM, NULL, &old_sigterm_action); + if(ret == -1){ + perror("sigaction"); + return EXIT_FAILURE; + } + if(old_sigterm_action.sa_handler != SIG_IGN){ + ret = sigaction(SIGTERM, &sigterm_action, NULL); + if(ret == -1){ + perror("sigaction"); + exitcode = EXIT_FAILURE; + goto end; + } + } + + /* If the interface is down, bring it up */ + if(interface[0] != '\0'){ + if_index = (AvahiIfIndex) if_nametoindex(interface); + if(if_index == 0){ + fprintf(stderr, "No such interface: \"%s\"\n", interface); + exitcode = EXIT_FAILURE; + goto end; + } + + if(quit_now){ + goto end; + } + +#ifdef __linux__ + /* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO + messages to mess up the prompt */ + ret = klogctl(8, NULL, 5); + bool restore_loglevel = true; + if(ret == -1){ + restore_loglevel = false; + perror("klogctl"); + } +#endif /* __linux__ */ + + sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); + if(sd < 0){ + perror("socket"); + exitcode = EXIT_FAILURE; +#ifdef __linux__ + if(restore_loglevel){ + ret = klogctl(7, NULL, 0); + if(ret == -1){ + perror("klogctl"); + } + } +#endif /* __linux__ */ + goto end; + } + strcpy(network.ifr_name, interface); + ret = ioctl(sd, SIOCGIFFLAGS, &network); + if(ret == -1){ + perror("ioctl SIOCGIFFLAGS"); +#ifdef __linux__ + if(restore_loglevel){ + ret = klogctl(7, NULL, 0); + if(ret == -1){ + perror("klogctl"); + } + } +#endif /* __linux__ */ + exitcode = EXIT_FAILURE; + goto end; + } + if((network.ifr_flags & IFF_UP) == 0){ + network.ifr_flags |= IFF_UP; + take_down_interface = true; + ret = ioctl(sd, SIOCSIFFLAGS, &network); + if(ret == -1){ + take_down_interface = false; + perror("ioctl SIOCSIFFLAGS"); + exitcode = EXIT_FAILURE; +#ifdef __linux__ + if(restore_loglevel){ + ret = klogctl(7, NULL, 0); + if(ret == -1){ + perror("klogctl"); } - break; - case 130: /* --priority */ - mc.priority = arg; - break; - case ARGP_KEY_ARG: - argp_usage (state); - case ARGP_KEY_END: - break; - default: - return ARGP_ERR_UNKNOWN; } - return 0; - } - - struct argp argp = { .options = options, .parser = parse_opt, - .args_doc = "", - .doc = "Mandos client -- Get and decrypt" - " passwords from a Mandos server" }; - ret = argp_parse (&argp, argc, argv, 0, 0, NULL); - if (ret == ARGP_ERR_UNKNOWN){ - fprintf(stderr, "Unknown error while parsing arguments\n"); - exitcode = EXIT_FAILURE; - goto end; - } - } - - ret = init_gnutls_global(&mc, pubkey, seckey); - if (ret == -1){ - fprintf(stderr, "init_gnutls_global failed\n"); - exitcode = EXIT_FAILURE; - goto end; - } else { - gnutls_initalized = true; - } - - if(mkdtemp(tempdir) == NULL){ - perror("mkdtemp"); - tempdir[0] = '\0'; - goto end; - } - - if(not init_gpgme(&mc, pubkey, seckey, tempdir)){ - fprintf(stderr, "pgpme_initalized failed\n"); - exitcode = EXIT_FAILURE; - goto end; - } else { - pgpme_initalized = true; - } - - /* If the interface is down, bring it up */ - { - sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); - if(sd < 0) { - perror("socket"); - exitcode = EXIT_FAILURE; - goto end; - } - strcpy(network.ifr_name, interface); +#endif /* __linux__ */ + goto end; + } + } + /* sleep checking until interface is running */ + for(int i=0; i < delay * 4; i++){ ret = ioctl(sd, SIOCGIFFLAGS, &network); if(ret == -1){ perror("ioctl SIOCGIFFLAGS"); - exitcode = EXIT_FAILURE; - goto end; - } - if((network.ifr_flags & IFF_UP) == 0){ - network.ifr_flags |= IFF_UP; - ret = ioctl(sd, SIOCSIFFLAGS, &network); - if(ret == -1){ - perror("ioctl SIOCSIFFLAGS"); - exitcode = EXIT_FAILURE; - goto end; - } - } - ret = TEMP_FAILURE_RETRY(close(sd)); + } else if(network.ifr_flags & IFF_RUNNING){ + break; + } + struct timespec sleeptime = { .tv_nsec = 250000000 }; + ret = nanosleep(&sleeptime, NULL); + if(ret == -1 and errno != EINTR){ + perror("nanosleep"); + } + } + if(not take_down_interface){ + /* We won't need the socket anymore */ + ret = (int)TEMP_FAILURE_RETRY(close(sd)); if(ret == -1){ perror("close"); } } - - uid = getuid(); - gid = getgid(); - - ret = setuid(uid); - if (ret == -1){ - perror("setuid"); - } - - setgid(gid); - if (ret == -1){ - perror("setgid"); - } - - if_index = (AvahiIfIndex) if_nametoindex(interface); - if(if_index == 0){ - fprintf(stderr, "No such interface: \"%s\"\n", interface); - exit(EXIT_FAILURE); - } - - if(connect_to != NULL){ - /* Connect directly, do not use Zeroconf */ - /* (Mainly meant for debugging) */ - char *address = strrchr(connect_to, ':'); - if(address == NULL){ - fprintf(stderr, "No colon in address\n"); - exitcode = EXIT_FAILURE; - goto end; - } - errno = 0; - uint16_t port = (uint16_t) strtol(address+1, NULL, 10); - if(errno){ - perror("Bad port number"); - exitcode = EXIT_FAILURE; - goto end; - } - *address = '\0'; - address = connect_to; - ret = start_mandos_communication(address, port, if_index, &mc); - if(ret < 0){ - exitcode = EXIT_FAILURE; - } else { - exitcode = EXIT_SUCCESS; - } - goto end; - } - - if (not debug){ - avahi_set_log_function(empty_log); - } - - /* Initialize the pseudo-RNG for Avahi */ - srand((unsigned int) time(NULL)); - - /* Allocate main Avahi loop object */ - mc.simple_poll = avahi_simple_poll_new(); - if (mc.simple_poll == NULL) { - fprintf(stderr, "Avahi: Failed to create simple poll" - " object.\n"); - exitcode = EXIT_FAILURE; - goto end; - } - - { - AvahiServerConfig config; - /* Do not publish any local Zeroconf records */ - avahi_server_config_init(&config); - config.publish_hinfo = 0; - config.publish_addresses = 0; - config.publish_workstation = 0; - config.publish_domain = 0; - - /* Allocate a new server */ - mc.server = avahi_server_new(avahi_simple_poll_get - (mc.simple_poll), &config, NULL, - NULL, &error); - - /* Free the Avahi configuration data */ - avahi_server_config_free(&config); - } - - /* Check if creating the Avahi server object succeeded */ - if (mc.server == NULL) { - fprintf(stderr, "Failed to create Avahi server: %s\n", - avahi_strerror(error)); - exitcode = EXIT_FAILURE; - goto end; - } - - /* Create the Avahi service browser */ - sb = avahi_s_service_browser_new(mc.server, if_index, - AVAHI_PROTO_INET6, - "_mandos._tcp", NULL, 0, - browse_callback, &mc); - if (sb == NULL) { - fprintf(stderr, "Failed to create service browser: %s\n", - avahi_strerror(avahi_server_errno(mc.server))); - exitcode = EXIT_FAILURE; - goto end; - } - - /* Run the main loop */ - - if (debug){ - fprintf(stderr, "Starting Avahi loop search\n"); - } - - avahi_simple_poll_loop(mc.simple_poll); - +#ifdef __linux__ + if(restore_loglevel){ + /* Restores kernel loglevel to default */ + ret = klogctl(7, NULL, 0); + if(ret == -1){ + perror("klogctl"); + } + } +#endif /* __linux__ */ + } + + if(quit_now){ + goto end; + } + + uid = getuid(); + gid = getgid(); + + errno = 0; + setgid(gid); + if(ret == -1){ + perror("setgid"); + } + + ret = setuid(uid); + if(ret == -1){ + perror("setuid"); + } + + if(quit_now){ + goto end; + } + + ret = init_gnutls_global(pubkey, seckey); + if(ret == -1){ + fprintf(stderr, "init_gnutls_global failed\n"); + exitcode = EXIT_FAILURE; + goto end; + } else { + gnutls_initialized = true; + } + + if(quit_now){ + goto end; + } + + tempdir_created = true; + if(mkdtemp(tempdir) == NULL){ + tempdir_created = false; + perror("mkdtemp"); + goto end; + } + + if(quit_now){ + goto end; + } + + if(not init_gpgme(pubkey, seckey, tempdir)){ + fprintf(stderr, "init_gpgme failed\n"); + exitcode = EXIT_FAILURE; + goto end; + } else { + gpgme_initialized = true; + } + + if(quit_now){ + goto end; + } + + if(connect_to != NULL){ + /* Connect directly, do not use Zeroconf */ + /* (Mainly meant for debugging) */ + char *address = strrchr(connect_to, ':'); + if(address == NULL){ + fprintf(stderr, "No colon in address\n"); + exitcode = EXIT_FAILURE; + goto end; + } + + if(quit_now){ + goto end; + } + + uint16_t port; + errno = 0; + tmpmax = strtoimax(address+1, &tmp, 10); + if(errno != 0 or tmp == address+1 or *tmp != '\0' + or tmpmax != (uint16_t)tmpmax){ + fprintf(stderr, "Bad port number\n"); + exitcode = EXIT_FAILURE; + goto end; + } + + if(quit_now){ + goto end; + } + + port = (uint16_t)tmpmax; + *address = '\0'; + address = connect_to; + /* Colon in address indicates IPv6 */ + int af; + if(strchr(address, ':') != NULL){ + af = AF_INET6; + } else { + af = AF_INET; + } + + if(quit_now){ + goto end; + } + + ret = start_mandos_communication(address, port, if_index, af); + if(ret < 0){ + exitcode = EXIT_FAILURE; + } else { + exitcode = EXIT_SUCCESS; + } + goto end; + } + + if(quit_now){ + goto end; + } + + { + AvahiServerConfig config; + /* Do not publish any local Zeroconf records */ + avahi_server_config_init(&config); + config.publish_hinfo = 0; + config.publish_addresses = 0; + config.publish_workstation = 0; + config.publish_domain = 0; + + /* Allocate a new server */ + mc.server = avahi_server_new(avahi_simple_poll_get + (mc.simple_poll), &config, NULL, + NULL, &error); + + /* Free the Avahi configuration data */ + avahi_server_config_free(&config); + } + + /* Check if creating the Avahi server object succeeded */ + if(mc.server == NULL){ + fprintf(stderr, "Failed to create Avahi server: %s\n", + avahi_strerror(error)); + exitcode = EXIT_FAILURE; + goto end; + } + + if(quit_now){ + goto end; + } + + /* Create the Avahi service browser */ + sb = avahi_s_service_browser_new(mc.server, if_index, + AVAHI_PROTO_UNSPEC, "_mandos._tcp", + NULL, 0, browse_callback, NULL); + if(sb == NULL){ + fprintf(stderr, "Failed to create service browser: %s\n", + avahi_strerror(avahi_server_errno(mc.server))); + exitcode = EXIT_FAILURE; + goto end; + } + + if(quit_now){ + goto end; + } + + /* Run the main loop */ + + if(debug){ + fprintf(stderr, "Starting Avahi loop search\n"); + } + + avahi_simple_poll_loop(mc.simple_poll); + end: - - if (debug){ - fprintf(stderr, "%s exiting\n", argv[0]); - } - - /* Cleanup things */ - if (sb != NULL) - avahi_s_service_browser_free(sb); - - if (mc.server != NULL) - avahi_server_free(mc.server); - - if (mc.simple_poll != NULL) - avahi_simple_poll_free(mc.simple_poll); - - if (gnutls_initalized){ - gnutls_certificate_free_credentials(mc.cred); - gnutls_global_deinit (); - } - - if(pgpme_initalized){ - gpgme_release(mc.ctx); - } - - /* Removes the temp directory used by GPGME */ - if(tempdir[0] != '\0'){ - DIR *d; - struct dirent *direntry; - d = opendir(tempdir); - if(d == NULL){ + + if(debug){ + fprintf(stderr, "%s exiting\n", argv[0]); + } + + /* Cleanup things */ + if(sb != NULL) + avahi_s_service_browser_free(sb); + + if(mc.server != NULL) + avahi_server_free(mc.server); + + if(mc.simple_poll != NULL) + avahi_simple_poll_free(mc.simple_poll); + + if(gnutls_initialized){ + gnutls_certificate_free_credentials(mc.cred); + gnutls_global_deinit(); + gnutls_dh_params_deinit(mc.dh_params); + } + + if(gpgme_initialized){ + gpgme_release(mc.ctx); + } + + /* Take down the network interface */ + if(take_down_interface){ + ret = ioctl(sd, SIOCGIFFLAGS, &network); + if(ret == -1){ + perror("ioctl SIOCGIFFLAGS"); + } else if(network.ifr_flags & IFF_UP) { + network.ifr_flags &= ~IFF_UP; /* clear flag */ + ret = ioctl(sd, SIOCSIFFLAGS, &network); + if(ret == -1){ + perror("ioctl SIOCSIFFLAGS"); + } + } + ret = (int)TEMP_FAILURE_RETRY(close(sd)); + if(ret == -1){ + perror("close"); + } + } + + /* Removes the temp directory used by GPGME */ + if(tempdir_created){ + DIR *d; + struct dirent *direntry; + d = opendir(tempdir); + if(d == NULL){ + if(errno != ENOENT){ perror("opendir"); - } else { - while(true){ - direntry = readdir(d); - if(direntry == NULL){ - break; - } - if (direntry->d_type == DT_REG){ - char *fullname = NULL; - ret = asprintf(&fullname, "%s/%s", tempdir, - direntry->d_name); - if(ret < 0){ - perror("asprintf"); - continue; - } - ret = unlink(fullname); - if(ret == -1){ - fprintf(stderr, "unlink(\"%s\"): %s", - fullname, strerror(errno)); - } - free(fullname); - } - } - } - ret = rmdir(tempdir); - if(ret == -1){ - perror("rmdir"); - } - } - - return exitcode; + } + } else { + while(true){ + direntry = readdir(d); + if(direntry == NULL){ + break; + } + /* Skip "." and ".." */ + if(direntry->d_name[0] == '.' + and (direntry->d_name[1] == '\0' + or (direntry->d_name[1] == '.' + and direntry->d_name[2] == '\0'))){ + continue; + } + char *fullname = NULL; + ret = asprintf(&fullname, "%s/%s", tempdir, + direntry->d_name); + if(ret < 0){ + perror("asprintf"); + continue; + } + ret = remove(fullname); + if(ret == -1){ + fprintf(stderr, "remove(\"%s\"): %s\n", fullname, + strerror(errno)); + } + free(fullname); + } + closedir(d); + } + ret = rmdir(tempdir); + if(ret == -1 and errno != ENOENT){ + perror("rmdir"); + } + } + + if(quit_now){ + sigemptyset(&old_sigterm_action.sa_mask); + old_sigterm_action.sa_handler = SIG_DFL; + ret = sigaction(signal_received, &old_sigterm_action, NULL); + if(ret == -1){ + perror("sigaction"); + } + raise(signal_received); + } + + return exitcode; } === renamed file 'plugins.d/password-request.xml' => 'plugins.d/mandos-client.xml' --- plugins.d/password-request.xml 2008-09-04 14:12:10 +0000 +++ plugins.d/mandos-client.xml 2009-02-09 02:01:13 +0000 @@ -1,17 +1,18 @@ - - + + + +%common; ]> Mandos Manual - + Mandos - &VERSION; + &version; &TIMESTAMP; @@ -31,12 +32,13 @@ 2008 + 2009 Teddy Hogeborn Björn Påhlsson - + &COMMANDNAME; 8mandos @@ -45,10 +47,10 @@ &COMMANDNAME; - Client for mandos + Client for Mandos - + &COMMANDNAME; @@ -91,6 +93,10 @@ + + + + @@ -113,18 +119,23 @@
- + DESCRIPTION &COMMANDNAME; is a client program that communicates with mandos8 - to get a password. It uses IPv6 link-local addresses to get - network connectivity, Zeroconf to find servers, and TLS with an - OpenPGP key to ensure authenticity and confidentiality. It - keeps running, trying all servers on the network, until it - receives a satisfactory reply or a TERM signal is received. + to get a password. In slightly more detail, this client program + brings up a network interface, uses the interface’s IPv6 + link-local address to get network connectivity, uses Zeroconf to + find servers on the local network, and communicates with servers + using TLS with an OpenPGP key to ensure authenticity and + confidentiality. This client program keeps running, trying all + servers on the network, until it receives a satisfactory reply + or a TERM signal is received. If no servers are found, or after + all servers have been tried, it waits indefinitely for new + servers to appear. This program is not meant to be run directly; it is really meant @@ -184,14 +195,14 @@ - + Network interface that will be brought up and scanned for - Mandos servers to connect to. The default it + Mandos servers to connect to. The default is eth0. @@ -199,6 +210,21 @@ specifies the interface to use to connect to the address given. + + Note that since this program will normally run in the + initial RAM disk environment, the interface must be an + interface which exists at that stage. Thus, the interface + can not be a pseudo-interface such as br0 + or tun0; such interfaces will not exist + until much later in the boot process, and can not be used + by this program. + + + NAME can be the 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. + @@ -215,7 +241,7 @@ - + @@ -238,7 +264,7 @@ xpointer="priority"/> - + @@ -249,6 +275,22 @@ + + + + + + After bringing the network interface up, the program waits + for the interface to arrive in a running + state before proceeding. During this time, the kernel log + level will be lowered to reduce clutter on the system + console, alleviating any other plugins which might be + using the system console. This option sets the upper + limit of seconds to wait. The default is 2.5 seconds. + + + @@ -284,7 +326,7 @@ - + @@ -296,7 +338,7 @@ - + OVERVIEW @@ -311,10 +353,12 @@ /etc/crypttab, but it would then be impossible to enter a password for the encrypted root disk at the console, since this program does not read from the console - at all. This is why a separate plugin ( - password-prompt - 8mandos) does that, which - will be run in parallel to this one by the plugin runner. + at all. This is why a separate plugin runner ( + plugin-runner + 8mandos) is used to run + both this program and others in in parallel, + one of which will prompt for passwords on + the system console. @@ -327,7 +371,7 @@ program will exit with a non-zero exit status only if a critical error occurs. Otherwise, it will forever connect to new Mandos servers as they appear, trying - to get a decryptable password. + to get a decryptable password and print it. @@ -341,7 +385,7 @@ - + FILES @@ -366,7 +410,7 @@ - + EXAMPLE @@ -408,20 +452,20 @@ Run in debug mode, with a custom key, and do not use Zeroconf - to locate a server; connect directly to the IPv6 address - 2001:db8:f983:bd0b:30de:ae4a:71f2:f672, - port 4711, using interface eth2: + to locate a server; connect directly to the IPv6 link-local + address fe80::aede:48ff:fe71:f6f2, port 4711, + using interface eth2: -&COMMANDNAME; --debug --pubkey keydir/pubkey.txt --seckey keydir/seckey.txt --connect 2001:db8:f983:bd0b:30de:ae4a:71f2:f672:4711 --interface eth2 +&COMMANDNAME; --debug --pubkey keydir/pubkey.txt --seckey keydir/seckey.txt --connect fe80::aede:48ff:fe71:f6f2:4711 --interface eth2 - + SECURITY @@ -447,11 +491,11 @@ The only remaining weak point is that someone with physical access to the client hard drive might turn off the client computer, read the OpenPGP keys directly from the hard drive, - and communicate with the server. The defense against this is - that the server is supposed to notice the client disappearing - and will stop giving out the encrypted data. Therefore, it is - important to set the timeout and checker interval values tightly - on the server. See mandos8. @@ -468,7 +512,7 @@ confidential. - + SEE ALSO @@ -599,8 +643,8 @@ -
+ === modified file 'plugins.d/password-prompt.c' --- plugins.d/password-prompt.c 2008-08-29 05:53:59 +0000 +++ plugins.d/password-prompt.c 2009-09-05 01:05:25 +0000 @@ -1,8 +1,9 @@ -/* -*- coding: utf-8 -*- */ +/* -*- coding: utf-8; mode: c; mode: orgtbl -*- */ /* - * Passprompt - Read a password from the terminal and print it - * - * Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson + * Password-prompt - Read a password from the terminal and print it + * + * Copyright © 2008,2009 Teddy Hogeborn + * Copyright © 2008,2009 Björn Påhlsson * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -32,7 +33,8 @@ #include /* sig_atomic_t, raise(), struct sigaction, sigemptyset(), sigaction(), sigaddset(), SIGINT, - SIGQUIT, SIGHUP, SIGTERM */ + SIGQUIT, SIGHUP, SIGTERM, + raise() */ #include /* NULL, size_t, ssize_t */ #include /* ssize_t */ #include /* EXIT_SUCCESS, EXIT_FAILURE, @@ -50,13 +52,18 @@ ARGP_KEY_ARG, ARGP_KEY_END, ARGP_ERR_UNKNOWN */ -volatile bool quit_now = false; +volatile sig_atomic_t quit_now = 0; +int signal_received; bool debug = false; -const char *argp_program_version = "password-prompt 1.0"; +const char *argp_program_version = "password-prompt " VERSION; const char *argp_program_bug_address = ""; -static void termination_handler(__attribute__((unused))int signum){ - quit_now = true; +static void termination_handler(int signum){ + if(quit_now){ + return; + } + quit_now = 1; + signal_received = signum; } int main(int argc, char **argv){ @@ -78,11 +85,9 @@ .doc = "Debug mode", .group = 3 }, { .name = NULL } }; - - error_t parse_opt (int key, char *arg, struct argp_state *state) { - /* Get the INPUT argument from `argp_parse', which we know is a - pointer to our plugin list pointer. */ - switch (key) { + + error_t parse_opt (int key, char *arg, struct argp_state *state){ + switch (key){ case 'p': prefix = arg; break; @@ -90,7 +95,7 @@ debug = true; break; case ARGP_KEY_ARG: - argp_usage (state); + argp_usage(state); break; case ARGP_KEY_END: break; @@ -99,40 +104,56 @@ } return 0; } - + struct argp argp = { .options = options, .parser = parse_opt, .args_doc = "", .doc = "Mandos password-prompt -- Read and" " output a password" }; - ret = argp_parse (&argp, argc, argv, 0, 0, NULL); - if (ret == ARGP_ERR_UNKNOWN){ + ret = argp_parse(&argp, argc, argv, 0, 0, NULL); + if(ret == ARGP_ERR_UNKNOWN){ fprintf(stderr, "Unknown error while parsing arguments\n"); return EXIT_FAILURE; } } - - if (debug){ + + if(debug){ fprintf(stderr, "Starting %s\n", argv[0]); } - if (debug){ + if(debug){ fprintf(stderr, "Storing current terminal attributes\n"); } - if (tcgetattr(STDIN_FILENO, &t_old) != 0){ + if(tcgetattr(STDIN_FILENO, &t_old) != 0){ perror("tcgetattr"); return EXIT_FAILURE; } sigemptyset(&new_action.sa_mask); - sigaddset(&new_action.sa_mask, SIGINT); - sigaddset(&new_action.sa_mask, SIGHUP); - sigaddset(&new_action.sa_mask, SIGTERM); + ret = sigaddset(&new_action.sa_mask, SIGINT); + if(ret == -1){ + perror("sigaddset"); + return EXIT_FAILURE; + } + ret = sigaddset(&new_action.sa_mask, SIGHUP); + if(ret == -1){ + perror("sigaddset"); + return EXIT_FAILURE; + } + ret = sigaddset(&new_action.sa_mask, SIGTERM); + if(ret == -1){ + perror("sigaddset"); + return EXIT_FAILURE; + } + /* Need to check if the handler is SIG_IGN before handling: + | [[info:libc:Initial Signal Actions]] | + | [[info:libc:Basic Signal Handling]] | + */ ret = sigaction(SIGINT, NULL, &old_action); if(ret == -1){ perror("sigaction"); return EXIT_FAILURE; } - if (old_action.sa_handler != SIG_IGN){ + if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGINT, &new_action, NULL); if(ret == -1){ perror("sigaction"); @@ -144,7 +165,7 @@ perror("sigaction"); return EXIT_FAILURE; } - if (old_action.sa_handler != SIG_IGN){ + if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGHUP, &new_action, NULL); if(ret == -1){ perror("sigaction"); @@ -156,7 +177,7 @@ perror("sigaction"); return EXIT_FAILURE; } - if (old_action.sa_handler != SIG_IGN){ + if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGTERM, &new_action, NULL); if(ret == -1){ perror("sigaction"); @@ -165,22 +186,22 @@ } - if (debug){ + if(debug){ fprintf(stderr, "Removing echo flag from terminal attributes\n"); } t_new = t_old; t_new.c_lflag &= ~ECHO; - if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_new) != 0){ + if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_new) != 0){ perror("tcsetattr-echo"); return EXIT_FAILURE; } - if (debug){ + if(debug){ fprintf(stderr, "Waiting for input from stdin \n"); } while(true){ - if (quit_now){ + if(quit_now){ if(debug){ fprintf(stderr, "Interrupted by signal, exiting.\n"); } @@ -212,10 +233,15 @@ } } ret = getline(&buffer, &n, stdin); - if (ret > 0){ + if(ret > 0){ status = EXIT_SUCCESS; /* Make n = data size instead of allocated buffer size */ n = (size_t)ret; + /* Strip final newline */ + if(n>0 and buffer[n-1] == '\n'){ + buffer[n-1] = '\0'; /* not strictly necessary */ + n--; + } size_t written = 0; while(written < n){ ret = write(STDOUT_FILENO, buffer + written, n - written); @@ -228,8 +254,8 @@ } break; } - if (ret < 0){ - if (errno != EINTR and not feof(stdin)){ + if(ret < 0){ + if(errno != EINTR and not feof(stdin)){ perror("getline"); status = EXIT_FAILURE; break; @@ -239,23 +265,38 @@ read from stdin */ fputc('\n', stderr); if(debug and not quit_now){ - /* If quit_now is true, we were interrupted by a signal, and + /* If quit_now is nonzero, we were interrupted by a signal, and will print that later, so no need to show this too. */ fprintf(stderr, "getline() returned 0, retrying.\n"); } } - if (debug){ + free(buffer); + + if(debug){ fprintf(stderr, "Restoring terminal attributes\n"); } - if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_old) != 0){ + if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_old) != 0){ perror("tcsetattr+echo"); } - if (debug){ + if(quit_now){ + sigemptyset(&old_action.sa_mask); + old_action.sa_handler = SIG_DFL; + ret = sigaction(signal_received, &old_action, NULL); + if(ret == -1){ + perror("sigaction"); + } + raise(signal_received); + } + + if(debug){ fprintf(stderr, "%s is exiting with status %d\n", argv[0], status); } + if(status == EXIT_SUCCESS){ + fputc('\n', stderr); + } return status; } === modified file 'plugins.d/password-prompt.xml' --- plugins.d/password-prompt.xml 2008-09-04 13:36:59 +0000 +++ plugins.d/password-prompt.xml 2009-01-04 21:54:55 +0000 @@ -1,9 +1,10 @@ - + + +%common; ]> @@ -11,7 +12,7 @@ Mandos Manual Mandos - &VERSION; + &version; &TIMESTAMP; @@ -31,6 +32,7 @@ 2008 + 2009 Teddy Hogeborn Björn Påhlsson @@ -83,12 +85,14 @@ DESCRIPTION All &COMMANDNAME; does is prompt for a - password and output any given password to standard output. This - is not very useful on its own. This program is really meant to - run as a plugin in the Mandos - client-side system, where it is used as a fallback and - alternative to retrieving passwords from a Mandos server. + password and output any given password to standard output. + + + This program is not very useful on its own. This program is + really meant to run as a plugin in the Mandos client-side system, where it is used as a + fallback and alternative to retrieving passwords from a + Mandos server. This program is little more than a crypttab 5 - password-request + mandos-client 8mandos plugin-runner 8mandos, === added file 'plugins.d/splashy.c' --- plugins.d/splashy.c 1970-01-01 00:00:00 +0000 +++ plugins.d/splashy.c 2009-02-12 18:56:52 +0000 @@ -0,0 +1,304 @@ +/* -*- coding: utf-8 -*- */ +/* + * Splashy - Read a password from splashy and output it + * + * Copyright © 2008,2009 Teddy Hogeborn + * Copyright © 2008,2009 Björn Påhlsson + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * 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 and + * . + */ + +#define _GNU_SOURCE /* asprintf() */ +#include /* sig_atomic_t, struct sigaction, + sigemptyset(), sigaddset(), SIGINT, + SIGHUP, SIGTERM, sigaction, + SIG_IGN, kill(), SIGKILL */ +#include /* NULL */ +#include /* getenv() */ +#include /* asprintf(), perror() */ +#include /* EXIT_FAILURE, free(), + EXIT_SUCCESS */ +#include /* pid_t, DIR, struct dirent, + ssize_t */ +#include /* opendir(), readdir(), closedir() */ +#include /* intmax_t, strtoimax() */ +#include /* struct stat, lstat(), S_ISLNK */ +#include /* not, or, and */ +#include /* readlink(), fork(), execl(), + sleep(), dup2() STDERR_FILENO, + STDOUT_FILENO, _exit() */ +#include /* memcmp() */ +#include /* errno */ +#include /* waitpid(), WIFEXITED(), + WEXITSTATUS() */ + +sig_atomic_t interrupted_by_signal = 0; + +static void termination_handler(__attribute__((unused))int signum){ + interrupted_by_signal = 1; +} + +int main(__attribute__((unused))int argc, + __attribute__((unused))char **argv){ + int ret = 0; + + /* Create prompt string */ + char *prompt = NULL; + { + const char *const cryptsource = getenv("cryptsource"); + const char *const crypttarget = getenv("crypttarget"); + const char *const prompt_start = "getpass " + "Enter passphrase to unlock the disk"; + + if(cryptsource == NULL){ + if(crypttarget == NULL){ + ret = asprintf(&prompt, "%s: ", prompt_start); + } else { + ret = asprintf(&prompt, "%s (%s): ", prompt_start, + crypttarget); + } + } else { + if(crypttarget == NULL){ + ret = asprintf(&prompt, "%s %s: ", prompt_start, cryptsource); + } else { + ret = asprintf(&prompt, "%s %s (%s): ", prompt_start, + cryptsource, crypttarget); + } + } + if(ret == -1){ + return EXIT_FAILURE; + } + } + + /* Find splashy process */ + pid_t splashy_pid = 0; + { + const char splashy_name[] = "/sbin/splashy"; + DIR *proc_dir = opendir("/proc"); + if(proc_dir == NULL){ + free(prompt); + perror("opendir"); + return EXIT_FAILURE; + } + for(struct dirent *proc_ent = readdir(proc_dir); + proc_ent != NULL; + proc_ent = readdir(proc_dir)){ + pid_t pid; + { + intmax_t tmpmax; + char *tmp; + errno = 0; + tmpmax = strtoimax(proc_ent->d_name, &tmp, 10); + if(errno != 0 or tmp == proc_ent->d_name or *tmp != '\0' + or tmpmax != (pid_t)tmpmax){ + /* Not a process */ + continue; + } + pid = (pid_t)tmpmax; + } + /* Find the executable name by doing readlink() on the + /proc//exe link */ + char exe_target[sizeof(splashy_name)]; + ssize_t sret; + { + char *exe_link; + ret = asprintf(&exe_link, "/proc/%s/exe", proc_ent->d_name); + if(ret == -1){ + perror("asprintf"); + free(prompt); + closedir(proc_dir); + return EXIT_FAILURE; + } + + /* Check that it refers to a symlink owned by root:root */ + struct stat exe_stat; + ret = lstat(exe_link, &exe_stat); + if(ret == -1){ + if(errno == ENOENT){ + free(exe_link); + continue; + } + perror("lstat"); + free(exe_link); + free(prompt); + closedir(proc_dir); + return EXIT_FAILURE; + } + if(not S_ISLNK(exe_stat.st_mode) + or exe_stat.st_uid != 0 + or exe_stat.st_gid != 0){ + free(exe_link); + continue; + } + + sret = readlink(exe_link, exe_target, sizeof(exe_target)); + free(exe_link); + } + if((sret == ((ssize_t)sizeof(exe_target)-1)) + and (memcmp(splashy_name, exe_target, + sizeof(exe_target)-1) == 0)){ + splashy_pid = pid; + break; + } + } + closedir(proc_dir); + } + if(splashy_pid == 0){ + free(prompt); + return EXIT_FAILURE; + } + + /* Set up the signal handler */ + { + struct sigaction old_action, + new_action = { .sa_handler = termination_handler, + .sa_flags = 0 }; + sigemptyset(&new_action.sa_mask); + sigaddset(&new_action.sa_mask, SIGINT); + sigaddset(&new_action.sa_mask, SIGHUP); + sigaddset(&new_action.sa_mask, SIGTERM); + ret = sigaction(SIGINT, NULL, &old_action); + if(ret == -1){ + perror("sigaction"); + free(prompt); + return EXIT_FAILURE; + } + if(old_action.sa_handler != SIG_IGN){ + ret = sigaction(SIGINT, &new_action, NULL); + if(ret == -1){ + perror("sigaction"); + free(prompt); + return EXIT_FAILURE; + } + } + ret = sigaction(SIGHUP, NULL, &old_action); + if(ret == -1){ + perror("sigaction"); + free(prompt); + return EXIT_FAILURE; + } + if(old_action.sa_handler != SIG_IGN){ + ret = sigaction(SIGHUP, &new_action, NULL); + if(ret == -1){ + perror("sigaction"); + free(prompt); + return EXIT_FAILURE; + } + } + ret = sigaction(SIGTERM, NULL, &old_action); + if(ret == -1){ + perror("sigaction"); + free(prompt); + return EXIT_FAILURE; + } + if(old_action.sa_handler != SIG_IGN){ + ret = sigaction(SIGTERM, &new_action, NULL); + if(ret == -1){ + perror("sigaction"); + free(prompt); + return EXIT_FAILURE; + } + } + } + + /* Fork off the splashy command to prompt for password */ + pid_t splashy_command_pid = 0; + if(not interrupted_by_signal){ + splashy_command_pid = fork(); + if(splashy_command_pid == -1){ + if(not interrupted_by_signal){ + perror("fork"); + } + return EXIT_FAILURE; + } + /* Child */ + if(splashy_command_pid == 0){ + const char splashy_command[] = "/sbin/splashy_update"; + ret = execl(splashy_command, splashy_command, prompt, + (char *)NULL); + if(not interrupted_by_signal){ + perror("execl"); + } + free(prompt); + _exit(EXIT_FAILURE); + } + } + + /* Parent */ + free(prompt); + + /* Wait for command to complete */ + if(not interrupted_by_signal and splashy_command_pid != 0){ + int status; + ret = waitpid(splashy_command_pid, &status, 0); + if(ret == -1){ + if(errno != EINTR){ + perror("waitpid"); + } + if(errno == ECHILD){ + splashy_command_pid = 0; + } + } else { + /* The child process has exited */ + splashy_command_pid = 0; + if(not interrupted_by_signal and WIFEXITED(status) + and WEXITSTATUS(status)==0){ + return EXIT_SUCCESS; + } + } + } + kill(splashy_pid, SIGTERM); + if(interrupted_by_signal and splashy_command_pid != 0){ + kill(splashy_command_pid, SIGTERM); + } + sleep(2); + while(kill(splashy_pid, 0) == 0){ + kill(splashy_pid, SIGKILL); + sleep(1); + } + pid_t new_splashy_pid = fork(); + if(new_splashy_pid == 0){ + /* Child; will become new splashy process */ + + /* Make the effective user ID (root) the only user ID instead of + the real user ID (_mandos) */ + ret = setuid(geteuid()); + if(ret == -1){ + perror("setuid"); + } + + setsid(); + ret = chdir("/"); +/* if(fork() != 0){ */ +/* _exit(EXIT_SUCCESS); */ +/* } */ + ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace our stdout */ + if(ret == -1){ + perror("dup2"); + _exit(EXIT_FAILURE); + } + + execl("/sbin/splashy", "/sbin/splashy", "boot", (char *)NULL); + if(not interrupted_by_signal){ + perror("execl"); + } + _exit(EXIT_FAILURE); + } + + return EXIT_FAILURE; +} === added file 'plugins.d/splashy.xml' --- plugins.d/splashy.xml 1970-01-01 00:00:00 +0000 +++ plugins.d/splashy.xml 2009-01-04 21:54:55 +0000 @@ -0,0 +1,283 @@ + + + + +%common; +]> + + + + Mandos Manual + + Mandos + &version; + &TIMESTAMP; + + + Björn + Påhlsson +
+ belorn@fukt.bsnet.se +
+
+ + Teddy + Hogeborn +
+ teddy@fukt.bsnet.se +
+
+
+ + 2008 + 2009 + Teddy Hogeborn + Björn Påhlsson + + +
+ + + &COMMANDNAME; + 8mandos + + + + &COMMANDNAME; + Mandos plugin to use splashy to get a + password. + + + + + &COMMANDNAME; + + + + + DESCRIPTION + + This program prompts for a password using + splashy_update + 8 and outputs any given + password to standard output. If no splashy8 + process can be found, this program will immediately exit with an + exit code indicating failure. + + + This program is not very useful on its own. This program is + really meant to run as a plugin in the Mandos client-side system, where it is used as a + fallback and alternative to retrieving passwords from a + Mandos server. + + + If this program is killed (presumably by + plugin-runner + 8mandos because some other + plugin provided the password), it cannot tell + splashy8 + to abort requesting a password, because + splashy + 8 does not support this. + Therefore, this program will then kill the + running splashy + 8 process and start a + new one, using boot as the only argument. + + + + + OPTIONS + + This program takes no options. + + + + + EXIT STATUS + + If exit status is 0, the output from the program is the password + as it was read. Otherwise, if exit status is other than 0, the + program was interrupted or encountered an error, and any output + so far could be corrupt and/or truncated, and should therefore + be ignored. + + + + + ENVIRONMENT + + + cryptsource + crypttarget + + + If set, these environment variables will be assumed to + contain the source device name and the target device + mapper name, respectively, and will be shown as part of + the prompt. + + + These variables will normally be inherited from + plugin-runner + 8mandos, which will + normally have inherited them from + /scripts/local-top/cryptroot in the + initial RAM disk environment, which will + have set them from parsing kernel arguments and + /conf/conf.d/cryptroot (also in the + initial RAM disk environment), which in turn will have been + created when the initial RAM disk image was created by + /usr/share/initramfs-tools/hooks/cryptroot, by + extracting the information of the root file system from + /etc/crypttab. + + + This behavior is meant to exactly mirror the behavior of + askpass, the default password prompter. + + + + + + + + FILES + + + /sbin/splashy_update + + + This is the command run to retrieve a password from + splashy + 8. See + splashy_update8 + . + + + + + /proc + + + To find the running splashy8 + , this directory will be searched for + numeric entries which will be assumed to be directories. + In all those directories, the exe + entry will be used to determine the name of the running + binary and the effective user and group + ID of the process. See + proc5. + + + + + /sbin/splashy + + + This is the name of the binary which will be searched for + in the process list. See splashy8 + . + + + + + + + + BUGS + + Killing splashy + 8 and starting a new one + is ugly, but necessary as long as it does not support aborting a + password request. + + + + + EXAMPLE + + Note that normally, this program will not be invoked directly, + but instead started by the Mandos plugin-runner8mandos + . + + + + This program takes no options. + + + &COMMANDNAME; + + + + + + SECURITY + + If this program is killed by a signal, it will kill the process + ID which at the start of this program was + determined to run splashy8 + as root (see also ). There is a very + slight risk that, in the time between those events, that process + ID was freed and then taken up by another + process; the wrong process would then be killed. Now, this + program can only be killed by the user who started it; see + plugin-runner + 8mandos. This program + should therefore be started by a completely separate + non-privileged user, and no other programs should be allowed to + run as that special user. This means that it is not recommended + to use the user "nobody" to start this program, as other + possibly less trusted programs could be running as "nobody", and + they would then be able to kill this program, triggering the + killing of the process ID which may or may not + be splashy + 8. + + + The only other thing that could be considered worthy of note is + this: This program is meant to be run by + plugin-runner8mandos, and will, when run + standalone, outside, in a normal environment, immediately output + on its standard output any presumably secret password it just + received. Therefore, when running this program standalone + (which should never normally be done), take care not to type in + any real secret password by force of habit, since it would then + immediately be shown as output. + + + + + SEE ALSO + + crypttab + 5, + plugin-runner + 8mandos, + proc + 5, + splashy + 8, + splashy_update + 8 + + +
+ + + + + === removed file 'plugins.d/usplash' --- plugins.d/usplash 2008-08-14 02:24:59 +0000 +++ plugins.d/usplash 1970-01-01 00:00:00 +0000 @@ -1,42 +0,0 @@ -#!/bin/sh -e - -# If not on a tty, then get rid of possibly disrupting stderr output -if ! tty -s; then - exec 2>/dev/null -fi - -test -x /sbin/usplash - -usplash="`pidof usplash -o $$`" -test -n "$usplash" - -# We get some variables from cryptsetup: -# $cryptsource the device node, like "/dev/sda3" -# $crypttarget the device mapper name, like "sda3_crypt". - -prompt="Enter passphrase to unlock" -if [ -n "$crypttarget" ]; then - prompt="$prompt the disk $crypttarget" -fi -if [ -n "$cryptsource" ]; then - prompt="$prompt ($cryptsource)" -fi - -splash_input_password(){ - test -p /dev/.initramfs/usplash_outfifo || return 1 - /sbin/usplash_write "INPUTQUIET $1" || return 1 - cat /dev/.initramfs/usplash_outfifo 2> /dev/null || return 1 -} - -# Usplash keeps waiting for input even if some other plugin provided -# the password, so we must kill it -trap "kill -TERM $usplash; sleep 2; kill -KILL $usplash; - kill -TERM $$" TERM HUP - -password="`splash_input_password \"$prompt: \" password`" - -trap - TERM - -/sbin/usplash_write "TIMEOUT 15" - -echo -n "$password" === added file 'plugins.d/usplash.c' --- plugins.d/usplash.c 1970-01-01 00:00:00 +0000 +++ plugins.d/usplash.c 2009-02-12 18:56:52 +0000 @@ -0,0 +1,531 @@ +/* -*- coding: utf-8 -*- */ +/* + * Usplash - Read a password from usplash and output it + * + * Copyright © 2008,2009 Teddy Hogeborn + * Copyright © 2008,2009 Björn Påhlsson + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * 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 and + * . + */ + +#define _GNU_SOURCE /* asprintf() */ +#include /* sig_atomic_t, struct sigaction, + sigemptyset(), sigaddset(), SIGINT, + SIGHUP, SIGTERM, sigaction(), + SIG_IGN, kill(), SIGKILL */ +#include /* bool, false, true */ +#include /* open(), O_WRONLY, O_RDONLY */ +#include /* and, or, not*/ +#include /* errno, EINTR */ +#include /* size_t, ssize_t, pid_t, DIR, struct + dirent */ +#include /* NULL */ +#include /* strlen(), memcmp() */ +#include /* asprintf(), perror() */ +#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() */ +#include /* opendir(), readdir(), closedir() */ +#include /* intmax_t, strtoimax() */ +#include /* struct stat, lstat(), S_ISLNK */ + +sig_atomic_t interrupted_by_signal = 0; + +static void termination_handler(__attribute__((unused))int signum){ + interrupted_by_signal = 1; +} + +static bool usplash_write(const char *cmd, const char *arg){ + /* + * usplash_write("TIMEOUT", "15") will write "TIMEOUT 15\0" + * usplash_write("PULSATE", NULL) will write "PULSATE\0" + * SEE ALSO + * usplash_write(8) + */ + int ret; + int fifo_fd; + do{ + fifo_fd = open("/dev/.initramfs/usplash_fifo", O_WRONLY); + if(fifo_fd == -1 and (errno != EINTR or interrupted_by_signal)){ + return false; + } + }while(fifo_fd == -1); + + const char *cmd_line; + size_t cmd_line_len; + char *cmd_line_alloc = NULL; + if(arg == NULL){ + cmd_line = cmd; + cmd_line_len = strlen(cmd); + }else{ + do{ + ret = asprintf(&cmd_line_alloc, "%s %s", cmd, arg); + if(ret == -1 and (errno != EINTR or interrupted_by_signal)){ + int e = errno; + close(fifo_fd); + errno = e; + return false; + } + }while(ret == -1); + cmd_line = cmd_line_alloc; + cmd_line_len = (size_t)ret + 1; + } + + size_t written = 0; + ssize_t sret = 0; + while(not interrupted_by_signal and written < cmd_line_len){ + sret = write(fifo_fd, cmd_line + written, + cmd_line_len - written); + if(sret == -1){ + if(errno != EINTR or interrupted_by_signal){ + int e = errno; + close(fifo_fd); + free(cmd_line_alloc); + errno = e; + return false; + } else { + continue; + } + } + written += (size_t)sret; + } + free(cmd_line_alloc); + do{ + ret = close(fifo_fd); + if(ret == -1 and (errno != EINTR or interrupted_by_signal)){ + return false; + } + }while(ret == -1); + if(interrupted_by_signal){ + return false; + } + return true; +} + +int main(__attribute__((unused))int argc, + __attribute__((unused))char **argv){ + int ret = 0; + ssize_t sret; + bool an_error_occured = false; + + /* Create prompt string */ + char *prompt = NULL; + { + const char *const cryptsource = getenv("cryptsource"); + const char *const crypttarget = getenv("crypttarget"); + const char prompt_start[] = "Enter passphrase to unlock the disk"; + + if(cryptsource == NULL){ + if(crypttarget == NULL){ + ret = asprintf(&prompt, "%s: ", prompt_start); + } else { + ret = asprintf(&prompt, "%s (%s): ", prompt_start, + crypttarget); + } + } else { + if(crypttarget == NULL){ + ret = asprintf(&prompt, "%s %s: ", prompt_start, cryptsource); + } else { + ret = asprintf(&prompt, "%s %s (%s): ", prompt_start, + cryptsource, crypttarget); + } + } + if(ret == -1){ + return EXIT_FAILURE; + } + } + + /* Find usplash process */ + pid_t usplash_pid = 0; + char *cmdline = NULL; + size_t cmdline_len = 0; + const char usplash_name[] = "/sbin/usplash"; + { + DIR *proc_dir = opendir("/proc"); + if(proc_dir == NULL){ + free(prompt); + perror("opendir"); + return EXIT_FAILURE; + } + for(struct dirent *proc_ent = readdir(proc_dir); + proc_ent != NULL; + proc_ent = readdir(proc_dir)){ + pid_t pid; + { + intmax_t tmpmax; + char *tmp; + errno = 0; + tmpmax = strtoimax(proc_ent->d_name, &tmp, 10); + if(errno != 0 or tmp == proc_ent->d_name or *tmp != '\0' + or tmpmax != (pid_t)tmpmax){ + /* Not a process */ + continue; + } + pid = (pid_t)tmpmax; + } + /* Find the executable name by doing readlink() on the + /proc//exe link */ + char exe_target[sizeof(usplash_name)]; + { + /* create file name string */ + char *exe_link; + ret = asprintf(&exe_link, "/proc/%s/exe", proc_ent->d_name); + if(ret == -1){ + perror("asprintf"); + free(prompt); + closedir(proc_dir); + return EXIT_FAILURE; + } + + /* Check that it refers to a symlink owned by root:root */ + struct stat exe_stat; + ret = lstat(exe_link, &exe_stat); + if(ret == -1){ + if(errno == ENOENT){ + free(exe_link); + continue; + } + perror("lstat"); + free(exe_link); + free(prompt); + closedir(proc_dir); + return EXIT_FAILURE; + } + if(not S_ISLNK(exe_stat.st_mode) + or exe_stat.st_uid != 0 + or exe_stat.st_gid != 0){ + free(exe_link); + continue; + } + + sret = readlink(exe_link, exe_target, sizeof(exe_target)); + free(exe_link); + } + if((sret == ((ssize_t)sizeof(exe_target)-1)) + and (memcmp(usplash_name, exe_target, + sizeof(exe_target)-1) == 0)){ + usplash_pid = pid; + /* Read and save the command line of usplash in "cmdline" */ + { + /* Open /proc//cmdline */ + int cl_fd; + { + char *cmdline_filename; + ret = asprintf(&cmdline_filename, "/proc/%s/cmdline", + proc_ent->d_name); + if(ret == -1){ + perror("asprintf"); + free(prompt); + closedir(proc_dir); + return EXIT_FAILURE; + } + cl_fd = open(cmdline_filename, O_RDONLY); + if(cl_fd == -1){ + perror("open"); + free(cmdline_filename); + free(prompt); + closedir(proc_dir); + return EXIT_FAILURE; + } + free(cmdline_filename); + } + size_t cmdline_allocated = 0; + char *tmp; + const size_t blocksize = 1024; + do{ + if(cmdline_len + blocksize > cmdline_allocated){ + tmp = realloc(cmdline, cmdline_allocated + blocksize); + if(tmp == NULL){ + perror("realloc"); + free(cmdline); + free(prompt); + closedir(proc_dir); + return EXIT_FAILURE; + } + cmdline = tmp; + cmdline_allocated += blocksize; + } + sret = read(cl_fd, cmdline + cmdline_len, + cmdline_allocated - cmdline_len); + if(sret == -1){ + perror("read"); + free(cmdline); + free(prompt); + closedir(proc_dir); + return EXIT_FAILURE; + } + cmdline_len += (size_t)sret; + } while(sret != 0); + close(cl_fd); + } + break; + } + } + closedir(proc_dir); + } + if(usplash_pid == 0){ + free(prompt); + return EXIT_FAILURE; + } + + /* Set up the signal handler */ + { + struct sigaction old_action, + new_action = { .sa_handler = termination_handler, + .sa_flags = 0 }; + sigemptyset(&new_action.sa_mask); + sigaddset(&new_action.sa_mask, SIGINT); + sigaddset(&new_action.sa_mask, SIGHUP); + sigaddset(&new_action.sa_mask, SIGTERM); + ret = sigaction(SIGINT, NULL, &old_action); + if(ret == -1){ + perror("sigaction"); + free(prompt); + return EXIT_FAILURE; + } + if(old_action.sa_handler != SIG_IGN){ + ret = sigaction(SIGINT, &new_action, NULL); + if(ret == -1){ + perror("sigaction"); + free(prompt); + return EXIT_FAILURE; + } + } + ret = sigaction(SIGHUP, NULL, &old_action); + if(ret == -1){ + perror("sigaction"); + free(prompt); + return EXIT_FAILURE; + } + if(old_action.sa_handler != SIG_IGN){ + ret = sigaction(SIGHUP, &new_action, NULL); + if(ret == -1){ + perror("sigaction"); + free(prompt); + return EXIT_FAILURE; + } + } + ret = sigaction(SIGTERM, NULL, &old_action); + if(ret == -1){ + perror("sigaction"); + free(prompt); + return EXIT_FAILURE; + } + if(old_action.sa_handler != SIG_IGN){ + ret = sigaction(SIGTERM, &new_action, NULL); + if(ret == -1){ + perror("sigaction"); + free(prompt); + return EXIT_FAILURE; + } + } + } + + /* Write command to FIFO */ + if(not interrupted_by_signal){ + if(not usplash_write("TIMEOUT", "0") + and (errno != EINTR)){ + perror("usplash_write"); + an_error_occured = true; + } + } + if(not interrupted_by_signal and not an_error_occured){ + if(not usplash_write("INPUTQUIET", prompt) + and (errno != EINTR)){ + perror("usplash_write"); + an_error_occured = true; + } + } + free(prompt); + + /* This is not really a loop; while() is used to be able to "break" + out of it; those breaks are marked "Big" */ + while(not interrupted_by_signal and not an_error_occured){ + char *buf = NULL; + size_t buf_len = 0; + + /* Open FIFO */ + int fifo_fd; + do{ + fifo_fd = open("/dev/.initramfs/usplash_outfifo", O_RDONLY); + if(fifo_fd == -1){ + if(errno != EINTR){ + perror("open"); + an_error_occured = true; + break; + } + if(interrupted_by_signal){ + break; + } + } + }while(fifo_fd == -1); + if(interrupted_by_signal or an_error_occured){ + break; /* Big */ + } + + /* Read from FIFO */ + size_t buf_allocated = 0; + const size_t blocksize = 1024; + do{ + if(buf_len + blocksize > buf_allocated){ + char *tmp = realloc(buf, buf_allocated + blocksize); + if(tmp == NULL){ + perror("realloc"); + an_error_occured = true; + break; + } + buf = tmp; + buf_allocated += blocksize; + } + do{ + sret = read(fifo_fd, buf + buf_len, buf_allocated - buf_len); + if(sret == -1){ + if(errno != EINTR){ + perror("read"); + an_error_occured = true; + break; + } + if(interrupted_by_signal){ + break; + } + } + }while(sret == -1); + if(interrupted_by_signal or an_error_occured){ + break; + } + + buf_len += (size_t)sret; + }while(sret != 0); + close(fifo_fd); + if(interrupted_by_signal or an_error_occured){ + break; /* Big */ + } + + if(not usplash_write("TIMEOUT", "15") + and (errno != EINTR)){ + perror("usplash_write"); + an_error_occured = true; + } + if(interrupted_by_signal or an_error_occured){ + break; /* Big */ + } + + /* Print password to stdout */ + size_t written = 0; + while(written < buf_len){ + do{ + sret = write(STDOUT_FILENO, buf + written, buf_len - written); + if(sret == -1){ + if(errno != EINTR){ + perror("write"); + an_error_occured = true; + break; + } + if(interrupted_by_signal){ + break; + } + } + }while(sret == -1); + if(interrupted_by_signal or an_error_occured){ + break; + } + written += (size_t)sret; + } + free(buf); + if(not interrupted_by_signal and not an_error_occured){ + free(cmdline); + return EXIT_SUCCESS; + } + break; /* Big */ + } /* end of non-loop while() */ + + /* If we got here, an error or interrupt must have happened */ + + /* 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; + } + /* Kill old usplash */ + kill(usplash_pid, SIGTERM); + sleep(2); + while(kill(usplash_pid, 0) == 0){ + kill(usplash_pid, SIGKILL); + sleep(1); + } + pid_t new_usplash_pid = fork(); + if(new_usplash_pid == 0){ + /* Child; will become new usplash process */ + + /* Make the effective user ID (root) the only user ID instead of + the real user ID (_mandos) */ + ret = setuid(geteuid()); + if(ret == -1){ + perror("setuid"); + } + + setsid(); + ret = chdir("/"); +/* if(fork() != 0){ */ +/* _exit(EXIT_SUCCESS); */ +/* } */ + ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace our stdout */ + if(ret == -1){ + perror("dup2"); + _exit(EXIT_FAILURE); + } + + execv(usplash_name, cmdline_argv); + if(not interrupted_by_signal){ + perror("execv"); + } + free(cmdline); + free(cmdline_argv); + _exit(EXIT_FAILURE); + } + free(cmdline); + free(cmdline_argv); + sleep(2); + if(not usplash_write("PULSATE", NULL) + and (errno != EINTR)){ + perror("usplash_write"); + } + + return EXIT_FAILURE; +} === added file 'plugins.d/usplash.xml' --- plugins.d/usplash.xml 1970-01-01 00:00:00 +0000 +++ plugins.d/usplash.xml 2009-01-04 21:54:55 +0000 @@ -0,0 +1,297 @@ + + + + +%common; +]> + + + + Mandos Manual + + Mandos + &version; + &TIMESTAMP; + + + Björn + Påhlsson +
+ belorn@fukt.bsnet.se +
+
+ + Teddy + Hogeborn +
+ teddy@fukt.bsnet.se +
+
+
+ + 2008 + 2009 + Teddy Hogeborn + Björn Påhlsson + + +
+ + + &COMMANDNAME; + 8mandos + + + + &COMMANDNAME; + Mandos plugin to use usplash to get a + password. + + + + + &COMMANDNAME; + + + + + DESCRIPTION + + This program prompts for a password using + usplash8 + and outputs any given password to standard + output. If no usplash8 + process can be found, this program will immediately exit with an + exit code indicating failure. + + + This program is not very useful on its own. This program is + really meant to run as a plugin in the Mandos client-side system, where it is used as a + fallback and alternative to retrieving passwords from a + Mandos server. + + + If this program is killed (presumably by + plugin-runner + 8mandos because some other + plugin provided the password), it cannot tell + usplash8 + to abort requesting a password, because + usplash + 8 does not support this. + Therefore, this program will then kill the + running usplash + 8 process and start a + new one using the same command line + arguments as the old one was using. + + + + + OPTIONS + + This program takes no options. + + + + + EXIT STATUS + + If exit status is 0, the output from the program is the password + as it was read. Otherwise, if exit status is other than 0, the + program was interrupted or encountered an error, and any output + so far could be corrupt and/or truncated, and should therefore + be ignored. + + + + + ENVIRONMENT + + + cryptsource + crypttarget + + + If set, these environment variables will be assumed to + contain the source device name and the target device + mapper name, respectively, and will be shown as part of + the prompt. + + + These variables will normally be inherited from + plugin-runner + 8mandos, which will + normally have inherited them from + /scripts/local-top/cryptroot in the + initial RAM disk environment, which will + have set them from parsing kernel arguments and + /conf/conf.d/cryptroot (also in the + initial RAM disk environment), which in turn will have been + created when the initial RAM disk image was created by + /usr/share/initramfs-tools/hooks/cryptroot, by + extracting the information of the root file system from + /etc/crypttab. + + + This behavior is meant to exactly mirror the behavior of + askpass, the default password prompter. + + + + + + + + FILES + + + /dev/.initramfs/usplash_fifo + + + This is the FIFO to where this program + will write the commands for usplash8 + . See fifo7 + . + + + + + /dev/.initramfs/usplash_outfifo + + + This is the FIFO where this program + will read the password from usplash8 + . See fifo7 + . + + + + + /proc + + + To find the running usplash8 + , this directory will be searched for + numeric entries which will be assumed to be directories. + In all those directories, the exe and + cmdline entries will be used to + determine the name of the running binary, effective user + and group ID, and the command line + arguments. See proc5 + . + + + + + /sbin/usplash + + + This is the name of the binary which will be searched for + in the process list. See usplash8 + . + + + + + + + + BUGS + + Killing usplash + 8 and starting a new one + is ugly, but necessary as long as it does not support aborting a + password request. + + + + + EXAMPLE + + Note that normally, this program will not be invoked directly, + but instead started by the Mandos plugin-runner8mandos + . + + + + This program takes no options. + + + &COMMANDNAME; + + + + + + SECURITY + + If this program is killed by a signal, it will kill the process + ID which at the start of this program was + determined to run usplash8 + as root (see also ). There is a very + slight risk that, in the time between those events, that process + ID was freed and then taken up by another + process; the wrong process would then be killed. Now, this + program can only be killed by the user who started it; see + plugin-runner + 8mandos. This program + should therefore be started by a completely separate + non-privileged user, and no other programs should be allowed to + run as that special user. This means that it is not recommended + to use the user "nobody" to start this program, as other + possibly less trusted programs could be running as "nobody", and + they would then be able to kill this program, triggering the + killing of the process ID which may or may not + be usplash + 8. + + + The only other thing that could be considered worthy of note is + this: This program is meant to be run by + plugin-runner8mandos, and will, when run + standalone, outside, in a normal environment, immediately output + on its standard output any presumably secret password it just + received. Therefore, when running this program standalone + (which should never normally be done), take care not to type in + any real secret password by force of habit, since it would then + immediately be shown as output. + + + + + SEE ALSO + + crypttab + 5, + fifo + 7, + plugin-runner + 8mandos, + proc + 5, + usplash + 8 + + +
+ + + + +