/mandos/release

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

« back to all changes in this revision

Viewing changes to mandos

Merge general D-Bus annotations support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
11
11
# "AvahiService" class, and some lines in "main".
12
12
13
13
# Everything else is
14
 
# Copyright © 2008-2011 Teddy Hogeborn
15
 
# Copyright © 2008-2011 Björn Påhlsson
 
14
# Copyright © 2008-2012 Teddy Hogeborn
 
15
# Copyright © 2008-2012 Björn Påhlsson
16
16
17
17
# This program is free software: you can redistribute it and/or modify
18
18
# it under the terms of the GNU General Public License as published by
65
65
import types
66
66
import binascii
67
67
import tempfile
 
68
import itertools
68
69
 
69
70
import dbus
70
71
import dbus.service
85
86
    except ImportError:
86
87
        SO_BINDTODEVICE = None
87
88
 
88
 
version = "1.4.1"
 
89
version = "1.5.3"
89
90
stored_state_file = "clients.pickle"
90
91
 
91
92
logger = logging.getLogger()
110
111
        return interface_index
111
112
 
112
113
 
113
 
def initlogger(level=logging.WARNING):
 
114
def initlogger(debug, level=logging.WARNING):
114
115
    """init logger and add loglevel"""
115
116
    
116
117
    syslogger.setFormatter(logging.Formatter
118
119
                            ' %(message)s'))
119
120
    logger.addHandler(syslogger)
120
121
    
121
 
    console = logging.StreamHandler()
122
 
    console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
123
 
                                           ' [%(process)d]:'
124
 
                                           ' %(levelname)s:'
125
 
                                           ' %(message)s'))
126
 
    logger.addHandler(console)
 
122
    if debug:
 
123
        console = logging.StreamHandler()
 
124
        console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
 
125
                                               ' [%(process)d]:'
 
126
                                               ' %(levelname)s:'
 
127
                                               ' %(message)s'))
 
128
        logger.addHandler(console)
127
129
    logger.setLevel(level)
128
130
 
129
131
 
141
143
        self.gnupg.options.meta_interactive = False
142
144
        self.gnupg.options.homedir = self.tempdir
143
145
        self.gnupg.options.extra_args.extend(['--force-mdc',
144
 
                                              '--quiet'])
 
146
                                              '--quiet',
 
147
                                              '--no-use-agent'])
145
148
    
146
149
    def __enter__(self):
147
150
        return self
173
176
    
174
177
    def encrypt(self, data, password):
175
178
        self.gnupg.passphrase = self.password_encode(password)
176
 
        with open(os.devnull) as devnull:
 
179
        with open(os.devnull, "w") as devnull:
177
180
            try:
178
181
                proc = self.gnupg.run(['--symmetric'],
179
182
                                      create_fhs=['stdin', 'stdout'],
190
193
    
191
194
    def decrypt(self, data, password):
192
195
        self.gnupg.passphrase = self.password_encode(password)
193
 
        with open(os.devnull) as devnull:
 
196
        with open(os.devnull, "w") as devnull:
194
197
            try:
195
198
                proc = self.gnupg.run(['--decrypt'],
196
199
                                      create_fhs=['stdin', 'stdout'],
197
200
                                      attach_fhs={'stderr': devnull})
198
 
                with contextlib.closing(proc.handles['stdin'] ) as f:
 
201
                with contextlib.closing(proc.handles['stdin']) as f:
199
202
                    f.write(data)
200
203
                with contextlib.closing(proc.handles['stdout']) as f:
201
204
                    decrypted_plaintext = f.read()
413
416
    last_checked_ok: datetime.datetime(); (UTC) or None
414
417
    last_checker_status: integer between 0 and 255 reflecting exit
415
418
                         status of last checker. -1 reflects crashed
416
 
                         checker, or None.
 
419
                         checker, -2 means no checker completed yet.
417
420
    last_enabled: datetime.datetime(); (UTC) or None
418
421
    name:       string; from the config file, used in log messages and
419
422
                        D-Bus identifiers
420
423
    secret:     bytestring; sent verbatim (over TLS) to client
421
424
    timeout:    datetime.timedelta(); How long from last_checked_ok
422
425
                                      until this client is disabled
423
 
    extended_timeout:   extra long timeout when password has been sent
 
426
    extended_timeout:   extra long timeout when secret has been sent
424
427
    runtime_expansions: Allowed attributes for runtime expansion.
425
428
    expires:    datetime.datetime(); time (UTC) when a client will be
426
429
                disabled, or None
458
461
 
459
462
    @staticmethod
460
463
    def config_parser(config):
461
 
        """ Construct a new dict of client settings of this form:
 
464
        """Construct a new dict of client settings of this form:
462
465
        { client_name: {setting_name: value, ...}, ...}
463
 
        with exceptions for any special settings as defined above"""
 
466
        with exceptions for any special settings as defined above.
 
467
        NOTE: Must be a pure function. Must return the same result
 
468
        value given the same arguments.
 
469
        """
464
470
        settings = {}
465
471
        for client_name in config.sections():
466
472
            section = dict(config.items(client_name))
470
476
            # Reformat values from string types to Python types
471
477
            client["approved_by_default"] = config.getboolean(
472
478
                client_name, "approved_by_default")
473
 
            client["enabled"] = config.getboolean(client_name, "enabled")
 
479
            client["enabled"] = config.getboolean(client_name,
 
480
                                                  "enabled")
474
481
            
475
482
            client["fingerprint"] = (section["fingerprint"].upper()
476
483
                                     .replace(" ", ""))
495
502
            client["checker_command"] = section["checker"]
496
503
            client["last_approval_request"] = None
497
504
            client["last_checked_ok"] = None
498
 
            client["last_checker_status"] = None
499
 
            if client["enabled"]:
500
 
                client["last_enabled"] = datetime.datetime.utcnow()
501
 
                client["expires"] = (datetime.datetime.utcnow()
502
 
                                     + client["timeout"])
503
 
            else:
504
 
                client["last_enabled"] = None
505
 
                client["expires"] = None
506
 
 
 
505
            client["last_checker_status"] = -2
 
506
        
507
507
        return settings
508
508
        
509
509
        
516
516
        for setting, value in settings.iteritems():
517
517
            setattr(self, setting, value)
518
518
        
 
519
        if self.enabled:
 
520
            if not hasattr(self, "last_enabled"):
 
521
                self.last_enabled = datetime.datetime.utcnow()
 
522
            if not hasattr(self, "expires"):
 
523
                self.expires = (datetime.datetime.utcnow()
 
524
                                + self.timeout)
 
525
        else:
 
526
            self.last_enabled = None
 
527
            self.expires = None
 
528
       
519
529
        logger.debug("Creating client %r", self.name)
520
530
        # Uppercase and remove spaces from fingerprint for later
521
531
        # comparison purposes with return value from the fingerprint()
522
532
        # function
523
533
        logger.debug("  Fingerprint: %s", self.fingerprint)
524
 
        self.created = settings.get("created", datetime.datetime.utcnow())
 
534
        self.created = settings.get("created",
 
535
                                    datetime.datetime.utcnow())
525
536
 
526
537
        # attributes specific for this server instance
527
538
        self.checker = None
616
627
            logger.warning("Checker for %(name)s crashed?",
617
628
                           vars(self))
618
629
    
619
 
    def checked_ok(self, timeout=None):
620
 
        """Bump up the timeout for this client.
621
 
        
622
 
        This should only be called when the client has been seen,
623
 
        alive and well.
624
 
        """
 
630
    def checked_ok(self):
 
631
        """Assert that the client has been seen, alive and well."""
 
632
        self.last_checked_ok = datetime.datetime.utcnow()
 
633
        self.last_checker_status = 0
 
634
        self.bump_timeout()
 
635
    
 
636
    def bump_timeout(self, timeout=None):
 
637
        """Bump up the timeout for this client."""
625
638
        if timeout is None:
626
639
            timeout = self.timeout
627
 
        self.last_checked_ok = datetime.datetime.utcnow()
628
640
        if self.disable_initiator_tag is not None:
629
641
            gobject.source_remove(self.disable_initiator_tag)
630
642
        if getattr(self, "enabled", False):
760
772
    return decorator
761
773
 
762
774
 
 
775
def dbus_interface_annotations(dbus_interface):
 
776
    """Decorator for marking functions returning interface annotations.
 
777
    
 
778
    Usage:
 
779
    
 
780
    @dbus_interface_annotations("org.example.Interface")
 
781
    def _foo(self):  # Function name does not matter
 
782
        return {"org.freedesktop.DBus.Deprecated": "true",
 
783
                "org.freedesktop.DBus.Property.EmitsChangedSignal":
 
784
                    "false"}
 
785
    """
 
786
    def decorator(func):
 
787
        func._dbus_is_interface = True
 
788
        func._dbus_interface = dbus_interface
 
789
        func._dbus_name = dbus_interface
 
790
        return func
 
791
    return decorator
 
792
 
 
793
 
 
794
def dbus_annotations(annotations):
 
795
    """Decorator to annotate D-Bus methods, signals or properties
 
796
    Usage:
 
797
    
 
798
    @dbus_service_property("org.example.Interface", signature="b",
 
799
                           access="r")
 
800
    @dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
 
801
                        "org.freedesktop.DBus.Property."
 
802
                        "EmitsChangedSignal": "false"})
 
803
    def Property_dbus_property(self):
 
804
        return dbus.Boolean(False)
 
805
    """
 
806
    def decorator(func):
 
807
        func._dbus_annotations = annotations
 
808
        return func
 
809
    return decorator
 
810
 
 
811
 
763
812
class DBusPropertyException(dbus.exceptions.DBusException):
764
813
    """A base class for D-Bus property-related exceptions
765
814
    """
788
837
    """
789
838
    
790
839
    @staticmethod
791
 
    def _is_dbus_property(obj):
792
 
        return getattr(obj, "_dbus_is_property", False)
 
840
    def _is_dbus_thing(thing):
 
841
        """Returns a function testing if an attribute is a D-Bus thing
 
842
        
 
843
        If called like _is_dbus_thing("method") it returns a function
 
844
        suitable for use as predicate to inspect.getmembers().
 
845
        """
 
846
        return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
 
847
                                   False)
793
848
    
794
 
    def _get_all_dbus_properties(self):
 
849
    def _get_all_dbus_things(self, thing):
795
850
        """Returns a generator of (name, attribute) pairs
796
851
        """
797
 
        return ((prop.__get__(self)._dbus_name, prop.__get__(self))
 
852
        return ((getattr(athing.__get__(self), "_dbus_name",
 
853
                         name),
 
854
                 athing.__get__(self))
798
855
                for cls in self.__class__.__mro__
799
 
                for name, prop in
800
 
                inspect.getmembers(cls, self._is_dbus_property))
 
856
                for name, athing in
 
857
                inspect.getmembers(cls,
 
858
                                   self._is_dbus_thing(thing)))
801
859
    
802
860
    def _get_dbus_property(self, interface_name, property_name):
803
861
        """Returns a bound method if one exists which is a D-Bus
805
863
        """
806
864
        for cls in  self.__class__.__mro__:
807
865
            for name, value in (inspect.getmembers
808
 
                                (cls, self._is_dbus_property)):
 
866
                                (cls,
 
867
                                 self._is_dbus_thing("property"))):
809
868
                if (value._dbus_name == property_name
810
869
                    and value._dbus_interface == interface_name):
811
870
                    return value.__get__(self)
840
899
            # signatures other than "ay".
841
900
            if prop._dbus_signature != "ay":
842
901
                raise ValueError
843
 
            value = dbus.ByteArray(''.join(unichr(byte)
844
 
                                           for byte in value))
 
902
            value = dbus.ByteArray(b''.join(chr(byte)
 
903
                                            for byte in value))
845
904
        prop(value)
846
905
    
847
906
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
853
912
        Note: Will not include properties with access="write".
854
913
        """
855
914
        properties = {}
856
 
        for name, prop in self._get_all_dbus_properties():
 
915
        for name, prop in self._get_all_dbus_things("property"):
857
916
            if (interface_name
858
917
                and interface_name != prop._dbus_interface):
859
918
                # Interface non-empty but did not match
874
933
                         path_keyword='object_path',
875
934
                         connection_keyword='connection')
876
935
    def Introspect(self, object_path, connection):
877
 
        """Standard D-Bus method, overloaded to insert property tags.
 
936
        """Overloading of standard D-Bus method.
 
937
        
 
938
        Inserts property tags and interface annotation tags.
878
939
        """
879
940
        xmlstring = dbus.service.Object.Introspect(self, object_path,
880
941
                                                   connection)
887
948
                e.setAttribute("access", prop._dbus_access)
888
949
                return e
889
950
            for if_tag in document.getElementsByTagName("interface"):
 
951
                # Add property tags
890
952
                for tag in (make_tag(document, name, prop)
891
953
                            for name, prop
892
 
                            in self._get_all_dbus_properties()
 
954
                            in self._get_all_dbus_things("property")
893
955
                            if prop._dbus_interface
894
956
                            == if_tag.getAttribute("name")):
895
957
                    if_tag.appendChild(tag)
 
958
                # Add annotation tags
 
959
                for typ in ("method", "signal", "property"):
 
960
                    for tag in if_tag.getElementsByTagName(typ):
 
961
                        annots = dict()
 
962
                        for name, prop in (self.
 
963
                                           _get_all_dbus_things(typ)):
 
964
                            if (name == tag.getAttribute("name")
 
965
                                and prop._dbus_interface
 
966
                                == if_tag.getAttribute("name")):
 
967
                                annots.update(getattr
 
968
                                              (prop,
 
969
                                               "_dbus_annotations",
 
970
                                               {}))
 
971
                        for name, value in annots.iteritems():
 
972
                            ann_tag = document.createElement(
 
973
                                "annotation")
 
974
                            ann_tag.setAttribute("name", name)
 
975
                            ann_tag.setAttribute("value", value)
 
976
                            tag.appendChild(ann_tag)
 
977
                # Add interface annotation tags
 
978
                for annotation, value in dict(
 
979
                    itertools.chain(
 
980
                        *(annotations().iteritems()
 
981
                          for name, annotations in
 
982
                          self._get_all_dbus_things("interface")
 
983
                          if name == if_tag.getAttribute("name")
 
984
                          ))).iteritems():
 
985
                    ann_tag = document.createElement("annotation")
 
986
                    ann_tag.setAttribute("name", annotation)
 
987
                    ann_tag.setAttribute("value", value)
 
988
                    if_tag.appendChild(ann_tag)
896
989
                # Add the names to the return values for the
897
990
                # "org.freedesktop.DBus.Properties" methods
898
991
                if (if_tag.getAttribute("name")
933
1026
    def __new__(mcs, name, bases, attr):
934
1027
        # Go through all the base classes which could have D-Bus
935
1028
        # methods, signals, or properties in them
 
1029
        old_interface_names = []
936
1030
        for base in (b for b in bases
937
1031
                     if issubclass(b, dbus.service.Object)):
938
1032
            # Go though all attributes of the base class
948
1042
                alt_interface = (attribute._dbus_interface
949
1043
                                 .replace("se.recompile.Mandos",
950
1044
                                          "se.bsnet.fukt.Mandos"))
 
1045
                if alt_interface != attribute._dbus_interface:
 
1046
                    old_interface_names.append(alt_interface)
951
1047
                # Is this a D-Bus signal?
952
1048
                if getattr(attribute, "_dbus_is_signal", False):
953
1049
                    # Extract the original non-method function by
968
1064
                                nonmethod_func.func_name,
969
1065
                                nonmethod_func.func_defaults,
970
1066
                                nonmethod_func.func_closure)))
 
1067
                    # Copy annotations, if any
 
1068
                    try:
 
1069
                        new_function._dbus_annotations = (
 
1070
                            dict(attribute._dbus_annotations))
 
1071
                    except AttributeError:
 
1072
                        pass
971
1073
                    # Define a creator of a function to call both the
972
1074
                    # old and new functions, so both the old and new
973
1075
                    # signals gets sent when the function is called
1001
1103
                                        attribute.func_name,
1002
1104
                                        attribute.func_defaults,
1003
1105
                                        attribute.func_closure)))
 
1106
                    # Copy annotations, if any
 
1107
                    try:
 
1108
                        attr[attrname]._dbus_annotations = (
 
1109
                            dict(attribute._dbus_annotations))
 
1110
                    except AttributeError:
 
1111
                        pass
1004
1112
                # Is this a D-Bus property?
1005
1113
                elif getattr(attribute, "_dbus_is_property", False):
1006
1114
                    # Create a new, but exactly alike, function
1020
1128
                                        attribute.func_name,
1021
1129
                                        attribute.func_defaults,
1022
1130
                                        attribute.func_closure)))
 
1131
                    # Copy annotations, if any
 
1132
                    try:
 
1133
                        attr[attrname]._dbus_annotations = (
 
1134
                            dict(attribute._dbus_annotations))
 
1135
                    except AttributeError:
 
1136
                        pass
 
1137
                # Is this a D-Bus interface?
 
1138
                elif getattr(attribute, "_dbus_is_interface", False):
 
1139
                    # Create a new, but exactly alike, function
 
1140
                    # object.  Decorate it to be a new D-Bus interface
 
1141
                    # with the alternate D-Bus interface name.  Add it
 
1142
                    # to the class.
 
1143
                    attr[attrname] = (dbus_interface_annotations
 
1144
                                      (alt_interface)
 
1145
                                      (types.FunctionType
 
1146
                                       (attribute.func_code,
 
1147
                                        attribute.func_globals,
 
1148
                                        attribute.func_name,
 
1149
                                        attribute.func_defaults,
 
1150
                                        attribute.func_closure)))
 
1151
        # Deprecate all old interfaces
 
1152
        basename="_AlternateDBusNamesMetaclass_interface_annotation{0}"
 
1153
        for old_interface_name in old_interface_names:
 
1154
            @dbus_interface_annotations(old_interface_name)
 
1155
            def func(self):
 
1156
                return { "org.freedesktop.DBus.Deprecated": "true" }
 
1157
            # Find an unused name
 
1158
            for aname in (basename.format(i) for i in
 
1159
                          itertools.count()):
 
1160
                if aname not in attr:
 
1161
                    attr[aname] = func
 
1162
                    break
1023
1163
        return type.__new__(mcs, name, bases, attr)
1024
1164
 
1025
1165
 
1039
1179
    def __init__(self, bus = None, *args, **kwargs):
1040
1180
        self.bus = bus
1041
1181
        Client.__init__(self, *args, **kwargs)
1042
 
        self._approvals_pending = 0
1043
 
        
1044
 
        self._approvals_pending = 0
1045
1182
        # Only now, when this client is initialized, can it show up on
1046
1183
        # the D-Bus
1047
1184
        client_object_name = unicode(self.name).translate(
1093
1230
                                       checker is not None)
1094
1231
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
1095
1232
                                           "LastCheckedOK")
 
1233
    last_checker_status = notifychangeproperty(dbus.Int16,
 
1234
                                               "LastCheckerStatus")
1096
1235
    last_approval_request = notifychangeproperty(
1097
1236
        datetime_to_dbus, "LastApprovalRequest")
1098
1237
    approved_by_default = notifychangeproperty(dbus.Boolean,
1176
1315
    ## D-Bus methods, signals & properties
1177
1316
    _interface = "se.recompile.Mandos.Client"
1178
1317
    
 
1318
    ## Interfaces
 
1319
    
 
1320
    @dbus_interface_annotations(_interface)
 
1321
    def _foo(self):
 
1322
        return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
 
1323
                     "false"}
 
1324
    
1179
1325
    ## Signals
1180
1326
    
1181
1327
    # CheckerCompleted - signal
1217
1363
        "D-Bus signal"
1218
1364
        return self.need_approval()
1219
1365
    
1220
 
    # NeRwequest - signal
1221
 
    @dbus.service.signal(_interface, signature="s")
1222
 
    def NewRequest(self, ip):
1223
 
        """D-Bus signal
1224
 
        Is sent after a client request a password.
1225
 
        """
1226
 
        pass
1227
 
    
1228
1366
    ## Methods
1229
1367
    
1230
1368
    # Approve - method
1340
1478
            return
1341
1479
        return datetime_to_dbus(self.last_checked_ok)
1342
1480
    
 
1481
    # LastCheckerStatus - property
 
1482
    @dbus_service_property(_interface, signature="n",
 
1483
                           access="read")
 
1484
    def LastCheckerStatus_dbus_property(self):
 
1485
        return dbus.Int16(self.last_checker_status)
 
1486
    
1343
1487
    # Expires - property
1344
1488
    @dbus_service_property(_interface, signature="s", access="read")
1345
1489
    def Expires_dbus_property(self):
1357
1501
        if value is None:       # get
1358
1502
            return dbus.UInt64(self.timeout_milliseconds())
1359
1503
        self.timeout = datetime.timedelta(0, 0, 0, value)
1360
 
        if getattr(self, "disable_initiator_tag", None) is None:
1361
 
            return
1362
1504
        # Reschedule timeout
1363
 
        gobject.source_remove(self.disable_initiator_tag)
1364
 
        self.disable_initiator_tag = None
1365
 
        self.expires = None
1366
 
        time_to_die = timedelta_to_milliseconds((self
1367
 
                                                 .last_checked_ok
1368
 
                                                 + self.timeout)
1369
 
                                                - datetime.datetime
1370
 
                                                .utcnow())
1371
 
        if time_to_die <= 0:
1372
 
            # The timeout has passed
1373
 
            self.disable()
1374
 
        else:
1375
 
            self.expires = (datetime.datetime.utcnow()
1376
 
                            + datetime.timedelta(milliseconds =
1377
 
                                                 time_to_die))
1378
 
            self.disable_initiator_tag = (gobject.timeout_add
1379
 
                                          (time_to_die, self.disable))
 
1505
        if self.enabled:
 
1506
            now = datetime.datetime.utcnow()
 
1507
            time_to_die = timedelta_to_milliseconds(
 
1508
                (self.last_checked_ok + self.timeout) - now)
 
1509
            if time_to_die <= 0:
 
1510
                # The timeout has passed
 
1511
                self.disable()
 
1512
            else:
 
1513
                self.expires = (now +
 
1514
                                datetime.timedelta(milliseconds =
 
1515
                                                   time_to_die))
 
1516
                if (getattr(self, "disable_initiator_tag", None)
 
1517
                    is None):
 
1518
                    return
 
1519
                gobject.source_remove(self.disable_initiator_tag)
 
1520
                self.disable_initiator_tag = (gobject.timeout_add
 
1521
                                              (time_to_die,
 
1522
                                               self.disable))
1380
1523
    
1381
1524
    # ExtendedTimeout - property
1382
1525
    @dbus_service_property(_interface, signature="t",
1538
1681
                except KeyError:
1539
1682
                    return
1540
1683
                
1541
 
                if self.server.use_dbus:
1542
 
                    # Emit D-Bus signal
1543
 
                    client.NewRequest(str(self.client_address))
1544
 
                
1545
1684
                if client.approval_delay:
1546
1685
                    delay = client.approval_delay
1547
1686
                    client.approvals_pending += 1
1611
1750
                
1612
1751
                logger.info("Sending secret to %s", client.name)
1613
1752
                # bump the timeout using extended_timeout
1614
 
                client.checked_ok(client.extended_timeout)
 
1753
                client.bump_timeout(client.extended_timeout)
1615
1754
                if self.server.use_dbus:
1616
1755
                    # Emit D-Bus signal
1617
1756
                    client.GotSecret()
1960
2099
        sys.exit()
1961
2100
    if not noclose:
1962
2101
        # Close all standard open file descriptors
1963
 
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
2102
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
1964
2103
        if not stat.S_ISCHR(os.fstat(null).st_mode):
1965
2104
            raise OSError(errno.ENODEV,
1966
2105
                          "%s not a character device"
1967
 
                          % os.path.devnull)
 
2106
                          % os.devnull)
1968
2107
        os.dup2(null, sys.stdin.fileno())
1969
2108
        os.dup2(null, sys.stdout.fileno())
1970
2109
        os.dup2(null, sys.stderr.fileno())
2078
2217
                                     stored_state_file)
2079
2218
    
2080
2219
    if debug:
2081
 
        initlogger(logging.DEBUG)
 
2220
        initlogger(debug, logging.DEBUG)
2082
2221
    else:
2083
2222
        if not debuglevel:
2084
 
            initlogger()
 
2223
            initlogger(debug)
2085
2224
        else:
2086
2225
            level = getattr(logging, debuglevel.upper())
2087
 
            initlogger(level)
 
2226
            initlogger(debug, level)
2088
2227
    
2089
2228
    if server_settings["servicename"] != "Mandos":
2090
2229
        syslogger.setFormatter(logging.Formatter
2093
2232
                                % server_settings["servicename"]))
2094
2233
    
2095
2234
    # Parse config file with clients
2096
 
    client_config = configparser.SafeConfigParser(Client.client_defaults)
 
2235
    client_config = configparser.SafeConfigParser(Client
 
2236
                                                  .client_defaults)
2097
2237
    client_config.read(os.path.join(server_settings["configdir"],
2098
2238
                                    "clients.conf"))
2099
2239
    
2152
2292
         .gnutls_global_set_log_function(debug_gnutls))
2153
2293
        
2154
2294
        # Redirect stdin so all checkers get /dev/null
2155
 
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
2295
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2156
2296
        os.dup2(null, sys.stdin.fileno())
2157
2297
        if null > 2:
2158
2298
            os.close(null)
2159
 
    else:
2160
 
        # No console logging
2161
 
        logger.removeHandler(console)
2162
2299
    
2163
2300
    # Need to fork before connecting to D-Bus
2164
2301
    if not debug:
2169
2306
    
2170
2307
    global main_loop
2171
2308
    # From the Avahi example code
2172
 
    DBusGMainLoop(set_as_default=True )
 
2309
    DBusGMainLoop(set_as_default=True)
2173
2310
    main_loop = gobject.MainLoop()
2174
2311
    bus = dbus.SystemBus()
2175
2312
    # End of Avahi example code
2243
2380
            
2244
2381
            # Clients who has passed its expire date can still be
2245
2382
            # enabled if its last checker was successful.  Clients
2246
 
            # whose checker failed before we stored its state is
2247
 
            # assumed to have failed all checkers during downtime.
 
2383
            # whose checker succeeded before we stored its state is
 
2384
            # assumed to have successfully run all checkers during
 
2385
            # downtime.
2248
2386
            if client["enabled"]:
2249
2387
                if datetime.datetime.utcnow() >= client["expires"]:
2250
2388
                    if not client["last_checked_ok"]:
2251
2389
                        logger.warning(
2252
2390
                            "disabling client {0} - Client never "
2253
 
                            "performed a successfull checker"
2254
 
                            .format(client["name"]))
 
2391
                            "performed a successful checker"
 
2392
                            .format(client_name))
2255
2393
                        client["enabled"] = False
2256
2394
                    elif client["last_checker_status"] != 0:
2257
2395
                        logger.warning(
2258
2396
                            "disabling client {0} - Client "
2259
2397
                            "last checker failed with error code {1}"
2260
 
                            .format(client["name"],
 
2398
                            .format(client_name,
2261
2399
                                    client["last_checker_status"]))
2262
2400
                        client["enabled"] = False
2263
2401
                    else:
2264
2402
                        client["expires"] = (datetime.datetime
2265
2403
                                             .utcnow()
2266
2404
                                             + client["timeout"])
2267
 
                    
 
2405
                        logger.debug("Last checker succeeded,"
 
2406
                                     " keeping {0} enabled"
 
2407
                                     .format(client_name))
2268
2408
            try:
2269
2409
                client["secret"] = (
2270
2410
                    pgp.decrypt(client["encrypted_secret"],
2279
2419
 
2280
2420
    
2281
2421
    # Add/remove clients based on new changes made to config
2282
 
    for client_name in set(old_client_settings) - set(client_settings):
 
2422
    for client_name in (set(old_client_settings)
 
2423
                        - set(client_settings)):
2283
2424
        del clients_data[client_name]
2284
 
    for client_name in set(client_settings) - set(old_client_settings):
 
2425
    for client_name in (set(client_settings)
 
2426
                        - set(old_client_settings)):
2285
2427
        clients_data[client_name] = client_settings[client_name]
2286
2428
 
2287
 
    # Create clients all clients
 
2429
    # Create all client objects
2288
2430
    for client_name, client in clients_data.iteritems():
2289
2431
        tcp_server.clients[client_name] = client_class(
2290
2432
            name = client_name, settings = client)
2311
2453
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2312
2454
    
2313
2455
    if use_dbus:
2314
 
        class MandosDBusService(dbus.service.Object):
 
2456
        class MandosDBusService(DBusObjectWithProperties):
2315
2457
            """A D-Bus proxy object"""
2316
2458
            def __init__(self):
2317
2459
                dbus.service.Object.__init__(self, bus, "/")
2318
2460
            _interface = "se.recompile.Mandos"
2319
2461
            
 
2462
            @dbus_interface_annotations(_interface)
 
2463
            def _foo(self):
 
2464
                return { "org.freedesktop.DBus.Property"
 
2465
                         ".EmitsChangedSignal":
 
2466
                             "false"}
 
2467
            
2320
2468
            @dbus.service.signal(_interface, signature="o")
2321
2469
            def ClientAdded(self, objpath):
2322
2470
                "D-Bus signal"
2405
2553
                del client_settings[client.name]["secret"]
2406
2554
        
2407
2555
        try:
2408
 
            with os.fdopen(os.open(stored_state_path,
2409
 
                                   os.O_CREAT|os.O_WRONLY|os.O_TRUNC,
2410
 
                                   0600), "wb") as stored_state:
 
2556
            tempfd, tempname = tempfile.mkstemp(suffix=".pickle",
 
2557
                                                prefix="clients-",
 
2558
                                                dir=os.path.dirname
 
2559
                                                (stored_state_path))
 
2560
            with os.fdopen(tempfd, "wb") as stored_state:
2411
2561
                pickle.dump((clients, client_settings), stored_state)
 
2562
            os.rename(tempname, stored_state_path)
2412
2563
        except (IOError, OSError) as e:
2413
2564
            logger.warning("Could not save persistent state: {0}"
2414
2565
                           .format(e))
2415
 
            if e.errno not in (errno.ENOENT, errno.EACCES):
2416
 
                raise
 
2566
            if not debug:
 
2567
                try:
 
2568
                    os.remove(tempname)
 
2569
                except NameError:
 
2570
                    pass
 
2571
            if e.errno not in set((errno.ENOENT, errno.EACCES,
 
2572
                                   errno.EEXIST)):
 
2573
                raise e
2417
2574
        
2418
2575
        # Delete all clients, and settings from config
2419
2576
        while tcp_server.clients: