=== modified file '.bzrignore' --- .bzrignore 2010-09-30 06:24:20 +0000 +++ .bzrignore 2012-05-17 01:55:58 +0000 @@ -3,6 +3,7 @@ *.8mandos confdir keydir +statedir man plugin-runner plugins.d/askpass-fifo === modified file 'Makefile' --- Makefile 2012-01-15 21:07:44 +0000 +++ Makefile 2012-05-20 13:52:09 +0000 @@ -23,7 +23,7 @@ OPTIMIZE=-Os LANGUAGE=-std=gnu99 htmldir=man -version=1.5.3 +version=1.5.4 SED=sed USER=$(firstword $(subst :, ,$(shell getent passwd _mandos || getent passwd nobody || echo 65534))) @@ -264,7 +264,7 @@ ./mandos-keygen --dir keydir --force # Run the server with a local config -run-server: confdir/mandos.conf confdir/clients.conf +run-server: confdir/mandos.conf confdir/clients.conf statedir ./mandos --debug --no-dbus --configdir=confdir \ --statedir=statedir $(SERVERARGS) === modified file 'NEWS' --- NEWS 2012-01-15 21:07:44 +0000 +++ NEWS 2012-05-20 13:52:09 +0000 @@ -1,6 +1,18 @@ This NEWS file records noteworthy changes, very tersely. See the manual for detailed information. +Version 1.5.4 (2012-05-20) +* Server +** Bug fix: Regression fix: Make non-zero approval timeout values work. +** Bug fix: Regression fix: Allow changing the Timeout D-Bus property. +** Fall back to not bind to an interface if an invalid interface name + is given. +** Removed support for undocumented feature of using plain "%%s" in + "checker" client option. +** Old D-Bus interface are now marked as deprecated. +** mandos-monitor: Bug fix: show approval timers correctly. +** mandos-ctl: Show "Extended Timeout" correctly, not as milliseconds. + Version 1.5.3 (2012-01-15) * Server ** Add D-Bus property se.recompile.Client.LastCheckerStatus and use it === modified file 'TODO' --- TODO 2012-01-15 20:27:28 +0000 +++ TODO 2012-05-12 19:29:05 +0000 @@ -11,6 +11,7 @@ ** TODO [#B] Use getaddrinfo(hints=AI_NUMERICHOST) instead of inet_pton() ** TODO [#B] Use getnameinfo(serv=NULL, NI_NUMERICHOST) instead of inet_ntop() ** TODO [#B] Prefer /run/tmp over /tmp, if it exists +** TODO [#B] Use in_port_t instead of uint16_t for port numbers. * splashy ** TODO [#B] use scandir(3) instead of readdir(3) @@ -39,7 +40,6 @@ ** TODO [#B] Use openat() * mandos (server) -** TODO Document why we ignore sigint ** TODO [#B] Log level :BUGS: *** TODO /etc/mandos/clients.d/*.conf Watch this directory and add/remove/update clients? @@ -54,8 +54,7 @@ ** TODO [#B] Global enable/disable flag ** 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 +** D-Bus Client method NeedsPassword(50) - Timeout, default disapprove + SetPass(u"gazonk", True) -> Approval, persistent + Approve(False) -> Close client connection immediately ** TODO [#C] python-parsedatetime @@ -78,6 +77,8 @@ and signals ClientAdded and ClientRemoved. ** TODO Save state periodically to recover better from hard shutdowns ** TODO CheckerCompleted method, deprecate CheckedOK +** TODO Secret Service API? + http://standards.freedesktop.org/secret-service/ * mandos.xml ** Add mandos contact info in manual pages === modified file 'common.ent' --- common.ent 2012-01-15 21:07:44 +0000 +++ common.ent 2012-05-20 13:52:09 +0000 @@ -1,3 +1,3 @@ - + === modified file 'debian/changelog' --- debian/changelog 2012-05-12 19:54:59 +0000 +++ debian/changelog 2012-05-20 13:52:09 +0000 @@ -1,3 +1,9 @@ +mandos (1.5.4-1) unstable; urgency=low + + * New upstream release. + + -- Teddy Hogeborn Sun, 20 May 2012 15:38:34 +0200 + mandos (1.5.3-1.2) unstable; urgency=low * Non-maintainer upload. === modified file 'mandos' --- mandos 2012-01-15 21:07:44 +0000 +++ mandos 2012-05-20 13:52:09 +0000 @@ -34,6 +34,8 @@ from __future__ import (division, absolute_import, print_function, unicode_literals) +from future_builtins import * + import SocketServer as socketserver import socket import argparse @@ -65,6 +67,7 @@ import types import binascii import tempfile +import itertools import dbus import dbus.service @@ -85,7 +88,7 @@ except ImportError: SO_BINDTODEVICE = None -version = "1.5.3" +version = "1.5.4" stored_state_file = "clients.pickle" logger = logging.getLogger() @@ -175,7 +178,7 @@ def encrypt(self, data, password): self.gnupg.passphrase = self.password_encode(password) - with open(os.devnull) as devnull: + with open(os.devnull, "w") as devnull: try: proc = self.gnupg.run(['--symmetric'], create_fhs=['stdin', 'stdout'], @@ -192,12 +195,12 @@ def decrypt(self, data, password): self.gnupg.passphrase = self.password_encode(password) - with open(os.devnull) as devnull: + with open(os.devnull, "w") as devnull: try: proc = self.gnupg.run(['--decrypt'], create_fhs=['stdin', 'stdout'], attach_fhs={'stderr': devnull}) - with contextlib.closing(proc.handles['stdin'] ) as f: + with contextlib.closing(proc.handles['stdin']) as f: f.write(data) with contextlib.closing(proc.handles['stdout']) as f: decrypted_plaintext = f.read() @@ -208,7 +211,6 @@ return decrypted_plaintext - class AvahiError(Exception): def __init__(self, value, *args, **kwargs): self.value = value @@ -243,6 +245,7 @@ server: D-Bus Server bus: dbus.SystemBus() """ + def __init__(self, interface = avahi.IF_UNSPEC, name = None, servicetype = None, port = None, TXT = None, domain = "", host = "", max_renames = 32768, @@ -261,6 +264,7 @@ self.server = None self.bus = bus self.entry_group_state_changed_match = None + def rename(self): """Derived from the Avahi example code""" if self.rename_count >= self.max_renames: @@ -276,10 +280,11 @@ try: self.add() except dbus.exceptions.DBusException as error: - logger.critical("DBusException: %s", error) + logger.critical("D-Bus Exception", exc_info=error) self.cleanup() os._exit(1) self.rename_count += 1 + def remove(self): """Derived from the Avahi example code""" if self.entry_group_state_changed_match is not None: @@ -287,6 +292,7 @@ self.entry_group_state_changed_match = None if self.group is not None: self.group.Reset() + def add(self): """Derived from the Avahi example code""" self.remove() @@ -309,6 +315,7 @@ dbus.UInt16(self.port), avahi.string_array_to_txt_array(self.TXT)) self.group.Commit() + def entry_group_state_changed(self, state, error): """Derived from the Avahi example code""" logger.debug("Avahi entry group state change: %i", state) @@ -321,8 +328,9 @@ elif state == avahi.ENTRY_GROUP_FAILURE: logger.critical("Avahi: Error in group state changed %s", unicode(error)) - raise AvahiGroupError("State changed: %s" - % unicode(error)) + raise AvahiGroupError("State changed: {0!s}" + .format(error)) + def cleanup(self): """Derived from the Avahi example code""" if self.group is not None: @@ -333,6 +341,7 @@ pass self.group = None self.remove() + def server_state_changed(self, state, error=None): """Derived from the Avahi example code""" logger.debug("Avahi server state change: %i", state) @@ -357,6 +366,7 @@ logger.debug("Unknown state: %r", state) else: logger.debug("Unknown state: %r: %r", state, error) + def activate(self): """Derived from the Avahi example code""" if self.server is None: @@ -369,22 +379,25 @@ 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)) + ('Mandos ({0}) [%(process)d]:' + ' %(levelname)s: %(message)s' + .format(self.name))) return ret + def timedelta_to_milliseconds(td): "Convert a datetime.timedelta() to milliseconds" return ((td.days * 24 * 60 * 60 * 1000) + (td.seconds * 1000) + (td.microseconds // 1000)) - + + class Client(object): """A representation of a client host served by this server. @@ -429,8 +442,9 @@ """ runtime_expansions = ("approval_delay", "approval_duration", - "created", "enabled", "fingerprint", - "host", "interval", "last_checked_ok", + "created", "enabled", "expires", + "fingerprint", "host", "interval", + "last_approval_request", "last_checked_ok", "last_enabled", "name", "timeout") client_defaults = { "timeout": "5m", "extended_timeout": "15m", @@ -457,7 +471,7 @@ def approval_delay_milliseconds(self): return timedelta_to_milliseconds(self.approval_delay) - + @staticmethod def config_parser(config): """Construct a new dict of client settings of this form: @@ -488,8 +502,8 @@ "rb") as secfile: client["secret"] = secfile.read() else: - raise TypeError("No secret or secfile for section %s" - % section) + raise TypeError("No secret or secfile for section {0}" + .format(section)) client["timeout"] = string_to_delta(section["timeout"]) client["extended_timeout"] = string_to_delta( section["extended_timeout"]) @@ -504,12 +518,8 @@ client["last_checker_status"] = -2 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 # adding all client settings for setting, value in settings.iteritems(): @@ -524,7 +534,7 @@ 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() @@ -532,7 +542,7 @@ logger.debug(" Fingerprint: %s", self.fingerprint) self.created = settings.get("created", datetime.datetime.utcnow()) - + # attributes specific for this server instance self.checker = None self.checker_initiator_tag = None @@ -566,29 +576,29 @@ if getattr(self, "enabled", False): # Already enabled return - self.send_changedstate() self.expires = datetime.datetime.utcnow() + self.timeout self.enabled = True self.last_enabled = datetime.datetime.utcnow() self.init_checker() + self.send_changedstate() def disable(self, quiet=True): """Disable this client.""" if not getattr(self, "enabled", False): return False if not quiet: - self.send_changedstate() - if not quiet: logger.info("Disabling client %s", self.name) - if getattr(self, "disable_initiator_tag", False): + if getattr(self, "disable_initiator_tag", None) is not None: gobject.source_remove(self.disable_initiator_tag) self.disable_initiator_tag = None self.expires = None - if getattr(self, "checker_initiator_tag", False): + if getattr(self, "checker_initiator_tag", None) is not None: gobject.source_remove(self.checker_initiator_tag) self.checker_initiator_tag = None self.stop_checker() self.enabled = False + if not quiet: + self.send_changedstate() # Do not run this again if called by a gobject.timeout_add return False @@ -598,10 +608,14 @@ def init_checker(self): # Schedule a new checker to be started an 'interval' from now, # and every interval from then on. + if self.checker_initiator_tag is not None: + gobject.source_remove(self.checker_initiator_tag) self.checker_initiator_tag = (gobject.timeout_add (self.interval_milliseconds(), self.start_checker)) # Schedule a disable() when 'timeout' has passed + if self.disable_initiator_tag is not None: + gobject.source_remove(self.disable_initiator_tag) self.disable_initiator_tag = (gobject.timeout_add (self.timeout_milliseconds(), self.disable)) @@ -638,6 +652,7 @@ timeout = self.timeout if self.disable_initiator_tag is not None: gobject.source_remove(self.disable_initiator_tag) + self.disable_initiator_tag = None if getattr(self, "enabled", False): self.disable_initiator_tag = (gobject.timeout_add (timedelta_to_milliseconds @@ -653,10 +668,10 @@ If a checker already exists, leave it running and do nothing.""" # The reason for not killing a running checker is that if we - # did that, then if a checker (for some reason) started - # running slowly and taking more than 'interval' time, the - # client would inevitably timeout, since no checker would get - # a chance to run to completion. If we instead leave running + # did that, and if a checker (for some reason) started running + # slowly and taking more than 'interval' time, then the client + # would inevitably timeout, since no checker would get a + # chance to run to completion. If we instead leave running # checkers alone, the checker would have to take more time # than 'timeout' for the client to be disabled, which is as it # should be. @@ -676,25 +691,17 @@ self.current_checker_command) # Start a new checker if needed if self.checker is None: + # Escape attributes for the shell + escaped_attrs = dict( + (attr, re.escape(unicode(getattr(self, attr)))) + for attr in + self.runtime_expansions) try: - # In case checker_command has exactly one % operator - command = self.checker_command % self.host - except TypeError: - # Escape attributes for the shell - escaped_attrs = dict( - (attr, - re.escape(unicode(str(getattr(self, attr, "")), - errors= - 'replace'))) - for attr in - self.runtime_expansions) - - try: - command = self.checker_command % escaped_attrs - except TypeError as error: - logger.error('Could not format string "%s":' - ' %s', self.checker_command, error) - return True # Try again later + command = self.checker_command % escaped_attrs + except TypeError as error: + logger.error('Could not format string "%s"', + self.checker_command, exc_info=error) + return True # Try again later self.current_checker_command = command try: logger.info("Starting checker %r for %s", @@ -706,19 +713,19 @@ self.checker = subprocess.Popen(command, close_fds=True, shell=True, cwd="/") - self.checker_callback_tag = (gobject.child_watch_add - (self.checker.pid, - self.checker_callback, - data=command)) - # The checker may have completed before the gobject - # watch was added. Check for this. - pid, status = os.waitpid(self.checker.pid, os.WNOHANG) - if pid: - gobject.source_remove(self.checker_callback_tag) - self.checker_callback(pid, status, command) except OSError as error: - logger.error("Failed to start subprocess: %s", - error) + logger.error("Failed to start subprocess", + exc_info=error) + self.checker_callback_tag = (gobject.child_watch_add + (self.checker.pid, + self.checker_callback, + data=command)) + # The checker may have completed before the gobject + # watch was added. Check for this. + pid, status = os.waitpid(self.checker.pid, os.WNOHANG) + if pid: + gobject.source_remove(self.checker_callback_tag) + self.checker_callback(pid, status, command) # Re-run this periodically if run by gobject.timeout_add return True @@ -731,10 +738,10 @@ return logger.debug("Stopping checker for %(name)s", vars(self)) try: - os.kill(self.checker.pid, signal.SIGTERM) + self.checker.terminate() #time.sleep(0.5) #if self.checker.poll() is None: - # os.kill(self.checker.pid, signal.SIGKILL) + # self.checker.kill() except OSError as error: if error.errno != errno.ESRCH: # No such process raise @@ -757,7 +764,7 @@ # "Set" method, so we fail early here: if byte_arrays and signature != "ay": raise ValueError("Byte arrays not supported for non-'ay'" - " signature %r" % signature) + " signature {0!r}".format(signature)) def decorator(func): func._dbus_is_property = True func._dbus_interface = dbus_interface @@ -771,6 +778,43 @@ return decorator +def dbus_interface_annotations(dbus_interface): + """Decorator for marking functions returning interface annotations + + Usage: + + @dbus_interface_annotations("org.example.Interface") + def _foo(self): # Function name does not matter + return {"org.freedesktop.DBus.Deprecated": "true", + "org.freedesktop.DBus.Property.EmitsChangedSignal": + "false"} + """ + def decorator(func): + func._dbus_is_interface = True + func._dbus_interface = dbus_interface + func._dbus_name = dbus_interface + return func + return decorator + + +def dbus_annotations(annotations): + """Decorator to annotate D-Bus methods, signals or properties + Usage: + + @dbus_service_property("org.example.Interface", signature="b", + access="r") + @dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true", + "org.freedesktop.DBus.Property." + "EmitsChangedSignal": "false"}) + def Property_dbus_property(self): + return dbus.Boolean(False) + """ + def decorator(func): + func._dbus_annotations = annotations + return func + return decorator + + class DBusPropertyException(dbus.exceptions.DBusException): """A base class for D-Bus property-related exceptions """ @@ -799,16 +843,25 @@ """ @staticmethod - def _is_dbus_property(obj): - return getattr(obj, "_dbus_is_property", False) + def _is_dbus_thing(thing): + """Returns a function testing if an attribute is a D-Bus thing + + If called like _is_dbus_thing("method") it returns a function + suitable for use as predicate to inspect.getmembers(). + """ + return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing), + False) - def _get_all_dbus_properties(self): + def _get_all_dbus_things(self, thing): """Returns a generator of (name, attribute) pairs """ - return ((prop.__get__(self)._dbus_name, prop.__get__(self)) + return ((getattr(athing.__get__(self), "_dbus_name", + name), + athing.__get__(self)) for cls in self.__class__.__mro__ - for name, prop in - inspect.getmembers(cls, self._is_dbus_property)) + for name, athing in + inspect.getmembers(cls, + self._is_dbus_thing(thing))) def _get_dbus_property(self, interface_name, property_name): """Returns a bound method if one exists which is a D-Bus @@ -816,7 +869,8 @@ """ for cls in self.__class__.__mro__: for name, value in (inspect.getmembers - (cls, self._is_dbus_property)): + (cls, + self._is_dbus_thing("property"))): if (value._dbus_name == property_name and value._dbus_interface == interface_name): return value.__get__(self) @@ -864,7 +918,7 @@ Note: Will not include properties with access="write". """ properties = {} - for name, prop in self._get_all_dbus_properties(): + for name, prop in self._get_all_dbus_things("property"): if (interface_name and interface_name != prop._dbus_interface): # Interface non-empty but did not match @@ -885,7 +939,9 @@ path_keyword='object_path', connection_keyword='connection') def Introspect(self, object_path, connection): - """Standard D-Bus method, overloaded to insert property tags. + """Overloading of standard D-Bus method. + + Inserts property tags and interface annotation tags. """ xmlstring = dbus.service.Object.Introspect(self, object_path, connection) @@ -898,12 +954,44 @@ e.setAttribute("access", prop._dbus_access) return e for if_tag in document.getElementsByTagName("interface"): + # Add property tags for tag in (make_tag(document, name, prop) for name, prop - in self._get_all_dbus_properties() + in self._get_all_dbus_things("property") if prop._dbus_interface == if_tag.getAttribute("name")): if_tag.appendChild(tag) + # Add annotation tags + for typ in ("method", "signal", "property"): + for tag in if_tag.getElementsByTagName(typ): + annots = dict() + for name, prop in (self. + _get_all_dbus_things(typ)): + if (name == tag.getAttribute("name") + and prop._dbus_interface + == if_tag.getAttribute("name")): + annots.update(getattr + (prop, + "_dbus_annotations", + {})) + for name, value in annots.iteritems(): + ann_tag = document.createElement( + "annotation") + ann_tag.setAttribute("name", name) + ann_tag.setAttribute("value", value) + tag.appendChild(ann_tag) + # Add interface annotation tags + for annotation, value in dict( + itertools.chain.from_iterable( + annotations().iteritems() + for name, annotations in + self._get_all_dbus_things("interface") + if name == if_tag.getAttribute("name") + )).iteritems(): + ann_tag = document.createElement("annotation") + ann_tag.setAttribute("name", annotation) + ann_tag.setAttribute("value", value) + if_tag.appendChild(ann_tag) # Add the names to the return values for the # "org.freedesktop.DBus.Properties" methods if (if_tag.getAttribute("name") @@ -924,7 +1012,7 @@ except (AttributeError, xml.dom.DOMException, xml.parsers.expat.ExpatError) as error: logger.error("Failed to override Introspection method", - error) + exc_info=error) return xmlstring @@ -936,29 +1024,48 @@ variant_level=variant_level) -class AlternateDBusNamesMetaclass(DBusObjectWithProperties - .__metaclass__): - """Applied to an empty subclass of a D-Bus object, this metaclass - will add additional D-Bus attributes matching a certain pattern. +def alternate_dbus_interfaces(alt_interface_names, deprecate=True): + """A class decorator; applied to a subclass of + dbus.service.Object, it will add alternate D-Bus attributes with + interface names according to the "alt_interface_names" mapping. + Usage: + + @alternate_dbus_names({"org.example.Interface": + "net.example.AlternateInterface"}) + class SampleDBusObject(dbus.service.Object): + @dbus.service.method("org.example.Interface") + def SampleDBusMethod(): + pass + + The above "SampleDBusMethod" on "SampleDBusObject" will be + reachable via two interfaces: "org.example.Interface" and + "net.example.AlternateInterface", the latter of which will have + its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to + "true", unless "deprecate" is passed with a False value. + + This works for methods and signals, and also for D-Bus properties + (from DBusObjectWithProperties) and interfaces (from the + dbus_interface_annotations decorator). """ - def __new__(mcs, name, bases, attr): - # Go through all the base classes which could have D-Bus - # methods, signals, or properties in them - for base in (b for b in bases - if issubclass(b, dbus.service.Object)): - # Go though all attributes of the base class - for attrname, attribute in inspect.getmembers(base): + def wrapper(cls): + for orig_interface_name, alt_interface_name in ( + alt_interface_names.iteritems()): + attr = {} + interface_names = set() + # Go though all attributes of the class + for attrname, attribute in inspect.getmembers(cls): # Ignore non-D-Bus attributes, and D-Bus attributes # with the wrong interface name if (not hasattr(attribute, "_dbus_interface") or not attribute._dbus_interface - .startswith("se.recompile.Mandos")): + .startswith(orig_interface_name)): continue # Create an alternate D-Bus interface name based on # the current name alt_interface = (attribute._dbus_interface - .replace("se.recompile.Mandos", - "se.bsnet.fukt.Mandos")) + .replace(orig_interface_name, + alt_interface_name)) + interface_names.add(alt_interface) # Is this a D-Bus signal? if getattr(attribute, "_dbus_is_signal", False): # Extract the original non-method function by @@ -979,9 +1086,16 @@ nonmethod_func.func_name, nonmethod_func.func_defaults, nonmethod_func.func_closure))) + # Copy annotations, if any + try: + new_function._dbus_annotations = ( + dict(attribute._dbus_annotations)) + except AttributeError: + pass # Define a creator of a function to call both the - # old and new functions, so both the old and new - # signals gets sent when the function is called + # original and alternate functions, so both the + # original and alternate signals gets sent when + # the function is called def fixscope(func1, func2): """This function is a scope container to pass func1 and func2 to the "call_both" function @@ -994,8 +1108,7 @@ return call_both # Create the "call_both" function and add it to # the class - attr[attrname] = fixscope(attribute, - new_function) + attr[attrname] = fixscope(attribute, new_function) # Is this a D-Bus method? elif getattr(attribute, "_dbus_is_method", False): # Create a new, but exactly alike, function @@ -1012,6 +1125,12 @@ attribute.func_name, attribute.func_defaults, attribute.func_closure))) + # Copy annotations, if any + try: + attr[attrname]._dbus_annotations = ( + dict(attribute._dbus_annotations)) + except AttributeError: + pass # Is this a D-Bus property? elif getattr(attribute, "_dbus_is_property", False): # Create a new, but exactly alike, function @@ -1031,9 +1150,51 @@ attribute.func_name, attribute.func_defaults, attribute.func_closure))) - return type.__new__(mcs, name, bases, attr) - - + # Copy annotations, if any + try: + attr[attrname]._dbus_annotations = ( + dict(attribute._dbus_annotations)) + except AttributeError: + pass + # Is this a D-Bus interface? + elif getattr(attribute, "_dbus_is_interface", False): + # Create a new, but exactly alike, function + # object. Decorate it to be a new D-Bus interface + # with the alternate D-Bus interface name. Add it + # to the class. + attr[attrname] = (dbus_interface_annotations + (alt_interface) + (types.FunctionType + (attribute.func_code, + attribute.func_globals, + attribute.func_name, + attribute.func_defaults, + attribute.func_closure))) + if deprecate: + # Deprecate all alternate interfaces + iname="_AlternateDBusNames_interface_annotation{0}" + for interface_name in interface_names: + @dbus_interface_annotations(interface_name) + def func(self): + return { "org.freedesktop.DBus.Deprecated": + "true" } + # Find an unused name + for aname in (iname.format(i) + for i in itertools.count()): + if aname not in attr: + attr[aname] = func + break + if interface_names: + # Replace the class with a new subclass of it with + # methods, signals, etc. as created above. + cls = type(b"{0}Alternate".format(cls.__name__), + (cls,), attr) + return cls + return wrapper + + +@alternate_dbus_interfaces({"se.recompile.Mandos": + "se.bsnet.fukt.Mandos"}) class ClientDBus(Client, DBusObjectWithProperties): """A Client class using D-Bus @@ -1059,7 +1220,7 @@ ("/clients/" + client_object_name)) DBusObjectWithProperties.__init__(self, self.bus, self.dbus_object_path) - + def notifychangeproperty(transform_func, dbus_name, type_func=lambda x: x, variant_level=1): @@ -1088,7 +1249,6 @@ return property(lambda self: getattr(self, attrname), setter) - expires = notifychangeproperty(datetime_to_dbus, "Expires") approvals_pending = notifychangeproperty(dbus.Boolean, "ApprovalPending", @@ -1176,16 +1336,22 @@ return False def approve(self, value=True): - self.send_changedstate() self.approved = value gobject.timeout_add(timedelta_to_milliseconds (self.approval_duration), self._reset_approved) - + self.send_changedstate() ## D-Bus methods, signals & properties _interface = "se.recompile.Mandos.Client" + ## Interfaces + + @dbus_interface_annotations(_interface) + def _foo(self): + return { "org.freedesktop.DBus.Property.EmitsChangedSignal": + "false"} + ## Signals # CheckerCompleted - signal @@ -1364,26 +1530,24 @@ def Timeout_dbus_property(self, value=None): if value is None: # get return dbus.UInt64(self.timeout_milliseconds()) + old_timeout = self.timeout self.timeout = datetime.timedelta(0, 0, 0, value) - # Reschedule timeout + # Reschedule disabling 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: + self.expires += self.timeout - old_timeout + if self.expires <= now: # 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)) + self.disable_initiator_tag = ( + gobject.timeout_add( + timedelta_to_milliseconds(self.expires - now), + self.disable)) # ExtendedTimeout - property @dbus_service_property(_interface, signature="t", @@ -1468,10 +1632,6 @@ self._pipe.send(('setattr', name, value)) -class ClientDBusTransitional(ClientDBus): - __metaclass__ = AlternateDBusNamesMetaclass - - class ClientHandler(socketserver.BaseRequestHandler, object): """A class to handle client connections. @@ -1581,9 +1741,9 @@ #wait until timeout or approved time = datetime.datetime.now() client.changedstate.acquire() - (client.changedstate.wait - (float(client.timedelta_to_milliseconds(delay) - / 1000))) + client.changedstate.wait( + float(timedelta_to_milliseconds(delay) + / 1000)) client.changedstate.release() time2 = datetime.datetime.now() if (time2 - time) >= delay: @@ -1605,7 +1765,8 @@ try: sent = session.send(client.secret[sent_size:]) except gnutls.errors.GNUTLSError as error: - logger.warning("gnutls send failed") + logger.warning("gnutls send failed", + exc_info=error) return logger.debug("Sent: %d, remaining: %d", sent, len(client.secret) @@ -1625,7 +1786,8 @@ try: session.bye() except gnutls.errors.GNUTLSError as error: - logger.warning("GnuTLS bye failed") + logger.warning("GnuTLS bye failed", + exc_info=error) @staticmethod def peer_certificate(session): @@ -1703,8 +1865,7 @@ def process_request(self, request, address): """Start a new process to process the request.""" proc = multiprocessing.Process(target = self.sub_process_main, - args = (request, - address)) + args = (request, address)) proc.start() return proc @@ -1760,14 +1921,18 @@ str(self.interface + '\0')) except socket.error as error: - if error[0] == errno.EPERM: + if error.errno == errno.EPERM: logger.error("No permission to" " bind to interface %s", self.interface) - elif error[0] == errno.ENOPROTOOPT: + elif error.errno == errno.ENOPROTOOPT: logger.error("SO_BINDTODEVICE not available;" " cannot bind to interface %s", self.interface) + elif error.errno == errno.ENODEV: + logger.error("Interface %s does not" + " exist, cannot bind", + self.interface) else: raise # Only bind(2) the socket if we really need to. @@ -1832,22 +1997,8 @@ def handle_ipc(self, source, condition, parent_pipe=None, proc = None, client_object=None): - condition_names = { - gobject.IO_IN: "IN", # There is data to read. - gobject.IO_OUT: "OUT", # Data can be written (without - # blocking). - gobject.IO_PRI: "PRI", # There is urgent data to read. - gobject.IO_ERR: "ERR", # Error condition. - gobject.IO_HUP: "HUP" # Hung up (the connection has been - # broken, usually for pipes and - # sockets). - } - conditions_string = ' | '.join(name - for cond, name in - condition_names.iteritems() - if cond & condition) # error, or the other end of multiprocessing.Pipe has closed - if condition & (gobject.IO_ERR | condition & gobject.IO_HUP): + if condition & (gobject.IO_ERR | gobject.IO_HUP): # Wait for other process to exit proc.join() return False @@ -1943,7 +2094,8 @@ elif suffix == "w": delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value) else: - raise ValueError("Unknown suffix %r" % suffix) + raise ValueError("Unknown suffix {0!r}" + .format(suffix)) except (ValueError, IndexError) as e: raise ValueError(*(e.args)) timevalue += delta @@ -1963,11 +2115,11 @@ sys.exit() if not noclose: # Close all standard open file descriptors - null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR) + null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR) if not stat.S_ISCHR(os.fstat(null).st_mode): raise OSError(errno.ENODEV, - "%s not a character device" - % os.path.devnull) + "{0} not a character device" + .format(os.devnull)) os.dup2(null, sys.stdin.fileno()) os.dup2(null, sys.stdout.fileno()) os.dup2(null, sys.stderr.fileno()) @@ -1982,7 +2134,7 @@ parser = argparse.ArgumentParser() parser.add_argument("-v", "--version", action="version", - version = "%%(prog)s %s" % version, + version = "%(prog)s {0}".format(version), help="show version number and exit") parser.add_argument("-i", "--interface", metavar="IF", help="Bind to interface IF") @@ -2091,9 +2243,10 @@ if server_settings["servicename"] != "Mandos": syslogger.setFormatter(logging.Formatter - ('Mandos (%s) [%%(process)d]:' - ' %%(levelname)s: %%(message)s' - % server_settings["servicename"])) + ('Mandos ({0}) [%(process)d]:' + ' %(levelname)s: %(message)s' + .format(server_settings + ["servicename"]))) # Parse config file with clients client_config = configparser.SafeConfigParser(Client @@ -2117,28 +2270,25 @@ pidfilename = "/var/run/mandos.pid" try: pidfile = open(pidfilename, "w") - except IOError: - logger.error("Could not open file %r", pidfilename) + except IOError as e: + logger.error("Could not open file %r", pidfilename, + exc_info=e) - try: - uid = pwd.getpwnam("_mandos").pw_uid - gid = pwd.getpwnam("_mandos").pw_gid - except KeyError: + for name in ("_mandos", "mandos", "nobody"): try: - uid = pwd.getpwnam("mandos").pw_uid - gid = pwd.getpwnam("mandos").pw_gid + uid = pwd.getpwnam(name).pw_uid + gid = pwd.getpwnam(name).pw_gid + break except KeyError: - try: - uid = pwd.getpwnam("nobody").pw_uid - gid = pwd.getpwnam("nobody").pw_gid - except KeyError: - uid = 65534 - gid = 65534 + continue + else: + uid = 65534 + gid = 65534 try: os.setgid(gid) os.setuid(uid) except OSError as error: - if error[0] != errno.EPERM: + if error.errno != errno.EPERM: raise error if debug: @@ -2156,7 +2306,7 @@ .gnutls_global_set_log_function(debug_gnutls)) # Redirect stdin so all checkers get /dev/null - null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR) + null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR) os.dup2(null, sys.stdin.fileno()) if null > 2: os.close(null) @@ -2170,7 +2320,7 @@ global main_loop # From the Avahi example code - DBusGMainLoop(set_as_default=True ) + DBusGMainLoop(set_as_default=True) main_loop = gobject.MainLoop() bus = dbus.SystemBus() # End of Avahi example code @@ -2182,7 +2332,7 @@ ("se.bsnet.fukt.Mandos", bus, do_not_queue=True)) except dbus.exceptions.NameExistsException as e: - logger.error(unicode(e) + ", disabling D-Bus") + logger.error("Disabling D-Bus:", exc_info=e) use_dbus = False server_settings["use_dbus"] = False tcp_server.use_dbus = False @@ -2200,8 +2350,7 @@ client_class = Client if use_dbus: - client_class = functools.partial(ClientDBusTransitional, - bus = bus) + client_class = functools.partial(ClientDBus, bus = bus) client_settings = Client.config_parser(client_config) old_client_settings = {} @@ -2215,13 +2364,16 @@ (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: + if e.errno == errno.ENOENT: + logger.warning("Could not load persistent state: {0}" + .format(os.strerror(e.errno))) + else: + logger.critical("Could not load persistent state:", + exc_info=e) raise except EOFError as e: logger.warning("Could not load persistent state: " - "EOFError: {0}".format(e)) + "EOFError:", exc_info=e) with PGPEngine() as pgp: for client_name, client in clients_data.iteritems(): @@ -2280,7 +2432,6 @@ .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) @@ -2289,7 +2440,7 @@ for client_name in (set(client_settings) - set(old_client_settings)): clients_data[client_name] = client_settings[client_name] - + # Create all client objects for client_name, client in clients_data.iteritems(): tcp_server.clients[client_name] = client_class( @@ -2297,7 +2448,7 @@ if not tcp_server.clients: logger.warning("No clients defined") - + if not debug: try: with pidfile: @@ -2311,18 +2462,25 @@ # "pidfile" was never created pass del pidfilename - signal.signal(signal.SIGINT, signal.SIG_IGN) signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit()) signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit()) if use_dbus: - class MandosDBusService(dbus.service.Object): + @alternate_dbus_interfaces({"se.recompile.Mandos": + "se.bsnet.fukt.Mandos"}) + class MandosDBusService(DBusObjectWithProperties): """A D-Bus proxy object""" def __init__(self): dbus.service.Object.__init__(self, bus, "/") _interface = "se.recompile.Mandos" + @dbus_interface_annotations(_interface) + def _foo(self): + return { "org.freedesktop.DBus.Property" + ".EmitsChangedSignal": + "false"} + @dbus.service.signal(_interface, signature="o") def ClientAdded(self, objpath): "D-Bus signal" @@ -2370,9 +2528,7 @@ del _interface - class MandosDBusServiceTransitional(MandosDBusService): - __metaclass__ = AlternateDBusNamesMetaclass - mandos_dbus_service = MandosDBusServiceTransitional() + mandos_dbus_service = MandosDBusService() def cleanup(): "Cleanup function; run on exit" @@ -2411,23 +2567,25 @@ 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: + with (tempfile.NamedTemporaryFile + (mode='wb', suffix=".pickle", prefix='clients-', + dir=os.path.dirname(stored_state_path), + delete=False)) as stored_state: pickle.dump((clients, client_settings), stored_state) + tempname=stored_state.name 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)): + if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST): + logger.warning("Could not save persistent state: {0}" + .format(os.strerror(e.errno))) + else: + logger.warning("Could not save persistent state:", + exc_info=e) raise e # Delete all clients, and settings from config @@ -2461,11 +2619,11 @@ service.port = tcp_server.socket.getsockname()[1] if use_ipv6: logger.info("Now listening on address %r, port %d," - " flowinfo %d, scope_id %d" - % tcp_server.socket.getsockname()) + " flowinfo %d, scope_id %d", + *tcp_server.socket.getsockname()) else: # IPv4 - logger.info("Now listening on address %r, port %d" - % tcp_server.socket.getsockname()) + logger.info("Now listening on address %r, port %d", + *tcp_server.socket.getsockname()) #service.interface = tcp_server.socket.getsockname()[3] @@ -2474,7 +2632,7 @@ try: service.activate() except dbus.exceptions.DBusException as error: - logger.critical("DBusException: %s", error) + logger.critical("D-Bus Exception", exc_info=error) cleanup() sys.exit(1) # End of Avahi example code @@ -2487,7 +2645,7 @@ logger.debug("Starting main loop") main_loop.run() except AvahiError as error: - logger.critical("AvahiError: %s", error) + logger.critical("Avahi Error", exc_info=error) cleanup() sys.exit(1) except KeyboardInterrupt: === modified file 'mandos-clients.conf.xml' --- mandos-clients.conf.xml 2012-01-01 04:02:00 +0000 +++ mandos-clients.conf.xml 2012-05-12 19:29:05 +0000 @@ -3,7 +3,7 @@ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ /etc/mandos/clients.conf"> - + %common; ]> @@ -409,6 +409,7 @@ approval_duration, created, enabled, + expires, fingerprint, host, interval, === modified file 'mandos-ctl' --- mandos-ctl 2012-01-15 21:07:44 +0000 +++ mandos-ctl 2012-05-20 13:52:09 +0000 @@ -17,7 +17,8 @@ # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# along with this program. If not, see +# . # # Contact the authors at . # @@ -25,6 +26,8 @@ from __future__ import (division, absolute_import, print_function, unicode_literals) +from future_builtins import * + import sys import dbus import argparse @@ -60,7 +63,7 @@ server_path = "/" server_interface = domain + ".Mandos" client_interface = domain + ".Mandos.Client" -version = "1.5.3" +version = "1.5.4" def timedelta_to_milliseconds(td): """Convert a datetime.timedelta object to milliseconds""" @@ -70,12 +73,12 @@ def milliseconds_to_string(ms): td = datetime.timedelta(0, 0, 0, ms) - return ("%(days)s%(hours)02d:%(minutes)02d:%(seconds)02d" - % { "days": "%dT" % td.days if td.days else "", - "hours": td.seconds // 3600, - "minutes": (td.seconds % 3600) // 60, - "seconds": td.seconds % 60, - }) + return ("{days}{hours:02}:{minutes:02}:{seconds:02}" + .format(days = "{0}T".format(td.days) if td.days else "", + hours = td.seconds // 3600, + minutes = (td.seconds % 3600) // 60, + seconds = td.seconds % 60, + )) def string_to_delta(interval): """Parse a string and return a datetime.timedelta @@ -116,23 +119,25 @@ if type(value) is dbus.Boolean: return "Yes" if value else "No" if keyword in ("Timeout", "Interval", "ApprovalDelay", - "ApprovalDuration"): + "ApprovalDuration", "ExtendedTimeout"): return milliseconds_to_string(value) return unicode(value) # Create format string to print table rows - format_string = " ".join("%%-%ds" % - max(len(tablewords[key]), - max(len(valuetostring(client[key], - key)) - for client in - clients)) - for key in keywords) + format_string = " ".join("{{{key}:{width}}}".format( + width = max(len(tablewords[key]), + max(len(valuetostring(client[key], + key)) + for client in + clients)), + key = key) for key in keywords) # Print header line - print(format_string % tuple(tablewords[key] for key in keywords)) + print(format_string.format(**tablewords)) for client in clients: - print(format_string % tuple(valuetostring(client[key], key) - for key in keywords)) + print(format_string.format(**dict((key, + valuetostring(client[key], + key)) + for key in keywords))) def has_actions(options): return any((options.enable, @@ -157,7 +162,7 @@ def main(): parser = argparse.ArgumentParser() parser.add_argument("--version", action="version", - version = "%%prog %s" % version, + version = "%(prog)s {0}".format(version), help="show version number and exit") parser.add_argument("-a", "--all", action="store_true", help="Select all clients") @@ -205,7 +210,7 @@ parser.add_argument("client", nargs="*", help="Client name") options = parser.parse_args() - if has_actions(options) and not options.client and not options.all: + if has_actions(options) and not (options.client or options.all): parser.error("Options require clients names or --all.") if options.verbose and has_actions(options): parser.error("--verbose can only be used alone or with" @@ -256,8 +261,8 @@ clients[client_objc] = client break else: - print("Client not found on server: %r" % name, - file=sys.stderr) + print("Client not found on server: {0!r}" + .format(name), file=sys.stderr) sys.exit(1) if not has_actions(options) and clients: @@ -277,18 +282,28 @@ else: # Process each client in the list by all selected options for client in clients: + def set_client_prop(prop, value): + """Set a Client D-Bus property""" + client.Set(client_interface, prop, value, + dbus_interface=dbus.PROPERTIES_IFACE) + def set_client_prop_ms(prop, value): + """Set a Client D-Bus property, converted + from a string to milliseconds.""" + set_client_prop(prop, + timedelta_to_milliseconds + (string_to_delta(value))) if options.remove: mandos_serv.RemoveClient(client.__dbus_object_path__) if options.enable: - client.Enable(dbus_interface=client_interface) + set_client_prop("Enabled", dbus.Boolean(True)) if options.disable: - client.Disable(dbus_interface=client_interface) + set_client_prop("Enabled", dbus.Boolean(False)) if options.bump_timeout: - client.CheckedOK(dbus_interface=client_interface) + set_client_prop("LastCheckedOK", "") if options.start_checker: - client.StartChecker(dbus_interface=client_interface) + set_client_prop("CheckerRunning", dbus.Boolean(True)) if options.stop_checker: - client.StopChecker(dbus_interface=client_interface) + set_client_prop("CheckerRunning", dbus.Boolean(False)) if options.is_enabled: sys.exit(0 if client.Get(client_interface, "Enabled", @@ -296,48 +311,29 @@ dbus.PROPERTIES_IFACE) else 1) if options.checker is not None: - client.Set(client_interface, "Checker", - options.checker, - dbus_interface=dbus.PROPERTIES_IFACE) + set_client_prop("Checker", options.checker) if options.host is not None: - client.Set(client_interface, "Host", options.host, - dbus_interface=dbus.PROPERTIES_IFACE) + set_client_prop("Host", options.host) if options.interval is not None: - client.Set(client_interface, "Interval", - timedelta_to_milliseconds - (string_to_delta(options.interval)), - dbus_interface=dbus.PROPERTIES_IFACE) + set_client_prop_ms("Interval", options.interval) if options.approval_delay is not None: - client.Set(client_interface, "ApprovalDelay", - timedelta_to_milliseconds - (string_to_delta(options. - approval_delay)), - dbus_interface=dbus.PROPERTIES_IFACE) + set_client_prop_ms("ApprovalDelay", + options.approval_delay) if options.approval_duration is not None: - client.Set(client_interface, "ApprovalDuration", - timedelta_to_milliseconds - (string_to_delta(options. - approval_duration)), - dbus_interface=dbus.PROPERTIES_IFACE) + set_client_prop_ms("ApprovalDuration", + options.approval_duration) if options.timeout is not None: - client.Set(client_interface, "Timeout", - timedelta_to_milliseconds - (string_to_delta(options.timeout)), - dbus_interface=dbus.PROPERTIES_IFACE) + set_client_prop_ms("Timeout", options.timeout) if options.extended_timeout is not None: - client.Set(client_interface, "ExtendedTimeout", - timedelta_to_milliseconds - (string_to_delta(options.extended_timeout)), - dbus_interface=dbus.PROPERTIES_IFACE) + set_client_prop_ms("ExtendedTimeout", + options.extended_timeout) if options.secret is not None: - client.Set(client_interface, "Secret", - dbus.ByteArray(options.secret.read()), - dbus_interface=dbus.PROPERTIES_IFACE) + set_client_prop("Secret", + dbus.ByteArray(options.secret.read())) if options.approved_by_default is not None: - client.Set(client_interface, "ApprovedByDefault", - dbus.Boolean(options - .approved_by_default), - dbus_interface=dbus.PROPERTIES_IFACE) + set_client_prop("ApprovedByDefault", + dbus.Boolean(options + .approved_by_default)) if options.approve: client.Approve(dbus.Boolean(True), dbus_interface=client_interface) === modified file 'mandos-keygen' --- mandos-keygen 2012-01-15 21:07:44 +0000 +++ mandos-keygen 2012-05-20 13:52:09 +0000 @@ -21,7 +21,7 @@ # Contact the authors at . # -VERSION="1.5.3" +VERSION="1.5.4" KEYDIR="/etc/keys/mandos" KEYTYPE=DSA === modified file 'mandos-monitor' --- mandos-monitor 2012-01-15 21:07:44 +0000 +++ mandos-monitor 2012-05-20 13:52:09 +0000 @@ -17,7 +17,8 @@ # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# along with this program. If not, see +# . # # Contact the authors at . # @@ -25,6 +26,8 @@ from __future__ import (division, absolute_import, print_function, unicode_literals) +from future_builtins import * + import sys import os import signal @@ -52,7 +55,7 @@ domain = 'se.recompile' server_interface = domain + '.Mandos' client_interface = domain + '.Mandos.Client' -version = "1.5.3" +version = "1.5.4" # Always run in monochrome mode urwid.curses_display.curses.has_colors = lambda : False @@ -83,23 +86,26 @@ properties and calls a hook function when any of them are changed. """ - def __init__(self, proxy_object=None, *args, **kwargs): + def __init__(self, proxy_object=None, properties=None, **kwargs): self.proxy = proxy_object # Mandos Client proxy object - - self.properties = dict() + self.properties = dict() if properties is None else properties self.property_changed_match = ( self.proxy.connect_to_signal("PropertyChanged", - self.property_changed, + self._property_changed, client_interface, byte_arrays=True)) - self.properties.update( - self.proxy.GetAll(client_interface, - dbus_interface = dbus.PROPERTIES_IFACE)) - - #XXX This breaks good super behaviour -# super(MandosClientPropertyCache, self).__init__( -# *args, **kwargs) + if properties is None: + self.properties.update( + self.proxy.GetAll(client_interface, + dbus_interface + = dbus.PROPERTIES_IFACE)) + + super(MandosClientPropertyCache, self).__init__(**kwargs) + + def _property_changed(self, property, value): + """Helper which takes positional arguments""" + return self.property_changed(property=property, value=value) def property_changed(self, property=None, value=None): """This is called whenever we get a PropertyChanged signal @@ -108,10 +114,8 @@ # Update properties dict with new value self.properties[property] = value - def delete(self, *args, **kwargs): + def delete(self): self.property_changed_match.remove() - super(MandosClientPropertyCache, self).__init__( - *args, **kwargs) class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache): @@ -119,7 +123,7 @@ """ def __init__(self, server_proxy_object=None, update_hook=None, - delete_hook=None, logger=None, *args, **kwargs): + delete_hook=None, logger=None, **kwargs): # Called on update self.update_hook = update_hook # Called on delete @@ -130,27 +134,15 @@ self.logger = logger self._update_timer_callback_tag = None - self._update_timer_callback_lock = 0 # The widget shown normally self._text_widget = urwid.Text("") # The widget shown when we have focus self._focus_text_widget = urwid.Text("") - super(MandosClientWidget, self).__init__( - update_hook=update_hook, delete_hook=delete_hook, - *args, **kwargs) + super(MandosClientWidget, self).__init__(**kwargs) self.update() self.opened = False - last_checked_ok = isoformat_to_datetime(self.properties - ["LastCheckedOK"]) - - if self.properties ["LastCheckerStatus"] != 0: - self.using_timer(True) - - if self.need_approval: - self.using_timer(True) - self.match_objects = ( self.proxy.connect_to_signal("CheckerCompleted", self.checker_completed, @@ -172,34 +164,19 @@ self.rejected, client_interface, byte_arrays=True)) - #self.logger('Created client %s' % (self.properties["Name"])) - - def property_changed(self, property=None, value=None): - super(self, MandosClientWidget).property_changed(property, - value) - if property == "ApprovalPending": - using_timer(bool(value)) - if property == "LastCheckerStatus": - using_timer(value != 0) - #self.logger('Checker for client %s (command "%s")' - # ' was successful' - # % (self.properties["Name"], command)) + #self.logger('Created client {0}' + # .format(self.properties["Name"])) def using_timer(self, flag): """Call this method with True or False when timer should be activated or deactivated. """ - old = self._update_timer_callback_lock - if flag: - self._update_timer_callback_lock += 1 - else: - self._update_timer_callback_lock -= 1 - if old == 0 and self._update_timer_callback_lock: + if flag and self._update_timer_callback_tag is None: # Will update the shown timer value every second self._update_timer_callback_tag = (gobject.timeout_add (1000, self.update_timer)) - elif old and self._update_timer_callback_lock == 0: + elif not (flag or self._update_timer_callback_tag is None): gobject.source_remove(self._update_timer_callback_tag) self._update_timer_callback_tag = None @@ -209,47 +186,48 @@ return # Checker failed if os.WIFEXITED(condition): - self.logger('Checker for client %s (command "%s")' - ' failed with exit code %s' - % (self.properties["Name"], command, - os.WEXITSTATUS(condition))) + self.logger('Checker for client {0} (command "{1}")' + ' failed with exit code {2}' + .format(self.properties["Name"], command, + os.WEXITSTATUS(condition))) elif os.WIFSIGNALED(condition): - self.logger('Checker for client %s (command "%s")' - ' was killed by signal %s' - % (self.properties["Name"], command, - os.WTERMSIG(condition))) + self.logger('Checker for client {0} (command "{1}") was' + ' killed by signal {2}' + .format(self.properties["Name"], command, + os.WTERMSIG(condition))) elif os.WCOREDUMP(condition): - self.logger('Checker for client %s (command "%s")' + self.logger('Checker for client {0} (command "{1}")' ' dumped core' - % (self.properties["Name"], command)) + .format(self.properties["Name"], command)) else: - self.logger('Checker for client %s completed' - ' mysteriously') + self.logger('Checker for client {0} completed' + ' mysteriously' + .format(self.properties["Name"])) 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))) + #self.logger('Client {0} started checker "{1}"' + # .format(self.properties["Name"], + # unicode(command))) pass def got_secret(self): - self.logger('Client %s received its secret' - % self.properties["Name"]) + self.logger('Client {0} received its secret' + .format(self.properties["Name"])) def need_approval(self, timeout, default): if not default: - message = 'Client %s needs approval within %s seconds' + message = 'Client {0} needs approval within {1} seconds' else: - message = 'Client %s will get its secret in %s seconds' - self.logger(message - % (self.properties["Name"], timeout/1000)) - self.using_timer(True) + message = 'Client {0} will get its secret in {1} seconds' + self.logger(message.format(self.properties["Name"], + timeout/1000)) def rejected(self, reason): - self.logger('Client %s was rejected; reason: %s' - % (self.properties["Name"], reason)) + self.logger('Client {0} was rejected; reason: {1}' + .format(self.properties["Name"], reason)) def selectable(self): """Make this a "selectable" widget. @@ -277,14 +255,14 @@ "bold-underline-blink": "bold-underline-blink-standout", } - + # Rebuild focus and non-focus widgets using current properties - + # Base part of a client. Name! - base = ('%(name)s: ' - % {"name": self.properties["Name"]}) + base = '{name}: '.format(name=self.properties["Name"]) if not self.properties["Enabled"]: message = "DISABLED" + self.using_timer(False) elif self.properties["ApprovalPending"]: timeout = datetime.timedelta(milliseconds = self.properties @@ -292,31 +270,36 @@ last_approval_request = isoformat_to_datetime( self.properties["LastApprovalRequest"]) if last_approval_request is not None: - timer = timeout - (datetime.datetime.utcnow() - - last_approval_request) + timer = max(timeout - (datetime.datetime.utcnow() + - last_approval_request), + datetime.timedelta()) else: timer = datetime.timedelta() if self.properties["ApprovedByDefault"]: - message = "Approval in %s. (d)eny?" + message = "Approval in {0}. (d)eny?" else: - message = "Denial in %s. (a)pprove?" - message = message % unicode(timer).rsplit(".", 1)[0] + message = "Denial in {0}. (a)pprove?" + message = message.format(unicode(timer).rsplit(".", 1)[0]) + self.using_timer(True) elif self.properties["LastCheckerStatus"] != 0: - # When checker has failed, print a timer until client expires + # When checker has failed, show timer until client expires expires = self.properties["Expires"] if expires == "": timer = datetime.timedelta(0) else: - expires = datetime.datetime.strptime(expires, - '%Y-%m-%dT%H:%M:%S.%f') - timer = expires - datetime.datetime.utcnow() + expires = (datetime.datetime.strptime + (expires, '%Y-%m-%dT%H:%M:%S.%f')) + timer = max(expires - datetime.datetime.utcnow(), + datetime.timedelta()) message = ('A checker has failed! Time until client' - ' gets disabled: %s' - % unicode(timer).rsplit(".", 1)[0]) + ' gets disabled: {0}' + .format(unicode(timer).rsplit(".", 1)[0])) + self.using_timer(True) else: message = "enabled" - self._text = "%s%s" % (base, message) - + self.using_timer(False) + self._text = "{0}{1}".format(base, message) + if not urwid.supports_unicode(): self._text = self._text.encode("ascii", "replace") textlist = [("normal", self._text)] @@ -339,7 +322,7 @@ self.update() return True # Keep calling this - def delete(self, *args, **kwargs): + def delete(self, **kwargs): if self._update_timer_callback_tag is not None: gobject.source_remove(self._update_timer_callback_tag) self._update_timer_callback_tag = None @@ -348,7 +331,7 @@ self.match_objects = () if self.delete_hook is not None: self.delete_hook(self) - return super(MandosClientWidget, self).delete(*args, **kwargs) + return super(MandosClientWidget, self).delete(**kwargs) def render(self, maxcolrow, focus=False): """Render differently if we have focus. @@ -396,14 +379,13 @@ else: return key - def property_changed(self, property=None, value=None, - *args, **kwargs): + def property_changed(self, property=None, **kwargs): """Call self.update() if old value is not new value. This overrides the method from MandosClientPropertyCache""" property_name = unicode(property) old_value = self.properties.get(property_name) super(MandosClientWidget, self).property_changed( - property=property, value=value, *args, **kwargs) + property=property, **kwargs) if self.properties.get(property_name) != old_value: self.update() @@ -413,8 +395,8 @@ "down" key presses, thus not allowing any containing widgets to use them as an excuse to shift focus away from this widget. """ - def keypress(self, maxcolrow, key): - ret = super(ConstrainedListBox, self).keypress(maxcolrow, key) + def keypress(self, *args, **kwargs): + ret = super(ConstrainedListBox, self).keypress(*args, **kwargs) if ret in ("up", "down"): return return ret @@ -486,9 +468,9 @@ self.main_loop = gobject.MainLoop() def client_not_found(self, fingerprint, address): - self.log_message(("Client with address %s and fingerprint %s" - " could not be found" % (address, - fingerprint))) + self.log_message("Client with address {0} and fingerprint" + " {1} could not be found" + .format(address, fingerprint)) def rebuild(self): """This rebuilds the User Interface. @@ -547,8 +529,8 @@ client = self.clients_dict[path] except KeyError: # not found? - self.log_message("Unknown client %r (%r) removed", name, - path) + self.log_message("Unknown client {0!r} ({1!r}) removed" + .format(name, path)) return client.delete() @@ -635,7 +617,7 @@ logger =self.log_message), path=path) - + self.refresh() self._input_callback_tag = (gobject.io_add_watch (sys.stdin.fileno(), === modified file 'mandos.lsm' --- mandos.lsm 2012-01-15 21:07:44 +0000 +++ mandos.lsm 2012-05-20 13:52:09 +0000 @@ -1,7 +1,7 @@ Begin4 Title: Mandos -Version: 1.5.3 -Entered-date: 2012-01-15 +Version: 1.5.4 +Entered-date: 2012-05-20 Description: The Mandos system allows computers to have encrypted root file systems and at the same time be capable of remote and/or unattended reboots. @@ -12,9 +12,9 @@ Maintained-by: teddy@recompile.se (Teddy Hogeborn), belorn@recompile.se (Björn Påhlsson) Primary-site: http://www.recompile.se/mandos - 147K mandos_1.5.3.orig.tar.gz + 148K mandos_1.5.4.orig.tar.gz Alternate-site: ftp://ftp.recompile.se/pub/mandos - 147K mandos_1.5.3.orig.tar.gz + 148K mandos_1.5.4.orig.tar.gz Platforms: Requires GCC, GNU libC, Avahi, GnuPG, Python 2.6, and various other libraries. While made for Debian GNU/Linux, it is probably portable to other distributions, but not other Unixes. === modified file 'network-hooks.d/bridge' --- network-hooks.d/bridge 2012-01-15 16:10:09 +0000 +++ network-hooks.d/bridge 2012-04-24 06:55:34 +0000 @@ -6,8 +6,8 @@ # configuration file(s) should be copied into the # /etc/mandos/network-hooks.d directory. # -# Copyright © 2011 Teddy Hogeborn -# Copyright © 2011 Björn Påhlsson +# Copyright © 2012 Teddy Hogeborn +# Copyright © 2012 Björn Påhlsson # # Copying and distribution of this file, with or without modification, # are permitted in any medium without royalty provided the copyright @@ -45,35 +45,40 @@ 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" +do_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 +} + +do_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" +} + +case "${MODE:-$1}" in + start|stop) + do_"${MODE:-$1}" ;; files) echo /bin/ip === modified file 'network-hooks.d/openvpn' --- network-hooks.d/openvpn 2012-01-01 17:38:33 +0000 +++ network-hooks.d/openvpn 2012-04-24 06:55:34 +0000 @@ -37,18 +37,22 @@ 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 +do_start(){ + "$openvpn" --cd "$MANDOSNETHOOKDIR" --daemon 'openvpn(Mandos)' \ + --writepid "$PIDFILE" --config "$CONFIG" + sleep "$DELAY" +} + +do_stop(){ + PID="`cat \"$PIDFILE\"`" + if [ "$PID" -gt 0 ]; then + kill "$PID" + fi +} + +case "${MODE:-$1}" in + start|stop) + do_"${MODE:-$1}" ;; files) echo "$openvpn" === modified file 'network-hooks.d/wireless' --- network-hooks.d/wireless 2012-01-01 17:38:33 +0000 +++ network-hooks.d/wireless 2012-04-24 06:55:34 +0000 @@ -73,77 +73,81 @@ 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 +do_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 +} + +do_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 + "$ip" link set dev "$INTERFACE" down + done +} + +case "${MODE:-$1}" in + start|stop) + do_"${MODE:-$1}" ;; files) echo "$wpa_supplicant"