=== modified file 'TODO' --- TODO 2009-05-21 20:20:20 +0000 +++ TODO 2009-09-17 11:47:22 +0000 @@ -1,7 +1,27 @@ -*- org -*- * mandos-client -** TODO [#A] Clean up /tmp directory on signal +** TODO [#B] use scandir(3) instead of readdir(3) +** TODO [#B] Prefix all debug output with argv[0] +** TODO [#B] Retry a server which has a non-definite reply: +*** A closed connection during the TLS handshake +*** A TCP timeout +** TODO [#B] Use capabilities instead of seteuid(). + +* splashy +** TODO [#B] use scandir(3) instead of readdir(3) +** TODO [#B] Prefix all debug output with "Mandos plugin " + argv[0] + +* usplash +** TODO [#B] use scandir(3) instead of readdir(3) +** TODO [#B] Prefix all debug output with "Mandos plugin " + argv[0] + +* askpass-fifo +** TODO [#B] Prefix all debug output with "Mandos plugin " + argv[0] +** TODO [#B] Drop privileges after opening FIFO. + +* password-prompt +** TODO [#B] Prefix all debug output with "Mandos plugin " + argv[0] * plugin-runner ** TODO [#B] use scandir(3) instead of readdir(3) @@ -25,6 +45,12 @@ [[info:standards:Option%20Table][Table of Long Options]] ** TODO Date+time on console log messages :BUGS: Is this the default? +** TODO DBusServiceObjectUsingSuper +** Global enable/disable flag +** By-client countdown on secrets given +** Fix problem with fsck taking a really long time + Whenever a client successfully gets a secret it could get a + one-time timeout boost to allow for an fsck-incurred delay * mandos.xml ** [[file:mandos.xml::XXX][Document D-Bus interface]] @@ -35,7 +61,11 @@ *** Handle "no D-Bus server" and/or "no Mandos server found" better *** [#B] --dump option -* Curses interface +* mandos-monitor +** D-Bus mail loop w/ signal receiver +** Snack/Newt client data displayer +*** Client Widgets +*** Properties popup * mandos-keygen ** TODO Loop until passwords match when run interactively @@ -44,6 +74,10 @@ ** TODO [#B] "--test" option For testing decryption before rebooting. +* Makefile +** Implement DEB_BUILD_OPTIONS + http://www.debian.org/doc/debian-policy/ch-source.html#s-debianrules-options + * Package ** /usr/share/initramfs-tools/hooks/mandos *** TODO [#C] use same file name rules as run-parts(8) === modified file 'debian/control' --- debian/control 2009-05-21 20:20:20 +0000 +++ debian/control 2009-09-17 11:47:22 +0000 @@ -7,7 +7,7 @@ Build-Depends: debhelper (>= 7), docbook-xml, docbook-xsl, libavahi-core-dev, libgpgme11-dev, libgnutls-dev, xsltproc, pkg-config -Standards-Version: 3.8.1 +Standards-Version: 3.8.3 Vcs-Bzr: http://ftp.fukt.bsnet.se/pub/mandos/trunk Vcs-Browser: http://bzr.fukt.bsnet.se/loggerhead/mandos/trunk/files Homepage: http://www.fukt.bsnet.se/mandos === modified file 'debian/mandos-client.README.Debian' --- debian/mandos-client.README.Debian 2009-02-09 02:01:13 +0000 +++ debian/mandos-client.README.Debian 2009-09-08 06:28:20 +0000 @@ -1,27 +1,42 @@ -* 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. - -* 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 +* Choose the Client Network Interface + + You MUST make sure that the correct network interface is specified + in the DEVICE setting in the "/etc/initramfs-tools/initramfs.conf" + file. *If* this is changed, it will be necessary to update the + initrd image by running the command + + update-initramfs -k all -u + + The device can be overridden at boot time on the Linux kernel + command line using the sixth colon-separated field of the "ip=" + option; for exact syntax, read the documentation in the file + "/usr/share/doc/linux-doc-*/Documentation/filesystems/nfsroot.txt", + available in the "linux-doc-*" package. + + Note that since this network interface is used in the initial RAM + disk environment, the network interface *must* exist at that stage. + Thus, the interface can *not* be a pseudo-interface such as "br0" or + "tun0"; instead, a real interface (such as "eth0") must be used. + +* Adding a Client Password to the Server + + The server must be given a password to give back to the client on + boot time. This password must be a one which can be used to unlock + the root file system device. On the *client*, run this command: + + mandos-keygen --password + + It will prompt for a password and output a config file section. + This output should be copied to the Mandos server and added to the + file "/etc/mandos/clients.conf" there. + +* Testing that it Works (Without Rebooting) + + After the server has been started with this client's key added, it + is possible to verify that the correct password will be received by this client by running the command, on the client: - # /usr/lib/mandos/plugins.d/mandos-client \ + /usr/lib/mandos/plugins.d/mandos-client \ --pubkey=/etc/keys/mandos/pubkey.txt \ --seckey=/etc/keys/mandos/seckey.txt; echo @@ -31,16 +46,16 @@ * 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: + 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 + update-initramfs -k all -u -* Do *NOT* Edit /etc/crypttab +* 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 + 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. @@ -69,4 +84,4 @@ work, "--options-for=mandos-client:--connect=
:" needs to be manually added to the file "/etc/mandos/plugin-runner.conf". - -- Teddy Hogeborn , Mon, 9 Feb 2009 00:36:55 +0100 + -- Teddy Hogeborn , Tue, 8 Sep 2009 08:25:58 +0200 === modified file 'debian/mandos-client.postinst' --- debian/mandos-client.postinst 2009-05-17 03:13:49 +0000 +++ debian/mandos-client.postinst 2009-05-24 23:36:15 +0000 @@ -33,13 +33,15 @@ # Add user and group add_mandos_user(){ # Rename old "mandos" user and group - case "`getent passwd mandos`" in - *:Mandos\ password\ system,,,:/nonexistent:/bin/false) - usermod --login _mandos mandos - groupmod --new-name _mandos mandos - return - ;; - esac + 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 \ === modified file 'debian/mandos.README.Debian' --- debian/mandos.README.Debian 2009-01-04 22:15:01 +0000 +++ debian/mandos.README.Debian 2009-09-08 06:28:20 +0000 @@ -1,7 +1,10 @@ The Mandos server is useless without at least one configured client in /etc/mandos/clients.conf. To create one, install the "mandos-client" -package on a client computer, and run "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 +package on a client computer, and run the command + + # mandos-keygen --password + +there to get a config file stanza. Append the output of that command +to the file "/etc/mandos/clients.conf" on the Mandos server. + + -- Teddy Hogeborn , Tue, 8 Sep 2009 06:57:45 +0200 === modified file 'debian/mandos.postinst' --- debian/mandos.postinst 2009-01-18 00:16:57 +0000 +++ debian/mandos.postinst 2009-05-24 23:28:04 +0000 @@ -18,12 +18,14 @@ case "$1" in configure) # Rename old "mandos" user and group - case "`getent passwd mandos`" in - *:Mandos\ password\ system,,,:/nonexistent:/bin/false) - usermod --login _mandos mandos - groupmod --new-name _mandos mandos - ;; - esac + 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 \ === modified file 'init.d-mandos' --- init.d-mandos 2008-09-21 12:04:02 +0000 +++ init.d-mandos 2009-09-16 23:28:39 +0000 @@ -1,8 +1,8 @@ #! /bin/sh ### BEGIN INIT INFO # Provides: mandos -# Required-Start: $remote_fs avahi-daemon -# Required-Stop: $remote_fs +# Required-Start: $remote_fs $syslog avahi +# Required-Stop: $remote_fs $syslog avahi # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Mandos server === modified file 'initramfs-tools-hook' --- initramfs-tools-hook 2009-02-25 01:14:29 +0000 +++ initramfs-tools-hook 2009-09-07 23:48:17 +0000 @@ -51,16 +51,13 @@ exit 1 fi -mandos_user="`{ getent passwd _mandos \ - || getent passwd mandos \ - || getent passwd nobody \ - || echo ::65534::::; } \ - | cut --delimiter=: --fields=3 --only-delimited`" -mandos_group="`{ getent group _mandos \ - || getent group mandos \ - || getent group nogroup \ - || echo ::65534:; } \ - | cut --delimiter=: --fields=3 --only-delimited`" +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 @@ -91,8 +88,9 @@ continue fi case "$base" in - *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) : ;; - "*") : ;; + *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) + : ;; + "*") echo "W: Mandos client plugin directory is empty." >&2 ;; *) copy_exec "$file" "${PLUGINDIR}" ;; esac done @@ -101,7 +99,8 @@ for file in /etc/mandos/plugins.d/*; do base="`basename \"$file\"`" case "$base" in - *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) : ;; + *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) + : ;; "*") : ;; *) copy_exec "$file" "${PLUGINDIR}" ;; esac @@ -123,19 +122,13 @@ done if [ ${mandos_user} != 65534 ]; then - PLUGINRUNNERCONF="${DESTDIR}${CONFDIR}/plugin-runner.conf" - cat <<-EOF >> "$PLUGINRUNNERCONF" - - --userid=${mandos_user} -EOF + sed --in-place --expression="1i--userid=${mandos_user}" \ + "${DESTDIR}${CONFDIR}/plugin-runner.conf" fi if [ ${mandos_group} != 65534 ]; then - PLUGINRUNNERCONF="${DESTDIR}${CONFDIR}/plugin-runner.conf" - cat <<-EOF >> "$PLUGINRUNNERCONF" - - --groupid=${mandos_group} -EOF + sed --in-place --expression="1i--groupid=${mandos_group}" \ + "${DESTDIR}${CONFDIR}/plugin-runner.conf" fi # Key files === modified file 'initramfs-tools-script' --- initramfs-tools-script 2009-02-09 02:01:13 +0000 +++ initramfs-tools-script 2009-09-16 23:28:39 +0000 @@ -10,7 +10,6 @@ # eventually be "/scripts/init-premount/mandos" in the initrd.img # file. -# No initramfs pre-requirements. PREREQ="udev" prereqs() { === modified file 'mandos' --- mandos 2009-05-23 05:22:05 +0000 +++ mandos 2009-09-17 11:47:22 +0000 @@ -6,9 +6,9 @@ # This program is partly derived from an example program for an Avahi # service publisher, downloaded from # . This includes the -# methods "add" and "remove" in the "AvahiService" class, the -# "server_state_changed" and "entry_group_state_changed" functions, -# and some lines in "main". +# methods "add", "remove", "server_state_changed", +# "entry_group_state_changed", "cleanup", and "activate" in the +# "AvahiService" class, and some lines in "main". # # Everything else is # Copyright © 2008,2009 Teddy Hogeborn @@ -33,7 +33,7 @@ from __future__ import division, with_statement, absolute_import -import SocketServer +import SocketServer as socketserver import socket import optparse import datetime @@ -44,12 +44,11 @@ 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 @@ -57,6 +56,9 @@ import logging.handlers import pwd from contextlib import closing +import struct +import fcntl +import functools import dbus import dbus.service @@ -66,20 +68,30 @@ import ctypes 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('mandos') +logger = logging.Logger(u'mandos') syslogger = (logging.handlers.SysLogHandler (facility = logging.handlers.SysLogHandler.LOG_DAEMON, address = "/dev/log")) syslogger.setFormatter(logging.Formatter - ('Mandos [%(process)d]: %(levelname)s:' - ' %(message)s')) + (u'Mandos [%(process)d]: %(levelname)s:' + u' %(message)s')) logger.addHandler(syslogger) console = logging.StreamHandler() -console.setFormatter(logging.Formatter('%(name)s [%(process)d]:' - ' %(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): @@ -98,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 @@ -111,11 +124,14 @@ max_renames: integer; maximum number of renames rename_count: integer; counter so we only rename after collisions a sensible number of times + group: D-Bus Entry Group + server: D-Bus Server + bus: dbus.SystemBus() """ def __init__(self, interface = avahi.IF_UNSPEC, name = None, servicetype = None, port = None, TXT = None, - domain = "", host = "", max_renames = 32768, - protocol = avahi.PROTO_UNSPEC): + domain = u"", host = u"", max_renames = 32768, + protocol = avahi.PROTO_UNSPEC, bus = None): self.interface = interface self.name = name self.type = servicetype @@ -126,6 +142,9 @@ 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: @@ -133,53 +152,81 @@ u" after %i retries, exiting.", self.rename_count) raise AvahiServiceError(u"Too many renames") - self.name = server.GetAlternativeServiceName(self.name) + self.name = self.server.GetAlternativeServiceName(self.name) logger.info(u"Changing Zeroconf service name to %r ...", - str(self.name)) + unicode(self.name)) syslogger.setFormatter(logging.Formatter - ('Mandos (%s): %%(levelname)s:' - ' %%(message)s' % self.name)) + (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 - self.protocol, # 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 - - -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) - - -class Client(dbus.service.Object): + 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 and D-Bus identifiers @@ -206,23 +253,24 @@ runtime with vars(self) as dict, so that for instance %(name)s can be used in the command. current_checker_command: string; current running checker_command - use_dbus: bool(); Whether to provide D-Bus interface and signals - dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus """ + + @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.timeout.days * 24 * 60 * 60 * 1000) - + (self.timeout.seconds * 1000) - + (self.timeout.microseconds // 1000)) + return self._datetime_to_milliseconds(self.timeout) def interval_milliseconds(self): "Return the 'interval' attribute in milliseconds" - return ((self.interval.days * 24 * 60 * 60 * 1000) - + (self.interval.seconds * 1000) - + (self.interval.microseconds // 1000)) + return self._datetime_to_milliseconds(self.interval) - def __init__(self, name = None, disable_hook=None, config=None, - use_dbus=True): + 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.""" @@ -230,50 +278,43 @@ if config is None: config = {} logger.debug(u"Creating client %r", self.name) - self.use_dbus = False # During __init__ # Uppercase and remove spaces from fingerprint for later # comparison purposes with return value from the fingerprint() # function - self.fingerprint = (config["fingerprint"].upper() + 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: + 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["secfile"])))) as secfile: + (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.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.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.disable_initiator_tag = None self.checker_callback_tag = None - self.checker_command = config["checker"] + self.checker_command = config[u"checker"] self.current_checker_command = None self.last_connect = None - # Only now, when this client is initialized, can it show up on - # the D-Bus - self.use_dbus = use_dbus - if self.use_dbus: - self.dbus_object_path = (dbus.ObjectPath - ("/clients/" - + self.name.replace(".", "_"))) - dbus.service.Object.__init__(self, bus, - self.dbus_object_path) 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. @@ -287,33 +328,22 @@ (self.timeout_milliseconds(), self.disable)) self.enabled = True - if self.use_dbus: - # Emit D-Bus signals - self.PropertyChanged(dbus.String(u"enabled"), - dbus.Boolean(True, variant_level=1)) - self.PropertyChanged(dbus.String(u"last_enabled"), - (_datetime_to_dbus(self.last_enabled, - variant_level=1))) def disable(self): """Disable this client.""" if not getattr(self, "enabled", False): return False logger.info(u"Disabling client %s", self.name) - if getattr(self, "disable_initiator_tag", False): + if getattr(self, u"disable_initiator_tag", False): gobject.source_remove(self.disable_initiator_tag) self.disable_initiator_tag = None - if getattr(self, "checker_initiator_tag", False): + 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.disable_hook: self.disable_hook(self) self.enabled = False - if self.use_dbus: - # Emit D-Bus signal - self.PropertyChanged(dbus.String(u"enabled"), - dbus.Boolean(False, variant_level=1)) # Do not run this again if called by a gobject.timeout_add return False @@ -325,10 +355,6 @@ """The checker has completed, so take appropriate actions.""" self.checker_callback_tag = None self.checker = None - if self.use_dbus: - # 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) if exitstatus == 0: @@ -338,22 +364,13 @@ else: logger.info(u"Checker for %(name)s failed", vars(self)) - if self.use_dbus: - # Emit D-Bus signal - self.CheckerCompleted(dbus.Int16(exitstatus), - dbus.Int64(condition), - dbus.String(command)) else: logger.warning(u"Checker for %(name)s crashed?", vars(self)) - if self.use_dbus: - # Emit D-Bus signal - self.CheckerCompleted(dbus.Int16(-1), - dbus.Int64(condition), - dbus.String(command)) 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. """ @@ -362,15 +379,10 @@ self.disable_initiator_tag = (gobject.timeout_add (self.timeout_milliseconds(), self.disable)) - if self.use_dbus: - # Emit D-Bus signal - self.PropertyChanged( - dbus.String(u"last_checked_ok"), - (_datetime_to_dbus(self.last_checked_ok, - variant_level=1))) 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 @@ -386,7 +398,7 @@ if self.checker is not None: pid, status = os.waitpid(self.checker.pid, os.WNOHANG) if pid: - logger.warning("Checker was a zombie") + logger.warning(u"Checker was a zombie") gobject.source_remove(self.checker_callback_tag) self.checker_callback(pid, status, self.current_checker_command) @@ -397,7 +409,10 @@ 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: @@ -406,7 +421,7 @@ logger.error(u'Could not format string "%s":' u' %s', self.checker_command, error) return True # Try again later - self.current_checker_command = command + self.current_checker_command = command try: logger.info(u"Starting checker %r for %s", command, self.name) @@ -416,13 +431,7 @@ # always replaced by /dev/null.) self.checker = subprocess.Popen(command, close_fds=True, - shell=True, cwd="/") - if self.use_dbus: - # Emit D-Bus signal - self.CheckerStarted(command) - self.PropertyChanged( - dbus.String("checker_running"), - dbus.Boolean(True, variant_level=1)) + shell=True, cwd=u"/") self.checker_callback_tag = (gobject.child_watch_add (self.checker.pid, self.checker_callback, @@ -444,7 +453,7 @@ 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: @@ -456,94 +465,220 @@ if error.errno != errno.ESRCH: # No such process raise self.checker = None - if self.use_dbus: - self.PropertyChanged(dbus.String(u"checker_running"), - dbus.Boolean(False, variant_level=1)) def still_valid(self): """Has the timeout not yet passed for this client?""" - if not getattr(self, "enabled", False): + if not getattr(self, u"enabled", False): return False now = datetime.datetime.utcnow() if self.last_checked_ok is None: return now < (self.created + self.timeout) else: return now < (self.last_checked_ok + self.timeout) + + +class ClientDBus(Client, dbus.service.Object): + """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 - CheckedOK = dbus.service.method(_interface)(checked_ok) - CheckedOK.__name__ = "CheckedOK" + @dbus.service.method(_interface) + def CheckedOK(self): + return self.checked_ok() # CheckerCompleted - signal - @dbus.service.signal(_interface, signature="nxs") + @dbus.service.signal(_interface, signature=u"nxs") def CheckerCompleted(self, exitcode, waitstatus, command): "D-Bus signal" pass # CheckerStarted - signal - @dbus.service.signal(_interface, signature="s") + @dbus.service.signal(_interface, signature=u"s") def CheckerStarted(self, command): "D-Bus signal" pass # GetAllProperties - method - @dbus.service.method(_interface, out_signature="a{sv}") + @dbus.service.method(_interface, out_signature=u"a{sv}") def GetAllProperties(self): "D-Bus method" return dbus.Dictionary({ - dbus.String("name"): + dbus.String(u"name"): dbus.String(self.name, variant_level=1), - dbus.String("fingerprint"): + dbus.String(u"fingerprint"): dbus.String(self.fingerprint, variant_level=1), - dbus.String("host"): + dbus.String(u"host"): dbus.String(self.host, variant_level=1), - dbus.String("created"): - _datetime_to_dbus(self.created, variant_level=1), - dbus.String("last_enabled"): - (_datetime_to_dbus(self.last_enabled, - 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("enabled"): + dbus.String(u"enabled"): dbus.Boolean(self.enabled, variant_level=1), - dbus.String("last_checked_ok"): - (_datetime_to_dbus(self.last_checked_ok, - 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("timeout"): + dbus.String(u"timeout"): dbus.UInt64(self.timeout_milliseconds(), variant_level=1), - dbus.String("interval"): + dbus.String(u"interval"): dbus.UInt64(self.interval_milliseconds(), variant_level=1), - dbus.String("checker"): + dbus.String(u"checker"): dbus.String(self.checker_command, variant_level=1), - dbus.String("checker_running"): + dbus.String(u"checker_running"): dbus.Boolean(self.checker is not None, variant_level=1), - dbus.String("object_path"): + dbus.String(u"object_path"): dbus.ObjectPath(self.dbus_object_path, variant_level=1) - }, signature="sv") + }, signature=u"sv") # IsStillValid - method - IsStillValid = (dbus.service.method(_interface, out_signature="b") - (still_valid)) - IsStillValid.__name__ = "IsStillValid" + @dbus.service.method(_interface, out_signature=u"b") + def IsStillValid(self): + return self.still_valid() # PropertyChanged - signal - @dbus.service.signal(_interface, signature="sv") + @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="s") + @dbus.service.method(_interface, in_signature=u"s") def SetChecker(self, checker): "D-Bus setter method" self.checker_command = checker @@ -553,7 +688,7 @@ variant_level=1)) # SetHost - method - @dbus.service.method(_interface, in_signature="s") + @dbus.service.method(_interface, in_signature=u"s") def SetHost(self, host): "D-Bus setter method" self.host = host @@ -562,7 +697,7 @@ dbus.String(self.host, variant_level=1)) # SetInterval - method - @dbus.service.method(_interface, in_signature="t") + @dbus.service.method(_interface, in_signature=u"t") def SetInterval(self, milliseconds): self.interval = datetime.timedelta(0, 0, 0, milliseconds) # Emit D-Bus signal @@ -571,14 +706,14 @@ variant_level=1))) # SetSecret - method - @dbus.service.method(_interface, in_signature="ay", + @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="t") + @dbus.service.method(_interface, in_signature=u"t") def SetTimeout(self, milliseconds): self.timeout = datetime.timedelta(0, 0, 0, milliseconds) # Emit D-Bus signal @@ -587,8 +722,10 @@ variant_level=1))) # Enable - method - Enable = dbus.service.method(_interface)(enable) - Enable.__name__ = "Enable" + @dbus.service.method(_interface) + def Enable(self): + "D-Bus method" + self.enable() # StartChecker - method @dbus.service.method(_interface) @@ -603,199 +740,226 @@ self.disable() # StopChecker - method - StopChecker = dbus.service.method(_interface)(stop_checker) - StopChecker.__name__ = "StopChecker" + @dbus.service.method(_interface) + def StopChecker(self): + self.stop_checker() del _interface -def peer_certificate(session): - "Return the peer's OpenPGP certificate as a bytestring" - # If not an OpenPGP certificate... - if (gnutls.library.functions - .gnutls_certificate_type_get(session._c_object) - != gnutls.library.constants.GNUTLS_CRT_OPENPGP): - # ...do the normal thing - return session.peer_certificate - list_size = ctypes.c_uint(1) - cert_list = (gnutls.library.functions - .gnutls_certificate_get_peers - (session._c_object, ctypes.byref(list_size))) - if not bool(cert_list) and list_size.value != 0: - raise gnutls.errors.GNUTLSError("error getting peer" - " certificate") - if list_size.value == 0: - return None - cert = cert_list[0] - return ctypes.string_at(cert.data, cert.size) - - -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 - 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 TCP_handler(SocketServer.BaseRequestHandler, object): - """A TCP request handler class. - Instantiated by IPv6_TCPServer for each request to handle it. +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")) - # Use a fallback default, since this MUST be set. - priority = self.server.settings.get("priority", "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 = 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) - - for c in self.server.clients: - if c.fingerprint == fpr: - client = c - break - else: - 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 - ## This won't work here, since we're in a fork. - # client.checked_ok() - 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.ForkingMixIn, - SocketServer.TCPServer, object): + 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"] - if "use_ipv6" in kwargs: - if not kwargs["use_ipv6"]: - self.address_family = socket.AF_INET - del kwargs["use_ipv6"] - self.enabled = False - super(IPv6_TCPServer, 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 + 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]: if self.address_family == socket.AF_INET6: - any_address = "::" # in6addr_any + any_address = u"::" # in6addr_any else: any_address = socket.INADDR_ANY self.server_address = (any_address, @@ -803,35 +967,136 @@ 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(IPv6_TCPServer, 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(IPv6_TCPServer, 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) @@ -857,60 +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(u"State changed: %s" % unicode(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_nametoindex = (ctypes.cdll.LoadLibrary - (ctypes.util.find_library("c")) + (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 with closing(socket.socket()) as s: ifreq = fcntl.ioctl(s, SIOCGIFINDEX, - struct.pack("16s16x", interface)) - interface_index = struct.unpack("I", ifreq[16:20])[0] + 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: @@ -918,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()) @@ -927,31 +1171,35 @@ def main(): + + ###################################################################### + # Parsing of options, both command line and config file + parser = optparse.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", - 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") - parser.add_option("--no-dbus", action="store_false", - dest="use_dbus", + 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=optparse.SUPPRESS_HELP) # XXX: Not done yet - parser.add_option("--no-ipv6", action="store_false", - dest="use_ipv6", help="Do not use IPv6") + 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: @@ -960,95 +1208,104 @@ 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", - "use_dbus": "True", - "use_ipv6": "True", + 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 the appropriate methods on the non-string config options - server_settings["debug"] = server_config.getboolean("DEFAULT", - "debug") - server_settings["use_dbus"] = server_config.getboolean("DEFAULT", - "use_dbus") - server_settings["use_ipv6"] = server_config.getboolean("DEFAULT", - "use_ipv6") + 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("DEFAULT", - "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", - "use_dbus", "use_ipv6"): + 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" + ################################################################## + # For convenience - debug = server_settings["debug"] - use_dbus = server_settings["use_dbus"] + debug = server_settings[u"debug"] + use_dbus = server_settings[u"use_dbus"] use_dbus = False # XXX: Not done yet - use_ipv6 = server_settings["use_ipv6"] + use_ipv6 = server_settings[u"use_ipv6"] if not debug: syslogger.setLevel(logging.WARNING) console.setLevel(logging.WARNING) - if server_settings["servicename"] != "Mandos": + if server_settings[u"servicename"] != u"Mandos": syslogger.setFormatter(logging.Formatter - ('Mandos (%s): %%(levelname)s:' - ' %%(message)s' - % server_settings["servicename"])) + (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, use_ipv6=use_ipv6) - pidfilename = "/var/run/mandos.pid" + 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, "w") + pidfile = open(pidfilename, u"w") except IOError: - logger.error("Could not open file %r", pidfilename) + logger.error(u"Could not open file %r", pidfilename) try: - uid = pwd.getpwnam("_mandos").pw_uid - gid = pwd.getpwnam("_mandos").pw_gid + uid = pwd.getpwnam(u"_mandos").pw_uid + gid = pwd.getpwnam(u"_mandos").pw_gid except KeyError: try: - uid = pwd.getpwnam("mandos").pw_uid - gid = pwd.getpwnam("mandos").pw_gid + uid = pwd.getpwnam(u"mandos").pw_uid + gid = pwd.getpwnam(u"mandos").pw_gid except KeyError: try: - uid = pwd.getpwnam("nobody").pw_uid - gid = pwd.getpwnam("nogroup").pw_gid + uid = pwd.getpwnam(u"nobody").pw_uid + gid = pwd.getpwnam(u"nobody").pw_gid except KeyError: uid = 65534 gid = 65534 @@ -1067,40 +1324,35 @@ @gnutls.library.types.gnutls_log_func def debug_gnutls(level, string): - logger.debug("GnuTLS: %s", string[:-1]) + logger.debug(u"GnuTLS: %s", string[:-1]) (gnutls.library.functions .gnutls_global_set_log_function(debug_gnutls)) - global service - protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET - service = AvahiService(name = server_settings["servicename"], - servicetype = "_mandos._tcp", - protocol = protocol) - if server_settings["interface"]: - service.interface = (if_nametoindex - (server_settings["interface"])) - global main_loop - global bus - global server # From the Avahi example code DBusGMainLoop(set_as_default=True ) main_loop = gobject.MainLoop() bus = dbus.SystemBus() - server = dbus.Interface(bus.get_object(avahi.DBUS_NAME, - avahi.DBUS_PATH_SERVER), - avahi.DBUS_INTERFACE_SERVER) # End of Avahi example code if use_dbus: 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"]))) - clients.update(Set(Client(name = section, - config - = dict(client_config.items(section)), - use_dbus = use_dbus) - for section in client_config.sections())) - if not clients: + 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: @@ -1116,9 +1368,9 @@ daemon() try: - pid = os.getpid() - pidfile.write(str(pid) + "\n") - pidfile.close() + with closing(pidfile): + pid = os.getpid() + pidfile.write(str(pid) + "\n") del pidfile except IOError: logger.error(u"Could not write to file %r with PID %d", @@ -1130,15 +1382,10 @@ 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() + while tcp_server.clients: + client = tcp_server.clients.pop() client.disable_hook = None client.disable() @@ -1150,44 +1397,51 @@ signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit()) if use_dbus: - class MandosServer(dbus.service.Object): + class MandosDBusService(dbus.service.Object): """A D-Bus proxy object""" def __init__(self): - dbus.service.Object.__init__(self, bus, "/") + dbus.service.Object.__init__(self, bus, u"/") _interface = u"se.bsnet.fukt.Mandos" - @dbus.service.signal(_interface, signature="oa{sv}") + @dbus.service.signal(_interface, signature=u"oa{sv}") def ClientAdded(self, objpath, properties): "D-Bus signal" pass - @dbus.service.signal(_interface, signature="os") + @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="ao") + @dbus.service.method(_interface, out_signature=u"ao") def GetAllClients(self): "D-Bus method" - return dbus.Array(c.dbus_object_path for c in clients) + return dbus.Array(c.dbus_object_path + for c in tcp_server.clients) - @dbus.service.method(_interface, out_signature="a{oa{sv}}") + @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 clients), - signature="oa{sv}") + for c in tcp_server.clients), + signature=u"oa{sv}") - @dbus.service.method(_interface, in_signature="o") + @dbus.service.method(_interface, in_signature=u"o") def RemoveClient(self, object_path): "D-Bus method" - for c in clients: + for c in tcp_server.clients: if c.dbus_object_path == object_path: - clients.remove(c) + tcp_server.clients.remove(c) + c.remove_from_connection() # Don't signal anything except ClientRemoved - c.use_dbus = False - c.disable() + c.disable(signal=False) # Emit D-Bus signal self.ClientRemoved(object_path, c.name) return @@ -1195,13 +1449,13 @@ del _interface - mandos_server = MandosServer() + mandos_dbus_service = MandosDBusService() - for client in clients: + for client in tcp_server.clients: if use_dbus: # Emit D-Bus signal - mandos_server.ClientAdded(client.dbus_object_path, - client.GetAllProperties()) + mandos_dbus_service.ClientAdded(client.dbus_object_path, + client.GetAllProperties()) client.enable() tcp_server.enable() @@ -1221,9 +1475,8 @@ 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) @@ -1242,8 +1495,8 @@ except KeyboardInterrupt: if debug: print >> sys.stderr - logger.debug("Server received KeyboardInterrupt") - logger.debug("Server exiting") + 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 2009-02-15 09:09:27 +0000 +++ mandos-clients.conf.xml 2009-09-17 01:21:27 +0000 @@ -3,7 +3,7 @@ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ /etc/mandos/clients.conf"> - + %common; ]> @@ -108,10 +108,11 @@ This option is optional. - The timeout is how long the server will wait for a - successful checker run until a client is considered - invalid - that is, ineligible to get the data this server - holds. By default Mandos will use 1 hour. + The timeout is how long the server will wait (for either a + successful checker run or a client receiving its secret) + until a client is considered invalid - that is, ineligible + to get the data this server holds. By default Mandos will + use 1 hour. The TIME is specified as a === modified file 'mandos-ctl' --- mandos-ctl 2009-05-23 05:22:05 +0000 +++ mandos-ctl 2009-09-17 11:47:22 +0000 @@ -106,7 +106,7 @@ for client in clients)) for key in keywords) - print format_string % tuple(tablewords[key] 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) === modified file 'mandos.xml' --- mandos.xml 2009-02-25 01:21:37 +0000 +++ mandos.xml 2009-09-17 11:47:22 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -315,11 +315,14 @@ The server will, by default, continually check that the clients are still up. If a client has not been confirmed as being up for some time, the client is assumed to be compromised and is no - longer eligible to receive the encrypted password. The timeout, + longer eligible to receive the encrypted password. (Manual + intervention is required to re-enable a client.) The timeout, checker program, and interval between checks can be configured both globally and per client; see mandos-clients.conf - 5. + 5. A client successfully + receiving its password will also be treated as a successful + checker run. === modified file 'plugin-runner.c' --- plugin-runner.c 2009-02-12 19:08:35 +0000 +++ plugin-runner.c 2009-09-10 06:28:14 +0000 @@ -1,4 +1,4 @@ -/* -*- coding: utf-8 -*- */ +/* -*- coding: utf-8; mode: c; mode: orgtbl -*- */ /* * Mandos plugin runner - Run Mandos plugins * @@ -38,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(), @@ -52,7 +53,8 @@ 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, @@ -108,13 +110,18 @@ } } /* Create a new plugin */ - plugin *new_plugin = malloc(sizeof(plugin)); + plugin *new_plugin = NULL; + do { + new_plugin = malloc(sizeof(plugin)); + } while(new_plugin == NULL and errno == EINTR); if(new_plugin == NULL){ return NULL; } char *copy_name = NULL; if(name != NULL){ - copy_name = strdup(name); + do { + copy_name = strdup(name); + } while(copy_name == NULL and errno == EINTR); if(copy_name == NULL){ free(new_plugin); return NULL; @@ -126,7 +133,9 @@ .disabled = false, .next = plugin_list }; - new_plugin->argv = malloc(sizeof(char *) * 2); + do { + new_plugin->argv = malloc(sizeof(char *) * 2); + } while(new_plugin->argv == NULL and errno == EINTR); if(new_plugin->argv == NULL){ free(copy_name); free(new_plugin); @@ -135,7 +144,9 @@ new_plugin->argv[0] = copy_name; new_plugin->argv[1] = NULL; - new_plugin->environ = malloc(sizeof(char *)); + do { + new_plugin->environ = malloc(sizeof(char *)); + } while(new_plugin->environ == NULL and errno == EINTR); if(new_plugin->environ == NULL){ free(copy_name); free(new_plugin->argv); @@ -153,14 +164,19 @@ static bool add_to_char_array(const char *new, char ***array, int *len){ /* Resize the pointed-to array to hold one more pointer */ - *array = realloc(*array, sizeof(char *) - * (size_t) ((*len) + 2)); + do { + *array = realloc(*array, sizeof(char *) + * (size_t) ((*len) + 2)); + } while(*array == NULL and errno == EINTR); /* Malloc check */ if(*array == NULL){ return false; } /* Make a copy of the new string */ - char *copy = strdup(new); + char *copy; + do { + copy = strdup(new); + } while(copy == NULL and errno == EINTR); if(copy == NULL){ return false; } @@ -192,7 +208,10 @@ if(strncmp(*e, def, namelen + 1) == 0){ /* It already exists */ if(replace){ - char *new = realloc(*e, strlen(def) + 1); + char *new; + do { + new = realloc(*e, strlen(def) + 1); + } while(new == NULL and errno == EINTR); if(new == NULL){ return false; } @@ -208,16 +227,17 @@ /* * Based on the example in the GNU LibC manual chapter 13.13 "File * Descriptor Flags". - * *Note File Descriptor Flags:(libc)Descriptor Flags. + | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] | */ static int set_cloexec_flag(int fd){ - int ret = fcntl(fd, F_GETFD, 0); + int ret = (int)TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD, 0)); /* If reading the flags failed, return error indication now. */ if(ret < 0){ return ret; } /* Store modified flag word in the descriptor. */ - return fcntl(fd, F_SETFD, ret | FD_CLOEXEC); + return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD, + ret | FD_CLOEXEC)); } @@ -315,7 +335,6 @@ fd_set rfds_all; int ret, maxfd = 0; ssize_t sret; - intmax_t tmpmax; uid_t uid = 65534; gid_t gid = 65534; bool debug = false; @@ -380,8 +399,9 @@ error_t parse_opt(int key, char *arg, __attribute__((unused)) struct argp_state *state){ - char *tmp; switch(key){ + char *tmp; + intmax_t tmpmax; case 'g': /* --global-options */ if(arg != NULL){ char *plugin_option; @@ -457,7 +477,7 @@ plugindir = strdup(arg); if(plugindir == NULL){ perror("strdup"); - } + } break; case 129: /* --config-file */ /* This is already done by parse_opt_config_file() */ @@ -527,7 +547,7 @@ if(argfile == NULL){ perror("strdup"); } - break; + break; case 130: /* --userid */ case 131: /* --groupid */ case 132: /* --debug */ @@ -562,7 +582,7 @@ conffp = fopen(AFILE, "r"); } else { conffp = fopen(argfile, "r"); - } + } if(conffp != NULL){ char *org_line = NULL; char *p, *arg, *new_arg, *line; @@ -612,9 +632,17 @@ 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 @@ -699,7 +727,9 @@ /* Read and execute any executable in the plugin directory*/ while(true){ - dirst = readdir(dir); + do { + dirst = readdir(dir); + } while(dirst == NULL and errno == EINTR); /* All directory entries have been processed */ if(dirst == NULL){ @@ -759,16 +789,19 @@ char *filename; if(plugindir == NULL){ - ret = asprintf(&filename, PDIR "/%s", dirst->d_name); + ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, PDIR "/%s", + dirst->d_name)); } else { - ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name); + ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, "%s/%s", + plugindir, + dirst->d_name)); } if(ret < 0){ perror("asprintf"); continue; } - ret = stat(filename, &st); + ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st)); if(ret == -1){ perror("stat"); free(filename); @@ -776,7 +809,8 @@ } /* Ignore non-executable files */ - if(not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){ + if(not S_ISREG(st.st_mode) + or (TEMP_FAILURE_RETRY(access(filename, X_OK)) != 0)){ if(debug){ fprintf(stderr, "Ignoring plugin dir entry \"%s\"" " with bad type or mode\n", filename); @@ -828,7 +862,7 @@ } int pipefd[2]; - ret = pipe(pipefd); + ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd)); if(ret == -1){ perror("pipe"); exitstatus = EXIT_FAILURE; @@ -848,14 +882,19 @@ goto fallback; } /* Block SIGCHLD until process is safely in process list */ - ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL); + ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK, + &sigchld_action.sa_mask, + NULL)); if(ret < 0){ perror("sigprocmask"); exitstatus = EXIT_FAILURE; goto fallback; } /* Starting a new process to be watched */ - pid_t pid = fork(); + pid_t pid; + do { + pid = fork(); + } while(pid == -1 and errno == EINTR); if(pid == -1){ perror("fork"); exitstatus = EXIT_FAILURE; @@ -899,12 +938,15 @@ /* no return */ } /* Parent process */ - close(pipefd[1]); /* Close unused write end of pipe */ + TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of + pipe */ free(filename); plugin *new_plugin = getplugin(dirst->d_name); if(new_plugin == NULL){ perror("getplugin"); - ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL); + ret = (int)(TEMP_FAILURE_RETRY + (sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, + NULL))); if(ret < 0){ perror("sigprocmask"); } @@ -917,7 +959,9 @@ /* Unblock SIGCHLD so signal handler can be run if this process has already completed */ - ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL); + ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK, + &sigchld_action.sa_mask, + NULL)); if(ret < 0){ perror("sigprocmask"); exitstatus = EXIT_FAILURE; @@ -931,7 +975,7 @@ } } - closedir(dir); + TEMP_FAILURE_RETRY(closedir(dir)); dir = NULL; free_plugin(getplugin(NULL)); @@ -950,7 +994,7 @@ 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 and errno != EINTR){ perror("select"); exitstatus = EXIT_FAILURE; goto fallback; @@ -973,9 +1017,10 @@ WEXITSTATUS(proc->status)); } else if(WIFSIGNALED(proc->status)){ fprintf(stderr, "Plugin %s [%" PRIdMAX "] killed by" - " signal %d\n", proc->name, + " signal %d: %s\n", proc->name, (intmax_t) (proc->pid), - WTERMSIG(proc->status)); + WTERMSIG(proc->status), + strsignal(WTERMSIG(proc->status))); } else if(WCOREDUMP(proc->status)){ fprintf(stderr, "Plugin %s [%" PRIdMAX "] dumped" " core\n", proc->name, (intmax_t) (proc->pid)); @@ -986,7 +1031,10 @@ FD_CLR(proc->fd, &rfds_all); /* Block signal while modifying process_list */ - ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL); + ret = (int)TEMP_FAILURE_RETRY(sigprocmask + (SIG_BLOCK, + &sigchld_action.sa_mask, + NULL)); if(ret < 0){ perror("sigprocmask"); exitstatus = EXIT_FAILURE; @@ -998,8 +1046,9 @@ proc = next_plugin; /* We are done modifying process list, so unblock signal */ - ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, - NULL); + ret = (int)(TEMP_FAILURE_RETRY + (sigprocmask(SIG_UNBLOCK, + &sigchld_action.sa_mask, NULL))); if(ret < 0){ perror("sigprocmask"); exitstatus = EXIT_FAILURE; @@ -1042,8 +1091,10 @@ proc->buffer_size += BUFFER_SIZE; } /* Read from the process */ - sret = read(proc->fd, proc->buffer + proc->buffer_length, - BUFFER_SIZE); + sret = TEMP_FAILURE_RETRY(read(proc->fd, + proc->buffer + + proc->buffer_length, + BUFFER_SIZE)); if(sret < 0){ /* Read error from this process; ignore the error */ proc = proc->next; @@ -1111,7 +1162,7 @@ } /* Wait for any remaining child processes to terminate */ - do{ + do { ret = wait(NULL); } while(ret >= 0); if(errno != ECHILD){ === modified file 'plugin-runner.conf' --- plugin-runner.conf 2009-02-09 02:01:13 +0000 +++ plugin-runner.conf 2009-04-17 08:26:17 +0000 @@ -1,6 +1,7 @@ -## 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. +## 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! === modified file 'plugins.d/askpass-fifo.c' --- plugins.d/askpass-fifo.c 2009-01-10 06:00:50 +0000 +++ plugins.d/askpass-fifo.c 2009-09-16 23:28:39 +0000 @@ -19,8 +19,7 @@ * along with this program. If not, see * . * - * Contact the authors at and - * . + * Contact the authors at . */ #define _GNU_SOURCE /* TEMP_FAILURE_RETRY() */ @@ -29,7 +28,7 @@ #include /* and */ #include /* errno, EEXIST */ #include /* perror() */ -#include /* EXIT_FAILURE, NULL, size_t, free(), +#include /* EXIT_FAILURE, NULL, size_t, free(), realloc(), EXIT_SUCCESS */ #include /* open(), O_RDONLY */ #include /* read(), close(), write(), @@ -43,14 +42,14 @@ /* Create FIFO */ const char passfifo[] = "/lib/cryptsetup/passfifo"; - ret = (int)TEMP_FAILURE_RETRY(mkfifo(passfifo, S_IRUSR | S_IWUSR)); + ret = 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)); + int fifo_fd = open(passfifo, O_RDONLY); if(fifo_fd == -1){ perror("open"); return EXIT_FAILURE; @@ -62,7 +61,7 @@ { size_t buf_allocated = 0; const size_t blocksize = 1024; - do{ + do { if(buf_len + blocksize > buf_allocated){ char *tmp = realloc(buf, buf_allocated + blocksize); if(tmp == NULL){ @@ -73,25 +72,23 @@ buf = tmp; buf_allocated += blocksize; } - sret = TEMP_FAILURE_RETRY(read(fifo_fd, buf + buf_len, - buf_allocated - buf_len)); + sret = read(fifo_fd, buf + buf_len, buf_allocated - buf_len); if(sret == -1){ perror("read"); free(buf); return EXIT_FAILURE; } buf_len += (size_t)sret; - }while(sret != 0); + } while(sret != 0); } /* Close FIFO */ - TEMP_FAILURE_RETRY(close(fifo_fd)); + close(fifo_fd); /* Print password to stdout */ size_t written = 0; while(written < buf_len){ - sret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buf + written, - buf_len - written)); + sret = write(STDOUT_FILENO, buf + written, buf_len - written); if(sret == -1){ perror("write"); free(buf); === modified file 'plugins.d/mandos-client.c' --- plugins.d/mandos-client.c 2009-02-14 18:07:05 +0000 +++ plugins.d/mandos-client.c 2009-09-17 11:22:28 +0000 @@ -44,7 +44,7 @@ #include /* uint16_t, uint32_t */ #include /* NULL, size_t, ssize_t */ #include /* free(), EXIT_SUCCESS, EXIT_FAILURE, - srand(), strtof() */ + srand(), strtof(), abort() */ #include /* bool, false, true */ #include /* memset(), strcmp(), strlen(), strerror(), asprintf(), strcpy() */ @@ -71,8 +71,8 @@ INET_ADDRSTRLEN, INET6_ADDRSTRLEN */ #include /* close(), SEEK_SET, off_t, write(), - getuid(), getgid(), setuid(), - setgid() */ + getuid(), getgid(), seteuid(), + setgid(), pause() */ #include /* inet_pton(), htons */ #include /* not, or, and */ #include /* struct argp_option, error_t, struct @@ -80,8 +80,8 @@ argp_parse(), ARGP_KEY_ARG, ARGP_KEY_END, ARGP_ERR_UNKNOWN */ #include /* sigemptyset(), sigaddset(), - sigaction(), SIGTERM, sigaction, - sig_atomic_t */ + sigaction(), SIGTERM, sig_atomic_t, + raise() */ #ifdef __linux__ #include /* klogctl() */ @@ -142,6 +142,9 @@ .dh_bits = 1024, .priority = "SECURE256" ":!CTYPE-X.509:+CTYPE-OPENPGP" }; +sig_atomic_t quit_now = 0; +int signal_received = 0; + /* * Make additional room in "buffer" for at least BUFFER_SIZE more * bytes. "buffer_capacity" is how much is currently allocated, @@ -164,7 +167,6 @@ */ 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; @@ -173,6 +175,7 @@ * Helper function to insert pub and seckey to the engine keyring. */ bool import_key(const char *filename){ + int ret; int fd; gpgme_data_t pgp_data; @@ -249,7 +252,7 @@ return false; } - return true; + return true; } /* @@ -309,16 +312,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; } } } @@ -476,7 +477,12 @@ static int init_gnutls_session(gnutls_session_t *session){ int ret; /* GnuTLS session creation */ - ret = gnutls_init(session, GNUTLS_SERVER); + do { + ret = gnutls_init(session, GNUTLS_SERVER); + if(quit_now){ + return -1; + } + } while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN); if(ret != GNUTLS_E_SUCCESS){ fprintf(stderr, "Error in GnuTLS session initialization: %s\n", safer_gnutls_strerror(ret)); @@ -484,7 +490,13 @@ { const char *err; - ret = gnutls_priority_set_direct(*session, mc.priority, &err); + do { + ret = gnutls_priority_set_direct(*session, mc.priority, &err); + if(quit_now){ + gnutls_deinit(*session); + return -1; + } + } while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN); if(ret != GNUTLS_E_SUCCESS){ fprintf(stderr, "Syntax error at: %s\n", err); fprintf(stderr, "GnuTLS error: %s\n", @@ -494,8 +506,14 @@ } } - ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE, - mc.cred); + do { + ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE, + mc.cred); + if(quit_now){ + gnutls_deinit(*session); + return -1; + } + } while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN); if(ret != GNUTLS_E_SUCCESS){ fprintf(stderr, "Error setting GnuTLS credentials: %s\n", safer_gnutls_strerror(ret)); @@ -504,8 +522,7 @@ } /* 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); @@ -520,22 +537,25 @@ static int start_mandos_communication(const char *ip, uint16_t port, AvahiIfIndex if_index, int af){ - int ret, tcp_sd; + int ret, tcp_sd = -1; ssize_t sret; union { struct sockaddr_in in; struct sockaddr_in6 in6; } to; char *buffer = NULL; - char *decrypted_buffer; + char *decrypted_buffer = NULL; size_t buffer_length = 0; size_t buffer_capacity = 0; - ssize_t decrypted_buffer_size; size_t written; - int retval = 0; + int retval = -1; gnutls_session_t session; int pf; /* Protocol family */ + if(quit_now){ + return -1; + } + switch(af){ case AF_INET6: pf = PF_INET6; @@ -561,12 +581,16 @@ tcp_sd = socket(pf, SOCK_STREAM, 0); if(tcp_sd < 0){ perror("socket"); - return -1; + goto mandos_end; + } + + if(quit_now){ + goto mandos_end; } memset(&to, 0, sizeof(to)); if(af == AF_INET6){ - to.in6.sin6_family = (uint16_t)af; + 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; @@ -574,11 +598,11 @@ } if(ret < 0 ){ perror("inet_pton"); - return -1; + goto mandos_end; } if(ret == 0){ fprintf(stderr, "Bad address: %s\n", ip); - return -1; + goto mandos_end; } if(af == AF_INET6){ to.in6.sin6_port = htons(port); /* Spurious warnings from @@ -591,7 +615,7 @@ if(if_index == AVAHI_IF_UNSPEC){ fprintf(stderr, "An IPv6 link-local address is incomplete" " without a network interface\n"); - return -1; + goto mandos_end; } /* Set the network interface number as scope */ to.in6.sin6_scope_id = (uint32_t)if_index; @@ -602,6 +626,10 @@ -Wunreachable-code */ } + if(quit_now){ + goto mandos_end; + } + if(debug){ if(af == AF_INET6 and if_index != AVAHI_IF_UNSPEC){ char interface[IF_NAMESIZE]; @@ -634,6 +662,10 @@ } } + if(quit_now){ + goto mandos_end; + } + if(af == AF_INET6){ ret = connect(tcp_sd, &to.in6, sizeof(to)); } else { @@ -641,7 +673,11 @@ } if(ret < 0){ perror("connect"); - return -1; + goto mandos_end; + } + + if(quit_now){ + goto mandos_end; } const char *out = mandos_protocol_version; @@ -652,7 +688,6 @@ out_size - written)); if(ret == -1){ perror("write"); - retval = -1; goto mandos_end; } written += (size_t)ret; @@ -666,16 +701,31 @@ break; } } + + if(quit_now){ + goto mandos_end; + } } if(debug){ fprintf(stderr, "Establishing TLS session with %s\n", ip); } + if(quit_now){ + goto mandos_end; + } + gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) tcp_sd); - do{ + if(quit_now){ + goto mandos_end; + } + + do { 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){ @@ -683,7 +733,6 @@ fprintf(stderr, "*** GnuTLS Handshake failed ***\n"); gnutls_perror(ret); } - retval = -1; goto mandos_end; } @@ -695,11 +744,19 @@ } while(true){ + + if(quit_now){ + goto mandos_end; + } + buffer_capacity = incbuffer(&buffer, buffer_length, buffer_capacity); if(buffer_capacity == 0){ perror("incbuffer"); - retval = -1; + goto mandos_end; + } + + if(quit_now){ goto mandos_end; } @@ -714,20 +771,22 @@ case GNUTLS_E_AGAIN: break; case GNUTLS_E_REHANDSHAKE: - do{ + do { ret = gnutls_handshake(session); + + if(quit_now){ + goto mandos_end; + } } while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED); if(ret < 0){ fprintf(stderr, "*** GnuTLS Re-handshake failed ***\n"); gnutls_perror(ret); - retval = -1; goto mandos_end; } break; default: fprintf(stderr, "Unknown error while reading data from" " encrypted session with Mandos server\n"); - retval = -1; gnutls_bye(session, GNUTLS_SHUT_RDWR); goto mandos_end; } @@ -740,15 +799,30 @@ fprintf(stderr, "Closing TLS session\n"); } - gnutls_bye(session, GNUTLS_SHUT_RDWR); + if(quit_now){ + goto mandos_end; + } + + do { + ret = gnutls_bye(session, GNUTLS_SHUT_RDWR); + if(quit_now){ + goto mandos_end; + } + } while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED); if(buffer_length > 0){ + ssize_t decrypted_buffer_size; decrypted_buffer_size = pgp_packet_decrypt(buffer, buffer_length, &decrypted_buffer); if(decrypted_buffer_size >= 0){ + written = 0; while(written < (size_t) decrypted_buffer_size){ + if(quit_now){ + goto mandos_end; + } + ret = (int)fwrite(decrypted_buffer + written, 1, (size_t)decrypted_buffer_size - written, stdout); @@ -757,28 +831,29 @@ fprintf(stderr, "Error writing encrypted data: %s\n", strerror(errno)); } - retval = -1; - break; + goto mandos_end; } written += (size_t)ret; } - free(decrypted_buffer); - } else { - retval = -1; + retval = 0; } - } else { - retval = -1; } /* Shutdown procedure */ mandos_end: + free(decrypted_buffer); free(buffer); - ret = (int)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); + if(quit_now){ + retval = -1; + } return retval; } @@ -801,6 +876,10 @@ /* Called whenever a service has been resolved successfully or timed out */ + if(quit_now){ + return; + } + switch(event){ default: case AVAHI_RESOLVER_FAILURE: @@ -843,6 +922,10 @@ /* Called whenever a new services becomes available on the LAN or is removed from the LAN */ + if(quit_now){ + return; + } + switch(event){ default: case AVAHI_BROWSER_FAILURE: @@ -877,14 +960,13 @@ } } -sig_atomic_t quit_now = 0; - /* stop main loop after sigterm has been called */ -static void handle_sigterm(__attribute__((unused)) int sig){ +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); @@ -901,7 +983,8 @@ int exitcode = EXIT_SUCCESS; const char *interface = "eth0"; struct ifreq network; - int sd; + int sd = -1; + bool take_down_interface = false; uid_t uid; gid_t gid; char *connect_to = NULL; @@ -915,9 +998,30 @@ bool gpgme_initialized = false; float delay = 2.5f; - struct sigaction old_sigterm_action; + struct sigaction old_sigterm_action = { .sa_handler = SIG_DFL }; struct sigaction sigterm_action = { .sa_handler = handle_sigterm }; + uid = getuid(); + gid = getgid(); + + /* Lower any group privileges we might have, just to be safe */ + errno = 0; + ret = setgid(gid); + if(ret == -1){ + perror("setgid"); + } + + /* Lower user privileges (temporarily) */ + errno = 0; + ret = seteuid(uid); + if(ret == -1){ + perror("seteuid"); + } + + if(quit_now){ + goto end; + } + { struct argp_option options[] = { { .name = "debug", .key = 128, @@ -1050,15 +1154,70 @@ exitcode = EXIT_FAILURE; goto end; } - ret = sigaction(SIGTERM, &sigterm_action, &old_sigterm_action); - if(ret == -1){ - perror("sigaction"); - 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; + } + + /* Re-raise priviliges */ + errno = 0; + ret = seteuid(0); + if(ret == -1){ + perror("seteuid"); + } + #ifdef __linux__ /* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO messages to mess up the prompt */ @@ -1082,6 +1241,12 @@ } } #endif /* __linux__ */ + /* Lower privileges */ + errno = 0; + ret = seteuid(uid); + if(ret == -1){ + perror("seteuid"); + } goto end; } strcpy(network.ifr_name, interface); @@ -1097,12 +1262,20 @@ } #endif /* __linux__ */ exitcode = EXIT_FAILURE; + /* Lower privileges */ + errno = 0; + ret = seteuid(uid); + if(ret == -1){ + perror("seteuid"); + } goto end; } if((network.ifr_flags & IFF_UP) == 0){ network.ifr_flags |= IFF_UP; + take_down_interface = true; ret = ioctl(sd, SIOCSIFFLAGS, &network); if(ret == -1){ + take_down_interface = false; perror("ioctl SIOCSIFFLAGS"); exitcode = EXIT_FAILURE; #ifdef __linux__ @@ -1113,6 +1286,12 @@ } } #endif /* __linux__ */ + /* Lower privileges */ + errno = 0; + ret = seteuid(uid); + if(ret == -1){ + perror("seteuid"); + } goto end; } } @@ -1130,9 +1309,12 @@ perror("nanosleep"); } } - ret = (int)TEMP_FAILURE_RETRY(close(sd)); - if(ret == -1){ - perror("close"); + if(not take_down_interface){ + /* We won't need the socket anymore */ + ret = (int)TEMP_FAILURE_RETRY(close(sd)); + if(ret == -1){ + perror("close"); + } } #ifdef __linux__ if(restore_loglevel){ @@ -1143,20 +1325,25 @@ } } #endif /* __linux__ */ - } - - uid = getuid(); - gid = getgid(); - - errno = 0; - setgid(gid); - if(ret == -1){ - perror("setgid"); - } - - ret = setuid(uid); - if(ret == -1){ - perror("setuid"); + /* Lower privileges */ + errno = 0; + if(take_down_interface){ + /* Lower privileges */ + ret = seteuid(uid); + if(ret == -1){ + perror("seteuid"); + } + } else { + /* Lower privileges permanently */ + ret = setuid(uid); + if(ret == -1){ + perror("setuid"); + } + } + } + + if(quit_now){ + goto end; } ret = init_gnutls_global(pubkey, seckey); @@ -1168,11 +1355,20 @@ gnutls_initialized = true; } + if(quit_now){ + goto end; + } + + tempdir_created = true; if(mkdtemp(tempdir) == NULL){ + tempdir_created = false; perror("mkdtemp"); goto end; } - tempdir_created = true; + + if(quit_now){ + goto end; + } if(not init_gpgme(pubkey, seckey, tempdir)){ fprintf(stderr, "init_gpgme failed\n"); @@ -1182,13 +1378,8 @@ gpgme_initialized = true; } - 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; } if(connect_to != NULL){ @@ -1200,6 +1391,11 @@ exitcode = EXIT_FAILURE; goto end; } + + if(quit_now){ + goto end; + } + uint16_t port; errno = 0; tmpmax = strtoimax(address+1, &tmp, 10); @@ -1209,6 +1405,11 @@ exitcode = EXIT_FAILURE; goto end; } + + if(quit_now){ + goto end; + } + port = (uint16_t)tmpmax; *address = '\0'; address = connect_to; @@ -1219,6 +1420,11 @@ } else { af = AF_INET; } + + if(quit_now){ + goto end; + } + ret = start_mandos_communication(address, port, if_index, af); if(ret < 0){ exitcode = EXIT_FAILURE; @@ -1227,7 +1433,11 @@ } goto end; } - + + if(quit_now){ + goto end; + } + { AvahiServerConfig config; /* Do not publish any local Zeroconf records */ @@ -1254,6 +1464,10 @@ 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", @@ -1265,6 +1479,10 @@ goto end; } + if(quit_now){ + goto end; + } + /* Run the main loop */ if(debug){ @@ -1299,6 +1517,38 @@ gpgme_release(mc.ctx); } + /* Take down the network interface */ + if(take_down_interface){ + /* Re-raise priviliges */ + errno = 0; + ret = seteuid(0); + if(ret == -1){ + perror("seteuid"); + } + if(geteuid() == 0){ + ret = ioctl(sd, SIOCGIFFLAGS, &network); + if(ret == -1){ + perror("ioctl SIOCGIFFLAGS"); + } else if(network.ifr_flags & IFF_UP) { + network.ifr_flags &= ~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"); + } + /* Lower privileges permanently */ + errno = 0; + ret = setuid(uid); + if(ret == -1){ + perror("setuid"); + } + } + } + /* Removes the temp directory used by GPGME */ if(tempdir_created){ DIR *d; @@ -1343,5 +1593,24 @@ } } + if(quit_now){ + sigemptyset(&old_sigterm_action.sa_mask); + old_sigterm_action.sa_handler = SIG_DFL; + ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received, + &old_sigterm_action, + NULL)); + if(ret == -1){ + perror("sigaction"); + } + do { + ret = raise(signal_received); + } while(ret != 0 and errno == EINTR); + if(ret != 0){ + perror("raise"); + abort(); + } + TEMP_FAILURE_RETRY(pause()); + } + return exitcode; } === modified file 'plugins.d/password-prompt.c' --- plugins.d/password-prompt.c 2009-02-05 02:33:05 +0000 +++ plugins.d/password-prompt.c 2009-09-07 07:48:59 +0000 @@ -1,4 +1,4 @@ -/* -*- coding: utf-8 -*- */ +/* -*- coding: utf-8; mode: c; mode: orgtbl -*- */ /* * Password-prompt - Read a password from the terminal and print it * @@ -19,8 +19,7 @@ * along with this program. If not, see * . * - * Contact the authors at and - * . + * Contact the authors at . */ #define _GNU_SOURCE /* getline() */ @@ -33,7 +32,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, @@ -52,12 +52,17 @@ ARGP_ERR_UNKNOWN */ volatile sig_atomic_t quit_now = 0; +int signal_received; bool debug = false; const char *argp_program_version = "password-prompt " VERSION; const char *argp_program_bug_address = ""; -static void termination_handler(__attribute__((unused))int signum){ +static void termination_handler(int signum){ + if(quit_now){ + return; + } quit_now = 1; + signal_received = signum; } int main(int argc, char **argv){ @@ -123,9 +128,25 @@ } 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"); @@ -194,7 +215,7 @@ const char *cryptsource = getenv("cryptsource"); const char *crypttarget = getenv("crypttarget"); const char *const prompt - = "Enter passphrase to unlock the disk"; + = "Enter passphrase to unlock the disk"; if(cryptsource == NULL){ if(crypttarget == NULL){ fprintf(stderr, "%s: ", prompt); @@ -242,7 +263,7 @@ /* if(ret == 0), then the only sensible thing to do is to retry to read from stdin */ fputc('\n', stderr); - if(debug and quit_now == 0){ + if(debug and not quit_now){ /* If quit_now is nonzero, we were interrupted by a signal, and will print that later, so no need to show this too. */ fprintf(stderr, "getline() returned 0, retrying.\n"); @@ -258,6 +279,16 @@ perror("tcsetattr+echo"); } + if(quit_now){ + sigemptyset(&old_action.sa_mask); + old_action.sa_handler = SIG_DFL; + ret = sigaction(signal_received, &old_action, NULL); + if(ret == -1){ + perror("sigaction"); + } + raise(signal_received); + } + if(debug){ fprintf(stderr, "%s is exiting with status %d\n", argv[0], status); === modified file 'plugins.d/splashy.c' --- plugins.d/splashy.c 2009-02-12 18:56:52 +0000 +++ plugins.d/splashy.c 2009-09-16 23:28:39 +0000 @@ -19,11 +19,10 @@ * along with this program. If not, see * . * - * Contact the authors at and - * . + * Contact the authors at . */ -#define _GNU_SOURCE /* asprintf() */ +#define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), asprintf() */ #include /* sig_atomic_t, struct sigaction, sigemptyset(), sigaddset(), SIGINT, SIGHUP, SIGTERM, sigaction, @@ -41,24 +40,33 @@ #include /* not, or, and */ #include /* readlink(), fork(), execl(), sleep(), dup2() STDERR_FILENO, - STDOUT_FILENO, _exit() */ + STDOUT_FILENO, _exit(), + pause() */ #include /* memcmp() */ #include /* errno */ #include /* waitpid(), WIFEXITED(), WEXITSTATUS() */ sig_atomic_t interrupted_by_signal = 0; +int signal_received; -static void termination_handler(__attribute__((unused))int signum){ +static void termination_handler(int signum){ + if(interrupted_by_signal){ + return; + } interrupted_by_signal = 1; + signal_received = signum; } int main(__attribute__((unused))int argc, __attribute__((unused))char **argv){ int ret = 0; + char *prompt = NULL; + DIR *proc_dir = NULL; + pid_t splashy_pid = 0; + pid_t splashy_command_pid = 0; /* Create prompt string */ - char *prompt = NULL; { const char *const cryptsource = getenv("cryptsource"); const char *const crypttarget = getenv("crypttarget"); @@ -81,19 +89,18 @@ } } if(ret == -1){ - return EXIT_FAILURE; + prompt = NULL; + goto failure; } } /* Find splashy process */ - pid_t splashy_pid = 0; { const char splashy_name[] = "/sbin/splashy"; - DIR *proc_dir = opendir("/proc"); + proc_dir = opendir("/proc"); if(proc_dir == NULL){ - free(prompt); perror("opendir"); - return EXIT_FAILURE; + goto failure; } for(struct dirent *proc_ent = readdir(proc_dir); proc_ent != NULL; @@ -120,9 +127,7 @@ ret = asprintf(&exe_link, "/proc/%s/exe", proc_ent->d_name); if(ret == -1){ perror("asprintf"); - free(prompt); - closedir(proc_dir); - return EXIT_FAILURE; + goto failure; } /* Check that it refers to a symlink owned by root:root */ @@ -135,9 +140,7 @@ } perror("lstat"); free(exe_link); - free(prompt); - closedir(proc_dir); - return EXIT_FAILURE; + goto failure; } if(not S_ISLNK(exe_stat.st_mode) or exe_stat.st_uid != 0 @@ -157,10 +160,10 @@ } } closedir(proc_dir); + proc_dir = NULL; } if(splashy_pid == 0){ - free(prompt); - return EXIT_FAILURE; + goto failure; } /* Set up the signal handler */ @@ -169,135 +172,180 @@ new_action = { .sa_handler = termination_handler, .sa_flags = 0 }; sigemptyset(&new_action.sa_mask); - sigaddset(&new_action.sa_mask, SIGINT); - sigaddset(&new_action.sa_mask, SIGHUP); - sigaddset(&new_action.sa_mask, SIGTERM); + ret = sigaddset(&new_action.sa_mask, SIGINT); + if(ret == -1){ + perror("sigaddset"); + goto failure; + } + ret = sigaddset(&new_action.sa_mask, SIGHUP); + if(ret == -1){ + perror("sigaddset"); + goto failure; + } + ret = sigaddset(&new_action.sa_mask, SIGTERM); + if(ret == -1){ + perror("sigaddset"); + goto failure; + } ret = sigaction(SIGINT, NULL, &old_action); if(ret == -1){ perror("sigaction"); - free(prompt); - return EXIT_FAILURE; + goto failure; } if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGINT, &new_action, NULL); if(ret == -1){ perror("sigaction"); - free(prompt); - return EXIT_FAILURE; + goto failure; } } ret = sigaction(SIGHUP, NULL, &old_action); if(ret == -1){ perror("sigaction"); - free(prompt); - return EXIT_FAILURE; + goto failure; } if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGHUP, &new_action, NULL); if(ret == -1){ perror("sigaction"); - free(prompt); - return EXIT_FAILURE; + goto failure; } } ret = sigaction(SIGTERM, NULL, &old_action); if(ret == -1){ perror("sigaction"); - free(prompt); - return EXIT_FAILURE; + goto failure; } if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGTERM, &new_action, NULL); if(ret == -1){ perror("sigaction"); - free(prompt); - return EXIT_FAILURE; + goto failure; } } } + if(interrupted_by_signal){ + goto failure; + } + /* Fork off the splashy command to prompt for password */ - pid_t splashy_command_pid = 0; - if(not interrupted_by_signal){ - splashy_command_pid = fork(); - if(splashy_command_pid == -1){ - if(not interrupted_by_signal){ - perror("fork"); - } - return EXIT_FAILURE; - } - /* Child */ - if(splashy_command_pid == 0){ + splashy_command_pid = fork(); + if(splashy_command_pid != 0 and interrupted_by_signal){ + goto failure; + } + if(splashy_command_pid == -1){ + perror("fork"); + goto failure; + } + /* Child */ + if(splashy_command_pid == 0){ + if(not interrupted_by_signal){ const char splashy_command[] = "/sbin/splashy_update"; - ret = execl(splashy_command, splashy_command, prompt, - (char *)NULL); - if(not interrupted_by_signal){ - perror("execl"); - } - free(prompt); - _exit(EXIT_FAILURE); + execl(splashy_command, splashy_command, prompt, (char *)NULL); + perror("execl"); } + free(prompt); + _exit(EXIT_FAILURE); } /* Parent */ free(prompt); + prompt = NULL; + + if(interrupted_by_signal){ + goto failure; + } /* Wait for command to complete */ - if(not interrupted_by_signal and splashy_command_pid != 0){ + { int status; - ret = waitpid(splashy_command_pid, &status, 0); + do { + ret = waitpid(splashy_command_pid, &status, 0); + } while(ret == -1 and errno == EINTR + and not interrupted_by_signal); + if(interrupted_by_signal){ + goto failure; + } if(ret == -1){ - if(errno != EINTR){ - perror("waitpid"); - } + 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){ + if(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 */ + + failure: + + free(prompt); + + if(proc_dir != NULL){ + TEMP_FAILURE_RETRY(closedir(proc_dir)); + } + + if(splashy_command_pid != 0){ + TEMP_FAILURE_RETRY(kill(splashy_command_pid, SIGTERM)); - /* 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"); + TEMP_FAILURE_RETRY(kill(splashy_pid, SIGTERM)); + sleep(2); + while(TEMP_FAILURE_RETRY(kill(splashy_pid, 0)) == 0){ + TEMP_FAILURE_RETRY(kill(splashy_pid, SIGKILL)); + sleep(1); } + pid_t new_splashy_pid = (pid_t)TEMP_FAILURE_RETRY(fork()); + if(new_splashy_pid == 0){ + /* Child; will become new splashy process */ + + /* Make the effective user ID (root) the only user ID instead of + the real user ID (_mandos) */ + ret = setuid(geteuid()); + if(ret == -1){ + perror("setuid"); + } + + setsid(); + ret = chdir("/"); + if(ret == -1){ + perror("chdir"); + } +/* if(fork() != 0){ */ +/* _exit(EXIT_SUCCESS); */ +/* } */ + ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace stdout */ + if(ret == -1){ + perror("dup2"); + _exit(EXIT_FAILURE); + } - setsid(); - ret = chdir("/"); -/* if(fork() != 0){ */ -/* _exit(EXIT_SUCCESS); */ -/* } */ - ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace our stdout */ - if(ret == -1){ - perror("dup2"); + execl("/sbin/splashy", "/sbin/splashy", "boot", (char *)NULL); + perror("execl"); _exit(EXIT_FAILURE); } - - execl("/sbin/splashy", "/sbin/splashy", "boot", (char *)NULL); - if(not interrupted_by_signal){ - perror("execl"); - } - _exit(EXIT_FAILURE); + } + + if(interrupted_by_signal){ + struct sigaction signal_action; + sigemptyset(&signal_action.sa_mask); + signal_action.sa_handler = SIG_DFL; + ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received, + &signal_action, NULL)); + if(ret == -1){ + perror("sigaction"); + } + do { + ret = raise(signal_received); + } while(ret != 0 and errno == EINTR); + if(ret != 0){ + perror("raise"); + abort(); + } + TEMP_FAILURE_RETRY(pause()); } return EXIT_FAILURE; === modified file 'plugins.d/usplash.c' --- plugins.d/usplash.c 2009-02-12 18:56:52 +0000 +++ plugins.d/usplash.c 2009-09-17 04:16:32 +0000 @@ -19,11 +19,10 @@ * along with this program. If not, see * . * - * Contact the authors at and - * . + * Contact the authors at . */ -#define _GNU_SOURCE /* asprintf() */ +#define _GNU_SOURCE /* asprintf(), TEMP_FAILURE_RETRY() */ #include /* sig_atomic_t, struct sigaction, sigemptyset(), sigaddset(), SIGINT, SIGHUP, SIGTERM, sigaction(), @@ -50,242 +49,266 @@ #include /* struct stat, lstat(), S_ISLNK */ sig_atomic_t interrupted_by_signal = 0; +int signal_received; +const char usplash_name[] = "/sbin/usplash"; -static void termination_handler(__attribute__((unused))int signum){ +static void termination_handler(int signum){ + if(interrupted_by_signal){ + return; + } interrupted_by_signal = 1; + signal_received = signum; } -static bool usplash_write(const char *cmd, const char *arg){ +static bool usplash_write(int *fifo_fd_r, + const char *cmd, const char *arg){ /* - * usplash_write("TIMEOUT", "15") will write "TIMEOUT 15\0" - * usplash_write("PULSATE", NULL) will write "PULSATE\0" + * usplash_write(&fd, "TIMEOUT", "15") will write "TIMEOUT 15\0" + * usplash_write(&fd, "PULSATE", NULL) will write "PULSATE\0" * SEE ALSO * usplash_write(8) */ int ret; - int fifo_fd; - do{ - fifo_fd = open("/dev/.initramfs/usplash_fifo", O_WRONLY); - if(fifo_fd == -1 and (errno != EINTR or interrupted_by_signal)){ + if(*fifo_fd_r == -1){ + ret = open("/dev/.initramfs/usplash_fifo", O_WRONLY); + if(ret == -1){ return false; } - }while(fifo_fd == -1); + *fifo_fd_r = ret; + } const char *cmd_line; size_t cmd_line_len; char *cmd_line_alloc = NULL; if(arg == NULL){ cmd_line = cmd; - cmd_line_len = strlen(cmd); - }else{ - do{ + cmd_line_len = strlen(cmd) + 1; + } else { + do { ret = asprintf(&cmd_line_alloc, "%s %s", cmd, arg); - if(ret == -1 and (errno != EINTR or interrupted_by_signal)){ + if(ret == -1){ int e = errno; - close(fifo_fd); + TEMP_FAILURE_RETRY(close(*fifo_fd_r)); errno = e; return false; } - }while(ret == -1); + } while(ret == -1); cmd_line = cmd_line_alloc; cmd_line_len = (size_t)ret + 1; } size_t written = 0; ssize_t sret = 0; - while(not interrupted_by_signal and written < cmd_line_len){ - sret = write(fifo_fd, cmd_line + written, + while(written < cmd_line_len){ + sret = write(*fifo_fd_r, 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; - } + int e = errno; + TEMP_FAILURE_RETRY(close(*fifo_fd_r)); + free(cmd_line_alloc); + errno = e; + return false; } written += (size_t)sret; } free(cmd_line_alloc); - 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; } +/* Create prompt string */ +char *makeprompt(void){ + int ret = 0; + char *prompt; + const char *const cryptsource = getenv("cryptsource"); + const char *const crypttarget = getenv("crypttarget"); + const char prompt_start[] = "Enter passphrase to unlock the disk"; + + if(cryptsource == NULL){ + if(crypttarget == NULL){ + ret = asprintf(&prompt, "%s: ", prompt_start); + } else { + ret = asprintf(&prompt, "%s (%s): ", prompt_start, + crypttarget); + } + } else { + if(crypttarget == NULL){ + ret = asprintf(&prompt, "%s %s: ", prompt_start, cryptsource); + } else { + ret = asprintf(&prompt, "%s %s (%s): ", prompt_start, + cryptsource, crypttarget); + } + } + if(ret == -1){ + return NULL; + } + return prompt; +} + +pid_t find_usplash(char **cmdline_r, size_t *cmdline_len_r){ + int ret = 0; + ssize_t sret = 0; + char *cmdline = NULL; + size_t cmdline_len = 0; + DIR *proc_dir = opendir("/proc"); + if(proc_dir == NULL){ + perror("opendir"); + return -1; + } + errno = 0; + for(struct dirent *proc_ent = readdir(proc_dir); + proc_ent != NULL; + proc_ent = readdir(proc_dir)){ + pid_t pid; + { + intmax_t tmpmax; + char *tmp; + tmpmax = strtoimax(proc_ent->d_name, &tmp, 10); + if(errno != 0 or tmp == proc_ent->d_name or *tmp != '\0' + or tmpmax != (pid_t)tmpmax){ + /* Not a process */ + errno = 0; + continue; + } + pid = (pid_t)tmpmax; + } + /* Find the executable name by doing readlink() on the + /proc//exe link */ + char exe_target[sizeof(usplash_name)]; + { + /* create file name string */ + char *exe_link; + ret = asprintf(&exe_link, "/proc/%s/exe", proc_ent->d_name); + if(ret == -1){ + perror("asprintf"); + goto fail_find_usplash; + } + + /* Check that it refers to a symlink owned by root:root */ + struct stat exe_stat; + ret = lstat(exe_link, &exe_stat); + if(ret == -1){ + if(errno == ENOENT){ + free(exe_link); + continue; + } + perror("lstat"); + free(exe_link); + goto fail_find_usplash; + } + if(not S_ISLNK(exe_stat.st_mode) + or exe_stat.st_uid != 0 + or exe_stat.st_gid != 0){ + free(exe_link); + continue; + } + + sret = readlink(exe_link, exe_target, sizeof(exe_target)); + free(exe_link); + } + /* Compare executable name */ + if((sret != ((ssize_t)sizeof(exe_target)-1)) + or (memcmp(usplash_name, exe_target, + sizeof(exe_target)-1) != 0)){ + /* Not it */ + continue; + } + /* Found usplash */ + /* Read and save the command line of usplash in "cmdline" */ + { + /* Open /proc//cmdline */ + int cl_fd; + { + char *cmdline_filename; + ret = asprintf(&cmdline_filename, "/proc/%s/cmdline", + proc_ent->d_name); + if(ret == -1){ + perror("asprintf"); + goto fail_find_usplash; + } + cl_fd = open(cmdline_filename, O_RDONLY); + free(cmdline_filename); + if(cl_fd == -1){ + perror("open"); + goto fail_find_usplash; + } + } + size_t cmdline_allocated = 0; + char *tmp; + const size_t blocksize = 1024; + do { + /* Allocate more space? */ + if(cmdline_len + blocksize > cmdline_allocated){ + tmp = realloc(cmdline, cmdline_allocated + blocksize); + if(tmp == NULL){ + perror("realloc"); + close(cl_fd); + goto fail_find_usplash; + } + cmdline = tmp; + cmdline_allocated += blocksize; + } + /* Read data */ + sret = read(cl_fd, cmdline + cmdline_len, + cmdline_allocated - cmdline_len); + if(sret == -1){ + perror("read"); + close(cl_fd); + goto fail_find_usplash; + } + cmdline_len += (size_t)sret; + } while(sret != 0); + ret = close(cl_fd); + if(ret == -1){ + perror("close"); + goto fail_find_usplash; + } + } + /* Close directory */ + ret = closedir(proc_dir); + if(ret == -1){ + perror("closedir"); + goto fail_find_usplash; + } + /* Success */ + *cmdline_r = cmdline; + *cmdline_len_r = cmdline_len; + return pid; + } + + fail_find_usplash: + + free(cmdline); + if(proc_dir != NULL){ + int e = errno; + closedir(proc_dir); + errno = e; + } + return 0; +} + int main(__attribute__((unused))int argc, __attribute__((unused))char **argv){ int ret = 0; ssize_t sret; - bool an_error_occured = false; + int fifo_fd = -1; + int outfifo_fd = -1; + char *buf = NULL; + size_t buf_len = 0; + pid_t usplash_pid = -1; + bool usplash_accessed = false; - /* 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; - } + char *prompt = makeprompt(); + if(prompt == NULL){ + goto 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); - } + usplash_pid = find_usplash(&cmdline, &cmdline_len); if(usplash_pid == 0){ - free(prompt); - return EXIT_FAILURE; + goto failure; } /* Set up the signal handler */ @@ -294,172 +317,231 @@ new_action = { .sa_handler = termination_handler, .sa_flags = 0 }; sigemptyset(&new_action.sa_mask); - sigaddset(&new_action.sa_mask, SIGINT); - sigaddset(&new_action.sa_mask, SIGHUP); - sigaddset(&new_action.sa_mask, SIGTERM); + ret = sigaddset(&new_action.sa_mask, SIGINT); + if(ret == -1){ + perror("sigaddset"); + goto failure; + } + ret = sigaddset(&new_action.sa_mask, SIGHUP); + if(ret == -1){ + perror("sigaddset"); + goto failure; + } + ret = sigaddset(&new_action.sa_mask, SIGTERM); + if(ret == -1){ + perror("sigaddset"); + goto failure; + } ret = sigaction(SIGINT, NULL, &old_action); if(ret == -1){ - perror("sigaction"); - free(prompt); - return EXIT_FAILURE; + if(errno != EINTR){ + perror("sigaction"); + } + goto failure; } if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGINT, &new_action, NULL); if(ret == -1){ - perror("sigaction"); - free(prompt); - return EXIT_FAILURE; + if(errno != EINTR){ + perror("sigaction"); + } + goto failure; } } ret = sigaction(SIGHUP, NULL, &old_action); if(ret == -1){ - perror("sigaction"); - free(prompt); - return EXIT_FAILURE; + if(errno != EINTR){ + perror("sigaction"); + } + goto failure; } if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGHUP, &new_action, NULL); if(ret == -1){ - perror("sigaction"); - free(prompt); - return EXIT_FAILURE; + if(errno != EINTR){ + perror("sigaction"); + } + goto failure; } } ret = sigaction(SIGTERM, NULL, &old_action); if(ret == -1){ - perror("sigaction"); - free(prompt); - return EXIT_FAILURE; + if(errno != EINTR){ + perror("sigaction"); + } + goto failure; } if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGTERM, &new_action, NULL); if(ret == -1){ - perror("sigaction"); - free(prompt); - return EXIT_FAILURE; + if(errno != EINTR){ + perror("sigaction"); + } + goto failure; } } } + usplash_accessed = true; /* Write command to FIFO */ - if(not interrupted_by_signal){ - if(not usplash_write("TIMEOUT", "0") - and (errno != EINTR)){ - perror("usplash_write"); - an_error_occured = true; - } - } - if(not interrupted_by_signal and not an_error_occured){ - if(not usplash_write("INPUTQUIET", prompt) - and (errno != EINTR)){ - perror("usplash_write"); - an_error_occured = true; - } - } + if(not usplash_write(&fifo_fd, "TIMEOUT", "0")){ + if(errno != EINTR){ + perror("usplash_write"); + } + goto failure; + } + + if(interrupted_by_signal){ + goto failure; + } + + if(not usplash_write(&fifo_fd, "INPUTQUIET", prompt)){ + if(errno != EINTR){ + perror("usplash_write"); + } + goto failure; + } + + if(interrupted_by_signal){ + goto failure; + } + free(prompt); - - /* This is not really a loop; while() is used to be able to "break" - out of it; those breaks are marked "Big" */ - while(not interrupted_by_signal and not an_error_occured){ - char *buf = NULL; - size_t buf_len = 0; - - /* Open FIFO */ - int fifo_fd; - do{ - fifo_fd = open("/dev/.initramfs/usplash_outfifo", O_RDONLY); - if(fifo_fd == -1){ + prompt = NULL; + + /* Read reply from usplash */ + /* Open FIFO */ + outfifo_fd = open("/dev/.initramfs/usplash_outfifo", O_RDONLY); + if(outfifo_fd == -1){ + if(errno != EINTR){ + perror("open"); + } + goto failure; + } + + if(interrupted_by_signal){ + goto failure; + } + + /* Read from FIFO */ + size_t buf_allocated = 0; + const size_t blocksize = 1024; + do { + /* Allocate more space */ + if(buf_len + blocksize > buf_allocated){ + char *tmp = realloc(buf, buf_allocated + blocksize); + if(tmp == NULL){ if(errno != EINTR){ - 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 */ + } + goto failure; + } + buf = tmp; + buf_allocated += blocksize; + } + sret = read(outfifo_fd, buf + buf_len, + buf_allocated - buf_len); + if(sret == -1){ + if(errno != EINTR){ + perror("read"); + } + TEMP_FAILURE_RETRY(close(outfifo_fd)); + goto failure; + } + if(interrupted_by_signal){ + break; + } + + buf_len += (size_t)sret; + } while(sret != 0); + ret = close(outfifo_fd); + if(ret == -1){ + if(errno != EINTR){ + perror("close"); + } + goto failure; + } + outfifo_fd = -1; + + if(interrupted_by_signal){ + goto failure; + } + + if(not usplash_write(&fifo_fd, "TIMEOUT", "15")){ + if(errno != EINTR){ + perror("usplash_write"); + } + goto failure; + } + + if(interrupted_by_signal){ + goto failure; + } + + ret = close(fifo_fd); + if(ret == -1){ + if(errno != EINTR){ + perror("close"); + } + goto failure; + } + fifo_fd = -1; + + /* Print password to stdout */ + size_t written = 0; + while(written < buf_len){ + do { + sret = write(STDOUT_FILENO, buf + written, buf_len - written); + if(sret == -1){ + if(errno != EINTR){ + perror("write"); + } + goto failure; + } + } while(sret == -1); + + if(interrupted_by_signal){ + goto failure; + } + written += (size_t)sret; + } + free(buf); + buf = NULL; + + if(interrupted_by_signal){ + goto failure; + } + + free(cmdline); + return EXIT_SUCCESS; + + failure: + + free(buf); + + free(prompt); + + /* If usplash was never accessed, we can stop now */ + if(not usplash_accessed){ + return EXIT_FAILURE; + } + + /* Close FIFO */ + if(fifo_fd != -1){ + ret = (int)TEMP_FAILURE_RETRY(close(fifo_fd)); + if(ret == -1 and errno != EINTR){ + perror("close"); + } + fifo_fd = -1; + } + + /* Close output FIFO */ + if(outfifo_fd != -1){ + ret = (int)TEMP_FAILURE_RETRY(close(outfifo_fd)); + if(ret == -1){ + perror("close"); + } + } /* Create argc and argv for new usplash*/ int cmdline_argc = 0; @@ -489,6 +571,7 @@ kill(usplash_pid, SIGKILL); sleep(1); } + pid_t new_usplash_pid = fork(); if(new_usplash_pid == 0){ /* Child; will become new usplash process */ @@ -522,9 +605,37 @@ free(cmdline); free(cmdline_argv); sleep(2); - if(not usplash_write("PULSATE", NULL) - and (errno != EINTR)){ - perror("usplash_write"); + if(not usplash_write(&fifo_fd, "PULSATE", NULL)){ + if(errno != EINTR){ + perror("usplash_write"); + } + } + + /* Close FIFO (again) */ + if(fifo_fd != -1){ + ret = (int)TEMP_FAILURE_RETRY(close(fifo_fd)); + if(ret == -1 and errno != EINTR){ + perror("close"); + } + fifo_fd = -1; + } + + if(interrupted_by_signal){ + struct sigaction signal_action = { .sa_handler = SIG_DFL }; + sigemptyset(&signal_action.sa_mask); + ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received, + &signal_action, NULL)); + if(ret == -1){ + perror("sigaction"); + } + do { + ret = raise(signal_received); + } while(ret != 0 and errno == EINTR); + if(ret != 0){ + perror("raise"); + abort(); + } + TEMP_FAILURE_RETRY(pause()); } return EXIT_FAILURE;