/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
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: