=== modified file 'DBUS-API' --- DBUS-API 2011-10-02 19:18:24 +0000 +++ DBUS-API 2012-01-01 04:02:00 +0000 @@ -112,7 +112,7 @@ Disable(). f) The date and time this client will be disabled, as an RFC 3339 - string, or an empty string if this has not happened. + string, or an empty string if this is not scheduled. g) The date and time of the last approval request, as an RFC 3339 string, or an empty string if this has not happened. @@ -152,10 +152,13 @@ *** Rejected(s: Reason) This client was not given its secret for a specified Reason. +*** NewRequest(s: IPAddress) + A client at IPAdress has requested its secret. + * Copyright - Copyright © 2010-2011 Teddy Hogeborn - Copyright © 2010-2011 Björn Påhlsson + Copyright © 2010-2012 Teddy Hogeborn + Copyright © 2010-2012 Björn Påhlsson ** License: === modified file 'Makefile' --- Makefile 2011-10-15 16:48:03 +0000 +++ Makefile 2011-12-21 00:33:12 +0000 @@ -26,12 +26,16 @@ version=1.4.1 SED=sed +USER=$(firstword $(subst :, ,$(shell getent passwd _mandos || getent passwd nobody || echo 65534))) +GROUP=$(firstword $(subst :, ,$(shell getent group _mandos || getent group nobody || echo 65534))) + ## Use these settings for a traditional /usr/local install # PREFIX=$(DESTDIR)/usr/local # CONFDIR=$(DESTDIR)/etc/mandos # KEYDIR=$(DESTDIR)/etc/mandos/keys # MANDIR=$(PREFIX)/man # INITRAMFSTOOLS=$(DESTDIR)/etc/initramfs-tools +# STATEDIR=$(DESTDIR)/var/lib/mandos ## ## These settings are for a package-type install @@ -40,6 +44,7 @@ KEYDIR=$(DESTDIR)/etc/keys/mandos MANDIR=$(PREFIX)/share/man INITRAMFSTOOLS=$(DESTDIR)/usr/share/initramfs-tools +STATEDIR=$(DESTDIR)/var/lib/mandos ## GNUTLS_CFLAGS=$(shell pkg-config --cflags-only-I gnutls) @@ -230,7 +235,7 @@ distclean: clean mostlyclean: clean maintainer-clean: clean - -rm --force --recursive keydir confdir + -rm --force --recursive keydir confdir statedir check: all ./mandos --check @@ -250,7 +255,7 @@ @echo "###################################################################" ./plugin-runner --plugin-dir=plugins.d \ --config-file=plugin-runner.conf \ - --options-for=mandos-client:--seckey=keydir/seckey.txt,--pubkey=keydir/pubkey.txt \ + --options-for=mandos-client:--seckey=keydir/seckey.txt,--pubkey=keydir/pubkey.txt,--network-hook-dir=network-hooks.d \ $(CLIENTARGS) # Used by run-client @@ -260,13 +265,8 @@ # Run the server with a local config run-server: confdir/mandos.conf confdir/clients.conf - @echo "#################################################################" - @echo "# NOTE: Please IGNORE the error about \"Could not open file #" - @echo "# u'/var/run/mandos.pid'\" - it is harmless and is caused by #" - @echo "# the server not running as root. Do NOT run \"make run-server\" #" - @echo "# server as root if you didn't also unpack and compile it thus. #" - @echo "#################################################################" - ./mandos --debug --no-dbus --configdir=confdir $(SERVERARGS) + ./mandos --debug --no-dbus --configdir=confdir \ + --statedir=statedir $(SERVERARGS) # Used by run-server confdir/mandos.conf: mandos.conf @@ -277,6 +277,8 @@ install --mode=u=rw $< $@ # Add a client password ./mandos-keygen --dir keydir --password >> $@ +statedir: + install --directory statedir install: install-server install-client-nokey @@ -287,6 +289,8 @@ install-server: doc install --directory $(CONFDIR) + install --directory --mode=u=rwx --owner=$(USER) \ + --group=$(GROUP) $(STATEDIR) install --mode=u=rwx,go=rx mandos $(PREFIX)/sbin/mandos install --mode=u=rwx,go=rx --target-directory=$(PREFIX)/sbin \ mandos-ctl @@ -324,6 +328,8 @@ install --mode=u=rwx \ --directory "$(CONFDIR)/plugins.d"; \ fi + install --mode=u=rwx,go=rx --directory \ + "$(CONFDIR)/network-hooks.d" install --mode=u=rwx,go=rx \ --target-directory=$(PREFIX)/lib/mandos plugin-runner install --mode=u=rwx,go=rx --target-directory=$(PREFIX)/sbin \ === modified file 'TODO' --- TODO 2011-10-14 18:00:50 +0000 +++ TODO 2012-01-01 04:02:00 +0000 @@ -1,12 +1,11 @@ -*- org -*- -* Use _attribute_((nonnull)) wherever possible. - * [[http://www.undeadly.org/cgi?action=article&sid=20110530221728][OpenBSD]] * mandos-applet * mandos-client +** TODO [#A] Wireless network hook ** TODO [#B] Use capabilities instead of seteuid(). ** TODO [#B] Use struct sockaddr_storage instead of a union ** TODO [#B] Use getaddrinfo(hints=AI_NUMERICHOST) instead of inet_pton() @@ -40,24 +39,20 @@ ** TODO [#B] Use openat() * mandos (server) +** TODO Document why we ignore sigint ** TODO [#B] Log level :BUGS: -** TODO Persistent state :BUGS: - /var/lib/mandos/* *** TODO /etc/mandos/clients.d/*.conf Watch this directory and add/remove/update clients? ** TODO [#C] config for TXT record -** TODO Log level option - syslogger.setLevel(logging.WARNING) - + SetLogLevel D-Bus call +** TODO Log level dbus option + SetLogLevel D-Bus call ** TODO Implement --foreground :BUGS: [[info:standards:Option%20Table][Table of Long Options]] ** TODO Implement --socket [[info:standards:Option%20Table][Table of Long Options]] -** TODO Date+time on console log messages :BUGS: - Is this the default? ** TODO [#C] DBusServiceObjectUsingSuper ** TODO [#B] Global enable/disable flag -** TODO [#B] By-client countdown on secrets given +** TODO [#B] By-client countdown on number of secrets given ** TODO [#B] Support RFC 3339 time duration syntax ** More D-Bus methods *** NeedsPassword(50) - Timeout, default disapprove @@ -68,12 +63,20 @@ http://0pointer.de/blog/projects/systemd.html http://wiki.debian.org/systemd ** TODO Separate logging logic to own object -** TODO make clients to a dict! ** TODO [#A] Limit approval_delay to max gnutls/tls timeout value ** TODO [#B] break the wait on approval_delay if connection dies ** TODO Generate Client.runtime_expansions from client options + extra ** TODO Allow %%(checker)s as a runtime expansion ** TODO Use python-tlslite? +** TODO D-Bus AddClient() method on server object +** TODO Use org.freedesktop.DBus.Method.NoReply annotation on async methods. +** TODO Emit [[http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties][org.freedesktop.DBus.Properties.PropertiesChanged]] signal + TODO Deprecate se.recompile.Mandos.Client.PropertyChanged - annotate! + TODO Can use "invalidates" annotation to also emit on changed secret. +** TODO Support [[http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager][org.freedesktop.DBus.ObjectManager]] interface on server object + Deprecate methods GetAllClients(), GetAllClientsWithProperties() + and signals ClientAdded and ClientRemoved. +** TODO Save state periodically to recover better from hard shutdowns * mandos.xml ** Add mandos contact info in manual pages @@ -82,7 +85,6 @@ *** Handle "no D-Bus server" and/or "no Mandos server found" better *** [#B] --dump option ** TODO Support RFC 3339 time duration syntax -** TODO Send milliseconds if bare integer is passed as time duration * TODO mandos-dispatch Listens for specified D-Bus signals and spawns shell commands with @@ -93,8 +95,7 @@ ** Urwid client data displayer Better view of client data in the listing *** Properties popup -** Nicer crashes. Stack traces Messes up shell. -*** Print a nice "We are sorry" message, save stack trace to log. +** Print a nice "We are sorry" message, save stack trace to log. ** Show timeout countdown for approval * mandos-keygen === modified file 'clients.conf' --- clients.conf 2011-09-19 09:42:55 +0000 +++ clients.conf 2011-11-26 23:08:17 +0000 @@ -30,6 +30,9 @@ # How long one approval will last. ;approval_duration = 1s +# Whether this client is enabled by default +;enabled = True + ;#### ;# Example client === modified file 'debian/control' --- debian/control 2011-10-11 19:36:00 +0000 +++ debian/control 2011-12-21 17:09:12 +0000 @@ -17,7 +17,8 @@ Architecture: all Depends: ${misc:Depends}, python (>=2.6), python-gnutls, python-dbus, python-avahi, python-gobject, avahi-daemon, adduser, - python-urwid, python (>=2.7) | python-argparse + python-urwid, python (>=2.7) | python-argparse, + python-gnupginterface Recommends: fping Description: server giving encrypted passwords to Mandos clients This is the server part of the Mandos system, which allows @@ -37,7 +38,7 @@ Package: mandos-client Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, cryptsetup, - gnupg (<< 2) + gnupg (<< 2), initramfs-tools Breaks: dropbear (<= 0.53.1-1) Enhances: cryptsetup Description: do unattended reboots with an encrypted root file system === modified file 'debian/copyright' --- debian/copyright 2011-10-10 20:29:58 +0000 +++ debian/copyright 2011-12-31 23:05:34 +0000 @@ -4,8 +4,8 @@ Source: Files: * -Copyright: Copyright © 2008-2011 Teddy Hogeborn -Copyright: Copyright © 2008-2011 Björn Påhlsson +Copyright: Copyright © 2008-2012 Teddy Hogeborn +Copyright: Copyright © 2008-2012 Björn Påhlsson License: GPL-3+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as === modified file 'debian/mandos-client.README.Debian' --- debian/mandos-client.README.Debian 2011-10-05 16:00:56 +0000 +++ debian/mandos-client.README.Debian 2012-01-01 04:52:07 +0000 @@ -33,11 +33,12 @@ * Specifying a Client Network Interface At boot time the network interface to use will by default be - automatically detected. If should result in an incorrect interface, - edit the DEVICE setting in the "/etc/initramfs-tools/initramfs.conf" - file. (The default setting is empty, meaning to autodetect the - interface.) *If* the DEVICE setting is changed, it will be - necessary to update the initrd image by running the command + automatically detected. If this should result in an incorrect + interface, edit the DEVICE setting in the + "/etc/initramfs-tools/initramfs.conf" file. (The default setting is + empty, meaning it will autodetect the interface.) *If* the DEVICE + setting is changed, it will be necessary to update the initrd image + by running the command update-initramfs -k all -u @@ -51,6 +52,11 @@ 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, only real interface (such as "eth0") can be used. + This can be overcome by writing a "network hook" program to create + the interface (see mandos-client(8mandos)) and placing it in + "/etc/mandos/network-hooks.d", from where it will be copied into the + initial RAM disk. Example network hook scripts can be found in + "/usr/share/doc/mandos-client/network-hooks.d". * User-Supplied Plugins @@ -84,4 +90,4 @@ work, "--options-for=mandos-client:--connect=
:" needs to be manually added to the file "/etc/mandos/plugin-runner.conf". - -- Teddy Hogeborn , Wed, 5 Oct 2011 17:50:22 +0200 + -- Teddy Hogeborn , Mon, 28 Nov 2011 23:07:22 +0100 === modified file 'debian/mandos-client.docs' --- debian/mandos-client.docs 2008-10-18 11:17:22 +0000 +++ debian/mandos-client.docs 2011-11-27 02:32:20 +0000 @@ -1,3 +1,4 @@ NEWS README TODO +network-hooks.d === modified file 'debian/mandos.dirs' --- debian/mandos.dirs 2010-09-15 17:33:14 +0000 +++ debian/mandos.dirs 2011-11-26 22:22:20 +0000 @@ -4,3 +4,4 @@ etc/default etc/dbus-1/system.d usr/sbin +var/lib/mandos === modified file 'debian/mandos.postinst' --- debian/mandos.postinst 2011-10-10 20:29:58 +0000 +++ debian/mandos.postinst 2011-11-26 22:22:20 +0000 @@ -35,11 +35,12 @@ --disabled-password --gecos "Mandos password system" \ _mandos fi + chown _mandos:_mandos /var/lib/mandos ;; - + abort-upgrade|abort-deconfigure|abort-remove) ;; - + *) echo "$0 called with unknown argument '$1'" 1>&2 exit 1 === modified file 'debian/rules' --- debian/rules 2010-09-09 18:16:14 +0000 +++ debian/rules 2012-01-01 04:02:00 +0000 @@ -85,7 +85,8 @@ dh_fixperms --exclude etc/keys/mandos \ --exclude etc/mandos/clients.conf \ --exclude etc/mandos/plugins.d \ - --exclude usr/lib/mandos/plugins.d + --exclude usr/lib/mandos/plugins.d \ + --exclude usr/share/doc/mandos-client/network-hooks.d dh_installdeb dh_shlibdeps dh_gencontrol === modified file 'initramfs-tools-hook' --- initramfs-tools-hook 2011-10-05 16:56:06 +0000 +++ initramfs-tools-hook 2011-11-29 18:19:31 +0000 @@ -68,10 +68,11 @@ CONFDIR="/conf/conf.d/mandos" MANDOSDIR="/lib/mandos" PLUGINDIR="${MANDOSDIR}/plugins.d" +HOOKDIR="${MANDOSDIR}/network-hooks.d" # Make directories install --directory --mode=u=rwx,go=rx "${DESTDIR}${CONFDIR}" \ - "${DESTDIR}${MANDOSDIR}" + "${DESTDIR}${MANDOSDIR}" "${DESTDIR}${HOOKDIR}" install --owner=${mandos_user} --group=${mandos_group} --directory \ --mode=u=rwx "${DESTDIR}${PLUGINDIR}" @@ -106,6 +107,46 @@ esac done +# Get DEVICE from initramfs.conf and other files +. /etc/initramfs-tools/initramfs.conf +for conf in /etc/initramfs-tools/conf.d/*; do + if [ -n `basename \"$conf\" | grep '^[[:alnum:]][[:alnum:]\._-]*$' \ + | grep -v '\.dpkg-.*$'` ]; then + [ -f ${conf} ] && . ${conf} + fi +done +export DEVICE + +# Copy network hooks +for hook in /etc/mandos/network-hooks.d/*; do + case "`basename \"$hook\"`" in + "*") continue ;; + *[!A-Za-z0-9_.-]*) continue ;; + *) test -d "$hook" || copy_exec "$hook" "${HOOKDIR}" ;; + esac + if [ -x "$hook" ]; then + # Copy any files needed by the network hook + MANDOSNETHOOKDIR=/etc/mandos/network-hooks.d MODE=files \ + VERBOSITY=0 "$hook" files | while read file target; do + if [ ! -e "${file}" ]; then + echo "WARNING: file ${file} not found, requested by Mandos network hook '${hook##*/}'" >&2 + fi + if [ -z "${target}" ]; then + copy_exec "$file" + else + copy_exec "$file" "$target" + fi + done + # Copy and load any modules needed by the network hook + MANDOSNETHOOKDIR=/etc/mandos/network-hooks.d MODE=modules \ + VERBOSITY=0 "$hook" modules | while read module; do + if [ -z "${target}" ]; then + force_load "$module" + fi + done + fi +done + # GPGME needs /usr/bin/gpg if [ ! -e "${DESTDIR}/usr/bin/gpg" \ -a -n "`ls \"${DESTDIR}\"/usr/lib/libgpgme.so* \ === modified file 'intro.xml' --- intro.xml 2011-10-05 16:00:56 +0000 +++ intro.xml 2011-12-31 23:05:34 +0000 @@ -1,7 +1,7 @@ + %common; ]> @@ -31,6 +31,7 @@ 2011 + 2012 Teddy Hogeborn Björn Påhlsson === modified file 'mandos' --- mandos 2011-10-15 16:48:03 +0000 +++ mandos 2012-01-01 04:02:00 +0000 @@ -11,8 +11,8 @@ # "AvahiService" class, and some lines in "main". # # Everything else is -# Copyright © 2008-2011 Teddy Hogeborn -# Copyright © 2008-2011 Björn Påhlsson +# Copyright © 2008-2012 Teddy Hogeborn +# Copyright © 2008-2012 Björn Påhlsson # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -63,6 +63,8 @@ import cPickle as pickle import multiprocessing import types +import binascii +import tempfile import dbus import dbus.service @@ -73,6 +75,7 @@ import ctypes.util import xml.dom.minidom import inspect +import GnuPGInterface try: SO_BINDTODEVICE = socket.SO_BINDTODEVICE @@ -82,24 +85,129 @@ except ImportError: SO_BINDTODEVICE = None - version = "1.4.1" +stored_state_file = "clients.pickle" -#logger = logging.getLogger('mandos') -logger = logging.Logger('mandos') +logger = logging.getLogger() syslogger = (logging.handlers.SysLogHandler (facility = logging.handlers.SysLogHandler.LOG_DAEMON, address = str("/dev/log"))) -syslogger.setFormatter(logging.Formatter - ('Mandos [%(process)d]: %(levelname)s:' - ' %(message)s')) -logger.addHandler(syslogger) - -console = logging.StreamHandler() -console.setFormatter(logging.Formatter('%(name)s [%(process)d]:' - ' %(levelname)s:' - ' %(message)s')) -logger.addHandler(console) + +try: + if_nametoindex = (ctypes.cdll.LoadLibrary + (ctypes.util.find_library("c")) + .if_nametoindex) +except (OSError, AttributeError): + def if_nametoindex(interface): + "Get an interface index the hard way, i.e. using fcntl()" + SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h + with contextlib.closing(socket.socket()) as s: + ifreq = fcntl.ioctl(s, SIOCGIFINDEX, + struct.pack(str("16s16x"), + interface)) + interface_index = struct.unpack(str("I"), + ifreq[16:20])[0] + return interface_index + + +def initlogger(debug, level=logging.WARNING): + """init logger and add loglevel""" + + syslogger.setFormatter(logging.Formatter + ('Mandos [%(process)d]: %(levelname)s:' + ' %(message)s')) + logger.addHandler(syslogger) + + if debug: + console = logging.StreamHandler() + console.setFormatter(logging.Formatter('%(asctime)s %(name)s' + ' [%(process)d]:' + ' %(levelname)s:' + ' %(message)s')) + logger.addHandler(console) + logger.setLevel(level) + + +class PGPError(Exception): + """Exception if encryption/decryption fails""" + pass + + +class PGPEngine(object): + """A simple class for OpenPGP symmetric encryption & decryption""" + def __init__(self): + self.gnupg = GnuPGInterface.GnuPG() + self.tempdir = tempfile.mkdtemp(prefix="mandos-") + self.gnupg = GnuPGInterface.GnuPG() + self.gnupg.options.meta_interactive = False + self.gnupg.options.homedir = self.tempdir + self.gnupg.options.extra_args.extend(['--force-mdc', + '--quiet', + '--no-use-agent']) + + def __enter__(self): + return self + + def __exit__ (self, exc_type, exc_value, traceback): + self._cleanup() + return False + + def __del__(self): + self._cleanup() + + def _cleanup(self): + if self.tempdir is not None: + # Delete contents of tempdir + for root, dirs, files in os.walk(self.tempdir, + topdown = False): + for filename in files: + os.remove(os.path.join(root, filename)) + for dirname in dirs: + os.rmdir(os.path.join(root, dirname)) + # Remove tempdir + os.rmdir(self.tempdir) + self.tempdir = None + + def password_encode(self, password): + # Passphrase can not be empty and can not contain newlines or + # NUL bytes. So we prefix it and hex encode it. + return b"mandos" + binascii.hexlify(password) + + def encrypt(self, data, password): + self.gnupg.passphrase = self.password_encode(password) + with open(os.devnull) as devnull: + try: + proc = self.gnupg.run(['--symmetric'], + create_fhs=['stdin', 'stdout'], + attach_fhs={'stderr': devnull}) + with contextlib.closing(proc.handles['stdin']) as f: + f.write(data) + with contextlib.closing(proc.handles['stdout']) as f: + ciphertext = f.read() + proc.wait() + except IOError as e: + raise PGPError(e) + self.gnupg.passphrase = None + return ciphertext + + def decrypt(self, data, password): + self.gnupg.passphrase = self.password_encode(password) + with open(os.devnull) as devnull: + try: + proc = self.gnupg.run(['--decrypt'], + create_fhs=['stdin', 'stdout'], + attach_fhs={'stderr': devnull}) + with contextlib.closing(proc.handles['stdin'] ) as f: + f.write(data) + with contextlib.closing(proc.handles['stdout']) as f: + decrypted_plaintext = f.read() + proc.wait() + except IOError as e: + raise PGPError(e) + self.gnupg.passphrase = None + return decrypted_plaintext + + class AvahiError(Exception): def __init__(self, value, *args, **kwargs): @@ -164,10 +272,6 @@ .GetAlternativeServiceName(self.name)) logger.info("Changing Zeroconf service name to %r ...", self.name) - syslogger.setFormatter(logging.Formatter - ('Mandos (%s) [%%(process)d]:' - ' %%(levelname)s: %%(message)s' - % self.name)) self.remove() try: self.add() @@ -193,7 +297,7 @@ avahi.DBUS_INTERFACE_ENTRY_GROUP) self.entry_group_state_changed_match = ( self.group.connect_to_signal( - 'StateChanged', self .entry_group_state_changed)) + 'StateChanged', self.entry_group_state_changed)) logger.debug("Adding Zeroconf service '%s' of type '%s' ...", self.name, self.type) self.group.AddService( @@ -225,7 +329,7 @@ try: self.group.Free() except (dbus.exceptions.UnknownMethodException, - dbus.exceptions.DBusException) as e: + dbus.exceptions.DBusException): pass self.group = None self.remove() @@ -265,8 +369,17 @@ self.server_state_changed) self.server_state_changed(self.server.GetState()) +class AvahiServiceToSyslog(AvahiService): + def rename(self): + """Add the new name to the syslog messages""" + ret = AvahiService.rename(self) + syslogger.setFormatter(logging.Formatter + ('Mandos (%s) [%%(process)d]:' + ' %%(levelname)s: %%(message)s' + % self.name)) + return ret -def _timedelta_to_milliseconds(td): +def timedelta_to_milliseconds(td): "Convert a datetime.timedelta() to milliseconds" return ((td.days * 24 * 60 * 60 * 1000) + (td.seconds * 1000) @@ -276,7 +389,7 @@ """A representation of a client host served by this server. Attributes: - _approved: bool(); 'None' if not yet approved/disapproved + approved: bool(); 'None' if not yet approved/disapproved approval_delay: datetime.timedelta(); Time to wait for approval approval_duration: datetime.timedelta(); Duration of one approval checker: subprocess.Popen(); a running checker process used @@ -289,8 +402,9 @@ instance %(name)s can be used in the command. checker_initiator_tag: a gobject event source tag, or None created: datetime.datetime(); (UTC) object creation + client_structure: Object describing what attributes a client has + and is used for storing the client at exit current_checker_command: string; current running checker_command - disable_hook: If set, called by disable() as disable_hook(self) disable_initiator_tag: a gobject event source tag, or None enabled: bool() fingerprint: string (40 or 32 hexadecimal digits); used to @@ -299,7 +413,10 @@ interval: datetime.timedelta(); How often to start a new checker last_approval_request: datetime.datetime(); (UTC) or None last_checked_ok: datetime.datetime(); (UTC) or None - last_enabled: datetime.datetime(); (UTC) + last_checker_status: integer between 0 and 255 reflecting exit + status of last checker. -1 reflects crashed + checker, or None. + last_enabled: datetime.datetime(); (UTC) or None name: string; from the config file, used in log messages and D-Bus identifiers secret: bytestring; sent verbatim (over TLS) to client @@ -315,81 +432,134 @@ "created", "enabled", "fingerprint", "host", "interval", "last_checked_ok", "last_enabled", "name", "timeout") + client_defaults = { "timeout": "5m", + "extended_timeout": "15m", + "interval": "2m", + "checker": "fping -q -- %%(host)s", + "host": "", + "approval_delay": "0s", + "approval_duration": "1s", + "approved_by_default": "True", + "enabled": "True", + } def timeout_milliseconds(self): "Return the 'timeout' attribute in milliseconds" - return _timedelta_to_milliseconds(self.timeout) + return timedelta_to_milliseconds(self.timeout) def extended_timeout_milliseconds(self): "Return the 'extended_timeout' attribute in milliseconds" - return _timedelta_to_milliseconds(self.extended_timeout) + return timedelta_to_milliseconds(self.extended_timeout) def interval_milliseconds(self): "Return the 'interval' attribute in milliseconds" - return _timedelta_to_milliseconds(self.interval) + return timedelta_to_milliseconds(self.interval) def approval_delay_milliseconds(self): - return _timedelta_to_milliseconds(self.approval_delay) - - def __init__(self, name = None, disable_hook=None, config=None): + return timedelta_to_milliseconds(self.approval_delay) + + @staticmethod + def config_parser(config): + """Construct a new dict of client settings of this form: + { client_name: {setting_name: value, ...}, ...} + with exceptions for any special settings as defined above. + NOTE: Must be a pure function. Must return the same result + value given the same arguments. + """ + settings = {} + for client_name in config.sections(): + section = dict(config.items(client_name)) + client = settings[client_name] = {} + + client["host"] = section["host"] + # Reformat values from string types to Python types + client["approved_by_default"] = config.getboolean( + client_name, "approved_by_default") + client["enabled"] = config.getboolean(client_name, + "enabled") + + client["fingerprint"] = (section["fingerprint"].upper() + .replace(" ", "")) + if "secret" in section: + client["secret"] = section["secret"].decode("base64") + elif "secfile" in section: + with open(os.path.expanduser(os.path.expandvars + (section["secfile"])), + "rb") as secfile: + client["secret"] = secfile.read() + else: + raise TypeError("No secret or secfile for section %s" + % section) + client["timeout"] = string_to_delta(section["timeout"]) + client["extended_timeout"] = string_to_delta( + section["extended_timeout"]) + client["interval"] = string_to_delta(section["interval"]) + client["approval_delay"] = string_to_delta( + section["approval_delay"]) + client["approval_duration"] = string_to_delta( + section["approval_duration"]) + client["checker_command"] = section["checker"] + client["last_approval_request"] = None + client["last_checked_ok"] = None + client["last_checker_status"] = None + + return settings + + + def __init__(self, settings, name = None): """Note: the 'checker' key in 'config' sets the 'checker_command' attribute and *not* the 'checker' attribute.""" self.name = name - if config is None: - config = {} + # adding all client settings + for setting, value in settings.iteritems(): + setattr(self, setting, value) + + if self.enabled: + if not hasattr(self, "last_enabled"): + self.last_enabled = datetime.datetime.utcnow() + if not hasattr(self, "expires"): + self.expires = (datetime.datetime.utcnow() + + self.timeout) + else: + self.last_enabled = None + self.expires = None + logger.debug("Creating client %r", self.name) # Uppercase and remove spaces from fingerprint for later # comparison purposes with return value from the fingerprint() # function - self.fingerprint = (config["fingerprint"].upper() - .replace(" ", "")) logger.debug(" Fingerprint: %s", self.fingerprint) - if "secret" in config: - self.secret = config["secret"].decode("base64") - elif "secfile" in config: - with open(os.path.expanduser(os.path.expandvars - (config["secfile"])), - "rb") as secfile: - self.secret = secfile.read() - else: - raise TypeError("No secret or secfile for client %s" - % self.name) - self.host = config.get("host", "") - self.created = datetime.datetime.utcnow() - self.enabled = False - self.last_approval_request = None - self.last_enabled = None - self.last_checked_ok = None - self.timeout = string_to_delta(config["timeout"]) - self.extended_timeout = string_to_delta(config - ["extended_timeout"]) - self.interval = string_to_delta(config["interval"]) - self.disable_hook = disable_hook + self.created = settings.get("created", + datetime.datetime.utcnow()) + + # attributes specific for this server instance self.checker = None self.checker_initiator_tag = None self.disable_initiator_tag = None - self.expires = None self.checker_callback_tag = None - self.checker_command = config["checker"] self.current_checker_command = None - self.last_connect = None - self._approved = None - self.approved_by_default = config.get("approved_by_default", - True) + self.approved = None self.approvals_pending = 0 - self.approval_delay = string_to_delta( - config["approval_delay"]) - self.approval_duration = string_to_delta( - config["approval_duration"]) self.changedstate = (multiprocessing_manager .Condition(multiprocessing_manager .Lock())) + self.client_structure = [attr for attr in + self.__dict__.iterkeys() + if not attr.startswith("_")] + self.client_structure.append("client_structure") + + for name, t in inspect.getmembers(type(self), + lambda obj: + isinstance(obj, + property)): + if not name.startswith("_"): + self.client_structure.append(name) + # Send notice to process children that client state has changed def send_changedstate(self): - self.changedstate.acquire() - self.changedstate.notify_all() - self.changedstate.release() + with self.changedstate: + self.changedstate.notify_all() def enable(self): """Start this client's checker and timeout hooks""" @@ -397,20 +567,10 @@ # Already enabled return self.send_changedstate() - # Schedule a new checker to be started an 'interval' from now, - # and every interval from then on. - self.checker_initiator_tag = (gobject.timeout_add - (self.interval_milliseconds(), - self.start_checker)) - # Schedule a disable() when 'timeout' has passed self.expires = datetime.datetime.utcnow() + self.timeout - self.disable_initiator_tag = (gobject.timeout_add - (self.timeout_milliseconds(), - self.disable)) self.enabled = True self.last_enabled = datetime.datetime.utcnow() - # Also start a new checker *right now*. - self.start_checker() + self.init_checker() def disable(self, quiet=True): """Disable this client.""" @@ -428,23 +588,33 @@ 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 # Do not run this again if called by a gobject.timeout_add return False def __del__(self): - self.disable_hook = None self.disable() + def init_checker(self): + # Schedule a new checker to be started an 'interval' from now, + # and every interval from then on. + self.checker_initiator_tag = (gobject.timeout_add + (self.interval_milliseconds(), + self.start_checker)) + # Schedule a disable() when 'timeout' has passed + self.disable_initiator_tag = (gobject.timeout_add + (self.timeout_milliseconds(), + self.disable)) + # Also start a new checker *right now*. + self.start_checker() + def checker_callback(self, pid, condition, command): """The checker has completed, so take appropriate actions.""" self.checker_callback_tag = None self.checker = None if os.WIFEXITED(condition): - exitstatus = os.WEXITSTATUS(condition) - if exitstatus == 0: + self.last_checker_status = os.WEXITSTATUS(condition) + if self.last_checker_status == 0: logger.info("Checker for %(name)s succeeded", vars(self)) self.checked_ok() @@ -452,6 +622,7 @@ logger.info("Checker for %(name)s failed", vars(self)) else: + self.last_checker_status = -1 logger.warning("Checker for %(name)s crashed?", vars(self)) @@ -468,7 +639,7 @@ gobject.source_remove(self.disable_initiator_tag) if getattr(self, "enabled", False): self.disable_initiator_tag = (gobject.timeout_add - (_timedelta_to_milliseconds + (timedelta_to_milliseconds (timeout), self.disable)) self.expires = datetime.datetime.utcnow() + timeout @@ -679,8 +850,8 @@ # signatures other than "ay". if prop._dbus_signature != "ay": raise ValueError - value = dbus.ByteArray(''.join(unichr(byte) - for byte in value)) + value = dbus.ByteArray(b''.join(chr(byte) + for byte in value)) prop(value) @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s", @@ -691,7 +862,7 @@ Note: Will not include properties with access="write". """ - all = {} + properties = {} for name, prop in self._get_all_dbus_properties(): if (interface_name and interface_name != prop._dbus_interface): @@ -702,11 +873,11 @@ continue value = prop() if not hasattr(value, "variant_level"): - all[name] = value + properties[name] = value continue - all[name] = type(value)(value, variant_level= - value.variant_level+1) - return dbus.Dictionary(all, signature="sv") + properties[name] = type(value)(value, variant_level= + value.variant_level+1) + return dbus.Dictionary(properties, signature="sv") @dbus.service.method(dbus.INTROSPECTABLE_IFACE, out_signature="s", @@ -763,6 +934,7 @@ return dbus.String(dt.isoformat(), variant_level=variant_level) + class AlternateDBusNamesMetaclass(DBusObjectWithProperties .__metaclass__): """Applied to an empty subclass of a D-Bus object, this metaclass @@ -860,6 +1032,7 @@ attribute.func_closure))) return type.__new__(mcs, name, bases, attr) + class ClientDBus(Client, DBusObjectWithProperties): """A Client class using D-Bus @@ -874,9 +1047,11 @@ # dbus.service.Object doesn't use super(), so we can't either. def __init__(self, bus = None, *args, **kwargs): - self._approvals_pending = 0 self.bus = bus Client.__init__(self, *args, **kwargs) + self._approvals_pending = 0 + + self._approvals_pending = 0 # Only now, when this client is initialized, can it show up on # the D-Bus client_object_name = unicode(self.name).translate( @@ -892,7 +1067,7 @@ variant_level=1): """ Modify a variable so that it's a property which announces its changes to DBus. - + transform_fun: Function that takes a value and a variant_level and transforms it to a D-Bus type. dbus_name: D-Bus name of the variable @@ -932,24 +1107,24 @@ datetime_to_dbus, "LastApprovalRequest") approved_by_default = notifychangeproperty(dbus.Boolean, "ApprovedByDefault") - approval_delay = notifychangeproperty(dbus.UInt16, + approval_delay = notifychangeproperty(dbus.UInt64, "ApprovalDelay", type_func = - _timedelta_to_milliseconds) + timedelta_to_milliseconds) approval_duration = notifychangeproperty( - dbus.UInt16, "ApprovalDuration", - type_func = _timedelta_to_milliseconds) + dbus.UInt64, "ApprovalDuration", + type_func = timedelta_to_milliseconds) host = notifychangeproperty(dbus.String, "Host") - timeout = notifychangeproperty(dbus.UInt16, "Timeout", + timeout = notifychangeproperty(dbus.UInt64, "Timeout", type_func = - _timedelta_to_milliseconds) + timedelta_to_milliseconds) extended_timeout = notifychangeproperty( - dbus.UInt16, "ExtendedTimeout", - type_func = _timedelta_to_milliseconds) - interval = notifychangeproperty(dbus.UInt16, + dbus.UInt64, "ExtendedTimeout", + type_func = timedelta_to_milliseconds) + interval = notifychangeproperty(dbus.UInt64, "Interval", type_func = - _timedelta_to_milliseconds) + timedelta_to_milliseconds) checker_command = notifychangeproperty(dbus.String, "Checker") del notifychangeproperty @@ -997,13 +1172,13 @@ return r def _reset_approved(self): - self._approved = None + self.approved = None return False def approve(self, value=True): self.send_changedstate() - self._approved = value - gobject.timeout_add(_timedelta_to_milliseconds + self.approved = value + gobject.timeout_add(timedelta_to_milliseconds (self.approval_duration), self._reset_approved) @@ -1052,6 +1227,14 @@ "D-Bus signal" return self.need_approval() + # NeRwequest - signal + @dbus.service.signal(_interface, signature="s") + def NewRequest(self, ip): + """D-Bus signal + Is sent after a client request a password. + """ + pass + ## Methods # Approve - method @@ -1115,7 +1298,7 @@ access="readwrite") def ApprovalDuration_dbus_property(self, value=None): if value is None: # get - return dbus.UInt64(_timedelta_to_milliseconds( + return dbus.UInt64(timedelta_to_milliseconds( self.approval_duration)) self.approval_duration = datetime.timedelta(0, 0, 0, value) @@ -1135,12 +1318,12 @@ def Host_dbus_property(self, value=None): if value is None: # get return dbus.String(self.host) - self.host = value + self.host = unicode(value) # Created - property @dbus_service_property(_interface, signature="s", access="read") def Created_dbus_property(self): - return dbus.String(datetime_to_dbus(self.created)) + return datetime_to_dbus(self.created) # LastEnabled - property @dbus_service_property(_interface, signature="s", access="read") @@ -1184,26 +1367,25 @@ if value is None: # get return dbus.UInt64(self.timeout_milliseconds()) self.timeout = datetime.timedelta(0, 0, 0, value) - if getattr(self, "disable_initiator_tag", None) is None: - return # Reschedule timeout - gobject.source_remove(self.disable_initiator_tag) - self.disable_initiator_tag = None - self.expires = None - time_to_die = _timedelta_to_milliseconds((self - .last_checked_ok - + self.timeout) - - datetime.datetime - .utcnow()) - if time_to_die <= 0: - # The timeout has passed - self.disable() - else: - self.expires = (datetime.datetime.utcnow() - + datetime.timedelta(milliseconds = - time_to_die)) - self.disable_initiator_tag = (gobject.timeout_add - (time_to_die, self.disable)) + if self.enabled: + now = datetime.datetime.utcnow() + time_to_die = timedelta_to_milliseconds( + (self.last_checked_ok + self.timeout) - now) + if time_to_die <= 0: + # The timeout has passed + self.disable() + else: + self.expires = (now + + datetime.timedelta(milliseconds = + time_to_die)) + if (getattr(self, "disable_initiator_tag", None) + is None): + return + gobject.source_remove(self.disable_initiator_tag) + self.disable_initiator_tag = (gobject.timeout_add + (time_to_die, + self.disable)) # ExtendedTimeout - property @dbus_service_property(_interface, signature="t", @@ -1222,11 +1404,12 @@ self.interval = datetime.timedelta(0, 0, 0, value) if getattr(self, "checker_initiator_tag", None) is None: return - # Reschedule checker run - gobject.source_remove(self.checker_initiator_tag) - self.checker_initiator_tag = (gobject.timeout_add - (value, self.start_checker)) - self.start_checker() # Start one now, too + if self.enabled: + # Reschedule checker run + gobject.source_remove(self.checker_initiator_tag) + self.checker_initiator_tag = (gobject.timeout_add + (value, self.start_checker)) + self.start_checker() # Start one now, too # Checker - property @dbus_service_property(_interface, signature="s", @@ -1234,7 +1417,7 @@ def Checker_dbus_property(self, value=None): if value is None: # get return dbus.String(self.checker_command) - self.checker_command = value + self.checker_command = unicode(value) # CheckerRunning - property @dbus_service_property(_interface, signature="b", @@ -1269,7 +1452,7 @@ raise KeyError() def __getattribute__(self, name): - if(name == '_pipe'): + if name == '_pipe': return super(ProxyClient, self).__getattribute__(name) self._pipe.send(('getattr', name)) data = self._pipe.recv() @@ -1282,13 +1465,15 @@ return func def __setattr__(self, name, value): - if(name == '_pipe'): + if name == '_pipe': return super(ProxyClient, self).__setattr__(name, value) self._pipe.send(('setattr', name, value)) + class ClientDBusTransitional(ClientDBus): __metaclass__ = AlternateDBusNamesMetaclass + class ClientHandler(socketserver.BaseRequestHandler, object): """A class to handle client connections. @@ -1362,6 +1547,10 @@ except KeyError: return + if self.server.use_dbus: + # Emit D-Bus signal + client.NewRequest(str(self.client_address)) + if client.approval_delay: delay = client.approval_delay client.approvals_pending += 1 @@ -1376,10 +1565,10 @@ client.Rejected("Disabled") return - if client._approved or not client.approval_delay: + if client.approved or not client.approval_delay: #We are approved or approval is disabled break - elif client._approved is None: + elif client.approved is None: logger.info("Client %s needs approval", client.name) if self.server.use_dbus: @@ -1399,7 +1588,7 @@ time = datetime.datetime.now() client.changedstate.acquire() (client.changedstate.wait - (float(client._timedelta_to_milliseconds(delay) + (float(client.timedelta_to_milliseconds(delay) / 1000))) client.changedstate.release() time2 = datetime.datetime.now() @@ -1504,7 +1693,7 @@ # Convert the buffer to a Python bytestring fpr = ctypes.string_at(buf, buf_len.value) # Convert the bytestring to hexadecimal notation - hex_fpr = ''.join("%02X" % ord(char) for char in fpr) + hex_fpr = binascii.hexlify(fpr).upper() return hex_fpr @@ -1513,7 +1702,7 @@ def sub_process_main(self, request, address): try: self.finish_request(request, address) - except: + except Exception: self.handle_error(request, address) self.close_request(request) @@ -1624,7 +1813,7 @@ self.enabled = False self.clients = clients if self.clients is None: - self.clients = set() + self.clients = {} self.use_dbus = use_dbus self.gnutls_priority = gnutls_priority IPv6_TCPServer.__init__(self, server_address, @@ -1677,7 +1866,7 @@ fpr = request[1] address = request[2] - for c in self.clients: + for c in self.clients.itervalues(): if c.fingerprint == fpr: client = c break @@ -1767,30 +1956,6 @@ return timevalue -def if_nametoindex(interface): - """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")) - .if_nametoindex) - except (OSError, AttributeError): - logger.warning("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 contextlib.closing(socket.socket()) as s: - ifreq = fcntl.ioctl(s, SIOCGIFINDEX, - struct.pack(str("16s16x"), - interface)) - interface_index = struct.unpack(str("I"), - ifreq[16:20])[0] - return interface_index - return if_nametoindex(interface) - - def daemon(nochdir = False, noclose = False): """See daemon(3). Standard BSD Unix function. @@ -1851,6 +2016,12 @@ " system bus interface") parser.add_argument("--no-ipv6", action="store_false", dest="use_ipv6", help="Do not use IPv6") + parser.add_argument("--no-restore", action="store_false", + dest="restore", help="Do not restore stored" + " state") + parser.add_argument("--statedir", metavar="DIR", + help="Directory to save/restore state in") + options = parser.parse_args() if options.check: @@ -1869,6 +2040,8 @@ "use_dbus": "True", "use_ipv6": "True", "debuglevel": "", + "restore": "True", + "statedir": "/var/lib/mandos" } # Parse config file for server-global settings @@ -1891,7 +2064,8 @@ # options, if set. for option in ("interface", "address", "port", "debug", "priority", "servicename", "configdir", - "use_dbus", "use_ipv6", "debuglevel"): + "use_dbus", "use_ipv6", "debuglevel", "restore", + "statedir"): value = getattr(options, option) if value is not None: server_settings[option] = value @@ -1909,6 +2083,17 @@ debuglevel = server_settings["debuglevel"] use_dbus = server_settings["use_dbus"] use_ipv6 = server_settings["use_ipv6"] + stored_state_path = os.path.join(server_settings["statedir"], + stored_state_file) + + if debug: + initlogger(debug, logging.DEBUG) + else: + if not debuglevel: + initlogger(debug) + else: + level = getattr(logging, debuglevel.upper()) + initlogger(debug, level) if server_settings["servicename"] != "Mandos": syslogger.setFormatter(logging.Formatter @@ -1917,15 +2102,8 @@ % server_settings["servicename"])) # Parse config file with clients - client_defaults = { "timeout": "5m", - "extended_timeout": "15m", - "interval": "2m", - "checker": "fping -q -- %%(host)s", - "host": "", - "approval_delay": "0s", - "approval_duration": "1s", - } - client_config = configparser.SafeConfigParser(client_defaults) + client_config = configparser.SafeConfigParser(Client + .client_defaults) client_config.read(os.path.join(server_settings["configdir"], "clients.conf")) @@ -1969,14 +2147,6 @@ if error[0] != errno.EPERM: raise error - if not debug and not debuglevel: - syslogger.setLevel(logging.WARNING) - console.setLevel(logging.WARNING) - if debuglevel: - level = getattr(logging, debuglevel.upper()) - syslogger.setLevel(level) - console.setLevel(level) - if debug: # Enable all possible GnuTLS debugging @@ -1996,15 +2166,14 @@ os.dup2(null, sys.stdin.fileno()) if null > 2: os.close(null) - else: - # No console logging - logger.removeHandler(console) # Need to fork before connecting to D-Bus if not debug: # Close all input and output, do double fork, etc. daemon() + gobject.threads_init() + global main_loop # From the Avahi example code DBusGMainLoop(set_as_default=True ) @@ -2024,9 +2193,10 @@ server_settings["use_dbus"] = False tcp_server.use_dbus = False protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET - service = AvahiService(name = server_settings["servicename"], - servicetype = "_mandos._tcp", - protocol = protocol, bus = bus) + service = AvahiServiceToSyslog(name = + server_settings["servicename"], + servicetype = "_mandos._tcp", + protocol = protocol, bus = bus) if server_settings["interface"]: service.interface = (if_nametoindex (str(server_settings["interface"]))) @@ -2038,23 +2208,98 @@ if use_dbus: client_class = functools.partial(ClientDBusTransitional, bus = bus) - def client_config_items(config, section): - special_settings = { - "approved_by_default": - lambda: config.getboolean(section, - "approved_by_default"), - } - for name, value in config.items(section): + + client_settings = Client.config_parser(client_config) + old_client_settings = {} + clients_data = {} + + # Get client data and settings from last running state. + if server_settings["restore"]: + try: + with open(stored_state_path, "rb") as stored_state: + clients_data, old_client_settings = (pickle.load + (stored_state)) + os.remove(stored_state_path) + except IOError as e: + logger.warning("Could not load persistent state: {0}" + .format(e)) + if e.errno != errno.ENOENT: + raise + except EOFError as e: + logger.warning("Could not load persistent state: " + "EOFError: {0}".format(e)) + + with PGPEngine() as pgp: + for client_name, client in clients_data.iteritems(): + # Decide which value to use after restoring saved state. + # We have three different values: Old config file, + # new config file, and saved state. + # New config value takes precedence if it differs from old + # config value, otherwise use saved state. + for name, value in client_settings[client_name].items(): + try: + # For each value in new config, check if it + # differs from the old config value (Except for + # the "secret" attribute) + if (name != "secret" and + value != old_client_settings[client_name] + [name]): + client[name] = value + except KeyError: + pass + + # Clients who has passed its expire date can still be + # enabled if its last checker was successful. Clients + # whose checker failed before we stored its state is + # assumed to have failed all checkers during downtime. + if client["enabled"]: + if datetime.datetime.utcnow() >= client["expires"]: + if not client["last_checked_ok"]: + logger.warning( + "disabling client {0} - Client never " + "performed a successfull checker" + .format(client["name"])) + client["enabled"] = False + elif client["last_checker_status"] != 0: + logger.warning( + "disabling client {0} - Client " + "last checker failed with error code {1}" + .format(client["name"], + client["last_checker_status"])) + client["enabled"] = False + else: + client["expires"] = (datetime.datetime + .utcnow() + + client["timeout"]) + logger.debug("Last checker succeeded," + " keeping {0} enabled" + .format(client["name"])) try: - yield (name, special_settings[name]()) - except KeyError: - yield (name, value) - - tcp_server.clients.update(set( - client_class(name = section, - config= dict(client_config_items( - client_config, section))) - for section in client_config.sections())) + client["secret"] = ( + pgp.decrypt(client["encrypted_secret"], + client_settings[client_name] + ["secret"])) + except PGPError: + # If decryption fails, we use secret from new settings + logger.debug("Failed to decrypt {0} old secret" + .format(client_name)) + client["secret"] = ( + client_settings[client_name]["secret"]) + + + # Add/remove clients based on new changes made to config + for client_name in (set(old_client_settings) + - set(client_settings)): + del clients_data[client_name] + for client_name in (set(client_settings) + - set(old_client_settings)): + clients_data[client_name] = client_settings[client_name] + + # Create clients all clients + for client_name, client in clients_data.iteritems(): + tcp_server.clients[client_name] = client_class( + name = client_name, settings = client) + if not tcp_server.clients: logger.warning("No clients defined") @@ -2071,7 +2316,6 @@ # "pidfile" was never created pass del pidfilename - signal.signal(signal.SIGINT, signal.SIG_IGN) signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit()) @@ -2103,7 +2347,8 @@ def GetAllClients(self): "D-Bus method" return dbus.Array(c.dbus_object_path - for c in tcp_server.clients) + for c in + tcp_server.clients.itervalues()) @dbus.service.method(_interface, out_signature="a{oa{sv}}") @@ -2111,15 +2356,15 @@ "D-Bus method" return dbus.Dictionary( ((c.dbus_object_path, c.GetAll("")) - for c in tcp_server.clients), + for c in tcp_server.clients.itervalues()), signature="oa{sv}") @dbus.service.method(_interface, in_signature="o") def RemoveClient(self, object_path): "D-Bus method" - for c in tcp_server.clients: + for c in tcp_server.clients.itervalues(): if c.dbus_object_path == object_path: - tcp_server.clients.remove(c) + del tcp_server.clients[c.name] c.remove_from_connection() # Don't signal anything except ClientRemoved c.disable(quiet=True) @@ -2139,11 +2384,62 @@ service.cleanup() multiprocessing.active_children() + if not (tcp_server.clients or client_settings): + return + + # Store client before exiting. Secrets are encrypted with key + # based on what config file has. If config file is + # removed/edited, old secret will thus be unrecovable. + clients = {} + with PGPEngine() as pgp: + for client in tcp_server.clients.itervalues(): + key = client_settings[client.name]["secret"] + client.encrypted_secret = pgp.encrypt(client.secret, + key) + client_dict = {} + + # A list of attributes that can not be pickled + # + secret. + exclude = set(("bus", "changedstate", "secret", + "checker")) + for name, typ in (inspect.getmembers + (dbus.service.Object)): + exclude.add(name) + + client_dict["encrypted_secret"] = (client + .encrypted_secret) + for attr in client.client_structure: + if attr not in exclude: + client_dict[attr] = getattr(client, attr) + + clients[client.name] = client_dict + del client_settings[client.name]["secret"] + + try: + tempfd, tempname = tempfile.mkstemp(suffix=".pickle", + prefix="clients-", + dir=os.path.dirname + (stored_state_path)) + with os.fdopen(tempfd, "wb") as stored_state: + pickle.dump((clients, client_settings), stored_state) + os.rename(tempname, stored_state_path) + except (IOError, OSError) as e: + logger.warning("Could not save persistent state: {0}" + .format(e)) + if not debug: + try: + os.remove(tempname) + except NameError: + pass + if e.errno not in set((errno.ENOENT, errno.EACCES, + errno.EEXIST)): + raise e + + # Delete all clients, and settings from config while tcp_server.clients: - client = tcp_server.clients.pop() + name, client = tcp_server.clients.popitem() if use_dbus: client.remove_from_connection() - client.disable_hook = None # Don't signal anything except ClientRemoved client.disable(quiet=True) if use_dbus: @@ -2151,14 +2447,17 @@ mandos_dbus_service.ClientRemoved(client .dbus_object_path, client.name) + client_settings.clear() atexit.register(cleanup) - for client in tcp_server.clients: + for client in tcp_server.clients.itervalues(): if use_dbus: # Emit D-Bus signal mandos_dbus_service.ClientAdded(client.dbus_object_path) - client.enable() + # Need to initiate checking of clients + if client.enabled: + client.init_checker() tcp_server.enable() tcp_server.server_activate() @@ -2204,6 +2503,5 @@ # Must run before the D-Bus bus name gets deregistered cleanup() - if __name__ == '__main__': main() === modified file 'mandos-clients.conf.xml' --- mandos-clients.conf.xml 2011-10-10 20:29:58 +0000 +++ mandos-clients.conf.xml 2012-01-01 04:02:00 +0000 @@ -3,7 +3,7 @@ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ /etc/mandos/clients.conf"> - + %common; ]> @@ -36,6 +36,7 @@ 2009 2010 2011 + 2012 Teddy Hogeborn Björn Påhlsson @@ -65,8 +66,13 @@ >mandos 8, read by it at startup. The file needs to list all clients that should be able to use - the service. All clients listed will be regarded as enabled, - even if a client was disabled in a previous run of the server. + the service. The settings in this file can be overridden by + runtime changes to the server, which it saves across restarts. + (See the section called PERSISTENT STATE in + mandos8.) However, any changes to this file (including adding and removing + clients) will, at startup, override changes done during runtime. The format starts with a [section @@ -344,6 +350,20 @@ + + + + + Whether this client should be enabled by default. The + default is true. + + + + === modified file 'mandos-ctl' --- mandos-ctl 2011-10-15 16:48:03 +0000 +++ mandos-ctl 2012-01-01 01:16:15 +0000 @@ -3,8 +3,8 @@ # # Mandos Monitor - Control and monitor the Mandos server # -# Copyright © 2008-2011 Teddy Hogeborn -# Copyright © 2008-2011 Björn Påhlsson +# Copyright © 2008-2012 Teddy Hogeborn +# Copyright © 2008-2012 Björn Påhlsson # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -93,29 +93,23 @@ >>> string_to_delta("5m 30s") datetime.timedelta(0, 330) """ - timevalue = datetime.timedelta(0) - regexp = re.compile("\d+[dsmhw]") + value = datetime.timedelta(0) + regexp = re.compile("(\d+)([dsmhw]?)") - for s in regexp.findall(interval): - try: - suffix = unicode(s[-1]) - value = int(s[:-1]) - if suffix == "d": - delta = datetime.timedelta(value) - elif suffix == "s": - delta = datetime.timedelta(0, value) - elif suffix == "m": - delta = datetime.timedelta(0, 0, 0, 0, value) - elif suffix == "h": - delta = datetime.timedelta(0, 0, 0, 0, 0, value) - elif suffix == "w": - delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value) - else: - raise ValueError - except (ValueError, IndexError): - raise ValueError - timevalue += delta - return timevalue + for num, suffix in regexp.findall(interval): + if suffix == "d": + value += datetime.timedelta(int(num)) + elif suffix == "s": + value += datetime.timedelta(0, int(num)) + elif suffix == "m": + value += datetime.timedelta(0, 0, 0, 0, int(num)) + elif suffix == "h": + value += datetime.timedelta(0, 0, 0, 0, 0, int(num)) + elif suffix == "w": + value += datetime.timedelta(0, 0, 0, 0, 0, 0, int(num)) + elif suffix == "": + value += datetime.timedelta(0, 0, 0, int(num)) + return value def print_clients(clients, keywords): def valuetostring(value, keyword): @@ -242,7 +236,7 @@ #restore stderr os.dup2(stderrcopy, sys.stderr.fileno()) os.close(stderrcopy) - except dbus.exceptions.DBusException, e: + except dbus.exceptions.DBusException: print("Access denied: Accessing mandos server through dbus.", file=sys.stderr) sys.exit(1) @@ -337,8 +331,7 @@ dbus_interface=dbus.PROPERTIES_IFACE) if options.secret is not None: client.Set(client_interface, "Secret", - dbus.ByteArray(open(options.secret, - "rb").read()), + dbus.ByteArray(options.secret.read()), dbus_interface=dbus.PROPERTIES_IFACE) if options.approved_by_default is not None: client.Set(client_interface, "ApprovedByDefault", === modified file 'mandos-ctl.xml' --- mandos-ctl.xml 2011-10-05 16:00:56 +0000 +++ mandos-ctl.xml 2011-12-31 23:05:34 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -33,6 +33,7 @@ 2010 2011 + 2012 Teddy Hogeborn Björn Påhlsson === modified file 'mandos-keygen' --- mandos-keygen 2011-10-15 16:48:03 +0000 +++ mandos-keygen 2011-12-31 23:05:34 +0000 @@ -2,8 +2,8 @@ # # Mandos key generator - create a new OpenPGP key for a Mandos client # -# Copyright © 2008-2011 Teddy Hogeborn -# Copyright © 2008-2011 Björn Påhlsson +# Copyright © 2008-2012 Teddy Hogeborn +# Copyright © 2008-2012 Björn Påhlsson # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by === modified file 'mandos-keygen.xml' --- mandos-keygen.xml 2011-10-05 16:00:56 +0000 +++ mandos-keygen.xml 2011-12-31 23:05:34 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -34,6 +34,7 @@ 2008 2009 2011 + 2012 Teddy Hogeborn Björn Påhlsson @@ -214,7 +215,7 @@ Target directory for key files. Default is - /etc/mandos. + /etc/mandos. @@ -411,7 +412,7 @@ - /tmp + /tmp Temporary files will be written here if @@ -452,9 +453,9 @@ - Prompt for a password, encrypt it with the key in - /etc/mandos and output a section suitable - for clients.conf. + Prompt for a password, encrypt it with the key in /etc/mandos and output a section + suitable for clients.conf. &COMMANDNAME; --password === modified file 'mandos-monitor' --- mandos-monitor 2011-10-15 16:48:03 +0000 +++ mandos-monitor 2011-12-31 23:05:34 +0000 @@ -3,8 +3,8 @@ # # Mandos Monitor - Control and monitor the Mandos server # -# Copyright © 2009-2011 Teddy Hogeborn -# Copyright © 2009-2011 Björn Påhlsson +# Copyright © 2009-2012 Teddy Hogeborn +# Copyright © 2009-2012 Björn Påhlsson # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -242,6 +242,8 @@ self.update() def checker_started(self, command): + """Server signals that a checker started. This could be useful + to log in the future. """ #self.logger('Client %s started checker "%s"' # % (self.properties["Name"], unicode(command))) pass @@ -497,48 +499,6 @@ self.busname = domain + '.Mandos' self.main_loop = gobject.MainLoop() - self.bus = dbus.SystemBus() - mandos_dbus_objc = self.bus.get_object( - self.busname, "/", follow_name_owner_changes=True) - self.mandos_serv = dbus.Interface(mandos_dbus_objc, - dbus_interface - = server_interface) - try: - mandos_clients = (self.mandos_serv - .GetAllClientsWithProperties()) - except dbus.exceptions.DBusException: - mandos_clients = dbus.Dictionary() - - (self.mandos_serv - .connect_to_signal("ClientRemoved", - self.find_and_remove_client, - dbus_interface=server_interface, - byte_arrays=True)) - (self.mandos_serv - .connect_to_signal("ClientAdded", - self.add_new_client, - dbus_interface=server_interface, - byte_arrays=True)) - (self.mandos_serv - .connect_to_signal("ClientNotFound", - self.client_not_found, - dbus_interface=server_interface, - byte_arrays=True)) - for path, client in mandos_clients.iteritems(): - client_proxy_object = self.bus.get_object(self.busname, - path) - self.add_client(MandosClientWidget(server_proxy_object - =self.mandos_serv, - proxy_object - =client_proxy_object, - properties=client, - update_hook - =self.refresh, - delete_hook - =self.remove_client, - logger - =self.log_message), - path=path) def client_not_found(self, fingerprint, address): self.log_message(("Client with address %s and fingerprint %s" @@ -559,7 +519,6 @@ self.divider))) if self.log_visible: self.uilist.append(self.logbox) - pass self.topwidget = urwid.Pile(self.uilist) def log_message(self, message): @@ -649,6 +608,49 @@ def run(self): """Start the main loop and exit when it's done.""" + self.bus = dbus.SystemBus() + mandos_dbus_objc = self.bus.get_object( + self.busname, "/", follow_name_owner_changes=True) + self.mandos_serv = dbus.Interface(mandos_dbus_objc, + dbus_interface + = server_interface) + try: + mandos_clients = (self.mandos_serv + .GetAllClientsWithProperties()) + except dbus.exceptions.DBusException: + mandos_clients = dbus.Dictionary() + + (self.mandos_serv + .connect_to_signal("ClientRemoved", + self.find_and_remove_client, + dbus_interface=server_interface, + byte_arrays=True)) + (self.mandos_serv + .connect_to_signal("ClientAdded", + self.add_new_client, + dbus_interface=server_interface, + byte_arrays=True)) + (self.mandos_serv + .connect_to_signal("ClientNotFound", + self.client_not_found, + dbus_interface=server_interface, + byte_arrays=True)) + for path, client in mandos_clients.iteritems(): + client_proxy_object = self.bus.get_object(self.busname, + path) + self.add_client(MandosClientWidget(server_proxy_object + =self.mandos_serv, + proxy_object + =client_proxy_object, + properties=client, + update_hook + =self.refresh, + delete_hook + =self.remove_client, + logger + =self.log_message), + path=path) + self.refresh() self._input_callback_tag = (gobject.io_add_watch (sys.stdin.fileno(), === modified file 'mandos-monitor.xml' --- mandos-monitor.xml 2011-10-05 16:00:56 +0000 +++ mandos-monitor.xml 2011-12-31 23:05:34 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -33,6 +33,7 @@ 2010 2011 + 2012 Teddy Hogeborn Björn Påhlsson === modified file 'mandos-options.xml' --- mandos-options.xml 2009-02-13 05:38:21 +0000 +++ mandos-options.xml 2012-01-01 04:02:00 +0000 @@ -86,4 +86,15 @@ advanced users should consider changing this option. + + This option controls whether the server will restore its state + from the last time it ran. Default is to restore last state. + + + + Directory to save (and restore) state in. Default is + /var/lib/mandos. + + === modified file 'mandos.conf' --- mandos.conf 2009-02-13 05:38:21 +0000 +++ mandos.conf 2011-11-26 22:22:20 +0000 @@ -4,33 +4,27 @@ # These are the default values for the server, uncomment and change # them if needed. - # If "interface" is set, the server will only listen to a specific # network interface. ;interface = - # If "address" is set, the server will only listen to a specific # address. This must currently be an IPv6 address; an IPv4 address # can be specified using the "::FFFF:192.0.2.3" syntax. Also, if this # is a link-local address, an interface should be set above. ;address = - # If "port" is set, the server to bind to that port. By default, the # server will listen to an arbitrary port. ;port = - # If "debug" is true, the server will run in the foreground and print # a lot of debugging information. ;debug = False - # GnuTLS priority for the TLS handshake. See gnutls_priority_init(3). ;priority = SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP - # Zeroconf service name. You need to change this if you for some # reason want to run more than one server on the same *host*. # If there are name collisions on the same *network*, the server will @@ -42,3 +36,9 @@ # Whether to use IPv6. (Changing this is NOT recommended.) ;use_ipv6 = True + +# Whether to restore saved state on startup +;restore = True + +# The directory where state is saved +;statedir = /var/lib/mandos === modified file 'mandos.conf.xml' --- mandos.conf.xml 2011-10-05 16:00:56 +0000 +++ mandos.conf.xml 2011-12-31 23:05:34 +0000 @@ -3,7 +3,7 @@ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ /etc/mandos/mandos.conf"> - + %common; ]> @@ -35,6 +35,7 @@ 2008 2009 2011 + 2012 Teddy Hogeborn Björn Påhlsson @@ -154,6 +155,25 @@ + + + + + + + + + + + + + + @@ -198,6 +218,8 @@ servicename = Daena use_dbus = False use_ipv6 = True +restore = True +statedir = /var/lib/mandos === modified file 'mandos.xml' --- mandos.xml 2011-10-05 16:00:56 +0000 +++ mandos.xml 2012-01-01 04:02:00 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -35,6 +35,7 @@ 2009 2010 2011 + 2012 Teddy Hogeborn Björn Påhlsson @@ -94,6 +95,11 @@ + + + + &COMMANDNAME; @@ -275,6 +281,24 @@ + + + + + + + See also . + + + + + + + + + + @@ -387,12 +411,24 @@ LOGGING The server will send log message with various severity levels to - /dev/log. With the + /dev/log. With the option, it will log even more messages, and also show them on the console. + + PERSISTENT STATE + + Client settings, initially read from + clients.conf, are persistent across + restarts, and run-time changes will override settings in + clients.conf. However, if a setting is + changed (or a client added, or removed) in + clients.conf, this will take precedence. + + + D-BUS INTERFACE @@ -469,6 +505,20 @@ + /dev/log + + + /var/lib/mandos + + + Directory where persistent state will be saved. Change + this with the option. See + also the option. + + + + /dev/log @@ -498,20 +548,12 @@ backtrace. This could be considered a feature. - Currently, if a client is disabled due to having timed out, the - server does not record this fact onto permanent storage. This - has some security implications, see . - - There is no fine-grained control over logging and debug output. Debug mode is conflated with running in the foreground. - The console log messages do not show a time stamp. - - This server does not check the expire time of clients’ OpenPGP keys. @@ -530,9 +572,9 @@ Run the server in debug mode, read configuration files from - the ~/mandos directory, and use the - Zeroconf service name Test to not collide with - any other official Mandos server on this host: + the ~/mandos directory, + and use the Zeroconf service name Test to not + collide with any other official Mandos server on this host: @@ -587,21 +629,6 @@ compromised if they are gone for too long. - If a client is compromised, its downtime should be duly noted - by the server which would therefore disable the client. But - if the server was ever restarted, it would re-read its client - list from its configuration file and again regard all clients - therein as enabled, and hence eligible to receive their - passwords. Therefore, be careful when restarting servers if - it is suspected that a client has, in fact, been compromised - by parties who may now be running a fake Mandos client with - the keys from the non-encrypted initial RAM - image of the client host. What should be done in that case - (if restarting the server program really is necessary) is to - stop the server program, edit the configuration file to omit - any suspect clients, and restart the server program. - - For more details on client-side security, see mandos-client 8mandos. === added directory 'network-hooks.d' === added file 'network-hooks.d/bridge' --- network-hooks.d/bridge 1970-01-01 00:00:00 +0000 +++ network-hooks.d/bridge 2011-12-31 14:05:57 +0000 @@ -0,0 +1,81 @@ +#!/bin/sh +# +# This is an example of a Mandos client network hook. This hook +# brings up a bridge interface as specified in a separate +# configuration file. To be used, this file and any needed +# configuration file(s) should be copied into the +# /etc/mandos/network-hooks.d directory. +# +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided the copyright +# notice and this notice are preserved. This file is offered as-is, +# without any warranty. + +set -e + +CONFIG="$MANDOSNETHOOKDIR/bridge.conf" + +addrtoif(){ + grep -liFe "$1" /sys/class/net/*/address \ + | sed -e 's,.*/\([^/]*\)/[^/]*,\1,' +} + +# Read config file, which must set "BRIDGE", "PORT_ADDRESSES", and +# optionally "IPADDRS" and "ROUTES". +if [ -e "$CONFIG" ]; then + . "$CONFIG" +fi + +if [ -z "$BRIDGE" -o -z "$PORT_ADDRESSES" ]; then + exit +fi + +if [ -n "$DEVICE" -a "$DEVICE" != "$BRIDGE" ]; then + exit +fi + +for b in /sbin/brctl /usr/sbin/brctl; do + if [ -e "$b" ]; then + brctl="$b" + break + fi +done + +case "$1" in + start) + "$brctl" addbr "$BRIDGE" + for address in $PORT_ADDRESSES; do + interface=`addrtoif "$address"` + "$brctl" addif "$BRIDGE" "$interface" + ip link set dev "$interface" up + done + ip link set dev "$BRIDGE" up + sleep "$DELAY" + if [ -n "$IPADDRS" ]; then + for ipaddr in $IPADDRS; do + ip addr add "$ipaddr" dev "$BRIDGE" + done + fi + if [ -n "$ROUTES" ]; then + for route in $ROUTES; do + ip route add "$route" dev "$BRIDGE" + done + fi + ;; + stop) + ip link set dev "$BRIDGE" down + for address in $PORT_ADDRESSES; do + interface=`addrtoif "$address"` + ip link set dev "$interface" down + "$brctl" delif "$BRIDGE" "$interface" + done + "$brctl" delbr "$BRIDGE" + ;; + files) + echo /bin/ip + echo "$brctl" + ;; + modules) + echo bridge + ;; +esac === added file 'network-hooks.d/bridge.conf' --- network-hooks.d/bridge.conf 1970-01-01 00:00:00 +0000 +++ network-hooks.d/bridge.conf 2011-12-31 13:25:58 +0000 @@ -0,0 +1,11 @@ +## Required + +#BRIDGE=br0 + +#PORT_ADDRESSES="00:11:22:33:44:55 11:22:33:44:55:66" + +## Optional + +#IPADDRS="192.0.2.3/24 2001:DB8::aede:48ff:fe71:f6f2/32" + +#ROUTES="192.0.2.0/24 2001:DB8::/32" === added file 'network-hooks.d/openvpn' --- network-hooks.d/openvpn 1970-01-01 00:00:00 +0000 +++ network-hooks.d/openvpn 2011-12-31 14:05:57 +0000 @@ -0,0 +1,56 @@ +#!/bin/sh +# +# This is an example of a Mandos client network hook. This hook +# brings up an OpenVPN interface as specified in a separate +# configuration file. To be used, this file and any needed +# configuration file(s) should be copied into the +# /etc/mandos/network-hooks.d directory. +# +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided the copyright +# notice and this notice are preserved. This file is offered as-is, +# without any warranty. + +set -e + +CONFIG="openvpn.conf" + +# Extract the "dev" setting from the config file +VPNDEVICE=`sed -n -e 's/[[:space:]]#.*//' \ + -e 's/^[[:space:]]*dev[[:space:]]\+//p' \ + "$MANDOSNETHOOKDIR/$CONFIG"` + +PIDFILE=/run/openvpn-mandos.pid + +# Exit if no device set in config +if [ -z "$VPNDEVICE" ]; then + exit +fi + +# Exit if DEVICE is set and it doesn't match the VPN interface +if [ -n "$DEVICE" -a "$DEVICE" = "${DEVICE#$VPNDEVICE}" ]; then + exit +fi + +openvpn=/usr/sbin/openvpn + +case "$1" in + start) + "$openvpn" --cd "$MANDOSNETHOOKDIR" \ + --daemon 'openvpn(Mandos)' --writepid "$PIDFILE" \ + --config "$CONFIG" + sleep "$DELAY" + ;; + stop) + PID="`cat \"$PIDFILE\"`" + if [ "$PID" -gt 0 ]; then + kill "$PID" + fi + ;; + files) + echo "$openvpn" + ;; + modules) + echo tun + ;; +esac === added file 'network-hooks.d/openvpn.conf' --- network-hooks.d/openvpn.conf 1970-01-01 00:00:00 +0000 +++ network-hooks.d/openvpn.conf 2011-12-02 16:52:50 +0000 @@ -0,0 +1,19 @@ +# Sample OpenVPN configuration file +# Uncomment and change - see openvpn(8) + +# Network device. +#dev tun + +# Our remote peer +#remote 192.0.2.3 +#float 192.0.2.3 +#port 1194 + +# VPN endpoints +#ifconfig 10.1.0.1 10.1.0.2 + +# A pre-shared static key +#secret openvpn.key + +# Cipher +#cipher AES-128-CBC === added file 'network-hooks.d/wireless' --- network-hooks.d/wireless 1970-01-01 00:00:00 +0000 +++ network-hooks.d/wireless 2011-12-31 14:05:57 +0000 @@ -0,0 +1,157 @@ +#!/bin/sh +# +# This is an example of a Mandos client network hook. This hook +# brings up a wireless interface as specified in a separate +# configuration file. To be used, this file and any needed +# configuration file(s) should be copied into the +# /etc/mandos/network-hooks.d directory. +# +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided the copyright +# notice and this notice are preserved. This file is offered as-is, +# without any warranty. + +set -e + +RUNDIR="/run" +CTRL="$RUNDIR/wpa_supplicant-global" +CTRLDIR="$RUNDIR/wpa_supplicant" +PIDFILE="$RUNDIR/wpa_supplicant-mandos.pid" + +CONFIG="$MANDOSNETHOOKDIR/wireless.conf" + +addrtoif(){ + grep -liFe "$1" /sys/class/net/*/address \ + | sed -e 's,.*/\([^/]*\)/[^/]*,\1,' +} + +# Read config file +if [ -e "$CONFIG" ]; then + . "$CONFIG" +else + exit +fi + +ifkeys=`env | sed -n -e 's/^ADDRESS_\([^=]*\)=.*/\1/p' "$CONFIG" \ + | sort -u` + +# Exit if DEVICE is set and is not any of the wireless interfaces +if [ -n "$DEVICE" ]; then + while :; do + for KEY in $ifkeys; do + ADDRESS=`eval 'echo "$ADDRESS_'"$KEY"\"` + INTERFACE=`addrtoif "$ADDRESS"` + if [ "$INTERFACE" = "$DEVICE" ]; then + break 2 + fi + done + exit + done +fi + +wpa_supplicant=/sbin/wpa_supplicant +wpa_cli=/sbin/wpa_cli +ip=/bin/ip + +# Used by the wpa_interface_* functions in the wireless.conf file +wpa_cli_set(){ + case "$1" in + ssid|psk) arg="\"$2\"" ;; + *) arg="$2" ;; + esac + "$wpa_cli" -p "$CTRLDIR" -i "$INTERFACE" set_network "$NETWORK" \ + "$1" "$arg" 2>&1 | sed -e '/^OK$/d' +} + +if [ $VERBOSITY -gt 0 ]; then + WPAS_OPTIONS="-d $WPAS_OPTIONS" +fi +if [ -n "$PIDFILE" ]; then + WPAS_OPTIONS="-P$PIDFILE $WPAS_OPTIONS" +fi + +case "${MODE:-$1}" in + start) + mkdir -m u=rwx,go= -p "$CTRLDIR" + "$wpa_supplicant" -B -g "$CTRL" -p "$CTRLDIR" $WPAS_OPTIONS + for KEY in $ifkeys; do + ADDRESS=`eval 'echo "$ADDRESS_'"$KEY"\"` + INTERFACE=`addrtoif "$ADDRESS"` + DRIVER=`eval 'echo "$WPA_DRIVER_'"$KEY"\"` + IFDELAY=`eval 'echo "$DELAY_'"$KEY"\"` + "$wpa_cli" -g "$CTRL" interface_add "$INTERFACE" "" \ + "${DRIVER:-wext}" "$CTRLDIR" > /dev/null \ + | sed -e '/^OK$/d' + NETWORK=`"$wpa_cli" -p "$CTRLDIR" -i "$INTERFACE" \ + add_network` + eval wpa_interface_"$KEY" + "$wpa_cli" -p "$CTRLDIR" -i "$INTERFACE" enable_network \ + "$NETWORK" | sed -e '/^OK$/d' + sleep "${IFDELAY:-$DELAY}" & + sleep=$! + while :; do + kill -0 $sleep 2>/dev/null || break + STATE=`"$wpa_cli" -p "$CTRLDIR" -i "$INTERFACE" \ + status | sed -n -e 's/^wpa_state=//p'` + if [ "$STATE" = COMPLETED ]; then + while :; do + kill -0 $sleep 2>/dev/null || break 2 + UP=`cat /sys/class/net/"$INTERFACE"/operstate` + if [ "$UP" = up ]; then + kill $sleep 2>/dev/null + break 2 + fi + sleep 1 + done + fi + sleep 1 + done & + wait $sleep || : + IPADDRS=`eval 'echo "$IPADDRS_'"$KEY"\"` + if [ -n "$IPADDRS" ]; then + if [ "$IPADDRS" = dhcp ]; then + ipconfig -c dhcp -d "$INTERFACE" || : + #dhclient "$INTERFACE" + else + for ipaddr in $IPADDRS; do + "$ip" addr add "$ipaddr" dev "$INTERFACE" + done + fi + fi + ROUTES=`eval 'echo "$ROUTES_'"$KEY"\"` + if [ -n "$ROUTES" ]; then + for route in $ROUTES; do + "$ip" route add "$route" dev "$BRIDGE" + done + fi + done + ;; + stop) + "$wpa_cli" -g "$CTRL" terminate 2>&1 | sed -e '/^OK$/d' + for KEY in $ifkeys; do + ADDRESS=`eval 'echo "$ADDRESS_'"$KEY"\"` + INTERFACE=`addrtoif "$ADDRESS"` + "$ip" addr show scope global permanent dev "$INTERFACE" \ + | while read type addr rest; do + case "$type" in + inet|inet6) + "$ip" addr del "$addr" dev "$INTERFACE" + ;; + esac + done + "$ip" link set dev "$INTERFACE" down + done + ;; + files) + echo "$wpa_supplicant" + echo "$wpa_cli" + echo "$ip" + ;; + modules) + if [ "$IPADDRS" = dhcp ]; then + echo af_packet + fi + sed -n -e 's/#.*$//' -e 's/[ ]*$//' \ + -e 's/^MODULE_[^=]\+=//p' "$CONFIG" + ;; +esac === added file 'network-hooks.d/wireless.conf' --- network-hooks.d/wireless.conf 1970-01-01 00:00:00 +0000 +++ network-hooks.d/wireless.conf 2011-12-31 13:25:58 +0000 @@ -0,0 +1,23 @@ +# Extra options for wpa_supplicant, if any +#WPAS_OPTIONS="" + +# wlan0 +ADDRESS_0=00:11:22:33:44:55 +MODULE_0=ath9k +#WPA_DRIVER_0=wext +wpa_interface_0(){ + # Use this format to set simple things: + wpa_cli_set ssid home + wpa_cli_set psk "secret passphrase" + # Use this format to do more complex things with wpa_cli: + #"$wpa_cli" -p "$CTRLDIR" -i "$INTERFACE" bssid "$NETWORK" 00:11:22:33:44:55 + #"$wpa_cli" -g "$CTRL" ping +} +#DELAY_0=10 +IPADDRS_0=dhcp +#IPADDRS_0="192.0.2.3/24 2001:DB8::aede:48ff:fe71:f6f2/32" +#ROUTES_0="192.0.2.0/24 2001:DB8::/32" + +#ADDRESS_1=11:22:33:44:55:66 +#MODULE_1=... +#... === modified file 'plugin-runner.c' --- plugin-runner.c 2011-10-09 12:32:13 +0000 +++ plugin-runner.c 2011-12-31 23:05:34 +0000 @@ -2,8 +2,8 @@ /* * Mandos plugin runner - Run Mandos plugins * - * Copyright © 2008-2011 Teddy Hogeborn - * Copyright © 2008-2011 Björn Påhlsson + * Copyright © 2008-2012 Teddy Hogeborn + * Copyright © 2008-2012 Björn Påhlsson * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -171,6 +171,7 @@ } /* Helper function for add_argument and add_environment */ +__attribute__((nonnull)) static bool add_to_char_array(const char *new, char ***array, int *len){ /* Resize the pointed-to array to hold one more pointer */ @@ -199,6 +200,7 @@ } /* Add to a plugin's argument vector */ +__attribute__((nonnull(2))) static bool add_argument(plugin *p, const char *arg){ if(p == NULL){ return false; @@ -207,6 +209,7 @@ } /* Add to a plugin's environment */ +__attribute__((nonnull(2))) static bool add_environment(plugin *p, const char *def, bool replace){ if(p == NULL){ return false; @@ -286,6 +289,7 @@ } /* Prints out a password to stdout */ +__attribute__((nonnull)) static bool print_out_password(const char *buffer, size_t length){ ssize_t ret; for(size_t written = 0; written < length; written += (size_t)ret){ @@ -299,6 +303,7 @@ } /* Removes and free a plugin from the plugin list */ +__attribute__((nonnull)) static void free_plugin(plugin *plugin_node){ for(char **arg = plugin_node->argv; *arg != NULL; arg++){ @@ -416,11 +421,12 @@ { .name = NULL } }; + __attribute__((nonnull(3))) error_t parse_opt(int key, char *arg, struct argp_state *state){ errno = 0; switch(key){ char *tmp; - intmax_t tmpmax; + intmax_t tmp_id; case 'g': /* --global-options */ { char *plugin_option; @@ -499,24 +505,24 @@ /* This is already done by parse_opt_config_file() */ break; case 130: /* --userid */ - tmpmax = strtoimax(arg, &tmp, 10); + tmp_id = strtoimax(arg, &tmp, 10); if(errno != 0 or tmp == arg or *tmp != '\0' - or tmpmax != (uid_t)tmpmax){ + or tmp_id != (uid_t)tmp_id){ argp_error(state, "Bad user ID number: \"%s\", using %" PRIdMAX, arg, (intmax_t)uid); break; } - uid = (uid_t)tmpmax; + uid = (uid_t)tmp_id; break; case 131: /* --groupid */ - tmpmax = strtoimax(arg, &tmp, 10); + tmp_id = strtoimax(arg, &tmp, 10); if(errno != 0 or tmp == arg or *tmp != '\0' - or tmpmax != (gid_t)tmpmax){ + or tmp_id != (gid_t)tmp_id){ argp_error(state, "Bad group ID number: \"%s\", using %" PRIdMAX, arg, (intmax_t)gid); break; } - gid = (gid_t)tmpmax; + gid = (gid_t)tmp_id; break; case 132: /* --debug */ debug = true; @@ -742,7 +748,7 @@ } } - { + if(getuid() == 0){ /* Work around Debian bug #633582: */ int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY); === modified file 'plugin-runner.xml' --- plugin-runner.xml 2011-10-05 16:56:06 +0000 +++ plugin-runner.xml 2011-12-31 23:05:34 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -33,7 +33,7 @@ 2008 2009 - 2011 + 2012 Teddy Hogeborn Björn Påhlsson === modified file 'plugins.d/askpass-fifo.c' --- plugins.d/askpass-fifo.c 2011-10-05 16:00:56 +0000 +++ plugins.d/askpass-fifo.c 2011-12-31 23:05:34 +0000 @@ -2,8 +2,8 @@ /* * Askpass-FIFO - Read a password from a FIFO and output it * - * Copyright © 2008-2011 Teddy Hogeborn - * Copyright © 2008-2011 Björn Påhlsson + * Copyright © 2008-2012 Teddy Hogeborn + * Copyright © 2008-2012 Björn Påhlsson * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -46,6 +46,7 @@ /* Function to use when printing errors */ +__attribute__((format (gnu_printf, 3, 4))) void error_plus(int status, int errnum, const char *formatstring, ...){ va_list ap; === modified file 'plugins.d/askpass-fifo.xml' --- plugins.d/askpass-fifo.xml 2011-10-05 16:00:56 +0000 +++ plugins.d/askpass-fifo.xml 2011-12-31 23:05:34 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -34,6 +34,7 @@ 2008 2009 2011 + 2012 Teddy Hogeborn Björn Påhlsson === modified file 'plugins.d/mandos-client.c' --- plugins.d/mandos-client.c 2011-10-05 16:00:56 +0000 +++ plugins.d/mandos-client.c 2011-12-31 23:05:34 +0000 @@ -9,8 +9,8 @@ * "browse_callback", and parts of "main". * * Everything else is - * Copyright © 2008-2011 Teddy Hogeborn - * Copyright © 2008-2011 Björn Påhlsson + * Copyright © 2008-2012 Teddy Hogeborn + * Copyright © 2008-2012 Björn Påhlsson * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -53,7 +53,7 @@ sockaddr_in6, PF_INET6, SOCK_STREAM, uid_t, gid_t, open(), opendir(), DIR */ -#include /* open() */ +#include /* open(), S_ISREG */ #include /* socket(), struct sockaddr_in6, inet_pton(), connect() */ #include /* open() */ @@ -73,7 +73,7 @@ */ #include /* close(), SEEK_SET, off_t, write(), getuid(), getgid(), seteuid(), - setgid(), pause() */ + setgid(), pause(), _exit() */ #include /* inet_pton(), htons, inet_ntop() */ #include /* not, or, and */ #include /* struct argp_option, error_t, struct @@ -85,6 +85,9 @@ raise() */ #include /* EX_OSERR, EX_USAGE, EX_UNAVAILABLE, EX_NOHOST, EX_IOERR, EX_PROTOCOL */ +#include /* waitpid(), WIFEXITED(), + WEXITSTATUS(), WTERMSIG() */ +#include /* setgroups() */ #ifdef __linux__ #include /* klogctl() */ @@ -108,8 +111,8 @@ init_gnutls_session(), GNUTLS_* */ #include - /* gnutls_certificate_set_openpgp_key_file(), - GNUTLS_OPENPGP_FMT_BASE64 */ + /* gnutls_certificate_set_openpgp_key_file(), + GNUTLS_OPENPGP_FMT_BASE64 */ /* GPGME */ #include /* All GPGME types, constants and @@ -123,6 +126,7 @@ #define PATHDIR "/conf/conf.d/mandos" #define SECKEY "seckey.txt" #define PUBKEY "pubkey.txt" +#define HOOKDIR "/lib/mandos/network-hooks.d" bool debug = false; static const char mandos_protocol_version[] = "1"; @@ -130,6 +134,7 @@ const char *argp_program_bug_address = ""; static const char sys_class_net[] = "/sys/class/net"; char *connect_to = NULL; +const char *hookdir = HOOKDIR; /* Doubly linked list that need to be circularly linked when used */ typedef struct server{ @@ -165,18 +170,30 @@ /* Function to use when printing errors */ void perror_plus(const char *print_text){ + int e = errno; fprintf(stderr, "Mandos plugin %s: ", program_invocation_short_name); + errno = e; perror(print_text); } +__attribute__((format (gnu_printf, 2, 3))) +int fprintf_plus(FILE *stream, const char *format, ...){ + va_list ap; + va_start (ap, format); + + TEMP_FAILURE_RETRY(fprintf(stream, "Mandos plugin %s: ", + program_invocation_short_name)); + return TEMP_FAILURE_RETRY(vfprintf(stream, format, ap)); +} + /* * Make additional room in "buffer" for at least BUFFER_SIZE more * bytes. "buffer_capacity" is how much is currently allocated, * "buffer_length" is how much is already used. */ size_t incbuffer(char **buffer, size_t buffer_length, - size_t buffer_capacity){ + size_t buffer_capacity){ if(buffer_length + BUFFER_SIZE > buffer_capacity){ *buffer = realloc(*buffer, buffer_capacity + BUFFER_SIZE); if(buffer == NULL){ @@ -188,22 +205,21 @@ } /* Add server to set of servers to retry periodically */ -int add_server(const char *ip, uint16_t port, - AvahiIfIndex if_index, - int af){ +bool add_server(const char *ip, uint16_t port, AvahiIfIndex if_index, + int af){ int ret; server *new_server = malloc(sizeof(server)); if(new_server == NULL){ perror_plus("malloc"); - return -1; + return false; } *new_server = (server){ .ip = strdup(ip), - .port = port, - .if_index = if_index, - .af = af }; + .port = port, + .if_index = if_index, + .af = af }; if(new_server->ip == NULL){ perror_plus("strdup"); - return -1; + return false; } /* Special case of first server */ if (mc.current_server == NULL){ @@ -220,16 +236,16 @@ ret = clock_gettime(CLOCK_MONOTONIC, &mc.current_server->last_seen); if(ret == -1){ perror_plus("clock_gettime"); - return -1; + return false; } - return 0; + return true; } /* * Initialize GPGME. */ -static bool init_gpgme(const char *seckey, - const char *pubkey, const char *tempdir){ +static bool init_gpgme(const char *seckey, const char *pubkey, + const char *tempdir){ gpgme_error_t rc; gpgme_engine_info_t engine_info; @@ -250,15 +266,15 @@ rc = gpgme_data_new_from_fd(&pgp_data, fd); if(rc != GPG_ERR_NO_ERROR){ - fprintf(stderr, "bad gpgme_data_new_from_fd: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf_plus(stderr, "bad gpgme_data_new_from_fd: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); return false; } rc = gpgme_op_import(mc.ctx, pgp_data); if(rc != GPG_ERR_NO_ERROR){ - fprintf(stderr, "bad gpgme_op_import: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf_plus(stderr, "bad gpgme_op_import: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); return false; } @@ -271,23 +287,23 @@ } if(debug){ - fprintf(stderr, "Initializing GPGME\n"); + fprintf_plus(stderr, "Initializing GPGME\n"); } /* Init GPGME */ gpgme_check_version(NULL); rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP); if(rc != GPG_ERR_NO_ERROR){ - fprintf(stderr, "bad gpgme_engine_check_version: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf_plus(stderr, "bad gpgme_engine_check_version: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); return false; } /* Set GPGME home directory for the OpenPGP engine only */ rc = gpgme_get_engine_info(&engine_info); if(rc != GPG_ERR_NO_ERROR){ - fprintf(stderr, "bad gpgme_get_engine_info: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf_plus(stderr, "bad gpgme_get_engine_info: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); return false; } while(engine_info != NULL){ @@ -299,15 +315,17 @@ engine_info = engine_info->next; } if(engine_info == NULL){ - fprintf(stderr, "Could not set GPGME home dir to %s\n", tempdir); + fprintf_plus(stderr, "Could not set GPGME home dir to %s\n", + tempdir); return false; } /* Create new GPGME "context" */ rc = gpgme_new(&(mc.ctx)); if(rc != GPG_ERR_NO_ERROR){ - fprintf(stderr, "bad gpgme_new: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf_plus(stderr, "Mandos plugin mandos-client: " + "bad gpgme_new: %s: %s\n", gpgme_strsource(rc), + gpgme_strerror(rc)); return false; } @@ -332,23 +350,24 @@ ssize_t plaintext_length = 0; if(debug){ - fprintf(stderr, "Trying to decrypt OpenPGP data\n"); + fprintf_plus(stderr, "Trying to decrypt OpenPGP data\n"); } /* Create new GPGME data buffer from memory cryptotext */ rc = gpgme_data_new_from_mem(&dh_crypto, cryptotext, crypto_size, 0); if(rc != GPG_ERR_NO_ERROR){ - fprintf(stderr, "bad gpgme_data_new_from_mem: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf_plus(stderr, "bad gpgme_data_new_from_mem: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); return -1; } /* Create new empty GPGME data buffer for the plaintext */ rc = gpgme_data_new(&dh_plain); if(rc != GPG_ERR_NO_ERROR){ - fprintf(stderr, "bad gpgme_data_new: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf_plus(stderr, "Mandos plugin mandos-client: " + "bad gpgme_data_new: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); gpgme_data_release(dh_crypto); return -1; } @@ -357,31 +376,32 @@ data buffer */ rc = gpgme_op_decrypt(mc.ctx, dh_crypto, dh_plain); if(rc != GPG_ERR_NO_ERROR){ - fprintf(stderr, "bad gpgme_op_decrypt: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf_plus(stderr, "bad gpgme_op_decrypt: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); plaintext_length = -1; if(debug){ gpgme_decrypt_result_t result; result = gpgme_op_decrypt_result(mc.ctx); if(result == NULL){ - fprintf(stderr, "gpgme_op_decrypt_result failed\n"); + fprintf_plus(stderr, "gpgme_op_decrypt_result failed\n"); } else { - fprintf(stderr, "Unsupported algorithm: %s\n", - result->unsupported_algorithm); - fprintf(stderr, "Wrong key usage: %u\n", - result->wrong_key_usage); + fprintf_plus(stderr, "Unsupported algorithm: %s\n", + result->unsupported_algorithm); + fprintf_plus(stderr, "Wrong key usage: %u\n", + result->wrong_key_usage); if(result->file_name != NULL){ - fprintf(stderr, "File name: %s\n", result->file_name); + fprintf_plus(stderr, "File name: %s\n", result->file_name); } gpgme_recipient_t recipient; recipient = result->recipients; 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"); + fprintf_plus(stderr, "Public key algorithm: %s\n", + gpgme_pubkey_algo_name + (recipient->pubkey_algo)); + fprintf_plus(stderr, "Key ID: %s\n", recipient->keyid); + fprintf_plus(stderr, "Secret key available: %s\n", + recipient->status == GPG_ERR_NO_SECKEY + ? "No" : "Yes"); recipient = recipient->next; } } @@ -390,7 +410,7 @@ } if(debug){ - fprintf(stderr, "Decryption of OpenPGP data succeeded\n"); + fprintf_plus(stderr, "Decryption of OpenPGP data succeeded\n"); } /* Seek back to the beginning of the GPGME plaintext data buffer */ @@ -403,12 +423,12 @@ *plaintext = NULL; while(true){ plaintext_capacity = incbuffer(plaintext, - (size_t)plaintext_length, - plaintext_capacity); + (size_t)plaintext_length, + plaintext_capacity); if(plaintext_capacity == 0){ - perror_plus("incbuffer"); - plaintext_length = -1; - goto decrypt_end; + perror_plus("incbuffer"); + plaintext_length = -1; + goto decrypt_end; } ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length, @@ -427,7 +447,7 @@ } if(debug){ - fprintf(stderr, "Decrypted password is: "); + fprintf_plus(stderr, "Decrypted password is: "); for(ssize_t i = 0; i < plaintext_length; i++){ fprintf(stderr, "%02hhX ", (*plaintext)[i]); } @@ -455,7 +475,7 @@ /* GnuTLS log function callback */ static void debuggnutls(__attribute__((unused)) int level, const char* string){ - fprintf(stderr, "GnuTLS: %s", string); + fprintf_plus(stderr, "GnuTLS: %s", string); } static int init_gnutls_global(const char *pubkeyfilename, @@ -463,13 +483,13 @@ int ret; if(debug){ - fprintf(stderr, "Initializing GnuTLS\n"); + fprintf_plus(stderr, "Initializing GnuTLS\n"); } ret = gnutls_global_init(); if(ret != GNUTLS_E_SUCCESS){ - fprintf(stderr, "GnuTLS global_init: %s\n", - safer_gnutls_strerror(ret)); + fprintf_plus(stderr, "GnuTLS global_init: %s\n", + safer_gnutls_strerror(ret)); return -1; } @@ -484,41 +504,43 @@ /* OpenPGP credentials */ ret = gnutls_certificate_allocate_credentials(&mc.cred); if(ret != GNUTLS_E_SUCCESS){ - fprintf(stderr, "GnuTLS memory error: %s\n", - safer_gnutls_strerror(ret)); + fprintf_plus(stderr, "GnuTLS memory error: %s\n", + safer_gnutls_strerror(ret)); gnutls_global_deinit(); return -1; } if(debug){ - fprintf(stderr, "Attempting to use OpenPGP public key %s and" - " secret key %s as GnuTLS credentials\n", pubkeyfilename, - seckeyfilename); + fprintf_plus(stderr, "Attempting to use OpenPGP public key %s and" + " secret key %s as GnuTLS credentials\n", + pubkeyfilename, + seckeyfilename); } ret = gnutls_certificate_set_openpgp_key_file (mc.cred, pubkeyfilename, seckeyfilename, GNUTLS_OPENPGP_FMT_BASE64); if(ret != GNUTLS_E_SUCCESS){ - fprintf(stderr, - "Error[%d] while reading the OpenPGP key pair ('%s'," - " '%s')\n", ret, pubkeyfilename, seckeyfilename); - fprintf(stderr, "The GnuTLS error is: %s\n", - safer_gnutls_strerror(ret)); + fprintf_plus(stderr, + "Error[%d] while reading the OpenPGP key pair ('%s'," + " '%s')\n", ret, pubkeyfilename, seckeyfilename); + fprintf_plus(stderr, "The GnuTLS error is: %s\n", + safer_gnutls_strerror(ret)); goto globalfail; } /* GnuTLS server initialization */ ret = gnutls_dh_params_init(&mc.dh_params); if(ret != GNUTLS_E_SUCCESS){ - fprintf(stderr, "Error in GnuTLS DH parameter initialization:" - " %s\n", safer_gnutls_strerror(ret)); + fprintf_plus(stderr, "Error in GnuTLS DH parameter" + " initialization: %s\n", + safer_gnutls_strerror(ret)); goto globalfail; } ret = gnutls_dh_params_generate2(mc.dh_params, mc.dh_bits); if(ret != GNUTLS_E_SUCCESS){ - fprintf(stderr, "Error in GnuTLS prime generation: %s\n", - safer_gnutls_strerror(ret)); + fprintf_plus(stderr, "Error in GnuTLS prime generation: %s\n", + safer_gnutls_strerror(ret)); goto globalfail; } @@ -544,8 +566,9 @@ } } 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)); + fprintf_plus(stderr, + "Error in GnuTLS session initialization: %s\n", + safer_gnutls_strerror(ret)); } { @@ -558,9 +581,9 @@ } } while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN); if(ret != GNUTLS_E_SUCCESS){ - fprintf(stderr, "Syntax error at: %s\n", err); - fprintf(stderr, "GnuTLS error: %s\n", - safer_gnutls_strerror(ret)); + fprintf_plus(stderr, "Syntax error at: %s\n", err); + fprintf_plus(stderr, "GnuTLS error: %s\n", + safer_gnutls_strerror(ret)); gnutls_deinit(*session); return -1; } @@ -575,8 +598,8 @@ } } 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)); + fprintf_plus(stderr, "Error setting GnuTLS credentials: %s\n", + safer_gnutls_strerror(ret)); gnutls_deinit(*session); return -1; } @@ -627,7 +650,7 @@ pf = PF_INET; break; default: - fprintf(stderr, "Bad address family: %d\n", af); + fprintf_plus(stderr, "Bad address family: %d\n", af); errno = EINVAL; return -1; } @@ -638,8 +661,8 @@ } if(debug){ - fprintf(stderr, "Setting up a TCP connection to %s, port %" PRIu16 - "\n", ip, port); + fprintf_plus(stderr, "Setting up a TCP connection to %s, port %" + PRIu16 "\n", ip, port); } tcp_sd = socket(pf, SOCK_STREAM, 0); @@ -671,7 +694,7 @@ } if(ret == 0){ int e = errno; - fprintf(stderr, "Bad address: %s\n", ip); + fprintf_plus(stderr, "Bad address: %s\n", ip); errno = e; goto mandos_end; } @@ -682,10 +705,10 @@ if(IN6_IS_ADDR_LINKLOCAL /* Spurious warnings from */ (&to.in6.sin6_addr)){ /* -Wstrict-aliasing=2 or lower and - -Wunreachable-code*/ + -Wunreachable-code*/ if(if_index == AVAHI_IF_UNSPEC){ - fprintf(stderr, "An IPv6 link-local address is incomplete" - " without a network interface\n"); + fprintf_plus(stderr, "An IPv6 link-local address is" + " incomplete without a network interface\n"); errno = EINVAL; goto mandos_end; } @@ -709,12 +732,12 @@ if(if_indextoname((unsigned int)if_index, interface) == NULL){ perror_plus("if_indextoname"); } else { - fprintf(stderr, "Connection to: %s%%%s, port %" PRIu16 "\n", - ip, interface, port); + fprintf_plus(stderr, "Connection to: %s%%%s, port %" PRIu16 + "\n", ip, interface, port); } } else { - fprintf(stderr, "Connection to: %s, port %" PRIu16 "\n", ip, - port); + fprintf_plus(stderr, "Connection to: %s, port %" PRIu16 "\n", + ip, port); } char addrstr[(INET_ADDRSTRLEN > INET6_ADDRSTRLEN) ? INET_ADDRSTRLEN : INET6_ADDRSTRLEN] = ""; @@ -730,7 +753,7 @@ perror_plus("inet_ntop"); } else { if(strcmp(addrstr, ip) != 0){ - fprintf(stderr, "Canonical address form: %s\n", addrstr); + fprintf_plus(stderr, "Canonical address form: %s\n", addrstr); } } } @@ -764,7 +787,7 @@ while(true){ size_t out_size = strlen(out); ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written, - out_size - written)); + out_size - written)); if(ret == -1){ int e = errno; perror_plus("write"); @@ -790,7 +813,7 @@ } if(debug){ - fprintf(stderr, "Establishing TLS session with %s\n", ip); + fprintf_plus(stderr, "Establishing TLS session with %s\n", ip); } if(quit_now){ @@ -816,7 +839,7 @@ if(ret != GNUTLS_E_SUCCESS){ if(debug){ - fprintf(stderr, "*** GnuTLS Handshake failed ***\n"); + fprintf_plus(stderr, "*** GnuTLS Handshake failed ***\n"); gnutls_perror(ret); } errno = EPROTO; @@ -826,8 +849,8 @@ /* Read OpenPGP packet that contains the wanted password */ if(debug){ - fprintf(stderr, "Retrieving OpenPGP encrypted password from %s\n", - ip); + fprintf_plus(stderr, "Retrieving OpenPGP encrypted password from" + " %s\n", ip); } while(true){ @@ -838,7 +861,7 @@ } buffer_capacity = incbuffer(&buffer, buffer_length, - buffer_capacity); + buffer_capacity); if(buffer_capacity == 0){ int e = errno; perror_plus("incbuffer"); @@ -871,15 +894,16 @@ } } while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED); if(ret < 0){ - fprintf(stderr, "*** GnuTLS Re-handshake failed ***\n"); + fprintf_plus(stderr, "*** GnuTLS Re-handshake failed " + "***\n"); gnutls_perror(ret); errno = EPROTO; goto mandos_end; } break; default: - fprintf(stderr, "Unknown error while reading data from" - " encrypted session with Mandos server\n"); + fprintf_plus(stderr, "Unknown error while reading data from" + " encrypted session with Mandos server\n"); gnutls_bye(session, GNUTLS_SHUT_RDWR); errno = EIO; goto mandos_end; @@ -890,7 +914,7 @@ } if(debug){ - fprintf(stderr, "Closing TLS session\n"); + fprintf_plus(stderr, "Closing TLS session\n"); } if(quit_now){ @@ -908,8 +932,7 @@ if(buffer_length > 0){ ssize_t decrypted_buffer_size; - decrypted_buffer_size = pgp_packet_decrypt(buffer, - buffer_length, + decrypted_buffer_size = pgp_packet_decrypt(buffer, buffer_length, &decrypted_buffer); if(decrypted_buffer_size >= 0){ @@ -926,8 +949,8 @@ if(ret == 0 and ferror(stdout)){ int e = errno; if(debug){ - fprintf(stderr, "Error writing encrypted data: %s\n", - strerror(errno)); + fprintf_plus(stderr, "Error writing encrypted data: %s\n", + strerror(errno)); } errno = e; goto mandos_end; @@ -990,9 +1013,10 @@ switch(event){ default: case AVAHI_RESOLVER_FAILURE: - fprintf(stderr, "(Avahi Resolver) Failed to resolve service '%s'" - " of type '%s' in domain '%s': %s\n", name, type, domain, - avahi_strerror(avahi_server_errno(mc.server))); + fprintf_plus(stderr, "(Avahi Resolver) Failed to resolve service " + "'%s' of type '%s' in domain '%s': %s\n", name, type, + domain, + avahi_strerror(avahi_server_errno(mc.server))); break; case AVAHI_RESOLVER_FOUND: @@ -1000,17 +1024,20 @@ char ip[AVAHI_ADDRESS_STR_MAX]; avahi_address_snprint(ip, sizeof(ip), address); if(debug){ - fprintf(stderr, "Mandos server \"%s\" found on %s (%s, %" - PRIdMAX ") on port %" PRIu16 "\n", name, host_name, - ip, (intmax_t)interface, port); + fprintf_plus(stderr, "Mandos server \"%s\" found on %s (%s, %" + PRIdMAX ") on port %" PRIu16 "\n", name, + host_name, ip, (intmax_t)interface, port); } int ret = start_mandos_communication(ip, port, interface, avahi_proto_to_af(proto)); if(ret == 0){ avahi_simple_poll_quit(mc.simple_poll); } else { - ret = add_server(ip, port, interface, - avahi_proto_to_af(proto)); + if(not add_server(ip, port, interface, + avahi_proto_to_af(proto))){ + fprintf_plus(stderr, "Failed to add server \"%s\" to server" + " list\n", name); + } } } } @@ -1040,8 +1067,8 @@ default: case AVAHI_BROWSER_FAILURE: - fprintf(stderr, "(Avahi browser) %s\n", - avahi_strerror(avahi_server_errno(mc.server))); + fprintf_plus(stderr, "(Avahi browser) %s\n", + avahi_strerror(avahi_server_errno(mc.server))); avahi_simple_poll_quit(mc.simple_poll); return; @@ -1054,8 +1081,9 @@ if(avahi_s_service_resolver_new(mc.server, interface, protocol, name, type, domain, protocol, 0, resolve_callback, NULL) == NULL) - fprintf(stderr, "Avahi: Failed to resolve service '%s': %s\n", - name, avahi_strerror(avahi_server_errno(mc.server))); + fprintf_plus(stderr, "Avahi: Failed to resolve service '%s':" + " %s\n", name, + avahi_strerror(avahi_server_errno(mc.server))); break; case AVAHI_BROWSER_REMOVE: @@ -1064,7 +1092,8 @@ case AVAHI_BROWSER_ALL_FOR_NOW: case AVAHI_BROWSER_CACHE_EXHAUSTED: if(debug){ - fprintf(stderr, "No Mandos server found, still searching...\n"); + fprintf_plus(stderr, "No Mandos server found, still" + " searching...\n"); } break; } @@ -1085,107 +1114,131 @@ errno = old_errno; } -/* - * This function determines if a directory entry in /sys/class/net - * corresponds to an acceptable network device. - * (This function is passed to scandir(3) as a filter function.) - */ -int good_interface(const struct dirent *if_entry){ - ssize_t ssret; - char *flagname = NULL; - if(if_entry->d_name[0] == '.'){ - return 0; - } - int ret = asprintf(&flagname, "%s/%s/flags", sys_class_net, - if_entry->d_name); - if(ret < 0){ - perror_plus("asprintf"); - return 0; - } - int flags_fd = (int)TEMP_FAILURE_RETRY(open(flagname, O_RDONLY)); - if(flags_fd == -1){ - perror_plus("open"); - free(flagname); - return 0; - } - free(flagname); - typedef short ifreq_flags; /* ifreq.ifr_flags in netdevice(7) */ - /* read line from flags_fd */ - ssize_t to_read = 2+(sizeof(ifreq_flags)*2)+1; /* "0x1003\n" */ - char *flagstring = malloc((size_t)to_read+1); /* +1 for final \0 */ - flagstring[(size_t)to_read] = '\0'; - if(flagstring == NULL){ - perror_plus("malloc"); - close(flags_fd); - return 0; - } - while(to_read > 0){ - ssret = (ssize_t)TEMP_FAILURE_RETRY(read(flags_fd, flagstring, - (size_t)to_read)); - if(ssret == -1){ - perror_plus("read"); - free(flagstring); - close(flags_fd); - return 0; - } - to_read -= ssret; - if(ssret == 0){ - break; - } - } - close(flags_fd); - intmax_t tmpmax; - char *tmp; - errno = 0; - tmpmax = strtoimax(flagstring, &tmp, 0); - if(errno != 0 or tmp == flagstring or (*tmp != '\0' - and not (isspace(*tmp))) - or tmpmax != (ifreq_flags)tmpmax){ +bool get_flags(const char *ifname, struct ifreq *ifr){ + int ret; + + int s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); + if(s < 0){ + perror_plus("socket"); + return false; + } + strcpy(ifr->ifr_name, ifname); + ret = ioctl(s, SIOCGIFFLAGS, ifr); + if(ret == -1){ if(debug){ - fprintf(stderr, "Invalid flags \"%s\" for interface \"%s\"\n", - flagstring, if_entry->d_name); + perror_plus("ioctl SIOCGIFFLAGS"); } - free(flagstring); - return 0; + return false; } - free(flagstring); - ifreq_flags flags = (ifreq_flags)tmpmax; + return true; +} + +bool good_flags(const char *ifname, const struct ifreq *ifr){ + /* Reject the loopback device */ - if(flags & IFF_LOOPBACK){ + if(ifr->ifr_flags & IFF_LOOPBACK){ if(debug){ - fprintf(stderr, "Rejecting loopback interface \"%s\"\n", - if_entry->d_name); + fprintf_plus(stderr, "Rejecting loopback interface \"%s\"\n", + ifname); } - return 0; + return false; } /* Accept point-to-point devices only if connect_to is specified */ - if(connect_to != NULL and (flags & IFF_POINTOPOINT)){ + if(connect_to != NULL and (ifr->ifr_flags & IFF_POINTOPOINT)){ if(debug){ - fprintf(stderr, "Accepting point-to-point interface \"%s\"\n", - if_entry->d_name); + fprintf_plus(stderr, "Accepting point-to-point interface" + " \"%s\"\n", ifname); } - return 1; + return true; } /* Otherwise, reject non-broadcast-capable devices */ - if(not (flags & IFF_BROADCAST)){ + if(not (ifr->ifr_flags & IFF_BROADCAST)){ if(debug){ - fprintf(stderr, "Rejecting non-broadcast interface \"%s\"\n", - if_entry->d_name); + fprintf_plus(stderr, "Rejecting non-broadcast interface" + " \"%s\"\n", ifname); } - return 0; + return false; } /* Reject non-ARP interfaces (including dummy interfaces) */ - if(flags & IFF_NOARP){ + if(ifr->ifr_flags & IFF_NOARP){ if(debug){ - fprintf(stderr, "Rejecting non-ARP interface \"%s\"\n", - if_entry->d_name); + fprintf_plus(stderr, "Rejecting non-ARP interface \"%s\"\n", + ifname); } - return 0; + return false; } + /* Accept this device */ if(debug){ - fprintf(stderr, "Interface \"%s\" is acceptable\n", - if_entry->d_name); + fprintf_plus(stderr, "Interface \"%s\" is good\n", ifname); + } + return true; +} + +/* + * This function determines if a directory entry in /sys/class/net + * corresponds to an acceptable network device. + * (This function is passed to scandir(3) as a filter function.) + */ +int good_interface(const struct dirent *if_entry){ + if(if_entry->d_name[0] == '.'){ + return 0; + } + + struct ifreq ifr; + if(not get_flags(if_entry->d_name, &ifr)){ + if(debug){ + fprintf_plus(stderr, "Failed to get flags for interface " + "\"%s\"\n", if_entry->d_name); + } + return 0; + } + + if(not good_flags(if_entry->d_name, &ifr)){ + return 0; + } + return 1; +} + +/* + * This function determines if a directory entry in /sys/class/net + * corresponds to an acceptable network device which is up. + * (This function is passed to scandir(3) as a filter function.) + */ +int up_interface(const struct dirent *if_entry){ + if(if_entry->d_name[0] == '.'){ + return 0; + } + + struct ifreq ifr; + if(not get_flags(if_entry->d_name, &ifr)){ + if(debug){ + fprintf_plus(stderr, "Failed to get flags for interface " + "\"%s\"\n", if_entry->d_name); + } + return 0; + } + + /* Reject down interfaces */ + if(not (ifr.ifr_flags & IFF_UP)){ + if(debug){ + fprintf_plus(stderr, "Rejecting down interface \"%s\"\n", + if_entry->d_name); + } + return 0; + } + + /* Reject non-running interfaces */ + if(not (ifr.ifr_flags & IFF_RUNNING)){ + if(debug){ + fprintf_plus(stderr, "Rejecting non-running interface \"%s\"\n", + if_entry->d_name); + } + return 0; + } + + if(not good_flags(if_entry->d_name, &ifr)){ + return 0; } return 1; } @@ -1201,6 +1254,67 @@ return 1; } +/* Is this directory entry a runnable program? */ +int runnable_hook(const struct dirent *direntry){ + int ret; + size_t sret; + struct stat st; + + if((direntry->d_name)[0] == '\0'){ + /* Empty name? */ + return 0; + } + + sret = strspn(direntry->d_name, "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "_-"); + if((direntry->d_name)[sret] != '\0'){ + /* Contains non-allowed characters */ + if(debug){ + fprintf_plus(stderr, "Ignoring hook \"%s\" with bad name\n", + direntry->d_name); + } + return 0; + } + + char *fullname = NULL; + ret = asprintf(&fullname, "%s/%s", hookdir, direntry->d_name); + if(ret < 0){ + perror_plus("asprintf"); + return 0; + } + + ret = stat(fullname, &st); + if(ret == -1){ + if(debug){ + perror_plus("Could not stat hook"); + } + return 0; + } + if(not (S_ISREG(st.st_mode))){ + /* Not a regular file */ + if(debug){ + fprintf_plus(stderr, "Ignoring hook \"%s\" - not a file\n", + direntry->d_name); + } + return 0; + } + if(not (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))){ + /* Not executable */ + if(debug){ + fprintf_plus(stderr, "Ignoring hook \"%s\" - not executable\n", + direntry->d_name); + } + return 0; + } + if(debug){ + fprintf_plus(stderr, "Hook \"%s\" is acceptable\n", + direntry->d_name); + } + return 1; +} + int avahi_loop_with_timeout(AvahiSimplePoll *s, int retry_interval){ int ret; struct timespec now; @@ -1210,14 +1324,14 @@ while(true){ if(mc.current_server == NULL){ if (debug){ - fprintf(stderr, - "Wait until first server is found. No timeout!\n"); + fprintf_plus(stderr, "Wait until first server is found." + " No timeout!\n"); } ret = avahi_simple_poll_iterate(s, -1); } else { if (debug){ - fprintf(stderr, "Check current_server if we should run it," - " or wait\n"); + fprintf_plus(stderr, "Check current_server if we should run" + " it, or wait\n"); } /* the current time */ ret = clock_gettime(CLOCK_MONOTONIC, &now); @@ -1239,7 +1353,8 @@ - ((intmax_t)waited_time.tv_nsec / 1000000)); if (debug){ - fprintf(stderr, "Blocking for %" PRIdMAX " ms\n", block_time); + fprintf_plus(stderr, "Blocking for %" PRIdMAX " ms\n", + block_time); } if(block_time <= 0){ @@ -1265,13 +1380,149 @@ ret = avahi_simple_poll_iterate(s, (int)block_time); } if(ret != 0){ - if (ret > 0 or errno != EINTR) { + if (ret > 0 or errno != EINTR){ return (ret != 1) ? ret : 0; } } } } +bool run_network_hooks(const char *mode, const char *interface, + const float delay){ + struct dirent **direntries; + struct dirent *direntry; + int ret; + int numhooks = scandir(hookdir, &direntries, runnable_hook, + alphasort); + if(numhooks == -1){ + perror_plus("scandir"); + } else { + int devnull = open("/dev/null", O_RDONLY); + for(int i = 0; i < numhooks; i++){ + direntry = direntries[i]; + char *fullname = NULL; + ret = asprintf(&fullname, "%s/%s", hookdir, direntry->d_name); + if(ret < 0){ + perror_plus("asprintf"); + continue; + } + if(debug){ + fprintf_plus(stderr, "Running network hook \"%s\"\n", + direntry->d_name); + } + pid_t hook_pid = fork(); + if(hook_pid == 0){ + /* Child */ + /* Raise privileges */ + errno = 0; + ret = seteuid(0); + if(ret == -1){ + perror_plus("seteuid"); + } + /* Raise privileges even more */ + errno = 0; + ret = setuid(0); + if(ret == -1){ + perror_plus("setuid"); + } + /* Set group */ + errno = 0; + ret = setgid(0); + if(ret == -1){ + perror_plus("setgid"); + } + /* Reset supplementary groups */ + errno = 0; + ret = setgroups(0, NULL); + if(ret == -1){ + perror_plus("setgroups"); + } + dup2(devnull, STDIN_FILENO); + close(devnull); + dup2(STDERR_FILENO, STDOUT_FILENO); + ret = setenv("MANDOSNETHOOKDIR", hookdir, 1); + if(ret == -1){ + perror_plus("setenv"); + _exit(EX_OSERR); + } + ret = setenv("DEVICE", interface, 1); + if(ret == -1){ + perror_plus("setenv"); + _exit(EX_OSERR); + } + ret = setenv("VERBOSITY", debug ? "1" : "0", 1); + if(ret == -1){ + perror_plus("setenv"); + _exit(EX_OSERR); + } + ret = setenv("MODE", mode, 1); + if(ret == -1){ + perror_plus("setenv"); + _exit(EX_OSERR); + } + char *delaystring; + ret = asprintf(&delaystring, "%f", delay); + if(ret == -1){ + perror_plus("asprintf"); + _exit(EX_OSERR); + } + ret = setenv("DELAY", delaystring, 1); + if(ret == -1){ + free(delaystring); + perror_plus("setenv"); + _exit(EX_OSERR); + } + free(delaystring); + if(connect_to != NULL){ + ret = setenv("CONNECT", connect_to, 1); + if(ret == -1){ + perror_plus("setenv"); + _exit(EX_OSERR); + } + } + if(execl(fullname, direntry->d_name, mode, NULL) == -1){ + perror_plus("execl"); + _exit(EXIT_FAILURE); + } + } else { + int status; + if(TEMP_FAILURE_RETRY(waitpid(hook_pid, &status, 0)) == -1){ + perror_plus("waitpid"); + free(fullname); + continue; + } + if(WIFEXITED(status)){ + if(WEXITSTATUS(status) != 0){ + fprintf_plus(stderr, "Warning: network hook \"%s\" exited" + " with status %d\n", direntry->d_name, + WEXITSTATUS(status)); + free(fullname); + continue; + } + } else if(WIFSIGNALED(status)){ + fprintf_plus(stderr, "Warning: network hook \"%s\" died by" + " signal %d\n", direntry->d_name, + WTERMSIG(status)); + free(fullname); + continue; + } else { + fprintf_plus(stderr, "Warning: network hook \"%s\"" + " crashed\n", direntry->d_name); + free(fullname); + continue; + } + } + free(fullname); + if(debug){ + fprintf_plus(stderr, "Network hook \"%s\" ran successfully\n", + direntry->d_name); + } + } + close(devnull); + } + return true; +} + int main(int argc, char *argv[]){ AvahiSServiceBrowser *sb = NULL; int error; @@ -1357,7 +1608,11 @@ .group = 2 }, { .name = "retry", .key = 132, .arg = "SECONDS", - .doc = "Retry interval used when denied by the mandos server", + .doc = "Retry interval used when denied by the Mandos server", + .group = 2 }, + { .name = "network-hook-dir", .key = 133, + .arg = "DIR", + .doc = "Directory where network hooks are located", .group = 2 }, /* * These reproduce what we would get without ARGP_NO_HELP @@ -1417,6 +1672,9 @@ argp_error(state, "Bad retry interval"); } break; + case 133: /* --network-hook-dir */ + hookdir = arg; + break; /* * These reproduce what we would get without ARGP_NO_HELP */ @@ -1428,7 +1686,7 @@ argp_state_help(state, state->out_stream, ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR); case 'V': /* --version */ - fprintf(state->out_stream, "%s\n", argp_program_version); + fprintf_plus(state->out_stream, "%s\n", argp_program_version); exit(argp_err_exit_status); break; default: @@ -1461,76 +1719,91 @@ { /* Work around Debian bug #633582: */ - struct stat st; /* Re-raise priviliges */ errno = 0; ret = seteuid(0); if(ret == -1){ perror_plus("seteuid"); - } - - if(strcmp(seckey, PATHDIR "/" SECKEY) == 0){ - int seckey_fd = open(seckey, O_RDONLY); - if(seckey_fd == -1){ - perror_plus("open"); - } else { - ret = (int)TEMP_FAILURE_RETRY(fstat(seckey_fd, &st)); - if(ret == -1){ - perror_plus("fstat"); - } else { - if(S_ISREG(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){ - ret = fchown(seckey_fd, uid, gid); - if(ret == -1){ - perror_plus("fchown"); - } - } - } - TEMP_FAILURE_RETRY(close(seckey_fd)); - } - } - - if(strcmp(pubkey, PATHDIR "/" PUBKEY) == 0){ - int pubkey_fd = open(pubkey, O_RDONLY); - if(pubkey_fd == -1){ - perror_plus("open"); - } else { - ret = (int)TEMP_FAILURE_RETRY(fstat(pubkey_fd, &st)); - if(ret == -1){ - perror_plus("fstat"); - } else { - if(S_ISREG(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){ - ret = fchown(pubkey_fd, uid, gid); - if(ret == -1){ - perror_plus("fchown"); - } - } - } - TEMP_FAILURE_RETRY(close(pubkey_fd)); - } - } - - /* Lower privileges */ - errno = 0; - ret = seteuid(uid); - if(ret == -1){ - perror_plus("seteuid"); - } + } else { + struct stat st; + + if(strcmp(seckey, PATHDIR "/" SECKEY) == 0){ + int seckey_fd = open(seckey, O_RDONLY); + if(seckey_fd == -1){ + perror_plus("open"); + } else { + ret = (int)TEMP_FAILURE_RETRY(fstat(seckey_fd, &st)); + if(ret == -1){ + perror_plus("fstat"); + } else { + if(S_ISREG(st.st_mode) + and st.st_uid == 0 and st.st_gid == 0){ + ret = fchown(seckey_fd, uid, gid); + if(ret == -1){ + perror_plus("fchown"); + } + } + } + TEMP_FAILURE_RETRY(close(seckey_fd)); + } + } + + if(strcmp(pubkey, PATHDIR "/" PUBKEY) == 0){ + int pubkey_fd = open(pubkey, O_RDONLY); + if(pubkey_fd == -1){ + perror_plus("open"); + } else { + ret = (int)TEMP_FAILURE_RETRY(fstat(pubkey_fd, &st)); + if(ret == -1){ + perror_plus("fstat"); + } else { + if(S_ISREG(st.st_mode) + and st.st_uid == 0 and st.st_gid == 0){ + ret = fchown(pubkey_fd, uid, gid); + if(ret == -1){ + perror_plus("fchown"); + } + } + } + TEMP_FAILURE_RETRY(close(pubkey_fd)); + } + } + + /* Lower privileges */ + errno = 0; + ret = seteuid(uid); + if(ret == -1){ + perror_plus("seteuid"); + } + } + } + + /* Run network hooks */ + if(not run_network_hooks("start", interface, delay)){ + goto end; } if(not debug){ avahi_set_log_function(empty_log); } - + if(interface[0] == '\0'){ struct dirent **direntries; - ret = scandir(sys_class_net, &direntries, good_interface, + /* First look for interfaces that are up */ + ret = scandir(sys_class_net, &direntries, up_interface, alphasort); + if(ret == 0){ + /* No up interfaces, look for any good interfaces */ + free(direntries); + ret = scandir(sys_class_net, &direntries, good_interface, + alphasort); + } if(ret >= 1){ - /* Pick the first good interface */ + /* Pick the first interface returned */ interface = strdup(direntries[0]->d_name); if(debug){ - fprintf(stderr, "Using interface \"%s\"\n", interface); + fprintf_plus(stderr, "Using interface \"%s\"\n", interface); } if(interface == NULL){ perror_plus("malloc"); @@ -1541,7 +1814,7 @@ free(direntries); } else { free(direntries); - fprintf(stderr, "Could not find a network interface\n"); + fprintf_plus(stderr, "Could not find a network interface\n"); exitcode = EXIT_FAILURE; goto end; } @@ -1553,7 +1826,8 @@ srand((unsigned int) time(NULL)); mc.simple_poll = avahi_simple_poll_new(); if(mc.simple_poll == NULL){ - fprintf(stderr, "Avahi: Failed to create simple poll object.\n"); + fprintf_plus(stderr, + "Avahi: Failed to create simple poll object.\n"); exitcode = EX_UNAVAILABLE; goto end; } @@ -1625,7 +1899,7 @@ if(strcmp(interface, "none") != 0){ if_index = (AvahiIfIndex) if_nametoindex(interface); if(if_index == 0){ - fprintf(stderr, "No such interface: \"%s\"\n", interface); + fprintf_plus(stderr, "No such interface: \"%s\"\n", interface); exitcode = EX_UNAVAILABLE; goto end; } @@ -1751,18 +2025,10 @@ #endif /* __linux__ */ /* Lower privileges */ errno = 0; - if(take_down_interface){ - /* Lower privileges */ - ret = seteuid(uid); - if(ret == -1){ - perror_plus("seteuid"); - } - } else { - /* Lower privileges permanently */ - ret = setuid(uid); - if(ret == -1){ - perror_plus("setuid"); - } + /* Lower privileges */ + ret = seteuid(uid); + if(ret == -1){ + perror_plus("seteuid"); } } @@ -1772,7 +2038,7 @@ ret = init_gnutls_global(pubkey, seckey); if(ret == -1){ - fprintf(stderr, "init_gnutls_global failed\n"); + fprintf_plus(stderr, "init_gnutls_global failed\n"); exitcode = EX_UNAVAILABLE; goto end; } else { @@ -1794,7 +2060,7 @@ } if(not init_gpgme(pubkey, seckey, tempdir)){ - fprintf(stderr, "init_gpgme failed\n"); + fprintf_plus(stderr, "init_gpgme failed\n"); exitcode = EX_UNAVAILABLE; goto end; } else { @@ -1810,7 +2076,7 @@ /* (Mainly meant for debugging) */ char *address = strrchr(connect_to, ':'); if(address == NULL){ - fprintf(stderr, "No colon in address\n"); + fprintf_plus(stderr, "No colon in address\n"); exitcode = EX_USAGE; goto end; } @@ -1824,7 +2090,7 @@ tmpmax = strtoimax(address+1, &tmp, 10); if(errno != 0 or tmp == address+1 or *tmp != '\0' or tmpmax != (uint16_t)tmpmax){ - fprintf(stderr, "Bad port number\n"); + fprintf_plus(stderr, "Bad port number\n"); exitcode = EX_USAGE; goto end; } @@ -1860,8 +2126,8 @@ break; } if(debug){ - fprintf(stderr, "Retrying in %d seconds\n", - (int)retry_interval); + fprintf_plus(stderr, "Retrying in %d seconds\n", + (int)retry_interval); } sleep((int)retry_interval); } @@ -1869,7 +2135,7 @@ if (not quit_now){ exitcode = EXIT_SUCCESS; } - + goto end; } @@ -1897,8 +2163,8 @@ /* Check if creating the Avahi server object succeeded */ if(mc.server == NULL){ - fprintf(stderr, "Failed to create Avahi server: %s\n", - avahi_strerror(error)); + fprintf_plus(stderr, "Failed to create Avahi server: %s\n", + avahi_strerror(error)); exitcode = EX_UNAVAILABLE; goto end; } @@ -1912,8 +2178,8 @@ AVAHI_PROTO_UNSPEC, "_mandos._tcp", NULL, 0, browse_callback, NULL); if(sb == NULL){ - fprintf(stderr, "Failed to create service browser: %s\n", - avahi_strerror(avahi_server_errno(mc.server))); + fprintf_plus(stderr, "Failed to create service browser: %s\n", + avahi_strerror(avahi_server_errno(mc.server))); exitcode = EX_UNAVAILABLE; goto end; } @@ -1925,20 +2191,20 @@ /* Run the main loop */ if(debug){ - fprintf(stderr, "Starting Avahi loop search\n"); + fprintf_plus(stderr, "Starting Avahi loop search\n"); } ret = avahi_loop_with_timeout(mc.simple_poll, (int)(retry_interval * 1000)); if(debug){ - fprintf(stderr, "avahi_loop_with_timeout exited %s\n", - (ret == 0) ? "successfully" : "with error"); + fprintf_plus(stderr, "avahi_loop_with_timeout exited %s\n", + (ret == 0) ? "successfully" : "with error"); } end: if(debug){ - fprintf(stderr, "%s exiting\n", argv[0]); + fprintf_plus(stderr, "%s exiting\n", argv[0]); } /* Cleanup things */ @@ -1960,7 +2226,7 @@ if(gpgme_initialized){ gpgme_release(mc.ctx); } - + /* Cleans up the circular linked list of Mandos servers the client has seen */ if(mc.current_server != NULL){ @@ -1972,19 +2238,23 @@ } } - /* Take down the network interface */ - if(take_down_interface){ - /* Re-raise priviliges */ + /* Run network hooks */ + run_network_hooks("stop", interface, delay); + + /* Re-raise priviliges */ + { errno = 0; ret = seteuid(0); if(ret == -1){ perror_plus("seteuid"); } - if(geteuid() == 0){ + + /* Take down the network interface */ + if(take_down_interface and geteuid() == 0){ ret = ioctl(sd, SIOCGIFFLAGS, &network); if(ret == -1){ perror_plus("ioctl SIOCGIFFLAGS"); - } else if(network.ifr_flags & IFF_UP) { + } else if(network.ifr_flags & IFF_UP){ network.ifr_flags &= ~(short)IFF_UP; /* clear flag */ ret = ioctl(sd, SIOCSIFFLAGS, &network); if(ret == -1){ @@ -1995,14 +2265,14 @@ if(ret == -1){ perror_plus("close"); } - /* Lower privileges permanently */ - errno = 0; - ret = setuid(uid); - if(ret == -1){ - perror_plus("setuid"); - } } } + /* Lower privileges permanently */ + errno = 0; + ret = setuid(uid); + if(ret == -1){ + perror_plus("setuid"); + } /* Removes the GPGME temp directory and all files inside */ if(tempdir_created){ @@ -2022,8 +2292,8 @@ } ret = remove(fullname); if(ret == -1){ - fprintf(stderr, "remove(\"%s\"): %s\n", fullname, - strerror(errno)); + fprintf_plus(stderr, "remove(\"%s\"): %s\n", fullname, + strerror(errno)); } free(fullname); } === modified file 'plugins.d/mandos-client.xml' --- plugins.d/mandos-client.xml 2011-10-05 16:00:56 +0000 +++ plugins.d/mandos-client.xml 2011-12-31 23:05:34 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -33,7 +33,7 @@ 2008 2009 - 2011 + 2012 Teddy Hogeborn Björn Påhlsson @@ -102,6 +102,11 @@ + + + + @@ -143,6 +148,26 @@ will wait indefinitely for new servers to appear. + The network interface is selected like this: If an interface is + specified using the option, that + interface is used. Otherwise, &COMMANDNAME; + will choose any interface that is up and running and is not a + loopback interface, is not a point-to-point interface, is + capable of broadcasting and does not have the NOARP flag (see + netdevice + 7). (If the + option is used, point-to-point + interfaces and non-broadcast interfaces are accepted.) If no + acceptable interfaces are found, re-run the check but without + the up and running requirement, and manually take + the selected interface up (and later take it down on program + exit). + + + Before a network interface is selected, all network + hooks are run; see . + + This program is not meant to be run directly; it is really meant to run as a plugin of the Mandos plugin-runner @@ -223,7 +248,8 @@ can not be a pseudo-interface such as br0 or tun0; such interfaces will not exist until much later in the boot process, and can not be used - by this program. + by this program, unless created by a network + hook — see . NAME can be the string @@ -311,6 +337,18 @@ + + + + + + Network hook directory. The default directory is + /lib/mandos/network-hooks.d. + + + @@ -377,8 +415,10 @@ plugin-runner 8mandos) is used to run both this program and others in in parallel, - one of which will prompt for passwords on - the system console. + one of which ( + password-prompt + 8mandos) will prompt for + passwords on the system console. @@ -405,6 +445,174 @@ + + NETWORK HOOKS + + If a network interface like a bridge or tunnel is required to + find a Mandos server, this requires the interface to be up and + running before &COMMANDNAME; starts looking + for Mandos servers. This can be accomplished by creating a + network hook program, and placing it in a special + directory. + + + Before the network is used (and again before program exit), any + runnable programs found in the network hook directory are run + with the argument start or + stop. This should bring up or + down, respectively, any network interface which + &COMMANDNAME; should use. + + + REQUIREMENTS + + A network hook must be an executable file, and its name must + consist entirely of upper and lower case letters, digits, + underscores, periods, and hyphens. + + + A network hook will receive one argument, which can be one of + the following: + + + + start + + + This should make the network hook create (if necessary) + and bring up a network interface. + + + + + stop + + + This should make the network hook take down a network + interface, and delete it if it did not exist previously. + + + + + files + + + This should make the network hook print, one + file per line, all the files needed for it to + run. (These files will be copied into the initial RAM + filesystem.) Typical use is for a network hook which is + a shell script to print its needed binaries. + + + It is not necessary to print any non-executable files + already in the network hook directory, these will be + copied implicitly if they otherwise satisfy the name + requirement. + + + + + modules + + + This should make the network hook print, on + separate lines, all the kernel modules needed + for it to run. (These modules will be copied into the + initial RAM filesystem.) For instance, a tunnel + interface needs the + tun module. + + + + + + The network hook will be provided with a number of environment + variables: + + + + MANDOSNETHOOKDIR + + + The network hook directory, specified to + &COMMANDNAME; by the + option. Note: this + should always be used by the + network hook to refer to itself or any files in the hook + directory it may require. + + + + + DEVICE + + + The network interface, as specified to + &COMMANDNAME; by the + option. If this is not the + interface a hook will bring up, there is no reason for a + hook to continue. + + + + + MODE + + + This will be the same as the first argument; + i.e. start, + stop, + files, or + modules. + + + + + VERBOSITY + + + This will be the 1 if + the option is passed to + &COMMANDNAME;, otherwise + 0. + + + + + DELAY + + + This will be the same as the + option passed to &COMMANDNAME;. Is + only set if MODE is + start or + stop. + + + + + CONNECT + + + This will be the same as the + option passed to &COMMANDNAME;. Is + only set if is passed and + MODE is + start or + stop. + + + + + + A hook may not read from standard input, and should be + restrictive in printing to standard output or standard error + unless VERBOSITY is + 1. + + + + FILES @@ -422,6 +630,17 @@ + + /lib/mandos/network-hooks.d + + + Directory where network hooks are located. Change this + with the option. See + . + + + === modified file 'plugins.d/password-prompt.c' --- plugins.d/password-prompt.c 2011-10-09 12:32:13 +0000 +++ plugins.d/password-prompt.c 2011-12-31 23:05:34 +0000 @@ -2,8 +2,8 @@ /* * Password-prompt - Read a password from the terminal and print it * - * Copyright © 2008-2011 Teddy Hogeborn - * Copyright © 2008-2011 Björn Påhlsson + * Copyright © 2008-2012 Teddy Hogeborn + * Copyright © 2008-2012 Björn Påhlsson * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -72,7 +72,18 @@ /* Needed for conflict resolution */ const char plymouth_name[] = "plymouthd"; +__attribute__((format (gnu_printf, 2, 3), nonnull(1))) +int fprintf_plus(FILE *stream, const char *format, ...){ + va_list ap; + va_start (ap, format); + + TEMP_FAILURE_RETRY(fprintf(stream, "Mandos plugin %s: ", + program_invocation_short_name)); + return TEMP_FAILURE_RETRY(vfprintf(stream, format, ap)); +} + /* Function to use when printing errors */ +__attribute__((format (gnu_printf, 3, 4))) void error_plus(int status, int errnum, const char *formatstring, ...){ va_list ap; @@ -85,8 +96,7 @@ fprintf(stderr, "Mandos plugin %s: ", program_invocation_short_name); vfprintf(stderr, formatstring, ap); - fprintf(stderr, ": "); - fprintf(stderr, "%s\n", strerror(errnum)); + fprintf(stderr, ": %s\n", strerror(errnum)); error(status, errno, "vasprintf while printing error"); return; } @@ -109,17 +119,18 @@ from the terminal. Password-prompt will exit if it detects plymouth since plymouth performs the same functionality. */ + __attribute__((nonnull)) int is_plymouth(const struct dirent *proc_entry){ int ret; int cl_fd; { - uintmax_t maxvalue; + uintmax_t proc_id; char *tmp; errno = 0; - maxvalue = strtoumax(proc_entry->d_name, &tmp, 10); + proc_id = strtoumax(proc_entry->d_name, &tmp, 10); if(errno != 0 or *tmp != '\0' - or maxvalue != (uintmax_t)((pid_t)maxvalue)){ + or proc_id != (uintmax_t)((pid_t)proc_id)){ return 0; } } @@ -128,7 +139,7 @@ ret = asprintf(&cmdline_filename, "/proc/%s/cmdline", proc_entry->d_name); if(ret == -1){ - error(0, errno, "asprintf"); + error_plus(0, errno, "asprintf"); return 0; } @@ -137,7 +148,7 @@ free(cmdline_filename); if(cl_fd == -1){ if(errno != ENOENT){ - error(0, errno, "open"); + error_plus(0, errno, "open"); } return 0; } @@ -154,7 +165,7 @@ if(cmdline_len + blocksize + 1 > cmdline_allocated){ tmp = realloc(cmdline, cmdline_allocated + blocksize + 1); if(tmp == NULL){ - error(0, errno, "realloc"); + error_plus(0, errno, "realloc"); free(cmdline); close(cl_fd); return 0; @@ -167,7 +178,7 @@ sret = read(cl_fd, cmdline + cmdline_len, cmdline_allocated - cmdline_len); if(sret == -1){ - error(0, errno, "read"); + error_plus(0, errno, "read"); free(cmdline); close(cl_fd); return 0; @@ -176,7 +187,7 @@ } while(sret != 0); ret = close(cl_fd); if(ret == -1){ - error(0, errno, "close"); + error_plus(0, errno, "close"); free(cmdline); return 0; } @@ -212,7 +223,7 @@ int ret; ret = scandir("/proc", &direntries, is_plymouth, alphasort); if (ret == -1){ - error(1, errno, "scandir"); + error_plus(1, errno, "scandir"); } free(direntries); return ret > 0; @@ -249,6 +260,7 @@ { .name = NULL } }; + __attribute__((nonnull(3))) error_t parse_opt (int key, char *arg, struct argp_state *state){ errno = 0; switch (key){ @@ -290,7 +302,7 @@ case ENOMEM: default: errno = ret; - error(0, errno, "argp_parse"); + error_plus(0, errno, "argp_parse"); return EX_OSERR; case EINVAL: return EX_USAGE; @@ -314,7 +326,7 @@ if(tcgetattr(STDIN_FILENO, &t_old) != 0){ int e = errno; - error(0, errno, "tcgetattr"); + error_plus(0, errno, "tcgetattr"); switch(e){ case EBADF: case ENOTTY: @@ -327,17 +339,17 @@ sigemptyset(&new_action.sa_mask); ret = sigaddset(&new_action.sa_mask, SIGINT); if(ret == -1){ - error(0, errno, "sigaddset"); + error_plus(0, errno, "sigaddset"); return EX_OSERR; } ret = sigaddset(&new_action.sa_mask, SIGHUP); if(ret == -1){ - error(0, errno, "sigaddset"); + error_plus(0, errno, "sigaddset"); return EX_OSERR; } ret = sigaddset(&new_action.sa_mask, SIGTERM); if(ret == -1){ - error(0, errno, "sigaddset"); + error_plus(0, errno, "sigaddset"); return EX_OSERR; } /* Need to check if the handler is SIG_IGN before handling: @@ -346,37 +358,37 @@ */ ret = sigaction(SIGINT, NULL, &old_action); if(ret == -1){ - error(0, errno, "sigaction"); + error_plus(0, errno, "sigaction"); return EX_OSERR; } if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGINT, &new_action, NULL); if(ret == -1){ - error(0, errno, "sigaction"); + error_plus(0, errno, "sigaction"); return EX_OSERR; } } ret = sigaction(SIGHUP, NULL, &old_action); if(ret == -1){ - error(0, errno, "sigaction"); + error_plus(0, errno, "sigaction"); return EX_OSERR; } if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGHUP, &new_action, NULL); if(ret == -1){ - error(0, errno, "sigaction"); + error_plus(0, errno, "sigaction"); return EX_OSERR; } } ret = sigaction(SIGTERM, NULL, &old_action); if(ret == -1){ - error(0, errno, "sigaction"); + error_plus(0, errno, "sigaction"); return EX_OSERR; } if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGTERM, &new_action, NULL); if(ret == -1){ - error(0, errno, "sigaction"); + error_plus(0, errno, "sigaction"); return EX_OSERR; } } @@ -390,7 +402,7 @@ t_new.c_lflag &= ~(tcflag_t)ECHO; if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_new) != 0){ int e = errno; - error(0, errno, "tcsetattr-echo"); + error_plus(0, errno, "tcsetattr-echo"); switch(e){ case EBADF: case ENOTTY: @@ -460,7 +472,7 @@ sret = write(STDOUT_FILENO, buffer + written, n - written); if(sret < 0){ int e = errno; - error(0, errno, "write"); + error_plus(0, errno, "write"); switch(e){ case EBADF: case EFAULT: @@ -482,7 +494,7 @@ sret = close(STDOUT_FILENO); if(sret == -1){ int e = errno; - error(0, errno, "close"); + error_plus(0, errno, "close"); switch(e){ case EBADF: status = EX_OSFILE; @@ -498,10 +510,11 @@ if(sret < 0){ int e = errno; if(errno != EINTR and not feof(stdin)){ - error(0, errno, "getline"); + error_plus(0, errno, "getline"); switch(e){ case EBADF: status = EX_UNAVAILABLE; + break; case EIO: case EINVAL: default: @@ -527,7 +540,7 @@ fprintf(stderr, "Restoring terminal attributes\n"); } if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_old) != 0){ - error(0, errno, "tcsetattr+echo"); + error_plus(0, errno, "tcsetattr+echo"); } if(quit_now){ @@ -535,7 +548,7 @@ old_action.sa_handler = SIG_DFL; ret = sigaction(signal_received, &old_action, NULL); if(ret == -1){ - error(0, errno, "sigaction"); + error_plus(0, errno, "sigaction"); } raise(signal_received); } === modified file 'plugins.d/password-prompt.xml' --- plugins.d/password-prompt.xml 2011-10-05 16:00:56 +0000 +++ plugins.d/password-prompt.xml 2011-12-31 23:05:34 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -34,6 +34,7 @@ 2008 2009 2011 + 2012 Teddy Hogeborn Björn Påhlsson === modified file 'plugins.d/plymouth.c' --- plugins.d/plymouth.c 2011-10-05 16:00:56 +0000 +++ plugins.d/plymouth.c 2011-12-31 23:05:34 +0000 @@ -2,8 +2,8 @@ /* * Plymouth - Read a password from Plymouth and output it * - * Copyright © 2010-2011 Teddy Hogeborn - * Copyright © 2010-2011 Björn Påhlsson + * Copyright © 2010-2012 Teddy Hogeborn + * Copyright © 2010-2012 Björn Påhlsson * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -75,6 +75,7 @@ } /* Function to use when printing errors */ +__attribute__((format (gnu_printf, 3, 4))) void error_plus(int status, int errnum, const char *formatstring, ...){ va_list ap; @@ -153,6 +154,7 @@ return true; } +__attribute__((nonnull (2, 3))) bool exec_and_wait(pid_t *pid_return, const char *path, const char **argv, bool interruptable, bool daemonize){ @@ -212,16 +214,17 @@ return false; } +__attribute__((nonnull)) int is_plymouth(const struct dirent *proc_entry){ int ret; { - uintmax_t maxvalue; + uintmax_t proc_id; char *tmp; errno = 0; - maxvalue = strtoumax(proc_entry->d_name, &tmp, 10); + proc_id = strtoumax(proc_entry->d_name, &tmp, 10); if(errno != 0 or *tmp != '\0' - or maxvalue != (uintmax_t)((pid_t)maxvalue)){ + or proc_id != (uintmax_t)((pid_t)proc_id)){ return 0; } } @@ -262,36 +265,36 @@ pid_t get_pid(void){ int ret; - uintmax_t maxvalue = 0; + uintmax_t proc_id = 0; FILE *pidfile = fopen(plymouth_pid, "r"); /* Try the new pid file location */ if(pidfile != NULL){ - ret = fscanf(pidfile, "%" SCNuMAX, &maxvalue); + ret = fscanf(pidfile, "%" SCNuMAX, &proc_id); if(ret != 1){ - maxvalue = 0; + proc_id = 0; } fclose(pidfile); } /* Try the old pid file location */ - if(maxvalue == 0){ + if(proc_id == 0){ pidfile = fopen(plymouth_pid, "r"); if(pidfile != NULL){ - ret = fscanf(pidfile, "%" SCNuMAX, &maxvalue); + ret = fscanf(pidfile, "%" SCNuMAX, &proc_id); if(ret != 1){ - maxvalue = 0; + proc_id = 0; } fclose(pidfile); } } /* Look for a plymouth process */ - if(maxvalue == 0){ + if(proc_id == 0){ struct dirent **direntries = NULL; ret = scandir("/proc", &direntries, is_plymouth, alphasort); if (ret == -1){ error_plus(0, errno, "scandir"); } if (ret > 0){ - ret = sscanf(direntries[0]->d_name, "%" SCNuMAX, &maxvalue); + ret = sscanf(direntries[0]->d_name, "%" SCNuMAX, &proc_id); if (ret < 0){ error_plus(0, errno, "sscanf"); } @@ -301,8 +304,8 @@ free(direntries); } pid_t pid; - pid = (pid_t)maxvalue; - if((uintmax_t)pid == maxvalue){ + pid = (pid_t)proc_id; + if((uintmax_t)pid == proc_id){ return pid; } === modified file 'plugins.d/plymouth.xml' --- plugins.d/plymouth.xml 2011-10-05 16:00:56 +0000 +++ plugins.d/plymouth.xml 2011-12-31 23:05:34 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -33,6 +33,7 @@ 2010 2011 + 2012 Teddy Hogeborn Björn Påhlsson @@ -161,7 +162,7 @@ - /proc + /proc To find the running - + %common; ]> @@ -33,7 +33,7 @@ 2008 2009 - 2011 + 2012 Teddy Hogeborn Björn Påhlsson === modified file 'plugins.d/usplash.c' --- plugins.d/usplash.c 2011-10-05 16:00:56 +0000 +++ plugins.d/usplash.c 2011-12-31 23:05:34 +0000 @@ -2,8 +2,8 @@ /* * Usplash - Read a password from usplash and output it * - * Copyright © 2008-2011 Teddy Hogeborn - * Copyright © 2008-2011 Björn Påhlsson + * Copyright © 2008-2012 Teddy Hogeborn + * Copyright © 2008-2012 Björn Påhlsson * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -58,6 +58,7 @@ const char usplash_name[] = "/sbin/usplash"; /* Function to use when printing errors */ +__attribute__((format (gnu_printf, 3, 4))) void error_plus(int status, int errnum, const char *formatstring, ...){ va_list ap; === modified file 'plugins.d/usplash.xml' --- plugins.d/usplash.xml 2011-10-05 16:00:56 +0000 +++ plugins.d/usplash.xml 2011-12-31 23:05:34 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -34,6 +34,7 @@ 2008 2009 2011 + 2012 Teddy Hogeborn Björn Påhlsson @@ -178,7 +179,7 @@ - /proc + /proc To find the running