/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2012-05-06 16:13:00 UTC
  • Revision ID: teddy@recompile.se-20120506161300-43rls2rr4qub3zhw
* mandos: Use a class decorator instead of a metaclass to provide
          alternate D-Bus interface names on D-Bus object attributes.
  (alternate_dbus_interfaces): New class decorator.
  (AlternateDBusNamesMetaclass, ClientDBusTransitional,
   MandosDBusServiceTransitional): Removed; all users changed.
  (ClientDbus, MandosDBusService): Use new "alternate_dbus_interfaces"
                                   class decorator.

Show diffs side-by-side

added added

removed removed

Lines of Context:
11
11
# "AvahiService" class, and some lines in "main".
12
12
13
13
# Everything else is
14
 
# Copyright © 2008-2011 Teddy Hogeborn
15
 
# Copyright © 2008-2011 Björn Påhlsson
 
14
# Copyright © 2008-2012 Teddy Hogeborn
 
15
# Copyright © 2008-2012 Björn Påhlsson
16
16
17
17
# This program is free software: you can redistribute it and/or modify
18
18
# it under the terms of the GNU General Public License as published by
65
65
import types
66
66
import binascii
67
67
import tempfile
 
68
import itertools
68
69
 
69
70
import dbus
70
71
import dbus.service
85
86
    except ImportError:
86
87
        SO_BINDTODEVICE = None
87
88
 
88
 
version = "1.4.1"
 
89
version = "1.5.3"
89
90
stored_state_file = "clients.pickle"
90
91
 
91
92
logger = logging.getLogger()
110
111
        return interface_index
111
112
 
112
113
 
113
 
def initlogger(level=logging.WARNING):
 
114
def initlogger(debug, level=logging.WARNING):
114
115
    """init logger and add loglevel"""
115
116
    
116
117
    syslogger.setFormatter(logging.Formatter
118
119
                            ' %(message)s'))
119
120
    logger.addHandler(syslogger)
120
121
    
121
 
    console = logging.StreamHandler()
122
 
    console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
123
 
                                           ' [%(process)d]:'
124
 
                                           ' %(levelname)s:'
125
 
                                           ' %(message)s'))
126
 
    logger.addHandler(console)
 
122
    if debug:
 
123
        console = logging.StreamHandler()
 
124
        console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
 
125
                                               ' [%(process)d]:'
 
126
                                               ' %(levelname)s:'
 
127
                                               ' %(message)s'))
 
128
        logger.addHandler(console)
127
129
    logger.setLevel(level)
128
130
 
129
131
 
141
143
        self.gnupg.options.meta_interactive = False
142
144
        self.gnupg.options.homedir = self.tempdir
143
145
        self.gnupg.options.extra_args.extend(['--force-mdc',
144
 
                                              '--quiet'])
 
146
                                              '--quiet',
 
147
                                              '--no-use-agent'])
145
148
    
146
149
    def __enter__(self):
147
150
        return self
173
176
    
174
177
    def encrypt(self, data, password):
175
178
        self.gnupg.passphrase = self.password_encode(password)
176
 
        with open(os.devnull) as devnull:
 
179
        with open(os.devnull, "w") as devnull:
177
180
            try:
178
181
                proc = self.gnupg.run(['--symmetric'],
179
182
                                      create_fhs=['stdin', 'stdout'],
190
193
    
191
194
    def decrypt(self, data, password):
192
195
        self.gnupg.passphrase = self.password_encode(password)
193
 
        with open(os.devnull) as devnull:
 
196
        with open(os.devnull, "w") as devnull:
194
197
            try:
195
198
                proc = self.gnupg.run(['--decrypt'],
196
199
                                      create_fhs=['stdin', 'stdout'],
197
200
                                      attach_fhs={'stderr': devnull})
198
 
                with contextlib.closing(proc.handles['stdin'] ) as f:
 
201
                with contextlib.closing(proc.handles['stdin']) as f:
199
202
                    f.write(data)
200
203
                with contextlib.closing(proc.handles['stdout']) as f:
201
204
                    decrypted_plaintext = f.read()
206
209
        return decrypted_plaintext
207
210
 
208
211
 
209
 
 
210
212
class AvahiError(Exception):
211
213
    def __init__(self, value, *args, **kwargs):
212
214
        self.value = value
241
243
    server: D-Bus Server
242
244
    bus: dbus.SystemBus()
243
245
    """
 
246
    
244
247
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
245
248
                 servicetype = None, port = None, TXT = None,
246
249
                 domain = "", host = "", max_renames = 32768,
259
262
        self.server = None
260
263
        self.bus = bus
261
264
        self.entry_group_state_changed_match = None
 
265
    
262
266
    def rename(self):
263
267
        """Derived from the Avahi example code"""
264
268
        if self.rename_count >= self.max_renames:
274
278
        try:
275
279
            self.add()
276
280
        except dbus.exceptions.DBusException as error:
277
 
            logger.critical("DBusException: %s", error)
 
281
            logger.critical("D-Bus Exception", exc_info=error)
278
282
            self.cleanup()
279
283
            os._exit(1)
280
284
        self.rename_count += 1
 
285
    
281
286
    def remove(self):
282
287
        """Derived from the Avahi example code"""
283
288
        if self.entry_group_state_changed_match is not None:
285
290
            self.entry_group_state_changed_match = None
286
291
        if self.group is not None:
287
292
            self.group.Reset()
 
293
    
288
294
    def add(self):
289
295
        """Derived from the Avahi example code"""
290
296
        self.remove()
307
313
            dbus.UInt16(self.port),
308
314
            avahi.string_array_to_txt_array(self.TXT))
309
315
        self.group.Commit()
 
316
    
310
317
    def entry_group_state_changed(self, state, error):
311
318
        """Derived from the Avahi example code"""
312
319
        logger.debug("Avahi entry group state change: %i", state)
319
326
        elif state == avahi.ENTRY_GROUP_FAILURE:
320
327
            logger.critical("Avahi: Error in group state changed %s",
321
328
                            unicode(error))
322
 
            raise AvahiGroupError("State changed: %s"
323
 
                                  % unicode(error))
 
329
            raise AvahiGroupError("State changed: {0!s}"
 
330
                                  .format(error))
 
331
    
324
332
    def cleanup(self):
325
333
        """Derived from the Avahi example code"""
326
334
        if self.group is not None:
331
339
                pass
332
340
            self.group = None
333
341
        self.remove()
 
342
    
334
343
    def server_state_changed(self, state, error=None):
335
344
        """Derived from the Avahi example code"""
336
345
        logger.debug("Avahi server state change: %i", state)
355
364
                logger.debug("Unknown state: %r", state)
356
365
            else:
357
366
                logger.debug("Unknown state: %r: %r", state, error)
 
367
    
358
368
    def activate(self):
359
369
        """Derived from the Avahi example code"""
360
370
        if self.server is None:
372
382
        """Add the new name to the syslog messages"""
373
383
        ret = AvahiService.rename(self)
374
384
        syslogger.setFormatter(logging.Formatter
375
 
                               ('Mandos (%s) [%%(process)d]:'
376
 
                                ' %%(levelname)s: %%(message)s'
377
 
                                % self.name))
 
385
                               ('Mandos ({0}) [%(process)d]:'
 
386
                                ' %(levelname)s: %(message)s'
 
387
                                .format(self.name)))
378
388
        return ret
379
389
 
380
390
def timedelta_to_milliseconds(td):
382
392
    return ((td.days * 24 * 60 * 60 * 1000)
383
393
            + (td.seconds * 1000)
384
394
            + (td.microseconds // 1000))
385
 
        
 
395
 
386
396
class Client(object):
387
397
    """A representation of a client host served by this server.
388
398
    
413
423
    last_checked_ok: datetime.datetime(); (UTC) or None
414
424
    last_checker_status: integer between 0 and 255 reflecting exit
415
425
                         status of last checker. -1 reflects crashed
416
 
                         checker, or None.
 
426
                         checker, -2 means no checker completed yet.
417
427
    last_enabled: datetime.datetime(); (UTC) or None
418
428
    name:       string; from the config file, used in log messages and
419
429
                        D-Bus identifiers
420
430
    secret:     bytestring; sent verbatim (over TLS) to client
421
431
    timeout:    datetime.timedelta(); How long from last_checked_ok
422
432
                                      until this client is disabled
423
 
    extended_timeout:   extra long timeout when password has been sent
 
433
    extended_timeout:   extra long timeout when secret has been sent
424
434
    runtime_expansions: Allowed attributes for runtime expansion.
425
435
    expires:    datetime.datetime(); time (UTC) when a client will be
426
436
                disabled, or None
455
465
    
456
466
    def approval_delay_milliseconds(self):
457
467
        return timedelta_to_milliseconds(self.approval_delay)
458
 
 
 
468
    
459
469
    @staticmethod
460
470
    def config_parser(config):
461
 
        """ Construct a new dict of client settings of this form:
 
471
        """Construct a new dict of client settings of this form:
462
472
        { client_name: {setting_name: value, ...}, ...}
463
 
        with exceptions for any special settings as defined above"""
 
473
        with exceptions for any special settings as defined above.
 
474
        NOTE: Must be a pure function. Must return the same result
 
475
        value given the same arguments.
 
476
        """
464
477
        settings = {}
465
478
        for client_name in config.sections():
466
479
            section = dict(config.items(client_name))
470
483
            # Reformat values from string types to Python types
471
484
            client["approved_by_default"] = config.getboolean(
472
485
                client_name, "approved_by_default")
473
 
            client["enabled"] = config.getboolean(client_name, "enabled")
 
486
            client["enabled"] = config.getboolean(client_name,
 
487
                                                  "enabled")
474
488
            
475
489
            client["fingerprint"] = (section["fingerprint"].upper()
476
490
                                     .replace(" ", ""))
482
496
                          "rb") as secfile:
483
497
                    client["secret"] = secfile.read()
484
498
            else:
485
 
                raise TypeError("No secret or secfile for section %s"
486
 
                                % section)
 
499
                raise TypeError("No secret or secfile for section {0}"
 
500
                                .format(section))
487
501
            client["timeout"] = string_to_delta(section["timeout"])
488
502
            client["extended_timeout"] = string_to_delta(
489
503
                section["extended_timeout"])
495
509
            client["checker_command"] = section["checker"]
496
510
            client["last_approval_request"] = None
497
511
            client["last_checked_ok"] = None
498
 
            client["last_checker_status"] = None
499
 
            if client["enabled"]:
500
 
                client["last_enabled"] = datetime.datetime.utcnow()
501
 
                client["expires"] = (datetime.datetime.utcnow()
502
 
                                     + client["timeout"])
503
 
            else:
504
 
                client["last_enabled"] = None
505
 
                client["expires"] = None
506
 
 
 
512
            client["last_checker_status"] = -2
 
513
        
507
514
        return settings
508
 
        
509
 
        
 
515
    
510
516
    def __init__(self, settings, name = None):
511
 
        """Note: the 'checker' key in 'config' sets the
512
 
        'checker_command' attribute and *not* the 'checker'
513
 
        attribute."""
514
517
        self.name = name
515
518
        # adding all client settings
516
519
        for setting, value in settings.iteritems():
517
520
            setattr(self, setting, value)
518
521
        
 
522
        if self.enabled:
 
523
            if not hasattr(self, "last_enabled"):
 
524
                self.last_enabled = datetime.datetime.utcnow()
 
525
            if not hasattr(self, "expires"):
 
526
                self.expires = (datetime.datetime.utcnow()
 
527
                                + self.timeout)
 
528
        else:
 
529
            self.last_enabled = None
 
530
            self.expires = None
 
531
        
519
532
        logger.debug("Creating client %r", self.name)
520
533
        # Uppercase and remove spaces from fingerprint for later
521
534
        # comparison purposes with return value from the fingerprint()
522
535
        # function
523
536
        logger.debug("  Fingerprint: %s", self.fingerprint)
524
 
        self.created = settings.get("created", datetime.datetime.utcnow())
525
 
 
 
537
        self.created = settings.get("created",
 
538
                                    datetime.datetime.utcnow())
 
539
        
526
540
        # attributes specific for this server instance
527
541
        self.checker = None
528
542
        self.checker_initiator_tag = None
616
630
            logger.warning("Checker for %(name)s crashed?",
617
631
                           vars(self))
618
632
    
619
 
    def checked_ok(self, timeout=None):
620
 
        """Bump up the timeout for this client.
621
 
        
622
 
        This should only be called when the client has been seen,
623
 
        alive and well.
624
 
        """
 
633
    def checked_ok(self):
 
634
        """Assert that the client has been seen, alive and well."""
 
635
        self.last_checked_ok = datetime.datetime.utcnow()
 
636
        self.last_checker_status = 0
 
637
        self.bump_timeout()
 
638
    
 
639
    def bump_timeout(self, timeout=None):
 
640
        """Bump up the timeout for this client."""
625
641
        if timeout is None:
626
642
            timeout = self.timeout
627
 
        self.last_checked_ok = datetime.datetime.utcnow()
628
643
        if self.disable_initiator_tag is not None:
629
644
            gobject.source_remove(self.disable_initiator_tag)
630
645
        if getattr(self, "enabled", False):
681
696
                try:
682
697
                    command = self.checker_command % escaped_attrs
683
698
                except TypeError as error:
684
 
                    logger.error('Could not format string "%s":'
685
 
                                 ' %s', self.checker_command, error)
 
699
                    logger.error('Could not format string "%s"',
 
700
                                 self.checker_command, exc_info=error)
686
701
                    return True # Try again later
687
702
            self.current_checker_command = command
688
703
            try:
706
721
                    gobject.source_remove(self.checker_callback_tag)
707
722
                    self.checker_callback(pid, status, command)
708
723
            except OSError as error:
709
 
                logger.error("Failed to start subprocess: %s",
710
 
                             error)
 
724
                logger.error("Failed to start subprocess",
 
725
                             exc_info=error)
711
726
        # Re-run this periodically if run by gobject.timeout_add
712
727
        return True
713
728
    
720
735
            return
721
736
        logger.debug("Stopping checker for %(name)s", vars(self))
722
737
        try:
723
 
            os.kill(self.checker.pid, signal.SIGTERM)
 
738
            self.checker.terminate()
724
739
            #time.sleep(0.5)
725
740
            #if self.checker.poll() is None:
726
 
            #    os.kill(self.checker.pid, signal.SIGKILL)
 
741
            #    self.checker.kill()
727
742
        except OSError as error:
728
743
            if error.errno != errno.ESRCH: # No such process
729
744
                raise
746
761
    # "Set" method, so we fail early here:
747
762
    if byte_arrays and signature != "ay":
748
763
        raise ValueError("Byte arrays not supported for non-'ay'"
749
 
                         " signature %r" % signature)
 
764
                         " signature {0!r}".format(signature))
750
765
    def decorator(func):
751
766
        func._dbus_is_property = True
752
767
        func._dbus_interface = dbus_interface
760
775
    return decorator
761
776
 
762
777
 
 
778
def dbus_interface_annotations(dbus_interface):
 
779
    """Decorator for marking functions returning interface annotations
 
780
    
 
781
    Usage:
 
782
    
 
783
    @dbus_interface_annotations("org.example.Interface")
 
784
    def _foo(self):  # Function name does not matter
 
785
        return {"org.freedesktop.DBus.Deprecated": "true",
 
786
                "org.freedesktop.DBus.Property.EmitsChangedSignal":
 
787
                    "false"}
 
788
    """
 
789
    def decorator(func):
 
790
        func._dbus_is_interface = True
 
791
        func._dbus_interface = dbus_interface
 
792
        func._dbus_name = dbus_interface
 
793
        return func
 
794
    return decorator
 
795
 
 
796
 
 
797
def dbus_annotations(annotations):
 
798
    """Decorator to annotate D-Bus methods, signals or properties
 
799
    Usage:
 
800
    
 
801
    @dbus_service_property("org.example.Interface", signature="b",
 
802
                           access="r")
 
803
    @dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
 
804
                        "org.freedesktop.DBus.Property."
 
805
                        "EmitsChangedSignal": "false"})
 
806
    def Property_dbus_property(self):
 
807
        return dbus.Boolean(False)
 
808
    """
 
809
    def decorator(func):
 
810
        func._dbus_annotations = annotations
 
811
        return func
 
812
    return decorator
 
813
 
 
814
 
763
815
class DBusPropertyException(dbus.exceptions.DBusException):
764
816
    """A base class for D-Bus property-related exceptions
765
817
    """
788
840
    """
789
841
    
790
842
    @staticmethod
791
 
    def _is_dbus_property(obj):
792
 
        return getattr(obj, "_dbus_is_property", False)
 
843
    def _is_dbus_thing(thing):
 
844
        """Returns a function testing if an attribute is a D-Bus thing
 
845
        
 
846
        If called like _is_dbus_thing("method") it returns a function
 
847
        suitable for use as predicate to inspect.getmembers().
 
848
        """
 
849
        return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
 
850
                                   False)
793
851
    
794
 
    def _get_all_dbus_properties(self):
 
852
    def _get_all_dbus_things(self, thing):
795
853
        """Returns a generator of (name, attribute) pairs
796
854
        """
797
 
        return ((prop.__get__(self)._dbus_name, prop.__get__(self))
 
855
        return ((getattr(athing.__get__(self), "_dbus_name",
 
856
                         name),
 
857
                 athing.__get__(self))
798
858
                for cls in self.__class__.__mro__
799
 
                for name, prop in
800
 
                inspect.getmembers(cls, self._is_dbus_property))
 
859
                for name, athing in
 
860
                inspect.getmembers(cls,
 
861
                                   self._is_dbus_thing(thing)))
801
862
    
802
863
    def _get_dbus_property(self, interface_name, property_name):
803
864
        """Returns a bound method if one exists which is a D-Bus
805
866
        """
806
867
        for cls in  self.__class__.__mro__:
807
868
            for name, value in (inspect.getmembers
808
 
                                (cls, self._is_dbus_property)):
 
869
                                (cls,
 
870
                                 self._is_dbus_thing("property"))):
809
871
                if (value._dbus_name == property_name
810
872
                    and value._dbus_interface == interface_name):
811
873
                    return value.__get__(self)
840
902
            # signatures other than "ay".
841
903
            if prop._dbus_signature != "ay":
842
904
                raise ValueError
843
 
            value = dbus.ByteArray(''.join(unichr(byte)
844
 
                                           for byte in value))
 
905
            value = dbus.ByteArray(b''.join(chr(byte)
 
906
                                            for byte in value))
845
907
        prop(value)
846
908
    
847
909
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
853
915
        Note: Will not include properties with access="write".
854
916
        """
855
917
        properties = {}
856
 
        for name, prop in self._get_all_dbus_properties():
 
918
        for name, prop in self._get_all_dbus_things("property"):
857
919
            if (interface_name
858
920
                and interface_name != prop._dbus_interface):
859
921
                # Interface non-empty but did not match
874
936
                         path_keyword='object_path',
875
937
                         connection_keyword='connection')
876
938
    def Introspect(self, object_path, connection):
877
 
        """Standard D-Bus method, overloaded to insert property tags.
 
939
        """Overloading of standard D-Bus method.
 
940
        
 
941
        Inserts property tags and interface annotation tags.
878
942
        """
879
943
        xmlstring = dbus.service.Object.Introspect(self, object_path,
880
944
                                                   connection)
887
951
                e.setAttribute("access", prop._dbus_access)
888
952
                return e
889
953
            for if_tag in document.getElementsByTagName("interface"):
 
954
                # Add property tags
890
955
                for tag in (make_tag(document, name, prop)
891
956
                            for name, prop
892
 
                            in self._get_all_dbus_properties()
 
957
                            in self._get_all_dbus_things("property")
893
958
                            if prop._dbus_interface
894
959
                            == if_tag.getAttribute("name")):
895
960
                    if_tag.appendChild(tag)
 
961
                # Add annotation tags
 
962
                for typ in ("method", "signal", "property"):
 
963
                    for tag in if_tag.getElementsByTagName(typ):
 
964
                        annots = dict()
 
965
                        for name, prop in (self.
 
966
                                           _get_all_dbus_things(typ)):
 
967
                            if (name == tag.getAttribute("name")
 
968
                                and prop._dbus_interface
 
969
                                == if_tag.getAttribute("name")):
 
970
                                annots.update(getattr
 
971
                                              (prop,
 
972
                                               "_dbus_annotations",
 
973
                                               {}))
 
974
                        for name, value in annots.iteritems():
 
975
                            ann_tag = document.createElement(
 
976
                                "annotation")
 
977
                            ann_tag.setAttribute("name", name)
 
978
                            ann_tag.setAttribute("value", value)
 
979
                            tag.appendChild(ann_tag)
 
980
                # Add interface annotation tags
 
981
                for annotation, value in dict(
 
982
                    itertools.chain(
 
983
                        *(annotations().iteritems()
 
984
                          for name, annotations in
 
985
                          self._get_all_dbus_things("interface")
 
986
                          if name == if_tag.getAttribute("name")
 
987
                          ))).iteritems():
 
988
                    ann_tag = document.createElement("annotation")
 
989
                    ann_tag.setAttribute("name", annotation)
 
990
                    ann_tag.setAttribute("value", value)
 
991
                    if_tag.appendChild(ann_tag)
896
992
                # Add the names to the return values for the
897
993
                # "org.freedesktop.DBus.Properties" methods
898
994
                if (if_tag.getAttribute("name")
913
1009
        except (AttributeError, xml.dom.DOMException,
914
1010
                xml.parsers.expat.ExpatError) as error:
915
1011
            logger.error("Failed to override Introspection method",
916
 
                         error)
 
1012
                         exc_info=error)
917
1013
        return xmlstring
918
1014
 
919
1015
 
925
1021
                       variant_level=variant_level)
926
1022
 
927
1023
 
928
 
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
929
 
                                  .__metaclass__):
930
 
    """Applied to an empty subclass of a D-Bus object, this metaclass
931
 
    will add additional D-Bus attributes matching a certain pattern.
 
1024
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
 
1025
    """A class decorator; applied to a subclass of
 
1026
    dbus.service.Object, it will add alternate D-Bus attributes with
 
1027
    interface names according to the "alt_interface_names" mapping.
 
1028
    Usage:
 
1029
    
 
1030
    @alternate_dbus_names({"org.example.Interface":
 
1031
                               "net.example.AlternateInterface"})
 
1032
    class SampleDBusObject(dbus.service.Object):
 
1033
        @dbus.service.method("org.example.Interface")
 
1034
        def SampleDBusMethod():
 
1035
            pass
 
1036
    
 
1037
    The above "SampleDBusMethod" on "SampleDBusObject" will be
 
1038
    reachable via two interfaces: "org.example.Interface" and
 
1039
    "net.example.AlternateInterface", the latter of which will have
 
1040
    its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
 
1041
    "true", unless "deprecate" is passed with a False value.
 
1042
    
 
1043
    This works for methods and signals, and also for D-Bus properties
 
1044
    (from DBusObjectWithProperties) and interfaces (from the
 
1045
    dbus_interface_annotations decorator).
932
1046
    """
933
 
    def __new__(mcs, name, bases, attr):
934
 
        # Go through all the base classes which could have D-Bus
935
 
        # methods, signals, or properties in them
936
 
        for base in (b for b in bases
937
 
                     if issubclass(b, dbus.service.Object)):
938
 
            # Go though all attributes of the base class
939
 
            for attrname, attribute in inspect.getmembers(base):
 
1047
    def wrapper(cls):
 
1048
        for orig_interface_name, alt_interface_name in (
 
1049
            alt_interface_names.iteritems()):
 
1050
            attr = {}
 
1051
            interface_names = set()
 
1052
            # Go though all attributes of the class
 
1053
            for attrname, attribute in inspect.getmembers(cls):
940
1054
                # Ignore non-D-Bus attributes, and D-Bus attributes
941
1055
                # with the wrong interface name
942
1056
                if (not hasattr(attribute, "_dbus_interface")
943
1057
                    or not attribute._dbus_interface
944
 
                    .startswith("se.recompile.Mandos")):
 
1058
                    .startswith(orig_interface_name)):
945
1059
                    continue
946
1060
                # Create an alternate D-Bus interface name based on
947
1061
                # the current name
948
1062
                alt_interface = (attribute._dbus_interface
949
 
                                 .replace("se.recompile.Mandos",
950
 
                                          "se.bsnet.fukt.Mandos"))
 
1063
                                 .replace(orig_interface_name,
 
1064
                                          alt_interface_name))
 
1065
                interface_names.add(alt_interface)
951
1066
                # Is this a D-Bus signal?
952
1067
                if getattr(attribute, "_dbus_is_signal", False):
953
1068
                    # Extract the original non-method function by
968
1083
                                nonmethod_func.func_name,
969
1084
                                nonmethod_func.func_defaults,
970
1085
                                nonmethod_func.func_closure)))
 
1086
                    # Copy annotations, if any
 
1087
                    try:
 
1088
                        new_function._dbus_annotations = (
 
1089
                            dict(attribute._dbus_annotations))
 
1090
                    except AttributeError:
 
1091
                        pass
971
1092
                    # Define a creator of a function to call both the
972
 
                    # old and new functions, so both the old and new
973
 
                    # signals gets sent when the function is called
 
1093
                    # original and alternate functions, so both the
 
1094
                    # original and alternate signals gets sent when
 
1095
                    # the function is called
974
1096
                    def fixscope(func1, func2):
975
1097
                        """This function is a scope container to pass
976
1098
                        func1 and func2 to the "call_both" function
983
1105
                        return call_both
984
1106
                    # Create the "call_both" function and add it to
985
1107
                    # the class
986
 
                    attr[attrname] = fixscope(attribute,
987
 
                                              new_function)
 
1108
                    attr[attrname] = fixscope(attribute, new_function)
988
1109
                # Is this a D-Bus method?
989
1110
                elif getattr(attribute, "_dbus_is_method", False):
990
1111
                    # Create a new, but exactly alike, function
1001
1122
                                        attribute.func_name,
1002
1123
                                        attribute.func_defaults,
1003
1124
                                        attribute.func_closure)))
 
1125
                    # Copy annotations, if any
 
1126
                    try:
 
1127
                        attr[attrname]._dbus_annotations = (
 
1128
                            dict(attribute._dbus_annotations))
 
1129
                    except AttributeError:
 
1130
                        pass
1004
1131
                # Is this a D-Bus property?
1005
1132
                elif getattr(attribute, "_dbus_is_property", False):
1006
1133
                    # Create a new, but exactly alike, function
1020
1147
                                        attribute.func_name,
1021
1148
                                        attribute.func_defaults,
1022
1149
                                        attribute.func_closure)))
1023
 
        return type.__new__(mcs, name, bases, attr)
1024
 
 
1025
 
 
 
1150
                    # Copy annotations, if any
 
1151
                    try:
 
1152
                        attr[attrname]._dbus_annotations = (
 
1153
                            dict(attribute._dbus_annotations))
 
1154
                    except AttributeError:
 
1155
                        pass
 
1156
                # Is this a D-Bus interface?
 
1157
                elif getattr(attribute, "_dbus_is_interface", False):
 
1158
                    # Create a new, but exactly alike, function
 
1159
                    # object.  Decorate it to be a new D-Bus interface
 
1160
                    # with the alternate D-Bus interface name.  Add it
 
1161
                    # to the class.
 
1162
                    attr[attrname] = (dbus_interface_annotations
 
1163
                                      (alt_interface)
 
1164
                                      (types.FunctionType
 
1165
                                       (attribute.func_code,
 
1166
                                        attribute.func_globals,
 
1167
                                        attribute.func_name,
 
1168
                                        attribute.func_defaults,
 
1169
                                        attribute.func_closure)))
 
1170
            if deprecate:
 
1171
                # Deprecate all alternate interfaces
 
1172
                iname="_AlternateDBusNames_interface_annotation{0}"
 
1173
                for interface_name in interface_names:
 
1174
                    @dbus_interface_annotations(interface_name)
 
1175
                    def func(self):
 
1176
                        return { "org.freedesktop.DBus.Deprecated":
 
1177
                                     "true" }
 
1178
                    # Find an unused name
 
1179
                    for aname in (iname.format(i)
 
1180
                                  for i in itertools.count()):
 
1181
                        if aname not in attr:
 
1182
                            attr[aname] = func
 
1183
                            break
 
1184
            if interface_names:
 
1185
                # Replace the class with a new subclass of it with
 
1186
                # methods, signals, etc. as created above.
 
1187
                cls = type(b"{0}Alternate".format(cls.__name__),
 
1188
                           (cls,), attr)
 
1189
        return cls
 
1190
    return wrapper
 
1191
 
 
1192
 
 
1193
@alternate_dbus_interfaces({"se.recompile.Mandos":
 
1194
                                "se.bsnet.fukt.Mandos"})
1026
1195
class ClientDBus(Client, DBusObjectWithProperties):
1027
1196
    """A Client class using D-Bus
1028
1197
    
1039
1208
    def __init__(self, bus = None, *args, **kwargs):
1040
1209
        self.bus = bus
1041
1210
        Client.__init__(self, *args, **kwargs)
1042
 
        self._approvals_pending = 0
1043
 
        
1044
 
        self._approvals_pending = 0
1045
1211
        # Only now, when this client is initialized, can it show up on
1046
1212
        # the D-Bus
1047
1213
        client_object_name = unicode(self.name).translate(
1051
1217
                                 ("/clients/" + client_object_name))
1052
1218
        DBusObjectWithProperties.__init__(self, self.bus,
1053
1219
                                          self.dbus_object_path)
1054
 
        
 
1220
    
1055
1221
    def notifychangeproperty(transform_func,
1056
1222
                             dbus_name, type_func=lambda x: x,
1057
1223
                             variant_level=1):
1080
1246
        
1081
1247
        return property(lambda self: getattr(self, attrname), setter)
1082
1248
    
1083
 
    
1084
1249
    expires = notifychangeproperty(datetime_to_dbus, "Expires")
1085
1250
    approvals_pending = notifychangeproperty(dbus.Boolean,
1086
1251
                                             "ApprovalPending",
1093
1258
                                       checker is not None)
1094
1259
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
1095
1260
                                           "LastCheckedOK")
 
1261
    last_checker_status = notifychangeproperty(dbus.Int16,
 
1262
                                               "LastCheckerStatus")
1096
1263
    last_approval_request = notifychangeproperty(
1097
1264
        datetime_to_dbus, "LastApprovalRequest")
1098
1265
    approved_by_default = notifychangeproperty(dbus.Boolean,
1172
1339
                            (self.approval_duration),
1173
1340
                            self._reset_approved)
1174
1341
    
1175
 
    
1176
1342
    ## D-Bus methods, signals & properties
1177
1343
    _interface = "se.recompile.Mandos.Client"
1178
1344
    
 
1345
    ## Interfaces
 
1346
    
 
1347
    @dbus_interface_annotations(_interface)
 
1348
    def _foo(self):
 
1349
        return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
 
1350
                     "false"}
 
1351
    
1179
1352
    ## Signals
1180
1353
    
1181
1354
    # CheckerCompleted - signal
1217
1390
        "D-Bus signal"
1218
1391
        return self.need_approval()
1219
1392
    
1220
 
    # NeRwequest - signal
1221
 
    @dbus.service.signal(_interface, signature="s")
1222
 
    def NewRequest(self, ip):
1223
 
        """D-Bus signal
1224
 
        Is sent after a client request a password.
1225
 
        """
1226
 
        pass
1227
 
    
1228
1393
    ## Methods
1229
1394
    
1230
1395
    # Approve - method
1340
1505
            return
1341
1506
        return datetime_to_dbus(self.last_checked_ok)
1342
1507
    
 
1508
    # LastCheckerStatus - property
 
1509
    @dbus_service_property(_interface, signature="n",
 
1510
                           access="read")
 
1511
    def LastCheckerStatus_dbus_property(self):
 
1512
        return dbus.Int16(self.last_checker_status)
 
1513
    
1343
1514
    # Expires - property
1344
1515
    @dbus_service_property(_interface, signature="s", access="read")
1345
1516
    def Expires_dbus_property(self):
1357
1528
        if value is None:       # get
1358
1529
            return dbus.UInt64(self.timeout_milliseconds())
1359
1530
        self.timeout = datetime.timedelta(0, 0, 0, value)
1360
 
        if getattr(self, "disable_initiator_tag", None) is None:
1361
 
            return
1362
1531
        # Reschedule timeout
1363
 
        gobject.source_remove(self.disable_initiator_tag)
1364
 
        self.disable_initiator_tag = None
1365
 
        self.expires = None
1366
 
        time_to_die = timedelta_to_milliseconds((self
1367
 
                                                 .last_checked_ok
1368
 
                                                 + self.timeout)
1369
 
                                                - datetime.datetime
1370
 
                                                .utcnow())
1371
 
        if time_to_die <= 0:
1372
 
            # The timeout has passed
1373
 
            self.disable()
1374
 
        else:
1375
 
            self.expires = (datetime.datetime.utcnow()
1376
 
                            + datetime.timedelta(milliseconds =
1377
 
                                                 time_to_die))
1378
 
            self.disable_initiator_tag = (gobject.timeout_add
1379
 
                                          (time_to_die, self.disable))
 
1532
        if self.enabled:
 
1533
            now = datetime.datetime.utcnow()
 
1534
            time_to_die = timedelta_to_milliseconds(
 
1535
                (self.last_checked_ok + self.timeout) - now)
 
1536
            if time_to_die <= 0:
 
1537
                # The timeout has passed
 
1538
                self.disable()
 
1539
            else:
 
1540
                self.expires = (now +
 
1541
                                datetime.timedelta(milliseconds =
 
1542
                                                   time_to_die))
 
1543
                if (getattr(self, "disable_initiator_tag", None)
 
1544
                    is None):
 
1545
                    return
 
1546
                gobject.source_remove(self.disable_initiator_tag)
 
1547
                self.disable_initiator_tag = (gobject.timeout_add
 
1548
                                              (time_to_die,
 
1549
                                               self.disable))
1380
1550
    
1381
1551
    # ExtendedTimeout - property
1382
1552
    @dbus_service_property(_interface, signature="t",
1461
1631
        self._pipe.send(('setattr', name, value))
1462
1632
 
1463
1633
 
1464
 
class ClientDBusTransitional(ClientDBus):
1465
 
    __metaclass__ = AlternateDBusNamesMetaclass
1466
 
 
1467
 
 
1468
1634
class ClientHandler(socketserver.BaseRequestHandler, object):
1469
1635
    """A class to handle client connections.
1470
1636
    
1538
1704
                except KeyError:
1539
1705
                    return
1540
1706
                
1541
 
                if self.server.use_dbus:
1542
 
                    # Emit D-Bus signal
1543
 
                    client.NewRequest(str(self.client_address))
1544
 
                
1545
1707
                if client.approval_delay:
1546
1708
                    delay = client.approval_delay
1547
1709
                    client.approvals_pending += 1
1602
1764
                    try:
1603
1765
                        sent = session.send(client.secret[sent_size:])
1604
1766
                    except gnutls.errors.GNUTLSError as error:
1605
 
                        logger.warning("gnutls send failed")
 
1767
                        logger.warning("gnutls send failed",
 
1768
                                       exc_info=error)
1606
1769
                        return
1607
1770
                    logger.debug("Sent: %d, remaining: %d",
1608
1771
                                 sent, len(client.secret)
1611
1774
                
1612
1775
                logger.info("Sending secret to %s", client.name)
1613
1776
                # bump the timeout using extended_timeout
1614
 
                client.checked_ok(client.extended_timeout)
 
1777
                client.bump_timeout(client.extended_timeout)
1615
1778
                if self.server.use_dbus:
1616
1779
                    # Emit D-Bus signal
1617
1780
                    client.GotSecret()
1622
1785
                try:
1623
1786
                    session.bye()
1624
1787
                except gnutls.errors.GNUTLSError as error:
1625
 
                    logger.warning("GnuTLS bye failed")
 
1788
                    logger.warning("GnuTLS bye failed",
 
1789
                                   exc_info=error)
1626
1790
    
1627
1791
    @staticmethod
1628
1792
    def peer_certificate(session):
1940
2104
            elif suffix == "w":
1941
2105
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1942
2106
            else:
1943
 
                raise ValueError("Unknown suffix %r" % suffix)
 
2107
                raise ValueError("Unknown suffix {0!r}"
 
2108
                                 .format(suffix))
1944
2109
        except (ValueError, IndexError) as e:
1945
2110
            raise ValueError(*(e.args))
1946
2111
        timevalue += delta
1960
2125
        sys.exit()
1961
2126
    if not noclose:
1962
2127
        # Close all standard open file descriptors
1963
 
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
2128
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
1964
2129
        if not stat.S_ISCHR(os.fstat(null).st_mode):
1965
2130
            raise OSError(errno.ENODEV,
1966
 
                          "%s not a character device"
1967
 
                          % os.path.devnull)
 
2131
                          "{0} not a character device"
 
2132
                          .format(os.devnull))
1968
2133
        os.dup2(null, sys.stdin.fileno())
1969
2134
        os.dup2(null, sys.stdout.fileno())
1970
2135
        os.dup2(null, sys.stderr.fileno())
1979
2144
    
1980
2145
    parser = argparse.ArgumentParser()
1981
2146
    parser.add_argument("-v", "--version", action="version",
1982
 
                        version = "%%(prog)s %s" % version,
 
2147
                        version = "%(prog)s {0}".format(version),
1983
2148
                        help="show version number and exit")
1984
2149
    parser.add_argument("-i", "--interface", metavar="IF",
1985
2150
                        help="Bind to interface IF")
2078
2243
                                     stored_state_file)
2079
2244
    
2080
2245
    if debug:
2081
 
        initlogger(logging.DEBUG)
 
2246
        initlogger(debug, logging.DEBUG)
2082
2247
    else:
2083
2248
        if not debuglevel:
2084
 
            initlogger()
 
2249
            initlogger(debug)
2085
2250
        else:
2086
2251
            level = getattr(logging, debuglevel.upper())
2087
 
            initlogger(level)
 
2252
            initlogger(debug, level)
2088
2253
    
2089
2254
    if server_settings["servicename"] != "Mandos":
2090
2255
        syslogger.setFormatter(logging.Formatter
2091
 
                               ('Mandos (%s) [%%(process)d]:'
2092
 
                                ' %%(levelname)s: %%(message)s'
2093
 
                                % server_settings["servicename"]))
 
2256
                               ('Mandos ({0}) [%(process)d]:'
 
2257
                                ' %(levelname)s: %(message)s'
 
2258
                                .format(server_settings
 
2259
                                        ["servicename"])))
2094
2260
    
2095
2261
    # Parse config file with clients
2096
 
    client_config = configparser.SafeConfigParser(Client.client_defaults)
 
2262
    client_config = configparser.SafeConfigParser(Client
 
2263
                                                  .client_defaults)
2097
2264
    client_config.read(os.path.join(server_settings["configdir"],
2098
2265
                                    "clients.conf"))
2099
2266
    
2113
2280
        pidfilename = "/var/run/mandos.pid"
2114
2281
        try:
2115
2282
            pidfile = open(pidfilename, "w")
2116
 
        except IOError:
2117
 
            logger.error("Could not open file %r", pidfilename)
 
2283
        except IOError as e:
 
2284
            logger.error("Could not open file %r", pidfilename,
 
2285
                         exc_info=e)
2118
2286
    
2119
 
    try:
2120
 
        uid = pwd.getpwnam("_mandos").pw_uid
2121
 
        gid = pwd.getpwnam("_mandos").pw_gid
2122
 
    except KeyError:
 
2287
    for name in ("_mandos", "mandos", "nobody"):
2123
2288
        try:
2124
 
            uid = pwd.getpwnam("mandos").pw_uid
2125
 
            gid = pwd.getpwnam("mandos").pw_gid
 
2289
            uid = pwd.getpwnam(name).pw_uid
 
2290
            gid = pwd.getpwnam(name).pw_gid
 
2291
            break
2126
2292
        except KeyError:
2127
 
            try:
2128
 
                uid = pwd.getpwnam("nobody").pw_uid
2129
 
                gid = pwd.getpwnam("nobody").pw_gid
2130
 
            except KeyError:
2131
 
                uid = 65534
2132
 
                gid = 65534
 
2293
            continue
 
2294
    else:
 
2295
        uid = 65534
 
2296
        gid = 65534
2133
2297
    try:
2134
2298
        os.setgid(gid)
2135
2299
        os.setuid(uid)
2152
2316
         .gnutls_global_set_log_function(debug_gnutls))
2153
2317
        
2154
2318
        # Redirect stdin so all checkers get /dev/null
2155
 
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
2319
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2156
2320
        os.dup2(null, sys.stdin.fileno())
2157
2321
        if null > 2:
2158
2322
            os.close(null)
2159
 
    else:
2160
 
        # No console logging
2161
 
        logger.removeHandler(console)
2162
2323
    
2163
2324
    # Need to fork before connecting to D-Bus
2164
2325
    if not debug:
2165
2326
        # Close all input and output, do double fork, etc.
2166
2327
        daemon()
2167
2328
    
 
2329
    gobject.threads_init()
 
2330
    
2168
2331
    global main_loop
2169
2332
    # From the Avahi example code
2170
 
    DBusGMainLoop(set_as_default=True )
 
2333
    DBusGMainLoop(set_as_default=True)
2171
2334
    main_loop = gobject.MainLoop()
2172
2335
    bus = dbus.SystemBus()
2173
2336
    # End of Avahi example code
2179
2342
                            ("se.bsnet.fukt.Mandos", bus,
2180
2343
                             do_not_queue=True))
2181
2344
        except dbus.exceptions.NameExistsException as e:
2182
 
            logger.error(unicode(e) + ", disabling D-Bus")
 
2345
            logger.error("Disabling D-Bus:", exc_info=e)
2183
2346
            use_dbus = False
2184
2347
            server_settings["use_dbus"] = False
2185
2348
            tcp_server.use_dbus = False
2197
2360
    
2198
2361
    client_class = Client
2199
2362
    if use_dbus:
2200
 
        client_class = functools.partial(ClientDBusTransitional,
2201
 
                                         bus = bus)
 
2363
        client_class = functools.partial(ClientDBus, bus = bus)
2202
2364
    
2203
2365
    client_settings = Client.config_parser(client_config)
2204
2366
    old_client_settings = {}
2212
2374
                                                     (stored_state))
2213
2375
            os.remove(stored_state_path)
2214
2376
        except IOError as e:
2215
 
            logger.warning("Could not load persistent state: {0}"
2216
 
                           .format(e))
2217
 
            if e.errno != errno.ENOENT:
 
2377
            if e.errno == errno.ENOENT:
 
2378
                logger.warning("Could not load persistent state: {0}"
 
2379
                                .format(os.strerror(e.errno)))
 
2380
            else:
 
2381
                logger.critical("Could not load persistent state:",
 
2382
                                exc_info=e)
2218
2383
                raise
 
2384
        except EOFError as e:
 
2385
            logger.warning("Could not load persistent state: "
 
2386
                           "EOFError:", exc_info=e)
2219
2387
    
2220
2388
    with PGPEngine() as pgp:
2221
2389
        for client_name, client in clients_data.iteritems():
2238
2406
            
2239
2407
            # Clients who has passed its expire date can still be
2240
2408
            # enabled if its last checker was successful.  Clients
2241
 
            # whose checker failed before we stored its state is
2242
 
            # assumed to have failed all checkers during downtime.
 
2409
            # whose checker succeeded before we stored its state is
 
2410
            # assumed to have successfully run all checkers during
 
2411
            # downtime.
2243
2412
            if client["enabled"]:
2244
2413
                if datetime.datetime.utcnow() >= client["expires"]:
2245
2414
                    if not client["last_checked_ok"]:
2246
2415
                        logger.warning(
2247
2416
                            "disabling client {0} - Client never "
2248
 
                            "performed a successfull checker"
2249
 
                            .format(client["name"]))
 
2417
                            "performed a successful checker"
 
2418
                            .format(client_name))
2250
2419
                        client["enabled"] = False
2251
2420
                    elif client["last_checker_status"] != 0:
2252
2421
                        logger.warning(
2253
2422
                            "disabling client {0} - Client "
2254
2423
                            "last checker failed with error code {1}"
2255
 
                            .format(client["name"],
 
2424
                            .format(client_name,
2256
2425
                                    client["last_checker_status"]))
2257
2426
                        client["enabled"] = False
2258
2427
                    else:
2259
2428
                        client["expires"] = (datetime.datetime
2260
2429
                                             .utcnow()
2261
2430
                                             + client["timeout"])
2262
 
                    
 
2431
                        logger.debug("Last checker succeeded,"
 
2432
                                     " keeping {0} enabled"
 
2433
                                     .format(client_name))
2263
2434
            try:
2264
2435
                client["secret"] = (
2265
2436
                    pgp.decrypt(client["encrypted_secret"],
2271
2442
                             .format(client_name))
2272
2443
                client["secret"] = (
2273
2444
                    client_settings[client_name]["secret"])
2274
 
 
2275
2445
    
2276
2446
    # Add/remove clients based on new changes made to config
2277
 
    for client_name in set(old_client_settings) - set(client_settings):
 
2447
    for client_name in (set(old_client_settings)
 
2448
                        - set(client_settings)):
2278
2449
        del clients_data[client_name]
2279
 
    for client_name in set(client_settings) - set(old_client_settings):
 
2450
    for client_name in (set(client_settings)
 
2451
                        - set(old_client_settings)):
2280
2452
        clients_data[client_name] = client_settings[client_name]
2281
 
 
2282
 
    # Create clients all clients
 
2453
    
 
2454
    # Create all client objects
2283
2455
    for client_name, client in clients_data.iteritems():
2284
2456
        tcp_server.clients[client_name] = client_class(
2285
2457
            name = client_name, settings = client)
2286
2458
    
2287
2459
    if not tcp_server.clients:
2288
2460
        logger.warning("No clients defined")
2289
 
        
 
2461
    
2290
2462
    if not debug:
2291
2463
        try:
2292
2464
            with pidfile:
2306
2478
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2307
2479
    
2308
2480
    if use_dbus:
2309
 
        class MandosDBusService(dbus.service.Object):
 
2481
        @alternate_dbus_interfaces({"se.recompile.Mandos":
 
2482
                                        "se.bsnet.fukt.Mandos"})
 
2483
        class MandosDBusService(DBusObjectWithProperties):
2310
2484
            """A D-Bus proxy object"""
2311
2485
            def __init__(self):
2312
2486
                dbus.service.Object.__init__(self, bus, "/")
2313
2487
            _interface = "se.recompile.Mandos"
2314
2488
            
 
2489
            @dbus_interface_annotations(_interface)
 
2490
            def _foo(self):
 
2491
                return { "org.freedesktop.DBus.Property"
 
2492
                         ".EmitsChangedSignal":
 
2493
                             "false"}
 
2494
            
2315
2495
            @dbus.service.signal(_interface, signature="o")
2316
2496
            def ClientAdded(self, objpath):
2317
2497
                "D-Bus signal"
2359
2539
            
2360
2540
            del _interface
2361
2541
        
2362
 
        class MandosDBusServiceTransitional(MandosDBusService):
2363
 
            __metaclass__ = AlternateDBusNamesMetaclass
2364
 
        mandos_dbus_service = MandosDBusServiceTransitional()
 
2542
        mandos_dbus_service = MandosDBusService()
2365
2543
    
2366
2544
    def cleanup():
2367
2545
        "Cleanup function; run on exit"
2400
2578
                del client_settings[client.name]["secret"]
2401
2579
        
2402
2580
        try:
2403
 
            with os.fdopen(os.open(stored_state_path,
2404
 
                                   os.O_CREAT|os.O_WRONLY|os.O_TRUNC,
2405
 
                                   0600), "wb") as stored_state:
 
2581
            tempfd, tempname = tempfile.mkstemp(suffix=".pickle",
 
2582
                                                prefix="clients-",
 
2583
                                                dir=os.path.dirname
 
2584
                                                (stored_state_path))
 
2585
            with os.fdopen(tempfd, "wb") as stored_state:
2406
2586
                pickle.dump((clients, client_settings), stored_state)
 
2587
            os.rename(tempname, stored_state_path)
2407
2588
        except (IOError, OSError) as e:
2408
 
            logger.warning("Could not save persistent state: {0}"
2409
 
                           .format(e))
2410
 
            if e.errno not in (errno.ENOENT, errno.EACCES):
2411
 
                raise
 
2589
            if not debug:
 
2590
                try:
 
2591
                    os.remove(tempname)
 
2592
                except NameError:
 
2593
                    pass
 
2594
            if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
 
2595
                logger.warning("Could not save persistent state: {0}"
 
2596
                               .format(os.strerror(e.errno)))
 
2597
            else:
 
2598
                logger.warning("Could not save persistent state:",
 
2599
                               exc_info=e)
 
2600
                raise e
2412
2601
        
2413
2602
        # Delete all clients, and settings from config
2414
2603
        while tcp_server.clients:
2441
2630
    service.port = tcp_server.socket.getsockname()[1]
2442
2631
    if use_ipv6:
2443
2632
        logger.info("Now listening on address %r, port %d,"
2444
 
                    " flowinfo %d, scope_id %d"
2445
 
                    % tcp_server.socket.getsockname())
 
2633
                    " flowinfo %d, scope_id %d",
 
2634
                    *tcp_server.socket.getsockname())
2446
2635
    else:                       # IPv4
2447
 
        logger.info("Now listening on address %r, port %d"
2448
 
                    % tcp_server.socket.getsockname())
 
2636
        logger.info("Now listening on address %r, port %d",
 
2637
                    *tcp_server.socket.getsockname())
2449
2638
    
2450
2639
    #service.interface = tcp_server.socket.getsockname()[3]
2451
2640
    
2454
2643
        try:
2455
2644
            service.activate()
2456
2645
        except dbus.exceptions.DBusException as error:
2457
 
            logger.critical("DBusException: %s", error)
 
2646
            logger.critical("D-Bus Exception", exc_info=error)
2458
2647
            cleanup()
2459
2648
            sys.exit(1)
2460
2649
        # End of Avahi example code
2467
2656
        logger.debug("Starting main loop")
2468
2657
        main_loop.run()
2469
2658
    except AvahiError as error:
2470
 
        logger.critical("AvahiError: %s", error)
 
2659
        logger.critical("Avahi Error", exc_info=error)
2471
2660
        cleanup()
2472
2661
        sys.exit(1)
2473
2662
    except KeyboardInterrupt: