=== modified file 'TODO' --- TODO 2010-03-27 18:39:02 +0000 +++ TODO 2010-06-19 00:37:04 +0000 @@ -1,5 +1,7 @@ -*- org -*- +* _attribute_((nonnull)) + * mandos-client ** TODO [#B] use scandir(3) instead of readdir(3) ** TODO [#B] Prefix all debug output with "Mandos plugin " + program_invocation_short_name @@ -38,6 +40,8 @@ ** TODO [#C] use same file name rules as run-parts(8) ** kernel command line option for debug info ** TODO [#B] use error() instead of perror() +** TODO [$B] Use openat() and readdir64() + http://udrepper.livejournal.com/19395.html * mandos (server) ** TODO [#B] Log level :BUGS: @@ -68,6 +72,17 @@ A client needs manual approval on the server before it gets the secret ** TODO [#B] Support RFC 3339 time duration syntax +** More D-Bus methods +*** NeedsApproval(50, True) -> timeout, default approve + Default approval is configurable, but True by default + + Approval(True) -> approve sending saved + + Approval(False) -> Close client connection immediately +*** NeedsPassword(50) - Timeout, default disapprove + + SetPass(u"gazonk", True) -> Approval, persistent + + Approval(False) -> Close client connection immediately +** TODO [#C] python-parsedatetime +** TODO [#C] systemd/launchd + http://0pointer.de/blog/projects/systemd.html * mandos.xml ** [[file:mandos.xml::XXX][Document D-Bus interface]] @@ -98,6 +113,8 @@ For testing decryption before rebooting. * Makefile +** TODO Add "--Xlinker --as-needed" + http://udrepper.livejournal.com/19395.html ** TODO [#C] Implement DEB_BUILD_OPTIONS http://www.debian.org/doc/debian-policy/ch-source.html#s-debianrules-options === modified file 'mandos' --- mandos 2010-04-04 23:45:04 +0000 +++ mandos 2010-09-05 20:19:02 +0000 @@ -60,6 +60,7 @@ import fcntl import functools import cPickle as pickle +import select import dbus import dbus.service @@ -192,7 +193,7 @@ self.group.Commit() def entry_group_state_changed(self, state, error): """Derived from the Avahi example code""" - logger.debug(u"Avahi state change: %i", state) + logger.debug(u"Avahi entry group state change: %i", state) if state == avahi.ENTRY_GROUP_ESTABLISHED: logger.debug(u"Zeroconf service established.") @@ -211,6 +212,7 @@ self.group = None def server_state_changed(self, state): """Derived from the Avahi example code""" + logger.debug(u"Avahi server state change: %i", state) if state == avahi.SERVER_COLLISION: logger.error(u"Zeroconf server name collision") self.remove() @@ -1006,15 +1008,6 @@ gnutls.connection .X509Credentials())) - line = self.request.makefile().readline() - logger.debug(u"Protocol version: %r", line) - try: - if int(line.strip().split()[0]) > 1: - raise RuntimeError - except (ValueError, IndexError, RuntimeError), error: - logger.error(u"Unknown protocol version: %s", error) - return - # Note: gnutls.connection.X509Credentials is really a # generic GnuTLS certificate credentials object so long as # no X.509 keys are added to it. Therefore, we can use it @@ -1032,6 +1025,18 @@ .gnutls_priority_set_direct(session._c_object, priority, None)) + # Start communication using the Mandos protocol + # Get protocol number + line = self.request.makefile().readline() + logger.debug(u"Protocol version: %r", line) + try: + if int(line.strip().split()[0]) > 1: + raise RuntimeError + except (ValueError, IndexError, RuntimeError), error: + logger.error(u"Unknown protocol version: %s", error) + return + + # Start GnuTLS connection try: session.handshake() except gnutls.errors.GNUTLSError, error: @@ -1057,15 +1062,31 @@ ipc.write(u"NOTFOUND %s %s\n" % (fpr, unicode(self.client_address))) return + + class ClientProxy(object): + """Client proxy object. Not for calling methods.""" + def __init__(self, client): + self.client = client + def __getattr__(self, name): + if name.startswith("ipc_"): + def tempfunc(): + ipc.write("%s %s\n" % (name[4:].upper(), + self.client.name)) + return tempfunc + if not hasattr(self.client, name): + raise AttributeError + ipc.write(u"GETATTR %s %s\n" + % (name, self.client.fingerprint)) + return pickle.load(ipc_return) + clientproxy = ClientProxy(client) # Have to check if client.enabled, since it is # possible that the client was disabled since the # GnuTLS session was established. - ipc.write(u"GETATTR enabled %s\n" % fpr) - enabled = pickle.load(ipc_return) - if not enabled: - ipc.write(u"DISABLED %s\n" % client.name) + if not clientproxy.enabled: + clientproxy.ipc_disabled() return - ipc.write(u"SENDING %s\n" % client.name) + + clientproxy.ipc_sending() sent_size = 0 while sent_size < len(client.secret): sent = session.send(client.secret[sent_size:]) === modified file 'mandos-monitor' --- mandos-monitor 2009-12-25 23:13:47 +0000 +++ mandos-monitor 2010-09-05 20:19:02 +0000 @@ -37,6 +37,22 @@ urwid.curses_display.curses.A_UNDERLINE |= ( urwid.curses_display.curses.A_BLINK) +def isoformat_to_datetime(iso): + "Parse an ISO 8601 date string to a datetime.datetime()" + if not iso: + return None + d, t = iso.split(u"T", 1) + year, month, day = d.split(u"-", 2) + hour, minute, second = t.split(u":", 2) + second, fraction = divmod(float(second), 1) + return datetime.datetime(int(year), + int(month), + int(day), + int(hour), + int(minute), + int(second), # Whole seconds + int(fraction*1000000)) # Microseconds + class MandosClientPropertyCache(object): """This wraps a Mandos Client D-Bus proxy object, caches the properties and calls a hook function when any of them are @@ -50,7 +66,7 @@ self.property_changed, client_interface, byte_arrays=True) - + self.properties.update( self.proxy.GetAll(client_interface, dbus_interface = dbus.PROPERTIES_IFACE)) @@ -80,6 +96,9 @@ # Logger self.logger = logger + self._update_timer_callback_tag = None + self.last_checker_failed = False + # The widget shown normally self._text_widget = urwid.Text(u"") # The widget shown when we have focus @@ -105,30 +124,55 @@ self.rejected, client_interface, byte_arrays=True) + last_checked_ok = isoformat_to_datetime(self.properties + ["last_checked_ok"]) + if last_checked_ok is None: + self.last_checker_failed = True + else: + self.last_checker_failed = ((datetime.datetime.utcnow() + - last_checked_ok) + > datetime.timedelta + (milliseconds= + self.properties["interval"])) + if self.last_checker_failed: + self._update_timer_callback_tag = (gobject.timeout_add + (1000, + self.update_timer)) def checker_completed(self, exitstatus, condition, command): if exitstatus == 0: + if self.last_checker_failed: + self.last_checker_failed = False + gobject.source_remove(self._update_timer_callback_tag) + self._update_timer_callback_tag = None self.logger(u'Checker for client %s (command "%s")' u' was successful' % (self.properties[u"name"], command)) + self.update() return + # Checker failed + if not self.last_checker_failed: + self.last_checker_failed = True + self._update_timer_callback_tag = (gobject.timeout_add + (1000, + self.update_timer)) if os.WIFEXITED(condition): self.logger(u'Checker for client %s (command "%s")' u' failed with exit code %s' % (self.properties[u"name"], command, os.WEXITSTATUS(condition))) - return - if os.WIFSIGNALED(condition): + elif os.WIFSIGNALED(condition): self.logger(u'Checker for client %s (command "%s")' u' was killed by signal %s' % (self.properties[u"name"], command, os.WTERMSIG(condition))) - return - if os.WCOREDUMP(condition): + elif os.WCOREDUMP(condition): self.logger(u'Checker for client %s (command "%s")' u' dumped core' % (self.properties[u"name"], command)) - self.logger(u'Checker for client %s completed mysteriously') + else: + self.logger(u'Checker for client %s completed mysteriously') + self.update() def checker_started(self, command): self.logger(u'Client %s started checker "%s"' @@ -170,12 +214,29 @@ } # Rebuild focus and non-focus widgets using current properties - self._text = (u'%(name)s: %(enabled)s' + self._text = (u'%(name)s: %(enabled)s%(timer)s' % { u"name": self.properties[u"name"], u"enabled": (u"enabled" if self.properties[u"enabled"] - else u"DISABLED")}) + else u"DISABLED"), + u"timer": (unicode(datetime.timedelta + (milliseconds = + self.properties + [u"timeout"]) + - (datetime.datetime + .utcnow() + - isoformat_to_datetime + (max((self.properties + ["last_checked_ok"] + or + self.properties + ["created"]), + self.properties[u"last_enabled"])))) + if (self.last_checker_failed + and self.properties + [u"enabled"]) + else u"")}) if not urwid.supports_unicode(): self._text = self._text.encode("ascii", "replace") textlist = [(u"normal", self._text)] @@ -192,7 +253,15 @@ if self.update_hook is not None: self.update_hook() + def update_timer(self): + "called by gobject" + self.update() + return True # Keep calling this + def delete(self): + if self._update_timer_callback_tag is not None: + gobject.source_remove(self._update_timer_callback_tag) + self._update_timer_callback_tag = None if self.delete_hook is not None: self.delete_hook(self)