/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:54:24 UTC
  • Revision ID: teddy@recompile.se-20120506165424-6bqcnlr33ih657tj
* mandos (DBusObjectWithProperties.Introspect): Use
  itertools.chain.from_iterable(foo) instead of itertools.chain(*foo).

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()
142
143
        self.gnupg.options.meta_interactive = False
143
144
        self.gnupg.options.homedir = self.tempdir
144
145
        self.gnupg.options.extra_args.extend(['--force-mdc',
145
 
                                              '--quiet'])
 
146
                                              '--quiet',
 
147
                                              '--no-use-agent'])
146
148
    
147
149
    def __enter__(self):
148
150
        return self
174
176
    
175
177
    def encrypt(self, data, password):
176
178
        self.gnupg.passphrase = self.password_encode(password)
177
 
        with open(os.devnull) as devnull:
 
179
        with open(os.devnull, "w") as devnull:
178
180
            try:
179
181
                proc = self.gnupg.run(['--symmetric'],
180
182
                                      create_fhs=['stdin', 'stdout'],
191
193
    
192
194
    def decrypt(self, data, password):
193
195
        self.gnupg.passphrase = self.password_encode(password)
194
 
        with open(os.devnull) as devnull:
 
196
        with open(os.devnull, "w") as devnull:
195
197
            try:
196
198
                proc = self.gnupg.run(['--decrypt'],
197
199
                                      create_fhs=['stdin', 'stdout'],
198
200
                                      attach_fhs={'stderr': devnull})
199
 
                with contextlib.closing(proc.handles['stdin'] ) as f:
 
201
                with contextlib.closing(proc.handles['stdin']) as f:
200
202
                    f.write(data)
201
203
                with contextlib.closing(proc.handles['stdout']) as f:
202
204
                    decrypted_plaintext = f.read()
207
209
        return decrypted_plaintext
208
210
 
209
211
 
210
 
 
211
212
class AvahiError(Exception):
212
213
    def __init__(self, value, *args, **kwargs):
213
214
        self.value = value
242
243
    server: D-Bus Server
243
244
    bus: dbus.SystemBus()
244
245
    """
 
246
    
245
247
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
246
248
                 servicetype = None, port = None, TXT = None,
247
249
                 domain = "", host = "", max_renames = 32768,
260
262
        self.server = None
261
263
        self.bus = bus
262
264
        self.entry_group_state_changed_match = None
 
265
    
263
266
    def rename(self):
264
267
        """Derived from the Avahi example code"""
265
268
        if self.rename_count >= self.max_renames:
275
278
        try:
276
279
            self.add()
277
280
        except dbus.exceptions.DBusException as error:
278
 
            logger.critical("DBusException: %s", error)
 
281
            logger.critical("D-Bus Exception", exc_info=error)
279
282
            self.cleanup()
280
283
            os._exit(1)
281
284
        self.rename_count += 1
 
285
    
282
286
    def remove(self):
283
287
        """Derived from the Avahi example code"""
284
288
        if self.entry_group_state_changed_match is not None:
286
290
            self.entry_group_state_changed_match = None
287
291
        if self.group is not None:
288
292
            self.group.Reset()
 
293
    
289
294
    def add(self):
290
295
        """Derived from the Avahi example code"""
291
296
        self.remove()
308
313
            dbus.UInt16(self.port),
309
314
            avahi.string_array_to_txt_array(self.TXT))
310
315
        self.group.Commit()
 
316
    
311
317
    def entry_group_state_changed(self, state, error):
312
318
        """Derived from the Avahi example code"""
313
319
        logger.debug("Avahi entry group state change: %i", state)
320
326
        elif state == avahi.ENTRY_GROUP_FAILURE:
321
327
            logger.critical("Avahi: Error in group state changed %s",
322
328
                            unicode(error))
323
 
            raise AvahiGroupError("State changed: %s"
324
 
                                  % unicode(error))
 
329
            raise AvahiGroupError("State changed: {0!s}"
 
330
                                  .format(error))
 
331
    
325
332
    def cleanup(self):
326
333
        """Derived from the Avahi example code"""
327
334
        if self.group is not None:
332
339
                pass
333
340
            self.group = None
334
341
        self.remove()
 
342
    
335
343
    def server_state_changed(self, state, error=None):
336
344
        """Derived from the Avahi example code"""
337
345
        logger.debug("Avahi server state change: %i", state)
356
364
                logger.debug("Unknown state: %r", state)
357
365
            else:
358
366
                logger.debug("Unknown state: %r: %r", state, error)
 
367
    
359
368
    def activate(self):
360
369
        """Derived from the Avahi example code"""
361
370
        if self.server is None:
373
382
        """Add the new name to the syslog messages"""
374
383
        ret = AvahiService.rename(self)
375
384
        syslogger.setFormatter(logging.Formatter
376
 
                               ('Mandos (%s) [%%(process)d]:'
377
 
                                ' %%(levelname)s: %%(message)s'
378
 
                                % self.name))
 
385
                               ('Mandos ({0}) [%(process)d]:'
 
386
                                ' %(levelname)s: %(message)s'
 
387
                                .format(self.name)))
379
388
        return ret
380
389
 
381
390
def timedelta_to_milliseconds(td):
383
392
    return ((td.days * 24 * 60 * 60 * 1000)
384
393
            + (td.seconds * 1000)
385
394
            + (td.microseconds // 1000))
386
 
        
 
395
 
387
396
class Client(object):
388
397
    """A representation of a client host served by this server.
389
398
    
414
423
    last_checked_ok: datetime.datetime(); (UTC) or None
415
424
    last_checker_status: integer between 0 and 255 reflecting exit
416
425
                         status of last checker. -1 reflects crashed
417
 
                         checker, or None.
 
426
                         checker, -2 means no checker completed yet.
418
427
    last_enabled: datetime.datetime(); (UTC) or None
419
428
    name:       string; from the config file, used in log messages and
420
429
                        D-Bus identifiers
421
430
    secret:     bytestring; sent verbatim (over TLS) to client
422
431
    timeout:    datetime.timedelta(); How long from last_checked_ok
423
432
                                      until this client is disabled
424
 
    extended_timeout:   extra long timeout when password has been sent
 
433
    extended_timeout:   extra long timeout when secret has been sent
425
434
    runtime_expansions: Allowed attributes for runtime expansion.
426
435
    expires:    datetime.datetime(); time (UTC) when a client will be
427
436
                disabled, or None
456
465
    
457
466
    def approval_delay_milliseconds(self):
458
467
        return timedelta_to_milliseconds(self.approval_delay)
459
 
 
 
468
    
460
469
    @staticmethod
461
470
    def config_parser(config):
462
 
        """ Construct a new dict of client settings of this form:
 
471
        """Construct a new dict of client settings of this form:
463
472
        { client_name: {setting_name: value, ...}, ...}
464
 
        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
        """
465
477
        settings = {}
466
478
        for client_name in config.sections():
467
479
            section = dict(config.items(client_name))
471
483
            # Reformat values from string types to Python types
472
484
            client["approved_by_default"] = config.getboolean(
473
485
                client_name, "approved_by_default")
474
 
            client["enabled"] = config.getboolean(client_name, "enabled")
 
486
            client["enabled"] = config.getboolean(client_name,
 
487
                                                  "enabled")
475
488
            
476
489
            client["fingerprint"] = (section["fingerprint"].upper()
477
490
                                     .replace(" ", ""))
483
496
                          "rb") as secfile:
484
497
                    client["secret"] = secfile.read()
485
498
            else:
486
 
                raise TypeError("No secret or secfile for section %s"
487
 
                                % section)
 
499
                raise TypeError("No secret or secfile for section {0}"
 
500
                                .format(section))
488
501
            client["timeout"] = string_to_delta(section["timeout"])
489
502
            client["extended_timeout"] = string_to_delta(
490
503
                section["extended_timeout"])
496
509
            client["checker_command"] = section["checker"]
497
510
            client["last_approval_request"] = None
498
511
            client["last_checked_ok"] = None
499
 
            client["last_checker_status"] = None
500
 
            if client["enabled"]:
501
 
                client["last_enabled"] = datetime.datetime.utcnow()
502
 
                client["expires"] = (datetime.datetime.utcnow()
503
 
                                     + client["timeout"])
504
 
            else:
505
 
                client["last_enabled"] = None
506
 
                client["expires"] = None
507
 
 
 
512
            client["last_checker_status"] = -2
 
513
        
508
514
        return settings
509
 
        
510
 
        
 
515
    
511
516
    def __init__(self, settings, name = None):
512
 
        """Note: the 'checker' key in 'config' sets the
513
 
        'checker_command' attribute and *not* the 'checker'
514
 
        attribute."""
515
517
        self.name = name
516
518
        # adding all client settings
517
519
        for setting, value in settings.iteritems():
518
520
            setattr(self, setting, value)
519
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
        
520
532
        logger.debug("Creating client %r", self.name)
521
533
        # Uppercase and remove spaces from fingerprint for later
522
534
        # comparison purposes with return value from the fingerprint()
523
535
        # function
524
536
        logger.debug("  Fingerprint: %s", self.fingerprint)
525
 
        self.created = settings.get("created", datetime.datetime.utcnow())
526
 
 
 
537
        self.created = settings.get("created",
 
538
                                    datetime.datetime.utcnow())
 
539
        
527
540
        # attributes specific for this server instance
528
541
        self.checker = None
529
542
        self.checker_initiator_tag = None
617
630
            logger.warning("Checker for %(name)s crashed?",
618
631
                           vars(self))
619
632
    
620
 
    def checked_ok(self, timeout=None):
621
 
        """Bump up the timeout for this client.
622
 
        
623
 
        This should only be called when the client has been seen,
624
 
        alive and well.
625
 
        """
 
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."""
626
641
        if timeout is None:
627
642
            timeout = self.timeout
628
 
        self.last_checked_ok = datetime.datetime.utcnow()
629
643
        if self.disable_initiator_tag is not None:
630
644
            gobject.source_remove(self.disable_initiator_tag)
631
645
        if getattr(self, "enabled", False):
682
696
                try:
683
697
                    command = self.checker_command % escaped_attrs
684
698
                except TypeError as error:
685
 
                    logger.error('Could not format string "%s":'
686
 
                                 ' %s', self.checker_command, error)
 
699
                    logger.error('Could not format string "%s"',
 
700
                                 self.checker_command, exc_info=error)
687
701
                    return True # Try again later
688
702
            self.current_checker_command = command
689
703
            try:
707
721
                    gobject.source_remove(self.checker_callback_tag)
708
722
                    self.checker_callback(pid, status, command)
709
723
            except OSError as error:
710
 
                logger.error("Failed to start subprocess: %s",
711
 
                             error)
 
724
                logger.error("Failed to start subprocess",
 
725
                             exc_info=error)
712
726
        # Re-run this periodically if run by gobject.timeout_add
713
727
        return True
714
728
    
721
735
            return
722
736
        logger.debug("Stopping checker for %(name)s", vars(self))
723
737
        try:
724
 
            os.kill(self.checker.pid, signal.SIGTERM)
 
738
            self.checker.terminate()
725
739
            #time.sleep(0.5)
726
740
            #if self.checker.poll() is None:
727
 
            #    os.kill(self.checker.pid, signal.SIGKILL)
 
741
            #    self.checker.kill()
728
742
        except OSError as error:
729
743
            if error.errno != errno.ESRCH: # No such process
730
744
                raise
747
761
    # "Set" method, so we fail early here:
748
762
    if byte_arrays and signature != "ay":
749
763
        raise ValueError("Byte arrays not supported for non-'ay'"
750
 
                         " signature %r" % signature)
 
764
                         " signature {0!r}".format(signature))
751
765
    def decorator(func):
752
766
        func._dbus_is_property = True
753
767
        func._dbus_interface = dbus_interface
761
775
    return decorator
762
776
 
763
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
 
764
815
class DBusPropertyException(dbus.exceptions.DBusException):
765
816
    """A base class for D-Bus property-related exceptions
766
817
    """
789
840
    """
790
841
    
791
842
    @staticmethod
792
 
    def _is_dbus_property(obj):
793
 
        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)
794
851
    
795
 
    def _get_all_dbus_properties(self):
 
852
    def _get_all_dbus_things(self, thing):
796
853
        """Returns a generator of (name, attribute) pairs
797
854
        """
798
 
        return ((prop.__get__(self)._dbus_name, prop.__get__(self))
 
855
        return ((getattr(athing.__get__(self), "_dbus_name",
 
856
                         name),
 
857
                 athing.__get__(self))
799
858
                for cls in self.__class__.__mro__
800
 
                for name, prop in
801
 
                inspect.getmembers(cls, self._is_dbus_property))
 
859
                for name, athing in
 
860
                inspect.getmembers(cls,
 
861
                                   self._is_dbus_thing(thing)))
802
862
    
803
863
    def _get_dbus_property(self, interface_name, property_name):
804
864
        """Returns a bound method if one exists which is a D-Bus
806
866
        """
807
867
        for cls in  self.__class__.__mro__:
808
868
            for name, value in (inspect.getmembers
809
 
                                (cls, self._is_dbus_property)):
 
869
                                (cls,
 
870
                                 self._is_dbus_thing("property"))):
810
871
                if (value._dbus_name == property_name
811
872
                    and value._dbus_interface == interface_name):
812
873
                    return value.__get__(self)
841
902
            # signatures other than "ay".
842
903
            if prop._dbus_signature != "ay":
843
904
                raise ValueError
844
 
            value = dbus.ByteArray(''.join(unichr(byte)
845
 
                                           for byte in value))
 
905
            value = dbus.ByteArray(b''.join(chr(byte)
 
906
                                            for byte in value))
846
907
        prop(value)
847
908
    
848
909
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
854
915
        Note: Will not include properties with access="write".
855
916
        """
856
917
        properties = {}
857
 
        for name, prop in self._get_all_dbus_properties():
 
918
        for name, prop in self._get_all_dbus_things("property"):
858
919
            if (interface_name
859
920
                and interface_name != prop._dbus_interface):
860
921
                # Interface non-empty but did not match
875
936
                         path_keyword='object_path',
876
937
                         connection_keyword='connection')
877
938
    def Introspect(self, object_path, connection):
878
 
        """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.
879
942
        """
880
943
        xmlstring = dbus.service.Object.Introspect(self, object_path,
881
944
                                                   connection)
888
951
                e.setAttribute("access", prop._dbus_access)
889
952
                return e
890
953
            for if_tag in document.getElementsByTagName("interface"):
 
954
                # Add property tags
891
955
                for tag in (make_tag(document, name, prop)
892
956
                            for name, prop
893
 
                            in self._get_all_dbus_properties()
 
957
                            in self._get_all_dbus_things("property")
894
958
                            if prop._dbus_interface
895
959
                            == if_tag.getAttribute("name")):
896
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.from_iterable(
 
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)
897
992
                # Add the names to the return values for the
898
993
                # "org.freedesktop.DBus.Properties" methods
899
994
                if (if_tag.getAttribute("name")
914
1009
        except (AttributeError, xml.dom.DOMException,
915
1010
                xml.parsers.expat.ExpatError) as error:
916
1011
            logger.error("Failed to override Introspection method",
917
 
                         error)
 
1012
                         exc_info=error)
918
1013
        return xmlstring
919
1014
 
920
1015
 
926
1021
                       variant_level=variant_level)
927
1022
 
928
1023
 
929
 
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
930
 
                                  .__metaclass__):
931
 
    """Applied to an empty subclass of a D-Bus object, this metaclass
932
 
    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).
933
1046
    """
934
 
    def __new__(mcs, name, bases, attr):
935
 
        # Go through all the base classes which could have D-Bus
936
 
        # methods, signals, or properties in them
937
 
        for base in (b for b in bases
938
 
                     if issubclass(b, dbus.service.Object)):
939
 
            # Go though all attributes of the base class
940
 
            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):
941
1054
                # Ignore non-D-Bus attributes, and D-Bus attributes
942
1055
                # with the wrong interface name
943
1056
                if (not hasattr(attribute, "_dbus_interface")
944
1057
                    or not attribute._dbus_interface
945
 
                    .startswith("se.recompile.Mandos")):
 
1058
                    .startswith(orig_interface_name)):
946
1059
                    continue
947
1060
                # Create an alternate D-Bus interface name based on
948
1061
                # the current name
949
1062
                alt_interface = (attribute._dbus_interface
950
 
                                 .replace("se.recompile.Mandos",
951
 
                                          "se.bsnet.fukt.Mandos"))
 
1063
                                 .replace(orig_interface_name,
 
1064
                                          alt_interface_name))
 
1065
                interface_names.add(alt_interface)
952
1066
                # Is this a D-Bus signal?
953
1067
                if getattr(attribute, "_dbus_is_signal", False):
954
1068
                    # Extract the original non-method function by
969
1083
                                nonmethod_func.func_name,
970
1084
                                nonmethod_func.func_defaults,
971
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
972
1092
                    # Define a creator of a function to call both the
973
 
                    # old and new functions, so both the old and new
974
 
                    # 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
975
1096
                    def fixscope(func1, func2):
976
1097
                        """This function is a scope container to pass
977
1098
                        func1 and func2 to the "call_both" function
984
1105
                        return call_both
985
1106
                    # Create the "call_both" function and add it to
986
1107
                    # the class
987
 
                    attr[attrname] = fixscope(attribute,
988
 
                                              new_function)
 
1108
                    attr[attrname] = fixscope(attribute, new_function)
989
1109
                # Is this a D-Bus method?
990
1110
                elif getattr(attribute, "_dbus_is_method", False):
991
1111
                    # Create a new, but exactly alike, function
1002
1122
                                        attribute.func_name,
1003
1123
                                        attribute.func_defaults,
1004
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
1005
1131
                # Is this a D-Bus property?
1006
1132
                elif getattr(attribute, "_dbus_is_property", False):
1007
1133
                    # Create a new, but exactly alike, function
1021
1147
                                        attribute.func_name,
1022
1148
                                        attribute.func_defaults,
1023
1149
                                        attribute.func_closure)))
1024
 
        return type.__new__(mcs, name, bases, attr)
1025
 
 
1026
 
 
 
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"})
1027
1195
class ClientDBus(Client, DBusObjectWithProperties):
1028
1196
    """A Client class using D-Bus
1029
1197
    
1040
1208
    def __init__(self, bus = None, *args, **kwargs):
1041
1209
        self.bus = bus
1042
1210
        Client.__init__(self, *args, **kwargs)
1043
 
        self._approvals_pending = 0
1044
 
        
1045
 
        self._approvals_pending = 0
1046
1211
        # Only now, when this client is initialized, can it show up on
1047
1212
        # the D-Bus
1048
1213
        client_object_name = unicode(self.name).translate(
1052
1217
                                 ("/clients/" + client_object_name))
1053
1218
        DBusObjectWithProperties.__init__(self, self.bus,
1054
1219
                                          self.dbus_object_path)
1055
 
        
 
1220
    
1056
1221
    def notifychangeproperty(transform_func,
1057
1222
                             dbus_name, type_func=lambda x: x,
1058
1223
                             variant_level=1):
1081
1246
        
1082
1247
        return property(lambda self: getattr(self, attrname), setter)
1083
1248
    
1084
 
    
1085
1249
    expires = notifychangeproperty(datetime_to_dbus, "Expires")
1086
1250
    approvals_pending = notifychangeproperty(dbus.Boolean,
1087
1251
                                             "ApprovalPending",
1094
1258
                                       checker is not None)
1095
1259
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
1096
1260
                                           "LastCheckedOK")
 
1261
    last_checker_status = notifychangeproperty(dbus.Int16,
 
1262
                                               "LastCheckerStatus")
1097
1263
    last_approval_request = notifychangeproperty(
1098
1264
        datetime_to_dbus, "LastApprovalRequest")
1099
1265
    approved_by_default = notifychangeproperty(dbus.Boolean,
1173
1339
                            (self.approval_duration),
1174
1340
                            self._reset_approved)
1175
1341
    
1176
 
    
1177
1342
    ## D-Bus methods, signals & properties
1178
1343
    _interface = "se.recompile.Mandos.Client"
1179
1344
    
 
1345
    ## Interfaces
 
1346
    
 
1347
    @dbus_interface_annotations(_interface)
 
1348
    def _foo(self):
 
1349
        return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
 
1350
                     "false"}
 
1351
    
1180
1352
    ## Signals
1181
1353
    
1182
1354
    # CheckerCompleted - signal
1218
1390
        "D-Bus signal"
1219
1391
        return self.need_approval()
1220
1392
    
1221
 
    # NeRwequest - signal
1222
 
    @dbus.service.signal(_interface, signature="s")
1223
 
    def NewRequest(self, ip):
1224
 
        """D-Bus signal
1225
 
        Is sent after a client request a password.
1226
 
        """
1227
 
        pass
1228
 
    
1229
1393
    ## Methods
1230
1394
    
1231
1395
    # Approve - method
1341
1505
            return
1342
1506
        return datetime_to_dbus(self.last_checked_ok)
1343
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
    
1344
1514
    # Expires - property
1345
1515
    @dbus_service_property(_interface, signature="s", access="read")
1346
1516
    def Expires_dbus_property(self):
1358
1528
        if value is None:       # get
1359
1529
            return dbus.UInt64(self.timeout_milliseconds())
1360
1530
        self.timeout = datetime.timedelta(0, 0, 0, value)
1361
 
        if getattr(self, "disable_initiator_tag", None) is None:
1362
 
            return
1363
1531
        # Reschedule timeout
1364
 
        gobject.source_remove(self.disable_initiator_tag)
1365
 
        self.disable_initiator_tag = None
1366
 
        self.expires = None
1367
 
        time_to_die = timedelta_to_milliseconds((self
1368
 
                                                 .last_checked_ok
1369
 
                                                 + self.timeout)
1370
 
                                                - datetime.datetime
1371
 
                                                .utcnow())
1372
 
        if time_to_die <= 0:
1373
 
            # The timeout has passed
1374
 
            self.disable()
1375
 
        else:
1376
 
            self.expires = (datetime.datetime.utcnow()
1377
 
                            + datetime.timedelta(milliseconds =
1378
 
                                                 time_to_die))
1379
 
            self.disable_initiator_tag = (gobject.timeout_add
1380
 
                                          (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))
1381
1550
    
1382
1551
    # ExtendedTimeout - property
1383
1552
    @dbus_service_property(_interface, signature="t",
1462
1631
        self._pipe.send(('setattr', name, value))
1463
1632
 
1464
1633
 
1465
 
class ClientDBusTransitional(ClientDBus):
1466
 
    __metaclass__ = AlternateDBusNamesMetaclass
1467
 
 
1468
 
 
1469
1634
class ClientHandler(socketserver.BaseRequestHandler, object):
1470
1635
    """A class to handle client connections.
1471
1636
    
1539
1704
                except KeyError:
1540
1705
                    return
1541
1706
                
1542
 
                if self.server.use_dbus:
1543
 
                    # Emit D-Bus signal
1544
 
                    client.NewRequest(str(self.client_address))
1545
 
                
1546
1707
                if client.approval_delay:
1547
1708
                    delay = client.approval_delay
1548
1709
                    client.approvals_pending += 1
1603
1764
                    try:
1604
1765
                        sent = session.send(client.secret[sent_size:])
1605
1766
                    except gnutls.errors.GNUTLSError as error:
1606
 
                        logger.warning("gnutls send failed")
 
1767
                        logger.warning("gnutls send failed",
 
1768
                                       exc_info=error)
1607
1769
                        return
1608
1770
                    logger.debug("Sent: %d, remaining: %d",
1609
1771
                                 sent, len(client.secret)
1612
1774
                
1613
1775
                logger.info("Sending secret to %s", client.name)
1614
1776
                # bump the timeout using extended_timeout
1615
 
                client.checked_ok(client.extended_timeout)
 
1777
                client.bump_timeout(client.extended_timeout)
1616
1778
                if self.server.use_dbus:
1617
1779
                    # Emit D-Bus signal
1618
1780
                    client.GotSecret()
1623
1785
                try:
1624
1786
                    session.bye()
1625
1787
                except gnutls.errors.GNUTLSError as error:
1626
 
                    logger.warning("GnuTLS bye failed")
 
1788
                    logger.warning("GnuTLS bye failed",
 
1789
                                   exc_info=error)
1627
1790
    
1628
1791
    @staticmethod
1629
1792
    def peer_certificate(session):
1941
2104
            elif suffix == "w":
1942
2105
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1943
2106
            else:
1944
 
                raise ValueError("Unknown suffix %r" % suffix)
 
2107
                raise ValueError("Unknown suffix {0!r}"
 
2108
                                 .format(suffix))
1945
2109
        except (ValueError, IndexError) as e:
1946
2110
            raise ValueError(*(e.args))
1947
2111
        timevalue += delta
1961
2125
        sys.exit()
1962
2126
    if not noclose:
1963
2127
        # Close all standard open file descriptors
1964
 
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
2128
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
1965
2129
        if not stat.S_ISCHR(os.fstat(null).st_mode):
1966
2130
            raise OSError(errno.ENODEV,
1967
 
                          "%s not a character device"
1968
 
                          % os.path.devnull)
 
2131
                          "{0} not a character device"
 
2132
                          .format(os.devnull))
1969
2133
        os.dup2(null, sys.stdin.fileno())
1970
2134
        os.dup2(null, sys.stdout.fileno())
1971
2135
        os.dup2(null, sys.stderr.fileno())
1980
2144
    
1981
2145
    parser = argparse.ArgumentParser()
1982
2146
    parser.add_argument("-v", "--version", action="version",
1983
 
                        version = "%%(prog)s %s" % version,
 
2147
                        version = "%(prog)s {0}".format(version),
1984
2148
                        help="show version number and exit")
1985
2149
    parser.add_argument("-i", "--interface", metavar="IF",
1986
2150
                        help="Bind to interface IF")
2089
2253
    
2090
2254
    if server_settings["servicename"] != "Mandos":
2091
2255
        syslogger.setFormatter(logging.Formatter
2092
 
                               ('Mandos (%s) [%%(process)d]:'
2093
 
                                ' %%(levelname)s: %%(message)s'
2094
 
                                % server_settings["servicename"]))
 
2256
                               ('Mandos ({0}) [%(process)d]:'
 
2257
                                ' %(levelname)s: %(message)s'
 
2258
                                .format(server_settings
 
2259
                                        ["servicename"])))
2095
2260
    
2096
2261
    # Parse config file with clients
2097
 
    client_config = configparser.SafeConfigParser(Client.client_defaults)
 
2262
    client_config = configparser.SafeConfigParser(Client
 
2263
                                                  .client_defaults)
2098
2264
    client_config.read(os.path.join(server_settings["configdir"],
2099
2265
                                    "clients.conf"))
2100
2266
    
2114
2280
        pidfilename = "/var/run/mandos.pid"
2115
2281
        try:
2116
2282
            pidfile = open(pidfilename, "w")
2117
 
        except IOError:
2118
 
            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)
2119
2286
    
2120
 
    try:
2121
 
        uid = pwd.getpwnam("_mandos").pw_uid
2122
 
        gid = pwd.getpwnam("_mandos").pw_gid
2123
 
    except KeyError:
 
2287
    for name in ("_mandos", "mandos", "nobody"):
2124
2288
        try:
2125
 
            uid = pwd.getpwnam("mandos").pw_uid
2126
 
            gid = pwd.getpwnam("mandos").pw_gid
 
2289
            uid = pwd.getpwnam(name).pw_uid
 
2290
            gid = pwd.getpwnam(name).pw_gid
 
2291
            break
2127
2292
        except KeyError:
2128
 
            try:
2129
 
                uid = pwd.getpwnam("nobody").pw_uid
2130
 
                gid = pwd.getpwnam("nobody").pw_gid
2131
 
            except KeyError:
2132
 
                uid = 65534
2133
 
                gid = 65534
 
2293
            continue
 
2294
    else:
 
2295
        uid = 65534
 
2296
        gid = 65534
2134
2297
    try:
2135
2298
        os.setgid(gid)
2136
2299
        os.setuid(uid)
2153
2316
         .gnutls_global_set_log_function(debug_gnutls))
2154
2317
        
2155
2318
        # Redirect stdin so all checkers get /dev/null
2156
 
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
2319
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2157
2320
        os.dup2(null, sys.stdin.fileno())
2158
2321
        if null > 2:
2159
2322
            os.close(null)
2167
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
2219
2384
        except EOFError as e:
2220
2385
            logger.warning("Could not load persistent state: "
2221
 
                           "EOFError: {0}".format(e))
 
2386
                           "EOFError:", exc_info=e)
2222
2387
    
2223
2388
    with PGPEngine() as pgp:
2224
2389
        for client_name, client in clients_data.iteritems():
2241
2406
            
2242
2407
            # Clients who has passed its expire date can still be
2243
2408
            # enabled if its last checker was successful.  Clients
2244
 
            # whose checker failed before we stored its state is
2245
 
            # 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.
2246
2412
            if client["enabled"]:
2247
2413
                if datetime.datetime.utcnow() >= client["expires"]:
2248
2414
                    if not client["last_checked_ok"]:
2249
2415
                        logger.warning(
2250
2416
                            "disabling client {0} - Client never "
2251
 
                            "performed a successfull checker"
2252
 
                            .format(client["name"]))
 
2417
                            "performed a successful checker"
 
2418
                            .format(client_name))
2253
2419
                        client["enabled"] = False
2254
2420
                    elif client["last_checker_status"] != 0:
2255
2421
                        logger.warning(
2256
2422
                            "disabling client {0} - Client "
2257
2423
                            "last checker failed with error code {1}"
2258
 
                            .format(client["name"],
 
2424
                            .format(client_name,
2259
2425
                                    client["last_checker_status"]))
2260
2426
                        client["enabled"] = False
2261
2427
                    else:
2262
2428
                        client["expires"] = (datetime.datetime
2263
2429
                                             .utcnow()
2264
2430
                                             + client["timeout"])
2265
 
                    
 
2431
                        logger.debug("Last checker succeeded,"
 
2432
                                     " keeping {0} enabled"
 
2433
                                     .format(client_name))
2266
2434
            try:
2267
2435
                client["secret"] = (
2268
2436
                    pgp.decrypt(client["encrypted_secret"],
2274
2442
                             .format(client_name))
2275
2443
                client["secret"] = (
2276
2444
                    client_settings[client_name]["secret"])
2277
 
 
2278
2445
    
2279
2446
    # Add/remove clients based on new changes made to config
2280
 
    for client_name in set(old_client_settings) - set(client_settings):
 
2447
    for client_name in (set(old_client_settings)
 
2448
                        - set(client_settings)):
2281
2449
        del clients_data[client_name]
2282
 
    for client_name in set(client_settings) - set(old_client_settings):
 
2450
    for client_name in (set(client_settings)
 
2451
                        - set(old_client_settings)):
2283
2452
        clients_data[client_name] = client_settings[client_name]
2284
 
 
2285
 
    # Create clients all clients
 
2453
    
 
2454
    # Create all client objects
2286
2455
    for client_name, client in clients_data.iteritems():
2287
2456
        tcp_server.clients[client_name] = client_class(
2288
2457
            name = client_name, settings = client)
2289
2458
    
2290
2459
    if not tcp_server.clients:
2291
2460
        logger.warning("No clients defined")
2292
 
        
 
2461
    
2293
2462
    if not debug:
2294
2463
        try:
2295
2464
            with pidfile:
2309
2478
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2310
2479
    
2311
2480
    if use_dbus:
2312
 
        class MandosDBusService(dbus.service.Object):
 
2481
        @alternate_dbus_interfaces({"se.recompile.Mandos":
 
2482
                                        "se.bsnet.fukt.Mandos"})
 
2483
        class MandosDBusService(DBusObjectWithProperties):
2313
2484
            """A D-Bus proxy object"""
2314
2485
            def __init__(self):
2315
2486
                dbus.service.Object.__init__(self, bus, "/")
2316
2487
            _interface = "se.recompile.Mandos"
2317
2488
            
 
2489
            @dbus_interface_annotations(_interface)
 
2490
            def _foo(self):
 
2491
                return { "org.freedesktop.DBus.Property"
 
2492
                         ".EmitsChangedSignal":
 
2493
                             "false"}
 
2494
            
2318
2495
            @dbus.service.signal(_interface, signature="o")
2319
2496
            def ClientAdded(self, objpath):
2320
2497
                "D-Bus signal"
2362
2539
            
2363
2540
            del _interface
2364
2541
        
2365
 
        class MandosDBusServiceTransitional(MandosDBusService):
2366
 
            __metaclass__ = AlternateDBusNamesMetaclass
2367
 
        mandos_dbus_service = MandosDBusServiceTransitional()
 
2542
        mandos_dbus_service = MandosDBusService()
2368
2543
    
2369
2544
    def cleanup():
2370
2545
        "Cleanup function; run on exit"
2411
2586
                pickle.dump((clients, client_settings), stored_state)
2412
2587
            os.rename(tempname, stored_state_path)
2413
2588
        except (IOError, OSError) as e:
2414
 
            logger.warning("Could not save persistent state: {0}"
2415
 
                           .format(e))
2416
2589
            if not debug:
2417
2590
                try:
2418
2591
                    os.remove(tempname)
2419
2592
                except NameError:
2420
2593
                    pass
2421
 
            if e.errno not in set((errno.ENOENT, errno.EACCES,
2422
 
                                   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)
2423
2600
                raise e
2424
2601
        
2425
2602
        # Delete all clients, and settings from config
2453
2630
    service.port = tcp_server.socket.getsockname()[1]
2454
2631
    if use_ipv6:
2455
2632
        logger.info("Now listening on address %r, port %d,"
2456
 
                    " flowinfo %d, scope_id %d"
2457
 
                    % tcp_server.socket.getsockname())
 
2633
                    " flowinfo %d, scope_id %d",
 
2634
                    *tcp_server.socket.getsockname())
2458
2635
    else:                       # IPv4
2459
 
        logger.info("Now listening on address %r, port %d"
2460
 
                    % tcp_server.socket.getsockname())
 
2636
        logger.info("Now listening on address %r, port %d",
 
2637
                    *tcp_server.socket.getsockname())
2461
2638
    
2462
2639
    #service.interface = tcp_server.socket.getsockname()[3]
2463
2640
    
2466
2643
        try:
2467
2644
            service.activate()
2468
2645
        except dbus.exceptions.DBusException as error:
2469
 
            logger.critical("DBusException: %s", error)
 
2646
            logger.critical("D-Bus Exception", exc_info=error)
2470
2647
            cleanup()
2471
2648
            sys.exit(1)
2472
2649
        # End of Avahi example code
2479
2656
        logger.debug("Starting main loop")
2480
2657
        main_loop.run()
2481
2658
    except AvahiError as error:
2482
 
        logger.critical("AvahiError: %s", error)
 
2659
        logger.critical("Avahi Error", exc_info=error)
2483
2660
        cleanup()
2484
2661
        sys.exit(1)
2485
2662
    except KeyboardInterrupt: