/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:
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.5.2"
 
89
version = "1.5.3"
89
90
stored_state_file = "clients.pickle"
90
91
 
91
92
logger = logging.getLogger()
175
176
    
176
177
    def encrypt(self, data, password):
177
178
        self.gnupg.passphrase = self.password_encode(password)
178
 
        with open(os.devnull) as devnull:
 
179
        with open(os.devnull, "w") as devnull:
179
180
            try:
180
181
                proc = self.gnupg.run(['--symmetric'],
181
182
                                      create_fhs=['stdin', 'stdout'],
192
193
    
193
194
    def decrypt(self, data, password):
194
195
        self.gnupg.passphrase = self.password_encode(password)
195
 
        with open(os.devnull) as devnull:
 
196
        with open(os.devnull, "w") as devnull:
196
197
            try:
197
198
                proc = self.gnupg.run(['--decrypt'],
198
199
                                      create_fhs=['stdin', 'stdout'],
199
200
                                      attach_fhs={'stderr': devnull})
200
 
                with contextlib.closing(proc.handles['stdin'] ) as f:
 
201
                with contextlib.closing(proc.handles['stdin']) as f:
201
202
                    f.write(data)
202
203
                with contextlib.closing(proc.handles['stdout']) as f:
203
204
                    decrypted_plaintext = f.read()
208
209
        return decrypted_plaintext
209
210
 
210
211
 
211
 
 
212
212
class AvahiError(Exception):
213
213
    def __init__(self, value, *args, **kwargs):
214
214
        self.value = value
243
243
    server: D-Bus Server
244
244
    bus: dbus.SystemBus()
245
245
    """
 
246
    
246
247
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
247
248
                 servicetype = None, port = None, TXT = None,
248
249
                 domain = "", host = "", max_renames = 32768,
261
262
        self.server = None
262
263
        self.bus = bus
263
264
        self.entry_group_state_changed_match = None
 
265
    
264
266
    def rename(self):
265
267
        """Derived from the Avahi example code"""
266
268
        if self.rename_count >= self.max_renames:
276
278
        try:
277
279
            self.add()
278
280
        except dbus.exceptions.DBusException as error:
279
 
            logger.critical("DBusException: %s", error)
 
281
            logger.critical("D-Bus Exception", exc_info=error)
280
282
            self.cleanup()
281
283
            os._exit(1)
282
284
        self.rename_count += 1
 
285
    
283
286
    def remove(self):
284
287
        """Derived from the Avahi example code"""
285
288
        if self.entry_group_state_changed_match is not None:
287
290
            self.entry_group_state_changed_match = None
288
291
        if self.group is not None:
289
292
            self.group.Reset()
 
293
    
290
294
    def add(self):
291
295
        """Derived from the Avahi example code"""
292
296
        self.remove()
309
313
            dbus.UInt16(self.port),
310
314
            avahi.string_array_to_txt_array(self.TXT))
311
315
        self.group.Commit()
 
316
    
312
317
    def entry_group_state_changed(self, state, error):
313
318
        """Derived from the Avahi example code"""
314
319
        logger.debug("Avahi entry group state change: %i", state)
321
326
        elif state == avahi.ENTRY_GROUP_FAILURE:
322
327
            logger.critical("Avahi: Error in group state changed %s",
323
328
                            unicode(error))
324
 
            raise AvahiGroupError("State changed: %s"
325
 
                                  % unicode(error))
 
329
            raise AvahiGroupError("State changed: {0!s}"
 
330
                                  .format(error))
 
331
    
326
332
    def cleanup(self):
327
333
        """Derived from the Avahi example code"""
328
334
        if self.group is not None:
333
339
                pass
334
340
            self.group = None
335
341
        self.remove()
 
342
    
336
343
    def server_state_changed(self, state, error=None):
337
344
        """Derived from the Avahi example code"""
338
345
        logger.debug("Avahi server state change: %i", state)
357
364
                logger.debug("Unknown state: %r", state)
358
365
            else:
359
366
                logger.debug("Unknown state: %r: %r", state, error)
 
367
    
360
368
    def activate(self):
361
369
        """Derived from the Avahi example code"""
362
370
        if self.server is None:
374
382
        """Add the new name to the syslog messages"""
375
383
        ret = AvahiService.rename(self)
376
384
        syslogger.setFormatter(logging.Formatter
377
 
                               ('Mandos (%s) [%%(process)d]:'
378
 
                                ' %%(levelname)s: %%(message)s'
379
 
                                % self.name))
 
385
                               ('Mandos ({0}) [%(process)d]:'
 
386
                                ' %(levelname)s: %(message)s'
 
387
                                .format(self.name)))
380
388
        return ret
381
389
 
382
390
def timedelta_to_milliseconds(td):
384
392
    return ((td.days * 24 * 60 * 60 * 1000)
385
393
            + (td.seconds * 1000)
386
394
            + (td.microseconds // 1000))
387
 
        
 
395
 
388
396
class Client(object):
389
397
    """A representation of a client host served by this server.
390
398
    
457
465
    
458
466
    def approval_delay_milliseconds(self):
459
467
        return timedelta_to_milliseconds(self.approval_delay)
460
 
 
 
468
    
461
469
    @staticmethod
462
470
    def config_parser(config):
463
471
        """Construct a new dict of client settings of this form:
488
496
                          "rb") as secfile:
489
497
                    client["secret"] = secfile.read()
490
498
            else:
491
 
                raise TypeError("No secret or secfile for section %s"
492
 
                                % section)
 
499
                raise TypeError("No secret or secfile for section {0}"
 
500
                                .format(section))
493
501
            client["timeout"] = string_to_delta(section["timeout"])
494
502
            client["extended_timeout"] = string_to_delta(
495
503
                section["extended_timeout"])
504
512
            client["last_checker_status"] = -2
505
513
        
506
514
        return settings
507
 
        
508
 
        
 
515
    
509
516
    def __init__(self, settings, name = None):
510
 
        """Note: the 'checker' key in 'config' sets the
511
 
        'checker_command' attribute and *not* the 'checker'
512
 
        attribute."""
513
517
        self.name = name
514
518
        # adding all client settings
515
519
        for setting, value in settings.iteritems():
524
528
        else:
525
529
            self.last_enabled = None
526
530
            self.expires = None
527
 
       
 
531
        
528
532
        logger.debug("Creating client %r", self.name)
529
533
        # Uppercase and remove spaces from fingerprint for later
530
534
        # comparison purposes with return value from the fingerprint()
532
536
        logger.debug("  Fingerprint: %s", self.fingerprint)
533
537
        self.created = settings.get("created",
534
538
                                    datetime.datetime.utcnow())
535
 
 
 
539
        
536
540
        # attributes specific for this server instance
537
541
        self.checker = None
538
542
        self.checker_initiator_tag = None
692
696
                try:
693
697
                    command = self.checker_command % escaped_attrs
694
698
                except TypeError as error:
695
 
                    logger.error('Could not format string "%s":'
696
 
                                 ' %s', self.checker_command, error)
 
699
                    logger.error('Could not format string "%s"',
 
700
                                 self.checker_command, exc_info=error)
697
701
                    return True # Try again later
698
702
            self.current_checker_command = command
699
703
            try:
717
721
                    gobject.source_remove(self.checker_callback_tag)
718
722
                    self.checker_callback(pid, status, command)
719
723
            except OSError as error:
720
 
                logger.error("Failed to start subprocess: %s",
721
 
                             error)
 
724
                logger.error("Failed to start subprocess",
 
725
                             exc_info=error)
722
726
        # Re-run this periodically if run by gobject.timeout_add
723
727
        return True
724
728
    
731
735
            return
732
736
        logger.debug("Stopping checker for %(name)s", vars(self))
733
737
        try:
734
 
            os.kill(self.checker.pid, signal.SIGTERM)
 
738
            self.checker.terminate()
735
739
            #time.sleep(0.5)
736
740
            #if self.checker.poll() is None:
737
 
            #    os.kill(self.checker.pid, signal.SIGKILL)
 
741
            #    self.checker.kill()
738
742
        except OSError as error:
739
743
            if error.errno != errno.ESRCH: # No such process
740
744
                raise
757
761
    # "Set" method, so we fail early here:
758
762
    if byte_arrays and signature != "ay":
759
763
        raise ValueError("Byte arrays not supported for non-'ay'"
760
 
                         " signature %r" % signature)
 
764
                         " signature {0!r}".format(signature))
761
765
    def decorator(func):
762
766
        func._dbus_is_property = True
763
767
        func._dbus_interface = dbus_interface
771
775
    return decorator
772
776
 
773
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
 
774
815
class DBusPropertyException(dbus.exceptions.DBusException):
775
816
    """A base class for D-Bus property-related exceptions
776
817
    """
799
840
    """
800
841
    
801
842
    @staticmethod
802
 
    def _is_dbus_property(obj):
803
 
        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)
804
851
    
805
 
    def _get_all_dbus_properties(self):
 
852
    def _get_all_dbus_things(self, thing):
806
853
        """Returns a generator of (name, attribute) pairs
807
854
        """
808
 
        return ((prop.__get__(self)._dbus_name, prop.__get__(self))
 
855
        return ((getattr(athing.__get__(self), "_dbus_name",
 
856
                         name),
 
857
                 athing.__get__(self))
809
858
                for cls in self.__class__.__mro__
810
 
                for name, prop in
811
 
                inspect.getmembers(cls, self._is_dbus_property))
 
859
                for name, athing in
 
860
                inspect.getmembers(cls,
 
861
                                   self._is_dbus_thing(thing)))
812
862
    
813
863
    def _get_dbus_property(self, interface_name, property_name):
814
864
        """Returns a bound method if one exists which is a D-Bus
816
866
        """
817
867
        for cls in  self.__class__.__mro__:
818
868
            for name, value in (inspect.getmembers
819
 
                                (cls, self._is_dbus_property)):
 
869
                                (cls,
 
870
                                 self._is_dbus_thing("property"))):
820
871
                if (value._dbus_name == property_name
821
872
                    and value._dbus_interface == interface_name):
822
873
                    return value.__get__(self)
864
915
        Note: Will not include properties with access="write".
865
916
        """
866
917
        properties = {}
867
 
        for name, prop in self._get_all_dbus_properties():
 
918
        for name, prop in self._get_all_dbus_things("property"):
868
919
            if (interface_name
869
920
                and interface_name != prop._dbus_interface):
870
921
                # Interface non-empty but did not match
885
936
                         path_keyword='object_path',
886
937
                         connection_keyword='connection')
887
938
    def Introspect(self, object_path, connection):
888
 
        """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.
889
942
        """
890
943
        xmlstring = dbus.service.Object.Introspect(self, object_path,
891
944
                                                   connection)
898
951
                e.setAttribute("access", prop._dbus_access)
899
952
                return e
900
953
            for if_tag in document.getElementsByTagName("interface"):
 
954
                # Add property tags
901
955
                for tag in (make_tag(document, name, prop)
902
956
                            for name, prop
903
 
                            in self._get_all_dbus_properties()
 
957
                            in self._get_all_dbus_things("property")
904
958
                            if prop._dbus_interface
905
959
                            == if_tag.getAttribute("name")):
906
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)
907
992
                # Add the names to the return values for the
908
993
                # "org.freedesktop.DBus.Properties" methods
909
994
                if (if_tag.getAttribute("name")
924
1009
        except (AttributeError, xml.dom.DOMException,
925
1010
                xml.parsers.expat.ExpatError) as error:
926
1011
            logger.error("Failed to override Introspection method",
927
 
                         error)
 
1012
                         exc_info=error)
928
1013
        return xmlstring
929
1014
 
930
1015
 
936
1021
                       variant_level=variant_level)
937
1022
 
938
1023
 
939
 
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
940
 
                                  .__metaclass__):
941
 
    """Applied to an empty subclass of a D-Bus object, this metaclass
942
 
    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).
943
1046
    """
944
 
    def __new__(mcs, name, bases, attr):
945
 
        # Go through all the base classes which could have D-Bus
946
 
        # methods, signals, or properties in them
947
 
        for base in (b for b in bases
948
 
                     if issubclass(b, dbus.service.Object)):
949
 
            # Go though all attributes of the base class
950
 
            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):
951
1054
                # Ignore non-D-Bus attributes, and D-Bus attributes
952
1055
                # with the wrong interface name
953
1056
                if (not hasattr(attribute, "_dbus_interface")
954
1057
                    or not attribute._dbus_interface
955
 
                    .startswith("se.recompile.Mandos")):
 
1058
                    .startswith(orig_interface_name)):
956
1059
                    continue
957
1060
                # Create an alternate D-Bus interface name based on
958
1061
                # the current name
959
1062
                alt_interface = (attribute._dbus_interface
960
 
                                 .replace("se.recompile.Mandos",
961
 
                                          "se.bsnet.fukt.Mandos"))
 
1063
                                 .replace(orig_interface_name,
 
1064
                                          alt_interface_name))
 
1065
                interface_names.add(alt_interface)
962
1066
                # Is this a D-Bus signal?
963
1067
                if getattr(attribute, "_dbus_is_signal", False):
964
1068
                    # Extract the original non-method function by
979
1083
                                nonmethod_func.func_name,
980
1084
                                nonmethod_func.func_defaults,
981
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
982
1092
                    # Define a creator of a function to call both the
983
 
                    # old and new functions, so both the old and new
984
 
                    # 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
985
1096
                    def fixscope(func1, func2):
986
1097
                        """This function is a scope container to pass
987
1098
                        func1 and func2 to the "call_both" function
994
1105
                        return call_both
995
1106
                    # Create the "call_both" function and add it to
996
1107
                    # the class
997
 
                    attr[attrname] = fixscope(attribute,
998
 
                                              new_function)
 
1108
                    attr[attrname] = fixscope(attribute, new_function)
999
1109
                # Is this a D-Bus method?
1000
1110
                elif getattr(attribute, "_dbus_is_method", False):
1001
1111
                    # Create a new, but exactly alike, function
1012
1122
                                        attribute.func_name,
1013
1123
                                        attribute.func_defaults,
1014
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
1015
1131
                # Is this a D-Bus property?
1016
1132
                elif getattr(attribute, "_dbus_is_property", False):
1017
1133
                    # Create a new, but exactly alike, function
1031
1147
                                        attribute.func_name,
1032
1148
                                        attribute.func_defaults,
1033
1149
                                        attribute.func_closure)))
1034
 
        return type.__new__(mcs, name, bases, attr)
1035
 
 
1036
 
 
 
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"})
1037
1195
class ClientDBus(Client, DBusObjectWithProperties):
1038
1196
    """A Client class using D-Bus
1039
1197
    
1059
1217
                                 ("/clients/" + client_object_name))
1060
1218
        DBusObjectWithProperties.__init__(self, self.bus,
1061
1219
                                          self.dbus_object_path)
1062
 
        
 
1220
    
1063
1221
    def notifychangeproperty(transform_func,
1064
1222
                             dbus_name, type_func=lambda x: x,
1065
1223
                             variant_level=1):
1088
1246
        
1089
1247
        return property(lambda self: getattr(self, attrname), setter)
1090
1248
    
1091
 
    
1092
1249
    expires = notifychangeproperty(datetime_to_dbus, "Expires")
1093
1250
    approvals_pending = notifychangeproperty(dbus.Boolean,
1094
1251
                                             "ApprovalPending",
1182
1339
                            (self.approval_duration),
1183
1340
                            self._reset_approved)
1184
1341
    
1185
 
    
1186
1342
    ## D-Bus methods, signals & properties
1187
1343
    _interface = "se.recompile.Mandos.Client"
1188
1344
    
 
1345
    ## Interfaces
 
1346
    
 
1347
    @dbus_interface_annotations(_interface)
 
1348
    def _foo(self):
 
1349
        return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
 
1350
                     "false"}
 
1351
    
1189
1352
    ## Signals
1190
1353
    
1191
1354
    # CheckerCompleted - signal
1468
1631
        self._pipe.send(('setattr', name, value))
1469
1632
 
1470
1633
 
1471
 
class ClientDBusTransitional(ClientDBus):
1472
 
    __metaclass__ = AlternateDBusNamesMetaclass
1473
 
 
1474
 
 
1475
1634
class ClientHandler(socketserver.BaseRequestHandler, object):
1476
1635
    """A class to handle client connections.
1477
1636
    
1605
1764
                    try:
1606
1765
                        sent = session.send(client.secret[sent_size:])
1607
1766
                    except gnutls.errors.GNUTLSError as error:
1608
 
                        logger.warning("gnutls send failed")
 
1767
                        logger.warning("gnutls send failed",
 
1768
                                       exc_info=error)
1609
1769
                        return
1610
1770
                    logger.debug("Sent: %d, remaining: %d",
1611
1771
                                 sent, len(client.secret)
1625
1785
                try:
1626
1786
                    session.bye()
1627
1787
                except gnutls.errors.GNUTLSError as error:
1628
 
                    logger.warning("GnuTLS bye failed")
 
1788
                    logger.warning("GnuTLS bye failed",
 
1789
                                   exc_info=error)
1629
1790
    
1630
1791
    @staticmethod
1631
1792
    def peer_certificate(session):
1943
2104
            elif suffix == "w":
1944
2105
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1945
2106
            else:
1946
 
                raise ValueError("Unknown suffix %r" % suffix)
 
2107
                raise ValueError("Unknown suffix {0!r}"
 
2108
                                 .format(suffix))
1947
2109
        except (ValueError, IndexError) as e:
1948
2110
            raise ValueError(*(e.args))
1949
2111
        timevalue += delta
1963
2125
        sys.exit()
1964
2126
    if not noclose:
1965
2127
        # Close all standard open file descriptors
1966
 
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
2128
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
1967
2129
        if not stat.S_ISCHR(os.fstat(null).st_mode):
1968
2130
            raise OSError(errno.ENODEV,
1969
 
                          "%s not a character device"
1970
 
                          % os.path.devnull)
 
2131
                          "{0} not a character device"
 
2132
                          .format(os.devnull))
1971
2133
        os.dup2(null, sys.stdin.fileno())
1972
2134
        os.dup2(null, sys.stdout.fileno())
1973
2135
        os.dup2(null, sys.stderr.fileno())
1982
2144
    
1983
2145
    parser = argparse.ArgumentParser()
1984
2146
    parser.add_argument("-v", "--version", action="version",
1985
 
                        version = "%%(prog)s %s" % version,
 
2147
                        version = "%(prog)s {0}".format(version),
1986
2148
                        help="show version number and exit")
1987
2149
    parser.add_argument("-i", "--interface", metavar="IF",
1988
2150
                        help="Bind to interface IF")
2091
2253
    
2092
2254
    if server_settings["servicename"] != "Mandos":
2093
2255
        syslogger.setFormatter(logging.Formatter
2094
 
                               ('Mandos (%s) [%%(process)d]:'
2095
 
                                ' %%(levelname)s: %%(message)s'
2096
 
                                % server_settings["servicename"]))
 
2256
                               ('Mandos ({0}) [%(process)d]:'
 
2257
                                ' %(levelname)s: %(message)s'
 
2258
                                .format(server_settings
 
2259
                                        ["servicename"])))
2097
2260
    
2098
2261
    # Parse config file with clients
2099
2262
    client_config = configparser.SafeConfigParser(Client
2117
2280
        pidfilename = "/var/run/mandos.pid"
2118
2281
        try:
2119
2282
            pidfile = open(pidfilename, "w")
2120
 
        except IOError:
2121
 
            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)
2122
2286
    
2123
 
    try:
2124
 
        uid = pwd.getpwnam("_mandos").pw_uid
2125
 
        gid = pwd.getpwnam("_mandos").pw_gid
2126
 
    except KeyError:
 
2287
    for name in ("_mandos", "mandos", "nobody"):
2127
2288
        try:
2128
 
            uid = pwd.getpwnam("mandos").pw_uid
2129
 
            gid = pwd.getpwnam("mandos").pw_gid
 
2289
            uid = pwd.getpwnam(name).pw_uid
 
2290
            gid = pwd.getpwnam(name).pw_gid
 
2291
            break
2130
2292
        except KeyError:
2131
 
            try:
2132
 
                uid = pwd.getpwnam("nobody").pw_uid
2133
 
                gid = pwd.getpwnam("nobody").pw_gid
2134
 
            except KeyError:
2135
 
                uid = 65534
2136
 
                gid = 65534
 
2293
            continue
 
2294
    else:
 
2295
        uid = 65534
 
2296
        gid = 65534
2137
2297
    try:
2138
2298
        os.setgid(gid)
2139
2299
        os.setuid(uid)
2156
2316
         .gnutls_global_set_log_function(debug_gnutls))
2157
2317
        
2158
2318
        # Redirect stdin so all checkers get /dev/null
2159
 
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
2319
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2160
2320
        os.dup2(null, sys.stdin.fileno())
2161
2321
        if null > 2:
2162
2322
            os.close(null)
2170
2330
    
2171
2331
    global main_loop
2172
2332
    # From the Avahi example code
2173
 
    DBusGMainLoop(set_as_default=True )
 
2333
    DBusGMainLoop(set_as_default=True)
2174
2334
    main_loop = gobject.MainLoop()
2175
2335
    bus = dbus.SystemBus()
2176
2336
    # End of Avahi example code
2182
2342
                            ("se.bsnet.fukt.Mandos", bus,
2183
2343
                             do_not_queue=True))
2184
2344
        except dbus.exceptions.NameExistsException as e:
2185
 
            logger.error(unicode(e) + ", disabling D-Bus")
 
2345
            logger.error("Disabling D-Bus:", exc_info=e)
2186
2346
            use_dbus = False
2187
2347
            server_settings["use_dbus"] = False
2188
2348
            tcp_server.use_dbus = False
2200
2360
    
2201
2361
    client_class = Client
2202
2362
    if use_dbus:
2203
 
        client_class = functools.partial(ClientDBusTransitional,
2204
 
                                         bus = bus)
 
2363
        client_class = functools.partial(ClientDBus, bus = bus)
2205
2364
    
2206
2365
    client_settings = Client.config_parser(client_config)
2207
2366
    old_client_settings = {}
2215
2374
                                                     (stored_state))
2216
2375
            os.remove(stored_state_path)
2217
2376
        except IOError as e:
2218
 
            logger.warning("Could not load persistent state: {0}"
2219
 
                           .format(e))
2220
 
            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)
2221
2383
                raise
2222
2384
        except EOFError as e:
2223
2385
            logger.warning("Could not load persistent state: "
2224
 
                           "EOFError: {0}".format(e))
 
2386
                           "EOFError:", exc_info=e)
2225
2387
    
2226
2388
    with PGPEngine() as pgp:
2227
2389
        for client_name, client in clients_data.iteritems():
2280
2442
                             .format(client_name))
2281
2443
                client["secret"] = (
2282
2444
                    client_settings[client_name]["secret"])
2283
 
 
2284
2445
    
2285
2446
    # Add/remove clients based on new changes made to config
2286
2447
    for client_name in (set(old_client_settings)
2289
2450
    for client_name in (set(client_settings)
2290
2451
                        - set(old_client_settings)):
2291
2452
        clients_data[client_name] = client_settings[client_name]
2292
 
 
 
2453
    
2293
2454
    # Create all client objects
2294
2455
    for client_name, client in clients_data.iteritems():
2295
2456
        tcp_server.clients[client_name] = client_class(
2297
2458
    
2298
2459
    if not tcp_server.clients:
2299
2460
        logger.warning("No clients defined")
2300
 
        
 
2461
    
2301
2462
    if not debug:
2302
2463
        try:
2303
2464
            with pidfile:
2317
2478
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2318
2479
    
2319
2480
    if use_dbus:
2320
 
        class MandosDBusService(dbus.service.Object):
 
2481
        @alternate_dbus_interfaces({"se.recompile.Mandos":
 
2482
                                        "se.bsnet.fukt.Mandos"})
 
2483
        class MandosDBusService(DBusObjectWithProperties):
2321
2484
            """A D-Bus proxy object"""
2322
2485
            def __init__(self):
2323
2486
                dbus.service.Object.__init__(self, bus, "/")
2324
2487
            _interface = "se.recompile.Mandos"
2325
2488
            
 
2489
            @dbus_interface_annotations(_interface)
 
2490
            def _foo(self):
 
2491
                return { "org.freedesktop.DBus.Property"
 
2492
                         ".EmitsChangedSignal":
 
2493
                             "false"}
 
2494
            
2326
2495
            @dbus.service.signal(_interface, signature="o")
2327
2496
            def ClientAdded(self, objpath):
2328
2497
                "D-Bus signal"
2370
2539
            
2371
2540
            del _interface
2372
2541
        
2373
 
        class MandosDBusServiceTransitional(MandosDBusService):
2374
 
            __metaclass__ = AlternateDBusNamesMetaclass
2375
 
        mandos_dbus_service = MandosDBusServiceTransitional()
 
2542
        mandos_dbus_service = MandosDBusService()
2376
2543
    
2377
2544
    def cleanup():
2378
2545
        "Cleanup function; run on exit"
2419
2586
                pickle.dump((clients, client_settings), stored_state)
2420
2587
            os.rename(tempname, stored_state_path)
2421
2588
        except (IOError, OSError) as e:
2422
 
            logger.warning("Could not save persistent state: {0}"
2423
 
                           .format(e))
2424
2589
            if not debug:
2425
2590
                try:
2426
2591
                    os.remove(tempname)
2427
2592
                except NameError:
2428
2593
                    pass
2429
 
            if e.errno not in set((errno.ENOENT, errno.EACCES,
2430
 
                                   errno.EEXIST)):
 
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)
2431
2600
                raise e
2432
2601
        
2433
2602
        # Delete all clients, and settings from config
2461
2630
    service.port = tcp_server.socket.getsockname()[1]
2462
2631
    if use_ipv6:
2463
2632
        logger.info("Now listening on address %r, port %d,"
2464
 
                    " flowinfo %d, scope_id %d"
2465
 
                    % tcp_server.socket.getsockname())
 
2633
                    " flowinfo %d, scope_id %d",
 
2634
                    *tcp_server.socket.getsockname())
2466
2635
    else:                       # IPv4
2467
 
        logger.info("Now listening on address %r, port %d"
2468
 
                    % tcp_server.socket.getsockname())
 
2636
        logger.info("Now listening on address %r, port %d",
 
2637
                    *tcp_server.socket.getsockname())
2469
2638
    
2470
2639
    #service.interface = tcp_server.socket.getsockname()[3]
2471
2640
    
2474
2643
        try:
2475
2644
            service.activate()
2476
2645
        except dbus.exceptions.DBusException as error:
2477
 
            logger.critical("DBusException: %s", error)
 
2646
            logger.critical("D-Bus Exception", exc_info=error)
2478
2647
            cleanup()
2479
2648
            sys.exit(1)
2480
2649
        # End of Avahi example code
2487
2656
        logger.debug("Starting main loop")
2488
2657
        main_loop.run()
2489
2658
    except AvahiError as error:
2490
 
        logger.critical("AvahiError: %s", error)
 
2659
        logger.critical("Avahi Error", exc_info=error)
2491
2660
        cleanup()
2492
2661
        sys.exit(1)
2493
2662
    except KeyboardInterrupt: