/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
63
63
import cPickle as pickle
64
64
import multiprocessing
65
65
import types
66
 
import hashlib
 
66
import binascii
 
67
import tempfile
 
68
import itertools
67
69
 
68
70
import dbus
69
71
import dbus.service
74
76
import ctypes.util
75
77
import xml.dom.minidom
76
78
import inspect
77
 
import Crypto.Cipher.AES
 
79
import GnuPGInterface
78
80
 
79
81
try:
80
82
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
84
86
    except ImportError:
85
87
        SO_BINDTODEVICE = None
86
88
 
87
 
 
88
 
version = "1.4.1"
89
 
stored_state_path = "/var/lib/mandos/clients.pickle"
 
89
version = "1.5.3"
 
90
stored_state_file = "clients.pickle"
90
91
 
91
92
logger = logging.getLogger()
92
93
syslogger = (logging.handlers.SysLogHandler
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
 
 
132
class PGPError(Exception):
 
133
    """Exception if encryption/decryption fails"""
 
134
    pass
 
135
 
 
136
 
 
137
class PGPEngine(object):
 
138
    """A simple class for OpenPGP symmetric encryption & decryption"""
 
139
    def __init__(self):
 
140
        self.gnupg = GnuPGInterface.GnuPG()
 
141
        self.tempdir = tempfile.mkdtemp(prefix="mandos-")
 
142
        self.gnupg = GnuPGInterface.GnuPG()
 
143
        self.gnupg.options.meta_interactive = False
 
144
        self.gnupg.options.homedir = self.tempdir
 
145
        self.gnupg.options.extra_args.extend(['--force-mdc',
 
146
                                              '--quiet',
 
147
                                              '--no-use-agent'])
 
148
    
 
149
    def __enter__(self):
 
150
        return self
 
151
    
 
152
    def __exit__ (self, exc_type, exc_value, traceback):
 
153
        self._cleanup()
 
154
        return False
 
155
    
 
156
    def __del__(self):
 
157
        self._cleanup()
 
158
    
 
159
    def _cleanup(self):
 
160
        if self.tempdir is not None:
 
161
            # Delete contents of tempdir
 
162
            for root, dirs, files in os.walk(self.tempdir,
 
163
                                             topdown = False):
 
164
                for filename in files:
 
165
                    os.remove(os.path.join(root, filename))
 
166
                for dirname in dirs:
 
167
                    os.rmdir(os.path.join(root, dirname))
 
168
            # Remove tempdir
 
169
            os.rmdir(self.tempdir)
 
170
            self.tempdir = None
 
171
    
 
172
    def password_encode(self, password):
 
173
        # Passphrase can not be empty and can not contain newlines or
 
174
        # NUL bytes.  So we prefix it and hex encode it.
 
175
        return b"mandos" + binascii.hexlify(password)
 
176
    
 
177
    def encrypt(self, data, password):
 
178
        self.gnupg.passphrase = self.password_encode(password)
 
179
        with open(os.devnull, "w") as devnull:
 
180
            try:
 
181
                proc = self.gnupg.run(['--symmetric'],
 
182
                                      create_fhs=['stdin', 'stdout'],
 
183
                                      attach_fhs={'stderr': devnull})
 
184
                with contextlib.closing(proc.handles['stdin']) as f:
 
185
                    f.write(data)
 
186
                with contextlib.closing(proc.handles['stdout']) as f:
 
187
                    ciphertext = f.read()
 
188
                proc.wait()
 
189
            except IOError as e:
 
190
                raise PGPError(e)
 
191
        self.gnupg.passphrase = None
 
192
        return ciphertext
 
193
    
 
194
    def decrypt(self, data, password):
 
195
        self.gnupg.passphrase = self.password_encode(password)
 
196
        with open(os.devnull, "w") as devnull:
 
197
            try:
 
198
                proc = self.gnupg.run(['--decrypt'],
 
199
                                      create_fhs=['stdin', 'stdout'],
 
200
                                      attach_fhs={'stderr': devnull})
 
201
                with contextlib.closing(proc.handles['stdin']) as f:
 
202
                    f.write(data)
 
203
                with contextlib.closing(proc.handles['stdout']) as f:
 
204
                    decrypted_plaintext = f.read()
 
205
                proc.wait()
 
206
            except IOError as e:
 
207
                raise PGPError(e)
 
208
        self.gnupg.passphrase = None
 
209
        return decrypted_plaintext
 
210
 
 
211
 
 
212
 
130
213
class AvahiError(Exception):
131
214
    def __init__(self, value, *args, **kwargs):
132
215
        self.value = value
297
380
                                % self.name))
298
381
        return ret
299
382
 
300
 
def _timedelta_to_milliseconds(td):
 
383
def timedelta_to_milliseconds(td):
301
384
    "Convert a datetime.timedelta() to milliseconds"
302
385
    return ((td.days * 24 * 60 * 60 * 1000)
303
386
            + (td.seconds * 1000)
307
390
    """A representation of a client host served by this server.
308
391
    
309
392
    Attributes:
310
 
    _approved:   bool(); 'None' if not yet approved/disapproved
 
393
    approved:   bool(); 'None' if not yet approved/disapproved
311
394
    approval_delay: datetime.timedelta(); Time to wait for approval
312
395
    approval_duration: datetime.timedelta(); Duration of one approval
313
396
    checker:    subprocess.Popen(); a running checker process used
331
414
    interval:   datetime.timedelta(); How often to start a new checker
332
415
    last_approval_request: datetime.datetime(); (UTC) or None
333
416
    last_checked_ok: datetime.datetime(); (UTC) or None
334
 
 
335
417
    last_checker_status: integer between 0 and 255 reflecting exit
336
418
                         status of last checker. -1 reflects crashed
337
 
                         checker, or None.
338
 
    last_enabled: datetime.datetime(); (UTC)
 
419
                         checker, -2 means no checker completed yet.
 
420
    last_enabled: datetime.datetime(); (UTC) or None
339
421
    name:       string; from the config file, used in log messages and
340
422
                        D-Bus identifiers
341
423
    secret:     bytestring; sent verbatim (over TLS) to client
342
424
    timeout:    datetime.timedelta(); How long from last_checked_ok
343
425
                                      until this client is disabled
344
 
    extended_timeout:   extra long timeout when password has been sent
 
426
    extended_timeout:   extra long timeout when secret has been sent
345
427
    runtime_expansions: Allowed attributes for runtime expansion.
346
428
    expires:    datetime.datetime(); time (UTC) when a client will be
347
429
                disabled, or None
351
433
                          "created", "enabled", "fingerprint",
352
434
                          "host", "interval", "last_checked_ok",
353
435
                          "last_enabled", "name", "timeout")
 
436
    client_defaults = { "timeout": "5m",
 
437
                        "extended_timeout": "15m",
 
438
                        "interval": "2m",
 
439
                        "checker": "fping -q -- %%(host)s",
 
440
                        "host": "",
 
441
                        "approval_delay": "0s",
 
442
                        "approval_duration": "1s",
 
443
                        "approved_by_default": "True",
 
444
                        "enabled": "True",
 
445
                        }
354
446
    
355
447
    def timeout_milliseconds(self):
356
448
        "Return the 'timeout' attribute in milliseconds"
357
 
        return _timedelta_to_milliseconds(self.timeout)
 
449
        return timedelta_to_milliseconds(self.timeout)
358
450
    
359
451
    def extended_timeout_milliseconds(self):
360
452
        "Return the 'extended_timeout' attribute in milliseconds"
361
 
        return _timedelta_to_milliseconds(self.extended_timeout)
 
453
        return timedelta_to_milliseconds(self.extended_timeout)
362
454
    
363
455
    def interval_milliseconds(self):
364
456
        "Return the 'interval' attribute in milliseconds"
365
 
        return _timedelta_to_milliseconds(self.interval)
 
457
        return timedelta_to_milliseconds(self.interval)
366
458
    
367
459
    def approval_delay_milliseconds(self):
368
 
        return _timedelta_to_milliseconds(self.approval_delay)
369
 
    
370
 
    def __init__(self, name = None, config=None):
 
460
        return timedelta_to_milliseconds(self.approval_delay)
 
461
 
 
462
    @staticmethod
 
463
    def config_parser(config):
 
464
        """Construct a new dict of client settings of this form:
 
465
        { client_name: {setting_name: value, ...}, ...}
 
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
        """
 
470
        settings = {}
 
471
        for client_name in config.sections():
 
472
            section = dict(config.items(client_name))
 
473
            client = settings[client_name] = {}
 
474
            
 
475
            client["host"] = section["host"]
 
476
            # Reformat values from string types to Python types
 
477
            client["approved_by_default"] = config.getboolean(
 
478
                client_name, "approved_by_default")
 
479
            client["enabled"] = config.getboolean(client_name,
 
480
                                                  "enabled")
 
481
            
 
482
            client["fingerprint"] = (section["fingerprint"].upper()
 
483
                                     .replace(" ", ""))
 
484
            if "secret" in section:
 
485
                client["secret"] = section["secret"].decode("base64")
 
486
            elif "secfile" in section:
 
487
                with open(os.path.expanduser(os.path.expandvars
 
488
                                             (section["secfile"])),
 
489
                          "rb") as secfile:
 
490
                    client["secret"] = secfile.read()
 
491
            else:
 
492
                raise TypeError("No secret or secfile for section %s"
 
493
                                % section)
 
494
            client["timeout"] = string_to_delta(section["timeout"])
 
495
            client["extended_timeout"] = string_to_delta(
 
496
                section["extended_timeout"])
 
497
            client["interval"] = string_to_delta(section["interval"])
 
498
            client["approval_delay"] = string_to_delta(
 
499
                section["approval_delay"])
 
500
            client["approval_duration"] = string_to_delta(
 
501
                section["approval_duration"])
 
502
            client["checker_command"] = section["checker"]
 
503
            client["last_approval_request"] = None
 
504
            client["last_checked_ok"] = None
 
505
            client["last_checker_status"] = -2
 
506
        
 
507
        return settings
 
508
        
 
509
        
 
510
    def __init__(self, settings, name = None):
371
511
        """Note: the 'checker' key in 'config' sets the
372
512
        'checker_command' attribute and *not* the 'checker'
373
513
        attribute."""
374
514
        self.name = name
375
 
        if config is None:
376
 
            config = {}
 
515
        # adding all client settings
 
516
        for setting, value in settings.iteritems():
 
517
            setattr(self, setting, value)
 
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
       
377
529
        logger.debug("Creating client %r", self.name)
378
530
        # Uppercase and remove spaces from fingerprint for later
379
531
        # comparison purposes with return value from the fingerprint()
380
532
        # function
381
 
        self.fingerprint = (config["fingerprint"].upper()
382
 
                            .replace(" ", ""))
383
533
        logger.debug("  Fingerprint: %s", self.fingerprint)
384
 
        if "secret" in config:
385
 
            self.secret = config["secret"].decode("base64")
386
 
        elif "secfile" in config:
387
 
            with open(os.path.expanduser(os.path.expandvars
388
 
                                         (config["secfile"])),
389
 
                      "rb") as secfile:
390
 
                self.secret = secfile.read()
391
 
        else:
392
 
            raise TypeError("No secret or secfile for client %s"
393
 
                            % self.name)
394
 
        self.host = config.get("host", "")
395
 
        self.created = datetime.datetime.utcnow()
396
 
        self.enabled = True
397
 
        self.last_approval_request = None
398
 
        self.last_enabled = datetime.datetime.utcnow()
399
 
        self.last_checked_ok = None
400
 
        self.last_checker_status = None
401
 
        self.timeout = string_to_delta(config["timeout"])
402
 
        self.extended_timeout = string_to_delta(config
403
 
                                                ["extended_timeout"])
404
 
        self.interval = string_to_delta(config["interval"])
 
534
        self.created = settings.get("created",
 
535
                                    datetime.datetime.utcnow())
 
536
 
 
537
        # attributes specific for this server instance
405
538
        self.checker = None
406
539
        self.checker_initiator_tag = None
407
540
        self.disable_initiator_tag = None
408
 
        self.expires = datetime.datetime.utcnow() + self.timeout
409
541
        self.checker_callback_tag = None
410
 
        self.checker_command = config["checker"]
411
542
        self.current_checker_command = None
412
 
        self._approved = None
413
 
        self.approved_by_default = config.get("approved_by_default",
414
 
                                              True)
 
543
        self.approved = None
415
544
        self.approvals_pending = 0
416
 
        self.approval_delay = string_to_delta(
417
 
            config["approval_delay"])
418
 
        self.approval_duration = string_to_delta(
419
 
            config["approval_duration"])
420
545
        self.changedstate = (multiprocessing_manager
421
546
                             .Condition(multiprocessing_manager
422
547
                                        .Lock()))
489
614
        self.checker_callback_tag = None
490
615
        self.checker = None
491
616
        if os.WIFEXITED(condition):
492
 
            self.last_checker_status =  os.WEXITSTATUS(condition)
 
617
            self.last_checker_status = os.WEXITSTATUS(condition)
493
618
            if self.last_checker_status == 0:
494
619
                logger.info("Checker for %(name)s succeeded",
495
620
                            vars(self))
502
627
            logger.warning("Checker for %(name)s crashed?",
503
628
                           vars(self))
504
629
    
505
 
    def checked_ok(self, timeout=None):
506
 
        """Bump up the timeout for this client.
507
 
        
508
 
        This should only be called when the client has been seen,
509
 
        alive and well.
510
 
        """
 
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."""
511
638
        if timeout is None:
512
639
            timeout = self.timeout
513
 
        self.last_checked_ok = datetime.datetime.utcnow()
514
640
        if self.disable_initiator_tag is not None:
515
641
            gobject.source_remove(self.disable_initiator_tag)
516
642
        if getattr(self, "enabled", False):
517
643
            self.disable_initiator_tag = (gobject.timeout_add
518
 
                                          (_timedelta_to_milliseconds
 
644
                                          (timedelta_to_milliseconds
519
645
                                           (timeout), self.disable))
520
646
            self.expires = datetime.datetime.utcnow() + timeout
521
647
    
614
740
            if error.errno != errno.ESRCH: # No such process
615
741
                raise
616
742
        self.checker = None
617
 
    
618
 
    # Encrypts a client secret and stores it in a varible
619
 
    # encrypted_secret
620
 
    def encrypt_secret(self, key):
621
 
        # Encryption-key need to be of a specific size, so we hash
622
 
        # inputed key
623
 
        hasheng = hashlib.sha256()
624
 
        hasheng.update(key)
625
 
        encryptionkey = hasheng.digest()
626
 
        
627
 
        # Create validation hash so we know at decryption if it was
628
 
        # sucessful
629
 
        hasheng = hashlib.sha256()
630
 
        hasheng.update(self.secret)
631
 
        validationhash = hasheng.digest()
632
 
        
633
 
        # Encrypt secret
634
 
        iv = os.urandom(Crypto.Cipher.AES.block_size)
635
 
        ciphereng = Crypto.Cipher.AES.new(encryptionkey,
636
 
                                        Crypto.Cipher.AES.MODE_CFB,
637
 
                                          iv)
638
 
        ciphertext = ciphereng.encrypt(validationhash+self.secret)
639
 
        self.encrypted_secret = (ciphertext, iv)
640
 
    
641
 
    # Decrypt a encrypted client secret
642
 
    def decrypt_secret(self, key):
643
 
        # Decryption-key need to be of a specific size, so we hash inputed key
644
 
        hasheng = hashlib.sha256()
645
 
        hasheng.update(key)
646
 
        encryptionkey = hasheng.digest()
647
 
        
648
 
        # Decrypt encrypted secret
649
 
        ciphertext, iv = self.encrypted_secret
650
 
        ciphereng = Crypto.Cipher.AES.new(encryptionkey,
651
 
                                        Crypto.Cipher.AES.MODE_CFB,
652
 
                                          iv)
653
 
        plain = ciphereng.decrypt(ciphertext)
654
 
        
655
 
        # Validate decrypted secret to know if it was succesful
656
 
        hasheng = hashlib.sha256()
657
 
        validationhash = plain[:hasheng.digest_size]
658
 
        secret = plain[hasheng.digest_size:]
659
 
        hasheng.update(secret)
660
 
        
661
 
        # if validation fails, we use key as new secret. Otherwhise,
662
 
        # we use the decrypted secret
663
 
        if hasheng.digest() == validationhash:
664
 
            self.secret = secret
665
 
        else:
666
 
            self.secret = key
667
 
        del self.encrypted_secret
668
743
 
669
744
 
670
745
def dbus_service_property(dbus_interface, signature="v",
697
772
    return decorator
698
773
 
699
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
 
700
812
class DBusPropertyException(dbus.exceptions.DBusException):
701
813
    """A base class for D-Bus property-related exceptions
702
814
    """
725
837
    """
726
838
    
727
839
    @staticmethod
728
 
    def _is_dbus_property(obj):
729
 
        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)
730
848
    
731
 
    def _get_all_dbus_properties(self):
 
849
    def _get_all_dbus_things(self, thing):
732
850
        """Returns a generator of (name, attribute) pairs
733
851
        """
734
 
        return ((prop.__get__(self)._dbus_name, prop.__get__(self))
 
852
        return ((getattr(athing.__get__(self), "_dbus_name",
 
853
                         name),
 
854
                 athing.__get__(self))
735
855
                for cls in self.__class__.__mro__
736
 
                for name, prop in
737
 
                inspect.getmembers(cls, self._is_dbus_property))
 
856
                for name, athing in
 
857
                inspect.getmembers(cls,
 
858
                                   self._is_dbus_thing(thing)))
738
859
    
739
860
    def _get_dbus_property(self, interface_name, property_name):
740
861
        """Returns a bound method if one exists which is a D-Bus
742
863
        """
743
864
        for cls in  self.__class__.__mro__:
744
865
            for name, value in (inspect.getmembers
745
 
                                (cls, self._is_dbus_property)):
 
866
                                (cls,
 
867
                                 self._is_dbus_thing("property"))):
746
868
                if (value._dbus_name == property_name
747
869
                    and value._dbus_interface == interface_name):
748
870
                    return value.__get__(self)
777
899
            # signatures other than "ay".
778
900
            if prop._dbus_signature != "ay":
779
901
                raise ValueError
780
 
            value = dbus.ByteArray(''.join(unichr(byte)
781
 
                                           for byte in value))
 
902
            value = dbus.ByteArray(b''.join(chr(byte)
 
903
                                            for byte in value))
782
904
        prop(value)
783
905
    
784
906
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
790
912
        Note: Will not include properties with access="write".
791
913
        """
792
914
        properties = {}
793
 
        for name, prop in self._get_all_dbus_properties():
 
915
        for name, prop in self._get_all_dbus_things("property"):
794
916
            if (interface_name
795
917
                and interface_name != prop._dbus_interface):
796
918
                # Interface non-empty but did not match
811
933
                         path_keyword='object_path',
812
934
                         connection_keyword='connection')
813
935
    def Introspect(self, object_path, connection):
814
 
        """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.
815
939
        """
816
940
        xmlstring = dbus.service.Object.Introspect(self, object_path,
817
941
                                                   connection)
824
948
                e.setAttribute("access", prop._dbus_access)
825
949
                return e
826
950
            for if_tag in document.getElementsByTagName("interface"):
 
951
                # Add property tags
827
952
                for tag in (make_tag(document, name, prop)
828
953
                            for name, prop
829
 
                            in self._get_all_dbus_properties()
 
954
                            in self._get_all_dbus_things("property")
830
955
                            if prop._dbus_interface
831
956
                            == if_tag.getAttribute("name")):
832
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)
833
989
                # Add the names to the return values for the
834
990
                # "org.freedesktop.DBus.Properties" methods
835
991
                if (if_tag.getAttribute("name")
870
1026
    def __new__(mcs, name, bases, attr):
871
1027
        # Go through all the base classes which could have D-Bus
872
1028
        # methods, signals, or properties in them
 
1029
        old_interface_names = []
873
1030
        for base in (b for b in bases
874
1031
                     if issubclass(b, dbus.service.Object)):
875
1032
            # Go though all attributes of the base class
885
1042
                alt_interface = (attribute._dbus_interface
886
1043
                                 .replace("se.recompile.Mandos",
887
1044
                                          "se.bsnet.fukt.Mandos"))
 
1045
                if alt_interface != attribute._dbus_interface:
 
1046
                    old_interface_names.append(alt_interface)
888
1047
                # Is this a D-Bus signal?
889
1048
                if getattr(attribute, "_dbus_is_signal", False):
890
1049
                    # Extract the original non-method function by
905
1064
                                nonmethod_func.func_name,
906
1065
                                nonmethod_func.func_defaults,
907
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
908
1073
                    # Define a creator of a function to call both the
909
1074
                    # old and new functions, so both the old and new
910
1075
                    # signals gets sent when the function is called
938
1103
                                        attribute.func_name,
939
1104
                                        attribute.func_defaults,
940
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
941
1112
                # Is this a D-Bus property?
942
1113
                elif getattr(attribute, "_dbus_is_property", False):
943
1114
                    # Create a new, but exactly alike, function
957
1128
                                        attribute.func_name,
958
1129
                                        attribute.func_defaults,
959
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
960
1163
        return type.__new__(mcs, name, bases, attr)
961
1164
 
962
1165
 
976
1179
    def __init__(self, bus = None, *args, **kwargs):
977
1180
        self.bus = bus
978
1181
        Client.__init__(self, *args, **kwargs)
979
 
        
980
 
        self._approvals_pending = 0
981
1182
        # Only now, when this client is initialized, can it show up on
982
1183
        # the D-Bus
983
1184
        client_object_name = unicode(self.name).translate(
1029
1230
                                       checker is not None)
1030
1231
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
1031
1232
                                           "LastCheckedOK")
 
1233
    last_checker_status = notifychangeproperty(dbus.Int16,
 
1234
                                               "LastCheckerStatus")
1032
1235
    last_approval_request = notifychangeproperty(
1033
1236
        datetime_to_dbus, "LastApprovalRequest")
1034
1237
    approved_by_default = notifychangeproperty(dbus.Boolean,
1035
1238
                                               "ApprovedByDefault")
1036
 
    approval_delay = notifychangeproperty(dbus.UInt16,
 
1239
    approval_delay = notifychangeproperty(dbus.UInt64,
1037
1240
                                          "ApprovalDelay",
1038
1241
                                          type_func =
1039
 
                                          _timedelta_to_milliseconds)
 
1242
                                          timedelta_to_milliseconds)
1040
1243
    approval_duration = notifychangeproperty(
1041
 
        dbus.UInt16, "ApprovalDuration",
1042
 
        type_func = _timedelta_to_milliseconds)
 
1244
        dbus.UInt64, "ApprovalDuration",
 
1245
        type_func = timedelta_to_milliseconds)
1043
1246
    host = notifychangeproperty(dbus.String, "Host")
1044
 
    timeout = notifychangeproperty(dbus.UInt16, "Timeout",
 
1247
    timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1045
1248
                                   type_func =
1046
 
                                   _timedelta_to_milliseconds)
 
1249
                                   timedelta_to_milliseconds)
1047
1250
    extended_timeout = notifychangeproperty(
1048
 
        dbus.UInt16, "ExtendedTimeout",
1049
 
        type_func = _timedelta_to_milliseconds)
1050
 
    interval = notifychangeproperty(dbus.UInt16,
 
1251
        dbus.UInt64, "ExtendedTimeout",
 
1252
        type_func = timedelta_to_milliseconds)
 
1253
    interval = notifychangeproperty(dbus.UInt64,
1051
1254
                                    "Interval",
1052
1255
                                    type_func =
1053
 
                                    _timedelta_to_milliseconds)
 
1256
                                    timedelta_to_milliseconds)
1054
1257
    checker_command = notifychangeproperty(dbus.String, "Checker")
1055
1258
    
1056
1259
    del notifychangeproperty
1098
1301
        return r
1099
1302
    
1100
1303
    def _reset_approved(self):
1101
 
        self._approved = None
 
1304
        self.approved = None
1102
1305
        return False
1103
1306
    
1104
1307
    def approve(self, value=True):
1105
1308
        self.send_changedstate()
1106
 
        self._approved = value
1107
 
        gobject.timeout_add(_timedelta_to_milliseconds
 
1309
        self.approved = value
 
1310
        gobject.timeout_add(timedelta_to_milliseconds
1108
1311
                            (self.approval_duration),
1109
1312
                            self._reset_approved)
1110
1313
    
1112
1315
    ## D-Bus methods, signals & properties
1113
1316
    _interface = "se.recompile.Mandos.Client"
1114
1317
    
 
1318
    ## Interfaces
 
1319
    
 
1320
    @dbus_interface_annotations(_interface)
 
1321
    def _foo(self):
 
1322
        return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
 
1323
                     "false"}
 
1324
    
1115
1325
    ## Signals
1116
1326
    
1117
1327
    # CheckerCompleted - signal
1153
1363
        "D-Bus signal"
1154
1364
        return self.need_approval()
1155
1365
    
1156
 
    # NeRwequest - signal
1157
 
    @dbus.service.signal(_interface, signature="s")
1158
 
    def NewRequest(self, ip):
1159
 
        """D-Bus signal
1160
 
        Is sent after a client request a password.
1161
 
        """
1162
 
        pass
1163
 
    
1164
1366
    ## Methods
1165
1367
    
1166
1368
    # Approve - method
1224
1426
                           access="readwrite")
1225
1427
    def ApprovalDuration_dbus_property(self, value=None):
1226
1428
        if value is None:       # get
1227
 
            return dbus.UInt64(_timedelta_to_milliseconds(
 
1429
            return dbus.UInt64(timedelta_to_milliseconds(
1228
1430
                    self.approval_duration))
1229
1431
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
1230
1432
    
1244
1446
    def Host_dbus_property(self, value=None):
1245
1447
        if value is None:       # get
1246
1448
            return dbus.String(self.host)
1247
 
        self.host = value
 
1449
        self.host = unicode(value)
1248
1450
    
1249
1451
    # Created - property
1250
1452
    @dbus_service_property(_interface, signature="s", access="read")
1251
1453
    def Created_dbus_property(self):
1252
 
        return dbus.String(datetime_to_dbus(self.created))
 
1454
        return datetime_to_dbus(self.created)
1253
1455
    
1254
1456
    # LastEnabled - property
1255
1457
    @dbus_service_property(_interface, signature="s", access="read")
1276
1478
            return
1277
1479
        return datetime_to_dbus(self.last_checked_ok)
1278
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
    
1279
1487
    # Expires - property
1280
1488
    @dbus_service_property(_interface, signature="s", access="read")
1281
1489
    def Expires_dbus_property(self):
1293
1501
        if value is None:       # get
1294
1502
            return dbus.UInt64(self.timeout_milliseconds())
1295
1503
        self.timeout = datetime.timedelta(0, 0, 0, value)
1296
 
        if getattr(self, "disable_initiator_tag", None) is None:
1297
 
            return
1298
1504
        # Reschedule timeout
1299
 
        gobject.source_remove(self.disable_initiator_tag)
1300
 
        self.disable_initiator_tag = None
1301
 
        self.expires = None
1302
 
        time_to_die = _timedelta_to_milliseconds((self
1303
 
                                                  .last_checked_ok
1304
 
                                                  + self.timeout)
1305
 
                                                 - datetime.datetime
1306
 
                                                 .utcnow())
1307
 
        if time_to_die <= 0:
1308
 
            # The timeout has passed
1309
 
            self.disable()
1310
 
        else:
1311
 
            self.expires = (datetime.datetime.utcnow()
1312
 
                            + datetime.timedelta(milliseconds =
1313
 
                                                 time_to_die))
1314
 
            self.disable_initiator_tag = (gobject.timeout_add
1315
 
                                          (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))
1316
1523
    
1317
1524
    # ExtendedTimeout - property
1318
1525
    @dbus_service_property(_interface, signature="t",
1331
1538
        self.interval = datetime.timedelta(0, 0, 0, value)
1332
1539
        if getattr(self, "checker_initiator_tag", None) is None:
1333
1540
            return
1334
 
        # Reschedule checker run
1335
 
        gobject.source_remove(self.checker_initiator_tag)
1336
 
        self.checker_initiator_tag = (gobject.timeout_add
1337
 
                                      (value, self.start_checker))
1338
 
        self.start_checker()    # Start one now, too
 
1541
        if self.enabled:
 
1542
            # Reschedule checker run
 
1543
            gobject.source_remove(self.checker_initiator_tag)
 
1544
            self.checker_initiator_tag = (gobject.timeout_add
 
1545
                                          (value, self.start_checker))
 
1546
            self.start_checker()    # Start one now, too
1339
1547
    
1340
1548
    # Checker - property
1341
1549
    @dbus_service_property(_interface, signature="s",
1343
1551
    def Checker_dbus_property(self, value=None):
1344
1552
        if value is None:       # get
1345
1553
            return dbus.String(self.checker_command)
1346
 
        self.checker_command = value
 
1554
        self.checker_command = unicode(value)
1347
1555
    
1348
1556
    # CheckerRunning - property
1349
1557
    @dbus_service_property(_interface, signature="b",
1378
1586
            raise KeyError()
1379
1587
    
1380
1588
    def __getattribute__(self, name):
1381
 
        if(name == '_pipe'):
 
1589
        if name == '_pipe':
1382
1590
            return super(ProxyClient, self).__getattribute__(name)
1383
1591
        self._pipe.send(('getattr', name))
1384
1592
        data = self._pipe.recv()
1391
1599
            return func
1392
1600
    
1393
1601
    def __setattr__(self, name, value):
1394
 
        if(name == '_pipe'):
 
1602
        if name == '_pipe':
1395
1603
            return super(ProxyClient, self).__setattr__(name, value)
1396
1604
        self._pipe.send(('setattr', name, value))
1397
1605
 
1466
1674
                    logger.warning("Bad certificate: %s", error)
1467
1675
                    return
1468
1676
                logger.debug("Fingerprint: %s", fpr)
1469
 
                if self.server.use_dbus:
1470
 
                    # Emit D-Bus signal
1471
 
                    client.NewRequest(str(self.client_address))
1472
1677
                
1473
1678
                try:
1474
1679
                    client = ProxyClient(child_pipe, fpr,
1490
1695
                            client.Rejected("Disabled")
1491
1696
                        return
1492
1697
                    
1493
 
                    if client._approved or not client.approval_delay:
 
1698
                    if client.approved or not client.approval_delay:
1494
1699
                        #We are approved or approval is disabled
1495
1700
                        break
1496
 
                    elif client._approved is None:
 
1701
                    elif client.approved is None:
1497
1702
                        logger.info("Client %s needs approval",
1498
1703
                                    client.name)
1499
1704
                        if self.server.use_dbus:
1513
1718
                    time = datetime.datetime.now()
1514
1719
                    client.changedstate.acquire()
1515
1720
                    (client.changedstate.wait
1516
 
                     (float(client._timedelta_to_milliseconds(delay)
 
1721
                     (float(client.timedelta_to_milliseconds(delay)
1517
1722
                            / 1000)))
1518
1723
                    client.changedstate.release()
1519
1724
                    time2 = datetime.datetime.now()
1545
1750
                
1546
1751
                logger.info("Sending secret to %s", client.name)
1547
1752
                # bump the timeout using extended_timeout
1548
 
                client.checked_ok(client.extended_timeout)
 
1753
                client.bump_timeout(client.extended_timeout)
1549
1754
                if self.server.use_dbus:
1550
1755
                    # Emit D-Bus signal
1551
1756
                    client.GotSecret()
1618
1823
        # Convert the buffer to a Python bytestring
1619
1824
        fpr = ctypes.string_at(buf, buf_len.value)
1620
1825
        # Convert the bytestring to hexadecimal notation
1621
 
        hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
 
1826
        hex_fpr = binascii.hexlify(fpr).upper()
1622
1827
        return hex_fpr
1623
1828
 
1624
1829
 
1627
1832
    def sub_process_main(self, request, address):
1628
1833
        try:
1629
1834
            self.finish_request(request, address)
1630
 
        except:
 
1835
        except Exception:
1631
1836
            self.handle_error(request, address)
1632
1837
        self.close_request(request)
1633
1838
    
1894
2099
        sys.exit()
1895
2100
    if not noclose:
1896
2101
        # Close all standard open file descriptors
1897
 
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
2102
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
1898
2103
        if not stat.S_ISCHR(os.fstat(null).st_mode):
1899
2104
            raise OSError(errno.ENODEV,
1900
2105
                          "%s not a character device"
1901
 
                          % os.path.devnull)
 
2106
                          % os.devnull)
1902
2107
        os.dup2(null, sys.stdin.fileno())
1903
2108
        os.dup2(null, sys.stdout.fileno())
1904
2109
        os.dup2(null, sys.stderr.fileno())
1943
2148
                        dest="use_ipv6", help="Do not use IPv6")
1944
2149
    parser.add_argument("--no-restore", action="store_false",
1945
2150
                        dest="restore", help="Do not restore stored"
1946
 
                        " state", default=True)
 
2151
                        " state")
 
2152
    parser.add_argument("--statedir", metavar="DIR",
 
2153
                        help="Directory to save/restore state in")
1947
2154
    
1948
2155
    options = parser.parse_args()
1949
2156
    
1963
2170
                        "use_dbus": "True",
1964
2171
                        "use_ipv6": "True",
1965
2172
                        "debuglevel": "",
 
2173
                        "restore": "True",
 
2174
                        "statedir": "/var/lib/mandos"
1966
2175
                        }
1967
2176
    
1968
2177
    # Parse config file for server-global settings
1985
2194
    # options, if set.
1986
2195
    for option in ("interface", "address", "port", "debug",
1987
2196
                   "priority", "servicename", "configdir",
1988
 
                   "use_dbus", "use_ipv6", "debuglevel", "restore"):
 
2197
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
 
2198
                   "statedir"):
1989
2199
        value = getattr(options, option)
1990
2200
        if value is not None:
1991
2201
            server_settings[option] = value
2003
2213
    debuglevel = server_settings["debuglevel"]
2004
2214
    use_dbus = server_settings["use_dbus"]
2005
2215
    use_ipv6 = server_settings["use_ipv6"]
 
2216
    stored_state_path = os.path.join(server_settings["statedir"],
 
2217
                                     stored_state_file)
2006
2218
    
2007
2219
    if debug:
2008
 
        initlogger(logging.DEBUG)
 
2220
        initlogger(debug, logging.DEBUG)
2009
2221
    else:
2010
2222
        if not debuglevel:
2011
 
            initlogger()
 
2223
            initlogger(debug)
2012
2224
        else:
2013
2225
            level = getattr(logging, debuglevel.upper())
2014
 
            initlogger(level)
 
2226
            initlogger(debug, level)
2015
2227
    
2016
2228
    if server_settings["servicename"] != "Mandos":
2017
2229
        syslogger.setFormatter(logging.Formatter
2020
2232
                                % server_settings["servicename"]))
2021
2233
    
2022
2234
    # Parse config file with clients
2023
 
    client_defaults = { "timeout": "5m",
2024
 
                        "extended_timeout": "15m",
2025
 
                        "interval": "2m",
2026
 
                        "checker": "fping -q -- %%(host)s",
2027
 
                        "host": "",
2028
 
                        "approval_delay": "0s",
2029
 
                        "approval_duration": "1s",
2030
 
                        }
2031
 
    client_config = configparser.SafeConfigParser(client_defaults)
 
2235
    client_config = configparser.SafeConfigParser(Client
 
2236
                                                  .client_defaults)
2032
2237
    client_config.read(os.path.join(server_settings["configdir"],
2033
2238
                                    "clients.conf"))
2034
2239
    
2087
2292
         .gnutls_global_set_log_function(debug_gnutls))
2088
2293
        
2089
2294
        # Redirect stdin so all checkers get /dev/null
2090
 
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
2295
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2091
2296
        os.dup2(null, sys.stdin.fileno())
2092
2297
        if null > 2:
2093
2298
            os.close(null)
2094
 
    else:
2095
 
        # No console logging
2096
 
        logger.removeHandler(console)
2097
2299
    
2098
2300
    # Need to fork before connecting to D-Bus
2099
2301
    if not debug:
2100
2302
        # Close all input and output, do double fork, etc.
2101
2303
        daemon()
2102
2304
    
 
2305
    gobject.threads_init()
 
2306
    
2103
2307
    global main_loop
2104
2308
    # From the Avahi example code
2105
 
    DBusGMainLoop(set_as_default=True )
 
2309
    DBusGMainLoop(set_as_default=True)
2106
2310
    main_loop = gobject.MainLoop()
2107
2311
    bus = dbus.SystemBus()
2108
2312
    # End of Avahi example code
2135
2339
        client_class = functools.partial(ClientDBusTransitional,
2136
2340
                                         bus = bus)
2137
2341
    
2138
 
    special_settings = {
2139
 
        # Some settings need to be accessd by special methods;
2140
 
        # booleans need .getboolean(), etc.  Here is a list of them:
2141
 
        "approved_by_default":
2142
 
            lambda section:
2143
 
            client_config.getboolean(section, "approved_by_default"),
2144
 
        }
2145
 
    # Construct a new dict of client settings of this form:
2146
 
    # { client_name: {setting_name: value, ...}, ...}
2147
 
    # with exceptions for any special settings as defined above
2148
 
    client_settings = dict((clientname,
2149
 
                           dict((setting,
2150
 
                                 (value
2151
 
                                  if setting not in special_settings
2152
 
                                  else special_settings[setting]
2153
 
                                  (clientname)))
2154
 
                                for setting, value in
2155
 
                                client_config.items(clientname)))
2156
 
                          for clientname in client_config.sections())
2157
 
    
 
2342
    client_settings = Client.config_parser(client_config)
2158
2343
    old_client_settings = {}
2159
 
    clients_data = []
 
2344
    clients_data = {}
2160
2345
    
2161
2346
    # Get client data and settings from last running state.
2162
2347
    if server_settings["restore"]:
2166
2351
                                                     (stored_state))
2167
2352
            os.remove(stored_state_path)
2168
2353
        except IOError as e:
2169
 
            logger.warning("Could not load persistant state: {0}"
 
2354
            logger.warning("Could not load persistent state: {0}"
2170
2355
                           .format(e))
2171
2356
            if e.errno != errno.ENOENT:
2172
2357
                raise
 
2358
        except EOFError as e:
 
2359
            logger.warning("Could not load persistent state: "
 
2360
                           "EOFError: {0}".format(e))
2173
2361
    
2174
 
    for client in clients_data:
2175
 
        client_name = client["name"]
2176
 
        
2177
 
        # Decide which value to use after restoring saved state.
2178
 
        # We have three different values: Old config file,
2179
 
        # new config file, and saved state.
2180
 
        # New config value takes precedence if it differs from old
2181
 
        # config value, otherwise use saved state.
2182
 
        for name, value in client_settings[client_name].items():
 
2362
    with PGPEngine() as pgp:
 
2363
        for client_name, client in clients_data.iteritems():
 
2364
            # Decide which value to use after restoring saved state.
 
2365
            # We have three different values: Old config file,
 
2366
            # new config file, and saved state.
 
2367
            # New config value takes precedence if it differs from old
 
2368
            # config value, otherwise use saved state.
 
2369
            for name, value in client_settings[client_name].items():
 
2370
                try:
 
2371
                    # For each value in new config, check if it
 
2372
                    # differs from the old config value (Except for
 
2373
                    # the "secret" attribute)
 
2374
                    if (name != "secret" and
 
2375
                        value != old_client_settings[client_name]
 
2376
                        [name]):
 
2377
                        client[name] = value
 
2378
                except KeyError:
 
2379
                    pass
 
2380
            
 
2381
            # Clients who has passed its expire date can still be
 
2382
            # enabled if its last checker was successful.  Clients
 
2383
            # whose checker succeeded before we stored its state is
 
2384
            # assumed to have successfully run all checkers during
 
2385
            # downtime.
 
2386
            if client["enabled"]:
 
2387
                if datetime.datetime.utcnow() >= client["expires"]:
 
2388
                    if not client["last_checked_ok"]:
 
2389
                        logger.warning(
 
2390
                            "disabling client {0} - Client never "
 
2391
                            "performed a successful checker"
 
2392
                            .format(client_name))
 
2393
                        client["enabled"] = False
 
2394
                    elif client["last_checker_status"] != 0:
 
2395
                        logger.warning(
 
2396
                            "disabling client {0} - Client "
 
2397
                            "last checker failed with error code {1}"
 
2398
                            .format(client_name,
 
2399
                                    client["last_checker_status"]))
 
2400
                        client["enabled"] = False
 
2401
                    else:
 
2402
                        client["expires"] = (datetime.datetime
 
2403
                                             .utcnow()
 
2404
                                             + client["timeout"])
 
2405
                        logger.debug("Last checker succeeded,"
 
2406
                                     " keeping {0} enabled"
 
2407
                                     .format(client_name))
2183
2408
            try:
2184
 
                # For each value in new config, check if it differs
2185
 
                # from the old config value (Except for the "secret"
2186
 
                # attribute)
2187
 
                if (name != "secret" and
2188
 
                    value != old_client_settings[client_name][name]):
2189
 
                    setattr(client, name, value)
2190
 
            except KeyError:
2191
 
                pass
2192
 
        
2193
 
        # Clients who has passed its expire date, can still be enabled
2194
 
        # if its last checker was sucessful. Clients who checkers
2195
 
        # failed before we stored it state is asumed to had failed
2196
 
        # checker during downtime.
2197
 
        if client["enabled"] and client["last_checked_ok"]:
2198
 
            if ((datetime.datetime.utcnow()
2199
 
                 - client["last_checked_ok"]) > client["interval"]):
2200
 
                if client["last_checker_status"] != 0:
2201
 
                    client["enabled"] = False
2202
 
                else:
2203
 
                    client["expires"] = (datetime.datetime.utcnow()
2204
 
                                         + client["timeout"])
2205
 
        
2206
 
        client["changedstate"] = (multiprocessing_manager
2207
 
                                  .Condition(multiprocessing_manager
2208
 
                                             .Lock()))
2209
 
        if use_dbus:
2210
 
            new_client = (ClientDBusTransitional.__new__
2211
 
                          (ClientDBusTransitional))
2212
 
            tcp_server.clients[client_name] = new_client
2213
 
            new_client.bus = bus
2214
 
            for name, value in client.iteritems():
2215
 
                setattr(new_client, name, value)
2216
 
            client_object_name = unicode(client_name).translate(
2217
 
                {ord("."): ord("_"),
2218
 
                 ord("-"): ord("_")})
2219
 
            new_client.dbus_object_path = (dbus.ObjectPath
2220
 
                                           ("/clients/"
2221
 
                                            + client_object_name))
2222
 
            DBusObjectWithProperties.__init__(new_client,
2223
 
                                              new_client.bus,
2224
 
                                              new_client
2225
 
                                              .dbus_object_path)
2226
 
        else:
2227
 
            tcp_server.clients[client_name] = Client.__new__(Client)
2228
 
            for name, value in client.iteritems():
2229
 
                setattr(tcp_server.clients[client_name], name, value)
2230
 
                
2231
 
        tcp_server.clients[client_name].decrypt_secret(
2232
 
            client_settings[client_name]["secret"])
2233
 
        
2234
 
    # Create/remove clients based on new changes made to config
2235
 
    for clientname in set(old_client_settings) - set(client_settings):
2236
 
        del tcp_server.clients[clientname]
2237
 
    for clientname in set(client_settings) - set(old_client_settings):
2238
 
        tcp_server.clients[clientname] = (client_class(name
2239
 
                                                       = clientname,
2240
 
                                                       config =
2241
 
                                                       client_settings
2242
 
                                                       [clientname]))
 
2409
                client["secret"] = (
 
2410
                    pgp.decrypt(client["encrypted_secret"],
 
2411
                                client_settings[client_name]
 
2412
                                ["secret"]))
 
2413
            except PGPError:
 
2414
                # If decryption fails, we use secret from new settings
 
2415
                logger.debug("Failed to decrypt {0} old secret"
 
2416
                             .format(client_name))
 
2417
                client["secret"] = (
 
2418
                    client_settings[client_name]["secret"])
 
2419
 
 
2420
    
 
2421
    # Add/remove clients based on new changes made to config
 
2422
    for client_name in (set(old_client_settings)
 
2423
                        - set(client_settings)):
 
2424
        del clients_data[client_name]
 
2425
    for client_name in (set(client_settings)
 
2426
                        - set(old_client_settings)):
 
2427
        clients_data[client_name] = client_settings[client_name]
 
2428
 
 
2429
    # Create all client objects
 
2430
    for client_name, client in clients_data.iteritems():
 
2431
        tcp_server.clients[client_name] = client_class(
 
2432
            name = client_name, settings = client)
2243
2433
    
2244
2434
    if not tcp_server.clients:
2245
2435
        logger.warning("No clients defined")
2257
2447
            # "pidfile" was never created
2258
2448
            pass
2259
2449
        del pidfilename
2260
 
        
2261
2450
        signal.signal(signal.SIGINT, signal.SIG_IGN)
2262
2451
    
2263
2452
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2264
2453
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2265
2454
    
2266
2455
    if use_dbus:
2267
 
        class MandosDBusService(dbus.service.Object):
 
2456
        class MandosDBusService(DBusObjectWithProperties):
2268
2457
            """A D-Bus proxy object"""
2269
2458
            def __init__(self):
2270
2459
                dbus.service.Object.__init__(self, bus, "/")
2271
2460
            _interface = "se.recompile.Mandos"
2272
2461
            
 
2462
            @dbus_interface_annotations(_interface)
 
2463
            def _foo(self):
 
2464
                return { "org.freedesktop.DBus.Property"
 
2465
                         ".EmitsChangedSignal":
 
2466
                             "false"}
 
2467
            
2273
2468
            @dbus.service.signal(_interface, signature="o")
2274
2469
            def ClientAdded(self, objpath):
2275
2470
                "D-Bus signal"
2332
2527
        # Store client before exiting. Secrets are encrypted with key
2333
2528
        # based on what config file has. If config file is
2334
2529
        # removed/edited, old secret will thus be unrecovable.
2335
 
        clients = []
2336
 
        for client in tcp_server.clients.itervalues():
2337
 
            client.encrypt_secret(client_settings[client.name]
2338
 
                                  ["secret"])
2339
 
            
2340
 
            client_dict = {}
2341
 
            
2342
 
            # A list of attributes that will not be stored when
2343
 
            # shutting down.
2344
 
            exclude = set(("bus", "changedstate", "secret"))
2345
 
            for name, typ in inspect.getmembers(dbus.service.Object):
2346
 
                exclude.add(name)
2347
 
                
2348
 
            client_dict["encrypted_secret"] = client.encrypted_secret
2349
 
            for attr in client.client_structure:
2350
 
                if attr not in exclude:
2351
 
                    client_dict[attr] = getattr(client, attr)
2352
 
            
2353
 
            clients.append(client_dict)
2354
 
            del client_settings[client.name]["secret"]
2355
 
            
 
2530
        clients = {}
 
2531
        with PGPEngine() as pgp:
 
2532
            for client in tcp_server.clients.itervalues():
 
2533
                key = client_settings[client.name]["secret"]
 
2534
                client.encrypted_secret = pgp.encrypt(client.secret,
 
2535
                                                      key)
 
2536
                client_dict = {}
 
2537
                
 
2538
                # A list of attributes that can not be pickled
 
2539
                # + secret.
 
2540
                exclude = set(("bus", "changedstate", "secret",
 
2541
                               "checker"))
 
2542
                for name, typ in (inspect.getmembers
 
2543
                                  (dbus.service.Object)):
 
2544
                    exclude.add(name)
 
2545
                
 
2546
                client_dict["encrypted_secret"] = (client
 
2547
                                                   .encrypted_secret)
 
2548
                for attr in client.client_structure:
 
2549
                    if attr not in exclude:
 
2550
                        client_dict[attr] = getattr(client, attr)
 
2551
                
 
2552
                clients[client.name] = client_dict
 
2553
                del client_settings[client.name]["secret"]
 
2554
        
2356
2555
        try:
2357
 
            with os.fdopen(os.open(stored_state_path,
2358
 
                                   os.O_CREAT|os.O_WRONLY|os.O_TRUNC,
2359
 
                                   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:
2360
2561
                pickle.dump((clients, client_settings), stored_state)
2361
 
        except IOError as e:
2362
 
            logger.warning("Could not save persistant state: {0}"
 
2562
            os.rename(tempname, stored_state_path)
 
2563
        except (IOError, OSError) as e:
 
2564
            logger.warning("Could not save persistent state: {0}"
2363
2565
                           .format(e))
2364
 
            if e.errno != errno.ENOENT:
2365
 
                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
2366
2574
        
2367
2575
        # Delete all clients, and settings from config
2368
2576
        while tcp_server.clients:
2387
2595
        # Need to initiate checking of clients
2388
2596
        if client.enabled:
2389
2597
            client.init_checker()
2390
 
 
2391
2598
    
2392
2599
    tcp_server.enable()
2393
2600
    tcp_server.server_activate()
2433
2640
    # Must run before the D-Bus bus name gets deregistered
2434
2641
    cleanup()
2435
2642
 
2436
 
 
2437
2643
if __name__ == '__main__':
2438
2644
    main()