/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2011-11-26 22:22:20 UTC
  • mto: (518.1.8 mandos-persistent)
  • mto: This revision was merged to the branch mainline in revision 524.
  • Revision ID: teddy@recompile.se-20111126222220-1ubwjpb5ugqnrhec
Directory with persistent state can now be changed with the "statedir"
option.  The state directory /var/lib/mandos now gets created on
installation.  Added documentation about "restore" and "statedir"
options.

* Makefile (USER, GROUP, STATEDIR): New.
  (maintainer-clean): Also remove "statedir".
  (run-server): Replaced "--no-restore" with "--statedir=statedir".
  (statedir): New.
  (install-server): Make $(STATEDIR) directory.
* debian/mandos.dirs (var/lib/mandos): Added.
* debian/mandos.postinst: Fix ownership of /var/lib/mandos.
* mandos: New --statedir option.
  (stored_state_path): Not global anymore.
  (stored_state_file): New global.
* mandos.conf: Fix whitespace.
  (restore, statedir): Added.
* mandos.conf.xml (OPTIONS, EXAMPLE): Added "restore" and "statedir".
  mandos.xml (SYNOPSIS, OPTIONS): Added "--statedir".
  (FILES): Added "/var/lib/mandos".

Show diffs side-by-side

added added

removed removed

Lines of Context:
28
28
# along with this program.  If not, see
29
29
# <http://www.gnu.org/licenses/>.
30
30
31
 
# Contact the authors at <mandos@fukt.bsnet.se>.
 
31
# Contact the authors at <mandos@recompile.se>.
32
32
33
33
 
34
34
from __future__ import (division, absolute_import, print_function,
62
62
import functools
63
63
import cPickle as pickle
64
64
import multiprocessing
 
65
import types
 
66
import binascii
 
67
import tempfile
65
68
 
66
69
import dbus
67
70
import dbus.service
72
75
import ctypes.util
73
76
import xml.dom.minidom
74
77
import inspect
 
78
import GnuPGInterface
75
79
 
76
80
try:
77
81
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
82
86
        SO_BINDTODEVICE = None
83
87
 
84
88
 
85
 
version = "1.3.0"
 
89
version = "1.4.1"
 
90
stored_state_file = "clients.pickle"
86
91
 
87
 
#logger = logging.getLogger('mandos')
88
 
logger = logging.Logger('mandos')
 
92
logger = logging.getLogger()
89
93
syslogger = (logging.handlers.SysLogHandler
90
94
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
91
95
              address = str("/dev/log")))
92
 
syslogger.setFormatter(logging.Formatter
93
 
                       ('Mandos [%(process)d]: %(levelname)s:'
94
 
                        ' %(message)s'))
95
 
logger.addHandler(syslogger)
96
 
 
97
 
console = logging.StreamHandler()
98
 
console.setFormatter(logging.Formatter('%(name)s [%(process)d]:'
99
 
                                       ' %(levelname)s:'
100
 
                                       ' %(message)s'))
101
 
logger.addHandler(console)
 
96
 
 
97
try:
 
98
    if_nametoindex = (ctypes.cdll.LoadLibrary
 
99
                      (ctypes.util.find_library("c"))
 
100
                      .if_nametoindex)
 
101
except (OSError, AttributeError):
 
102
    def if_nametoindex(interface):
 
103
        "Get an interface index the hard way, i.e. using fcntl()"
 
104
        SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
 
105
        with contextlib.closing(socket.socket()) as s:
 
106
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
 
107
                                struct.pack(str("16s16x"),
 
108
                                            interface))
 
109
        interface_index = struct.unpack(str("I"),
 
110
                                        ifreq[16:20])[0]
 
111
        return interface_index
 
112
 
 
113
 
 
114
def initlogger(level=logging.WARNING):
 
115
    """init logger and add loglevel"""
 
116
    
 
117
    syslogger.setFormatter(logging.Formatter
 
118
                           ('Mandos [%(process)d]: %(levelname)s:'
 
119
                            ' %(message)s'))
 
120
    logger.addHandler(syslogger)
 
121
    
 
122
    console = logging.StreamHandler()
 
123
    console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
 
124
                                           ' [%(process)d]:'
 
125
                                           ' %(levelname)s:'
 
126
                                           ' %(message)s'))
 
127
    logger.addHandler(console)
 
128
    logger.setLevel(level)
 
129
 
 
130
 
 
131
class CryptoError(Exception):
 
132
    pass
 
133
 
 
134
 
 
135
class Crypto(object):
 
136
    """A simple class for OpenPGP symmetric encryption & decryption"""
 
137
    def __init__(self):
 
138
        self.gnupg = GnuPGInterface.GnuPG()
 
139
        self.tempdir = tempfile.mkdtemp(prefix="mandos-")
 
140
        self.gnupg = GnuPGInterface.GnuPG()
 
141
        self.gnupg.options.meta_interactive = False
 
142
        self.gnupg.options.homedir = self.tempdir
 
143
        self.gnupg.options.extra_args.extend(['--force-mdc',
 
144
                                              '--quiet'])
 
145
    
 
146
    def __enter__(self):
 
147
        return self
 
148
    
 
149
    def __exit__ (self, exc_type, exc_value, traceback):
 
150
        self._cleanup()
 
151
        return False
 
152
    
 
153
    def __del__(self):
 
154
        self._cleanup()
 
155
    
 
156
    def _cleanup(self):
 
157
        if self.tempdir is not None:
 
158
            # Delete contents of tempdir
 
159
            for root, dirs, files in os.walk(self.tempdir,
 
160
                                             topdown = False):
 
161
                for filename in files:
 
162
                    os.remove(os.path.join(root, filename))
 
163
                for dirname in dirs:
 
164
                    os.rmdir(os.path.join(root, dirname))
 
165
            # Remove tempdir
 
166
            os.rmdir(self.tempdir)
 
167
            self.tempdir = None
 
168
    
 
169
    def password_encode(self, password):
 
170
        # Passphrase can not be empty and can not contain newlines or
 
171
        # NUL bytes.  So we prefix it and hex encode it.
 
172
        return b"mandos" + binascii.hexlify(password)
 
173
    
 
174
    def encrypt(self, data, password):
 
175
        self.gnupg.passphrase = self.password_encode(password)
 
176
        with open(os.devnull) as devnull:
 
177
            try:
 
178
                proc = self.gnupg.run(['--symmetric'],
 
179
                                      create_fhs=['stdin', 'stdout'],
 
180
                                      attach_fhs={'stderr': devnull})
 
181
                with contextlib.closing(proc.handles['stdin']) as f:
 
182
                    f.write(data)
 
183
                with contextlib.closing(proc.handles['stdout']) as f:
 
184
                    ciphertext = f.read()
 
185
                proc.wait()
 
186
            except IOError as e:
 
187
                raise CryptoError(e)
 
188
        self.gnupg.passphrase = None
 
189
        return ciphertext
 
190
    
 
191
    def decrypt(self, data, password):
 
192
        self.gnupg.passphrase = self.password_encode(password)
 
193
        with open(os.devnull) as devnull:
 
194
            try:
 
195
                proc = self.gnupg.run(['--decrypt'],
 
196
                                      create_fhs=['stdin', 'stdout'],
 
197
                                      attach_fhs={'stderr': devnull})
 
198
                with contextlib.closing(proc.handles['stdin'] ) as f:
 
199
                    f.write(data)
 
200
                with contextlib.closing(proc.handles['stdout']) as f:
 
201
                    decrypted_plaintext = f.read()
 
202
                proc.wait()
 
203
            except IOError as e:
 
204
                raise CryptoError(e)
 
205
        self.gnupg.passphrase = None
 
206
        return decrypted_plaintext
 
207
 
 
208
 
102
209
 
103
210
class AvahiError(Exception):
104
211
    def __init__(self, value, *args, **kwargs):
159
266
                            " after %i retries, exiting.",
160
267
                            self.rename_count)
161
268
            raise AvahiServiceError("Too many renames")
162
 
        self.name = unicode(self.server.GetAlternativeServiceName(self.name))
 
269
        self.name = unicode(self.server
 
270
                            .GetAlternativeServiceName(self.name))
163
271
        logger.info("Changing Zeroconf service name to %r ...",
164
272
                    self.name)
165
 
        syslogger.setFormatter(logging.Formatter
166
 
                               ('Mandos (%s) [%%(process)d]:'
167
 
                                ' %%(levelname)s: %%(message)s'
168
 
                                % self.name))
169
273
        self.remove()
170
274
        try:
171
275
            self.add()
191
295
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
192
296
        self.entry_group_state_changed_match = (
193
297
            self.group.connect_to_signal(
194
 
                'StateChanged', self .entry_group_state_changed))
 
298
                'StateChanged', self.entry_group_state_changed))
195
299
        logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
196
300
                     self.name, self.type)
197
301
        self.group.AddService(
223
327
            try:
224
328
                self.group.Free()
225
329
            except (dbus.exceptions.UnknownMethodException,
226
 
                    dbus.exceptions.DBusException) as e:
 
330
                    dbus.exceptions.DBusException):
227
331
                pass
228
332
            self.group = None
229
333
        self.remove()
263
367
                                 self.server_state_changed)
264
368
        self.server_state_changed(self.server.GetState())
265
369
 
 
370
class AvahiServiceToSyslog(AvahiService):
 
371
    def rename(self):
 
372
        """Add the new name to the syslog messages"""
 
373
        ret = AvahiService.rename(self)
 
374
        syslogger.setFormatter(logging.Formatter
 
375
                               ('Mandos (%s) [%%(process)d]:'
 
376
                                ' %%(levelname)s: %%(message)s'
 
377
                                % self.name))
 
378
        return ret
266
379
 
 
380
def _timedelta_to_milliseconds(td):
 
381
    "Convert a datetime.timedelta() to milliseconds"
 
382
    return ((td.days * 24 * 60 * 60 * 1000)
 
383
            + (td.seconds * 1000)
 
384
            + (td.microseconds // 1000))
 
385
        
267
386
class Client(object):
268
387
    """A representation of a client host served by this server.
269
388
    
281
400
                     instance %(name)s can be used in the command.
282
401
    checker_initiator_tag: a gobject event source tag, or None
283
402
    created:    datetime.datetime(); (UTC) object creation
 
403
    client_structure: Object describing what attributes a client has
 
404
                      and is used for storing the client at exit
284
405
    current_checker_command: string; current running checker_command
285
 
    disable_hook:  If set, called by disable() as disable_hook(self)
286
406
    disable_initiator_tag: a gobject event source tag, or None
287
407
    enabled:    bool()
288
408
    fingerprint: string (40 or 32 hexadecimal digits); used to
291
411
    interval:   datetime.timedelta(); How often to start a new checker
292
412
    last_approval_request: datetime.datetime(); (UTC) or None
293
413
    last_checked_ok: datetime.datetime(); (UTC) or None
 
414
 
 
415
    last_checker_status: integer between 0 and 255 reflecting exit
 
416
                         status of last checker. -1 reflects crashed
 
417
                         checker, or None.
294
418
    last_enabled: datetime.datetime(); (UTC)
295
419
    name:       string; from the config file, used in log messages and
296
420
                        D-Bus identifiers
297
421
    secret:     bytestring; sent verbatim (over TLS) to client
298
422
    timeout:    datetime.timedelta(); How long from last_checked_ok
299
423
                                      until this client is disabled
 
424
    extended_timeout:   extra long timeout when password has been sent
300
425
    runtime_expansions: Allowed attributes for runtime expansion.
 
426
    expires:    datetime.datetime(); time (UTC) when a client will be
 
427
                disabled, or None
301
428
    """
302
429
    
303
430
    runtime_expansions = ("approval_delay", "approval_duration",
305
432
                          "host", "interval", "last_checked_ok",
306
433
                          "last_enabled", "name", "timeout")
307
434
    
308
 
    @staticmethod
309
 
    def _timedelta_to_milliseconds(td):
310
 
        "Convert a datetime.timedelta() to milliseconds"
311
 
        return ((td.days * 24 * 60 * 60 * 1000)
312
 
                + (td.seconds * 1000)
313
 
                + (td.microseconds // 1000))
314
 
    
315
435
    def timeout_milliseconds(self):
316
436
        "Return the 'timeout' attribute in milliseconds"
317
 
        return self._timedelta_to_milliseconds(self.timeout)
 
437
        return _timedelta_to_milliseconds(self.timeout)
 
438
    
 
439
    def extended_timeout_milliseconds(self):
 
440
        "Return the 'extended_timeout' attribute in milliseconds"
 
441
        return _timedelta_to_milliseconds(self.extended_timeout)
318
442
    
319
443
    def interval_milliseconds(self):
320
444
        "Return the 'interval' attribute in milliseconds"
321
 
        return self._timedelta_to_milliseconds(self.interval)
322
 
 
 
445
        return _timedelta_to_milliseconds(self.interval)
 
446
    
323
447
    def approval_delay_milliseconds(self):
324
 
        return self._timedelta_to_milliseconds(self.approval_delay)
 
448
        return _timedelta_to_milliseconds(self.approval_delay)
325
449
    
326
 
    def __init__(self, name = None, disable_hook=None, config=None):
 
450
    def __init__(self, name = None, config=None):
327
451
        """Note: the 'checker' key in 'config' sets the
328
452
        'checker_command' attribute and *not* the 'checker'
329
453
        attribute."""
349
473
                            % self.name)
350
474
        self.host = config.get("host", "")
351
475
        self.created = datetime.datetime.utcnow()
352
 
        self.enabled = False
 
476
        self.enabled = True
353
477
        self.last_approval_request = None
354
 
        self.last_enabled = None
 
478
        self.last_enabled = datetime.datetime.utcnow()
355
479
        self.last_checked_ok = None
 
480
        self.last_checker_status = None
356
481
        self.timeout = string_to_delta(config["timeout"])
 
482
        self.extended_timeout = string_to_delta(config
 
483
                                                ["extended_timeout"])
357
484
        self.interval = string_to_delta(config["interval"])
358
 
        self.disable_hook = disable_hook
359
485
        self.checker = None
360
486
        self.checker_initiator_tag = None
361
487
        self.disable_initiator_tag = None
 
488
        self.expires = datetime.datetime.utcnow() + self.timeout
362
489
        self.checker_callback_tag = None
363
490
        self.checker_command = config["checker"]
364
491
        self.current_checker_command = None
365
 
        self.last_connect = None
366
492
        self._approved = None
367
493
        self.approved_by_default = config.get("approved_by_default",
368
494
                                              True)
371
497
            config["approval_delay"])
372
498
        self.approval_duration = string_to_delta(
373
499
            config["approval_duration"])
374
 
        self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
 
500
        self.changedstate = (multiprocessing_manager
 
501
                             .Condition(multiprocessing_manager
 
502
                                        .Lock()))
 
503
        self.client_structure = [attr for attr in
 
504
                                 self.__dict__.iterkeys()
 
505
                                 if not attr.startswith("_")]
 
506
        self.client_structure.append("client_structure")
 
507
        
 
508
        for name, t in inspect.getmembers(type(self),
 
509
                                          lambda obj:
 
510
                                              isinstance(obj,
 
511
                                                         property)):
 
512
            if not name.startswith("_"):
 
513
                self.client_structure.append(name)
375
514
    
 
515
    # Send notice to process children that client state has changed
376
516
    def send_changedstate(self):
377
 
        self.changedstate.acquire()
378
 
        self.changedstate.notify_all()
379
 
        self.changedstate.release()
380
 
        
 
517
        with self.changedstate:
 
518
            self.changedstate.notify_all()
 
519
    
381
520
    def enable(self):
382
521
        """Start this client's checker and timeout hooks"""
383
522
        if getattr(self, "enabled", False):
384
523
            # Already enabled
385
524
            return
386
525
        self.send_changedstate()
 
526
        self.expires = datetime.datetime.utcnow() + self.timeout
 
527
        self.enabled = True
387
528
        self.last_enabled = datetime.datetime.utcnow()
388
 
        # Schedule a new checker to be started an 'interval' from now,
389
 
        # and every interval from then on.
390
 
        self.checker_initiator_tag = (gobject.timeout_add
391
 
                                      (self.interval_milliseconds(),
392
 
                                       self.start_checker))
393
 
        # Schedule a disable() when 'timeout' has passed
394
 
        self.disable_initiator_tag = (gobject.timeout_add
395
 
                                   (self.timeout_milliseconds(),
396
 
                                    self.disable))
397
 
        self.enabled = True
398
 
        # Also start a new checker *right now*.
399
 
        self.start_checker()
 
529
        self.init_checker()
400
530
    
401
531
    def disable(self, quiet=True):
402
532
        """Disable this client."""
409
539
        if getattr(self, "disable_initiator_tag", False):
410
540
            gobject.source_remove(self.disable_initiator_tag)
411
541
            self.disable_initiator_tag = None
 
542
        self.expires = None
412
543
        if getattr(self, "checker_initiator_tag", False):
413
544
            gobject.source_remove(self.checker_initiator_tag)
414
545
            self.checker_initiator_tag = None
415
546
        self.stop_checker()
416
 
        if self.disable_hook:
417
 
            self.disable_hook(self)
418
547
        self.enabled = False
419
548
        # Do not run this again if called by a gobject.timeout_add
420
549
        return False
421
550
    
422
551
    def __del__(self):
423
 
        self.disable_hook = None
424
552
        self.disable()
425
553
    
 
554
    def init_checker(self):
 
555
        # Schedule a new checker to be started an 'interval' from now,
 
556
        # and every interval from then on.
 
557
        self.checker_initiator_tag = (gobject.timeout_add
 
558
                                      (self.interval_milliseconds(),
 
559
                                       self.start_checker))
 
560
        # Schedule a disable() when 'timeout' has passed
 
561
        self.disable_initiator_tag = (gobject.timeout_add
 
562
                                   (self.timeout_milliseconds(),
 
563
                                    self.disable))
 
564
        # Also start a new checker *right now*.
 
565
        self.start_checker()
 
566
    
426
567
    def checker_callback(self, pid, condition, command):
427
568
        """The checker has completed, so take appropriate actions."""
428
569
        self.checker_callback_tag = None
429
570
        self.checker = None
430
571
        if os.WIFEXITED(condition):
431
 
            exitstatus = os.WEXITSTATUS(condition)
432
 
            if exitstatus == 0:
 
572
            self.last_checker_status =  os.WEXITSTATUS(condition)
 
573
            if self.last_checker_status == 0:
433
574
                logger.info("Checker for %(name)s succeeded",
434
575
                            vars(self))
435
576
                self.checked_ok()
437
578
                logger.info("Checker for %(name)s failed",
438
579
                            vars(self))
439
580
        else:
 
581
            self.last_checker_status = -1
440
582
            logger.warning("Checker for %(name)s crashed?",
441
583
                           vars(self))
442
584
    
443
 
    def checked_ok(self):
 
585
    def checked_ok(self, timeout=None):
444
586
        """Bump up the timeout for this client.
445
587
        
446
588
        This should only be called when the client has been seen,
447
589
        alive and well.
448
590
        """
 
591
        if timeout is None:
 
592
            timeout = self.timeout
449
593
        self.last_checked_ok = datetime.datetime.utcnow()
450
 
        gobject.source_remove(self.disable_initiator_tag)
451
 
        self.disable_initiator_tag = (gobject.timeout_add
452
 
                                      (self.timeout_milliseconds(),
453
 
                                       self.disable))
 
594
        if self.disable_initiator_tag is not None:
 
595
            gobject.source_remove(self.disable_initiator_tag)
 
596
        if getattr(self, "enabled", False):
 
597
            self.disable_initiator_tag = (gobject.timeout_add
 
598
                                          (_timedelta_to_milliseconds
 
599
                                           (timeout), self.disable))
 
600
            self.expires = datetime.datetime.utcnow() + timeout
454
601
    
455
602
    def need_approval(self):
456
603
        self.last_approval_request = datetime.datetime.utcnow()
496
643
                                       'replace')))
497
644
                    for attr in
498
645
                    self.runtime_expansions)
499
 
 
 
646
                
500
647
                try:
501
648
                    command = self.checker_command % escaped_attrs
502
649
                except TypeError as error:
548
695
                raise
549
696
        self.checker = None
550
697
 
 
698
 
551
699
def dbus_service_property(dbus_interface, signature="v",
552
700
                          access="readwrite", byte_arrays=False):
553
701
    """Decorators for marking methods of a DBusObjectWithProperties to
599
747
 
600
748
class DBusObjectWithProperties(dbus.service.Object):
601
749
    """A D-Bus object with properties.
602
 
 
 
750
    
603
751
    Classes inheriting from this can use the dbus_service_property
604
752
    decorator to expose methods as D-Bus properties.  It exposes the
605
753
    standard Get(), Set(), and GetAll() methods on the D-Bus.
612
760
    def _get_all_dbus_properties(self):
613
761
        """Returns a generator of (name, attribute) pairs
614
762
        """
615
 
        return ((prop._dbus_name, prop)
 
763
        return ((prop.__get__(self)._dbus_name, prop.__get__(self))
 
764
                for cls in self.__class__.__mro__
616
765
                for name, prop in
617
 
                inspect.getmembers(self, self._is_dbus_property))
 
766
                inspect.getmembers(cls, self._is_dbus_property))
618
767
    
619
768
    def _get_dbus_property(self, interface_name, property_name):
620
769
        """Returns a bound method if one exists which is a D-Bus
621
770
        property with the specified name and interface.
622
771
        """
623
 
        for name in (property_name,
624
 
                     property_name + "_dbus_property"):
625
 
            prop = getattr(self, name, None)
626
 
            if (prop is None
627
 
                or not self._is_dbus_property(prop)
628
 
                or prop._dbus_name != property_name
629
 
                or (interface_name and prop._dbus_interface
630
 
                    and interface_name != prop._dbus_interface)):
631
 
                continue
632
 
            return prop
 
772
        for cls in  self.__class__.__mro__:
 
773
            for name, value in (inspect.getmembers
 
774
                                (cls, self._is_dbus_property)):
 
775
                if (value._dbus_name == property_name
 
776
                    and value._dbus_interface == interface_name):
 
777
                    return value.__get__(self)
 
778
        
633
779
        # No such property
634
780
        raise DBusPropertyNotFound(self.dbus_object_path + ":"
635
781
                                   + interface_name + "."
669
815
    def GetAll(self, interface_name):
670
816
        """Standard D-Bus property GetAll() method, see D-Bus
671
817
        standard.
672
 
 
 
818
        
673
819
        Note: Will not include properties with access="write".
674
820
        """
675
 
        all = {}
 
821
        properties = {}
676
822
        for name, prop in self._get_all_dbus_properties():
677
823
            if (interface_name
678
824
                and interface_name != prop._dbus_interface):
683
829
                continue
684
830
            value = prop()
685
831
            if not hasattr(value, "variant_level"):
686
 
                all[name] = value
 
832
                properties[name] = value
687
833
                continue
688
 
            all[name] = type(value)(value, variant_level=
689
 
                                    value.variant_level+1)
690
 
        return dbus.Dictionary(all, signature="sv")
 
834
            properties[name] = type(value)(value, variant_level=
 
835
                                           value.variant_level+1)
 
836
        return dbus.Dictionary(properties, signature="sv")
691
837
    
692
838
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
693
839
                         out_signature="s",
737
883
        return xmlstring
738
884
 
739
885
 
 
886
def datetime_to_dbus (dt, variant_level=0):
 
887
    """Convert a UTC datetime.datetime() to a D-Bus type."""
 
888
    if dt is None:
 
889
        return dbus.String("", variant_level = variant_level)
 
890
    return dbus.String(dt.isoformat(),
 
891
                       variant_level=variant_level)
 
892
 
 
893
 
 
894
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
 
895
                                  .__metaclass__):
 
896
    """Applied to an empty subclass of a D-Bus object, this metaclass
 
897
    will add additional D-Bus attributes matching a certain pattern.
 
898
    """
 
899
    def __new__(mcs, name, bases, attr):
 
900
        # Go through all the base classes which could have D-Bus
 
901
        # methods, signals, or properties in them
 
902
        for base in (b for b in bases
 
903
                     if issubclass(b, dbus.service.Object)):
 
904
            # Go though all attributes of the base class
 
905
            for attrname, attribute in inspect.getmembers(base):
 
906
                # Ignore non-D-Bus attributes, and D-Bus attributes
 
907
                # with the wrong interface name
 
908
                if (not hasattr(attribute, "_dbus_interface")
 
909
                    or not attribute._dbus_interface
 
910
                    .startswith("se.recompile.Mandos")):
 
911
                    continue
 
912
                # Create an alternate D-Bus interface name based on
 
913
                # the current name
 
914
                alt_interface = (attribute._dbus_interface
 
915
                                 .replace("se.recompile.Mandos",
 
916
                                          "se.bsnet.fukt.Mandos"))
 
917
                # Is this a D-Bus signal?
 
918
                if getattr(attribute, "_dbus_is_signal", False):
 
919
                    # Extract the original non-method function by
 
920
                    # black magic
 
921
                    nonmethod_func = (dict(
 
922
                            zip(attribute.func_code.co_freevars,
 
923
                                attribute.__closure__))["func"]
 
924
                                      .cell_contents)
 
925
                    # Create a new, but exactly alike, function
 
926
                    # object, and decorate it to be a new D-Bus signal
 
927
                    # with the alternate D-Bus interface name
 
928
                    new_function = (dbus.service.signal
 
929
                                    (alt_interface,
 
930
                                     attribute._dbus_signature)
 
931
                                    (types.FunctionType(
 
932
                                nonmethod_func.func_code,
 
933
                                nonmethod_func.func_globals,
 
934
                                nonmethod_func.func_name,
 
935
                                nonmethod_func.func_defaults,
 
936
                                nonmethod_func.func_closure)))
 
937
                    # Define a creator of a function to call both the
 
938
                    # old and new functions, so both the old and new
 
939
                    # signals gets sent when the function is called
 
940
                    def fixscope(func1, func2):
 
941
                        """This function is a scope container to pass
 
942
                        func1 and func2 to the "call_both" function
 
943
                        outside of its arguments"""
 
944
                        def call_both(*args, **kwargs):
 
945
                            """This function will emit two D-Bus
 
946
                            signals by calling func1 and func2"""
 
947
                            func1(*args, **kwargs)
 
948
                            func2(*args, **kwargs)
 
949
                        return call_both
 
950
                    # Create the "call_both" function and add it to
 
951
                    # the class
 
952
                    attr[attrname] = fixscope(attribute,
 
953
                                              new_function)
 
954
                # Is this a D-Bus method?
 
955
                elif getattr(attribute, "_dbus_is_method", False):
 
956
                    # Create a new, but exactly alike, function
 
957
                    # object.  Decorate it to be a new D-Bus method
 
958
                    # with the alternate D-Bus interface name.  Add it
 
959
                    # to the class.
 
960
                    attr[attrname] = (dbus.service.method
 
961
                                      (alt_interface,
 
962
                                       attribute._dbus_in_signature,
 
963
                                       attribute._dbus_out_signature)
 
964
                                      (types.FunctionType
 
965
                                       (attribute.func_code,
 
966
                                        attribute.func_globals,
 
967
                                        attribute.func_name,
 
968
                                        attribute.func_defaults,
 
969
                                        attribute.func_closure)))
 
970
                # Is this a D-Bus property?
 
971
                elif getattr(attribute, "_dbus_is_property", False):
 
972
                    # Create a new, but exactly alike, function
 
973
                    # object, and decorate it to be a new D-Bus
 
974
                    # property with the alternate D-Bus interface
 
975
                    # name.  Add it to the class.
 
976
                    attr[attrname] = (dbus_service_property
 
977
                                      (alt_interface,
 
978
                                       attribute._dbus_signature,
 
979
                                       attribute._dbus_access,
 
980
                                       attribute
 
981
                                       ._dbus_get_args_options
 
982
                                       ["byte_arrays"])
 
983
                                      (types.FunctionType
 
984
                                       (attribute.func_code,
 
985
                                        attribute.func_globals,
 
986
                                        attribute.func_name,
 
987
                                        attribute.func_defaults,
 
988
                                        attribute.func_closure)))
 
989
        return type.__new__(mcs, name, bases, attr)
 
990
 
 
991
 
740
992
class ClientDBus(Client, DBusObjectWithProperties):
741
993
    """A Client class using D-Bus
742
994
    
751
1003
    # dbus.service.Object doesn't use super(), so we can't either.
752
1004
    
753
1005
    def __init__(self, bus = None, *args, **kwargs):
 
1006
        self.bus = bus
 
1007
        Client.__init__(self, *args, **kwargs)
 
1008
        
754
1009
        self._approvals_pending = 0
755
 
        self.bus = bus
756
 
        Client.__init__(self, *args, **kwargs)
757
1010
        # Only now, when this client is initialized, can it show up on
758
1011
        # the D-Bus
759
1012
        client_object_name = unicode(self.name).translate(
764
1017
        DBusObjectWithProperties.__init__(self, self.bus,
765
1018
                                          self.dbus_object_path)
766
1019
        
767
 
    def _get_approvals_pending(self):
768
 
        return self._approvals_pending
769
 
    def _set_approvals_pending(self, value):
770
 
        old_value = self._approvals_pending
771
 
        self._approvals_pending = value
772
 
        bval = bool(value)
773
 
        if (hasattr(self, "dbus_object_path")
774
 
            and bval is not bool(old_value)):
775
 
            dbus_bool = dbus.Boolean(bval, variant_level=1)
776
 
            self.PropertyChanged(dbus.String("ApprovalPending"),
777
 
                                 dbus_bool)
778
 
 
779
 
    approvals_pending = property(_get_approvals_pending,
780
 
                                 _set_approvals_pending)
781
 
    del _get_approvals_pending, _set_approvals_pending
782
 
    
783
 
    @staticmethod
784
 
    def _datetime_to_dbus(dt, variant_level=0):
785
 
        """Convert a UTC datetime.datetime() to a D-Bus type."""
786
 
        return dbus.String(dt.isoformat(),
787
 
                           variant_level=variant_level)
788
 
    
789
 
    def enable(self):
790
 
        oldstate = getattr(self, "enabled", False)
791
 
        r = Client.enable(self)
792
 
        if oldstate != self.enabled:
793
 
            # Emit D-Bus signals
794
 
            self.PropertyChanged(dbus.String("Enabled"),
795
 
                                 dbus.Boolean(True, variant_level=1))
796
 
            self.PropertyChanged(
797
 
                dbus.String("LastEnabled"),
798
 
                self._datetime_to_dbus(self.last_enabled,
799
 
                                       variant_level=1))
800
 
        return r
801
 
    
802
 
    def disable(self, quiet = False):
803
 
        oldstate = getattr(self, "enabled", False)
804
 
        r = Client.disable(self, quiet=quiet)
805
 
        if not quiet and oldstate != self.enabled:
806
 
            # Emit D-Bus signal
807
 
            self.PropertyChanged(dbus.String("Enabled"),
808
 
                                 dbus.Boolean(False, variant_level=1))
809
 
        return r
 
1020
    def notifychangeproperty(transform_func,
 
1021
                             dbus_name, type_func=lambda x: x,
 
1022
                             variant_level=1):
 
1023
        """ Modify a variable so that it's a property which announces
 
1024
        its changes to DBus.
 
1025
        
 
1026
        transform_fun: Function that takes a value and a variant_level
 
1027
                       and transforms it to a D-Bus type.
 
1028
        dbus_name: D-Bus name of the variable
 
1029
        type_func: Function that transform the value before sending it
 
1030
                   to the D-Bus.  Default: no transform
 
1031
        variant_level: D-Bus variant level.  Default: 1
 
1032
        """
 
1033
        attrname = "_{0}".format(dbus_name)
 
1034
        def setter(self, value):
 
1035
            if hasattr(self, "dbus_object_path"):
 
1036
                if (not hasattr(self, attrname) or
 
1037
                    type_func(getattr(self, attrname, None))
 
1038
                    != type_func(value)):
 
1039
                    dbus_value = transform_func(type_func(value),
 
1040
                                                variant_level
 
1041
                                                =variant_level)
 
1042
                    self.PropertyChanged(dbus.String(dbus_name),
 
1043
                                         dbus_value)
 
1044
            setattr(self, attrname, value)
 
1045
        
 
1046
        return property(lambda self: getattr(self, attrname), setter)
 
1047
    
 
1048
    
 
1049
    expires = notifychangeproperty(datetime_to_dbus, "Expires")
 
1050
    approvals_pending = notifychangeproperty(dbus.Boolean,
 
1051
                                             "ApprovalPending",
 
1052
                                             type_func = bool)
 
1053
    enabled = notifychangeproperty(dbus.Boolean, "Enabled")
 
1054
    last_enabled = notifychangeproperty(datetime_to_dbus,
 
1055
                                        "LastEnabled")
 
1056
    checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
 
1057
                                   type_func = lambda checker:
 
1058
                                       checker is not None)
 
1059
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
 
1060
                                           "LastCheckedOK")
 
1061
    last_approval_request = notifychangeproperty(
 
1062
        datetime_to_dbus, "LastApprovalRequest")
 
1063
    approved_by_default = notifychangeproperty(dbus.Boolean,
 
1064
                                               "ApprovedByDefault")
 
1065
    approval_delay = notifychangeproperty(dbus.UInt16,
 
1066
                                          "ApprovalDelay",
 
1067
                                          type_func =
 
1068
                                          _timedelta_to_milliseconds)
 
1069
    approval_duration = notifychangeproperty(
 
1070
        dbus.UInt16, "ApprovalDuration",
 
1071
        type_func = _timedelta_to_milliseconds)
 
1072
    host = notifychangeproperty(dbus.String, "Host")
 
1073
    timeout = notifychangeproperty(dbus.UInt16, "Timeout",
 
1074
                                   type_func =
 
1075
                                   _timedelta_to_milliseconds)
 
1076
    extended_timeout = notifychangeproperty(
 
1077
        dbus.UInt16, "ExtendedTimeout",
 
1078
        type_func = _timedelta_to_milliseconds)
 
1079
    interval = notifychangeproperty(dbus.UInt16,
 
1080
                                    "Interval",
 
1081
                                    type_func =
 
1082
                                    _timedelta_to_milliseconds)
 
1083
    checker_command = notifychangeproperty(dbus.String, "Checker")
 
1084
    
 
1085
    del notifychangeproperty
810
1086
    
811
1087
    def __del__(self, *args, **kwargs):
812
1088
        try:
821
1097
                         *args, **kwargs):
822
1098
        self.checker_callback_tag = None
823
1099
        self.checker = None
824
 
        # Emit D-Bus signal
825
 
        self.PropertyChanged(dbus.String("CheckerRunning"),
826
 
                             dbus.Boolean(False, variant_level=1))
827
1100
        if os.WIFEXITED(condition):
828
1101
            exitstatus = os.WEXITSTATUS(condition)
829
1102
            # Emit D-Bus signal
839
1112
        return Client.checker_callback(self, pid, condition, command,
840
1113
                                       *args, **kwargs)
841
1114
    
842
 
    def checked_ok(self, *args, **kwargs):
843
 
        Client.checked_ok(self, *args, **kwargs)
844
 
        # Emit D-Bus signal
845
 
        self.PropertyChanged(
846
 
            dbus.String("LastCheckedOK"),
847
 
            (self._datetime_to_dbus(self.last_checked_ok,
848
 
                                    variant_level=1)))
849
 
    
850
 
    def need_approval(self, *args, **kwargs):
851
 
        r = Client.need_approval(self, *args, **kwargs)
852
 
        # Emit D-Bus signal
853
 
        self.PropertyChanged(
854
 
            dbus.String("LastApprovalRequest"),
855
 
            (self._datetime_to_dbus(self.last_approval_request,
856
 
                                    variant_level=1)))
857
 
        return r
858
 
    
859
1115
    def start_checker(self, *args, **kwargs):
860
1116
        old_checker = self.checker
861
1117
        if self.checker is not None:
868
1124
            and old_checker_pid != self.checker.pid):
869
1125
            # Emit D-Bus signal
870
1126
            self.CheckerStarted(self.current_checker_command)
871
 
            self.PropertyChanged(
872
 
                dbus.String("CheckerRunning"),
873
 
                dbus.Boolean(True, variant_level=1))
874
1127
        return r
875
1128
    
876
 
    def stop_checker(self, *args, **kwargs):
877
 
        old_checker = getattr(self, "checker", None)
878
 
        r = Client.stop_checker(self, *args, **kwargs)
879
 
        if (old_checker is not None
880
 
            and getattr(self, "checker", None) is None):
881
 
            self.PropertyChanged(dbus.String("CheckerRunning"),
882
 
                                 dbus.Boolean(False, variant_level=1))
883
 
        return r
884
 
 
885
1129
    def _reset_approved(self):
886
1130
        self._approved = None
887
1131
        return False
889
1133
    def approve(self, value=True):
890
1134
        self.send_changedstate()
891
1135
        self._approved = value
892
 
        gobject.timeout_add(self._timedelta_to_milliseconds
 
1136
        gobject.timeout_add(_timedelta_to_milliseconds
893
1137
                            (self.approval_duration),
894
1138
                            self._reset_approved)
895
1139
    
896
1140
    
897
1141
    ## D-Bus methods, signals & properties
898
 
    _interface = "se.bsnet.fukt.Mandos.Client"
 
1142
    _interface = "se.recompile.Mandos.Client"
899
1143
    
900
1144
    ## Signals
901
1145
    
938
1182
        "D-Bus signal"
939
1183
        return self.need_approval()
940
1184
    
 
1185
    # NeRwequest - signal
 
1186
    @dbus.service.signal(_interface, signature="s")
 
1187
    def NewRequest(self, ip):
 
1188
        """D-Bus signal
 
1189
        Is sent after a client request a password.
 
1190
        """
 
1191
        pass
 
1192
    
941
1193
    ## Methods
942
1194
    
943
1195
    # Approve - method
987
1239
        if value is None:       # get
988
1240
            return dbus.Boolean(self.approved_by_default)
989
1241
        self.approved_by_default = bool(value)
990
 
        # Emit D-Bus signal
991
 
        self.PropertyChanged(dbus.String("ApprovedByDefault"),
992
 
                             dbus.Boolean(value, variant_level=1))
993
1242
    
994
1243
    # ApprovalDelay - property
995
1244
    @dbus_service_property(_interface, signature="t",
998
1247
        if value is None:       # get
999
1248
            return dbus.UInt64(self.approval_delay_milliseconds())
1000
1249
        self.approval_delay = datetime.timedelta(0, 0, 0, value)
1001
 
        # Emit D-Bus signal
1002
 
        self.PropertyChanged(dbus.String("ApprovalDelay"),
1003
 
                             dbus.UInt64(value, variant_level=1))
1004
1250
    
1005
1251
    # ApprovalDuration - property
1006
1252
    @dbus_service_property(_interface, signature="t",
1007
1253
                           access="readwrite")
1008
1254
    def ApprovalDuration_dbus_property(self, value=None):
1009
1255
        if value is None:       # get
1010
 
            return dbus.UInt64(self._timedelta_to_milliseconds(
 
1256
            return dbus.UInt64(_timedelta_to_milliseconds(
1011
1257
                    self.approval_duration))
1012
1258
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
1013
 
        # Emit D-Bus signal
1014
 
        self.PropertyChanged(dbus.String("ApprovalDuration"),
1015
 
                             dbus.UInt64(value, variant_level=1))
1016
1259
    
1017
1260
    # Name - property
1018
1261
    @dbus_service_property(_interface, signature="s", access="read")
1031
1274
        if value is None:       # get
1032
1275
            return dbus.String(self.host)
1033
1276
        self.host = value
1034
 
        # Emit D-Bus signal
1035
 
        self.PropertyChanged(dbus.String("Host"),
1036
 
                             dbus.String(value, variant_level=1))
1037
1277
    
1038
1278
    # Created - property
1039
1279
    @dbus_service_property(_interface, signature="s", access="read")
1040
1280
    def Created_dbus_property(self):
1041
 
        return dbus.String(self._datetime_to_dbus(self.created))
 
1281
        return dbus.String(datetime_to_dbus(self.created))
1042
1282
    
1043
1283
    # LastEnabled - property
1044
1284
    @dbus_service_property(_interface, signature="s", access="read")
1045
1285
    def LastEnabled_dbus_property(self):
1046
 
        if self.last_enabled is None:
1047
 
            return dbus.String("")
1048
 
        return dbus.String(self._datetime_to_dbus(self.last_enabled))
 
1286
        return datetime_to_dbus(self.last_enabled)
1049
1287
    
1050
1288
    # Enabled - property
1051
1289
    @dbus_service_property(_interface, signature="b",
1065
1303
        if value is not None:
1066
1304
            self.checked_ok()
1067
1305
            return
1068
 
        if self.last_checked_ok is None:
1069
 
            return dbus.String("")
1070
 
        return dbus.String(self._datetime_to_dbus(self
1071
 
                                                  .last_checked_ok))
 
1306
        return datetime_to_dbus(self.last_checked_ok)
 
1307
    
 
1308
    # Expires - property
 
1309
    @dbus_service_property(_interface, signature="s", access="read")
 
1310
    def Expires_dbus_property(self):
 
1311
        return datetime_to_dbus(self.expires)
1072
1312
    
1073
1313
    # LastApprovalRequest - property
1074
1314
    @dbus_service_property(_interface, signature="s", access="read")
1075
1315
    def LastApprovalRequest_dbus_property(self):
1076
 
        if self.last_approval_request is None:
1077
 
            return dbus.String("")
1078
 
        return dbus.String(self.
1079
 
                           _datetime_to_dbus(self
1080
 
                                             .last_approval_request))
 
1316
        return datetime_to_dbus(self.last_approval_request)
1081
1317
    
1082
1318
    # Timeout - property
1083
1319
    @dbus_service_property(_interface, signature="t",
1086
1322
        if value is None:       # get
1087
1323
            return dbus.UInt64(self.timeout_milliseconds())
1088
1324
        self.timeout = datetime.timedelta(0, 0, 0, value)
1089
 
        # Emit D-Bus signal
1090
 
        self.PropertyChanged(dbus.String("Timeout"),
1091
 
                             dbus.UInt64(value, variant_level=1))
1092
1325
        if getattr(self, "disable_initiator_tag", None) is None:
1093
1326
            return
1094
1327
        # Reschedule timeout
1095
1328
        gobject.source_remove(self.disable_initiator_tag)
1096
1329
        self.disable_initiator_tag = None
1097
 
        time_to_die = (self.
1098
 
                       _timedelta_to_milliseconds((self
1099
 
                                                   .last_checked_ok
1100
 
                                                   + self.timeout)
1101
 
                                                  - datetime.datetime
1102
 
                                                  .utcnow()))
 
1330
        self.expires = None
 
1331
        time_to_die = _timedelta_to_milliseconds((self
 
1332
                                                  .last_checked_ok
 
1333
                                                  + self.timeout)
 
1334
                                                 - datetime.datetime
 
1335
                                                 .utcnow())
1103
1336
        if time_to_die <= 0:
1104
1337
            # The timeout has passed
1105
1338
            self.disable()
1106
1339
        else:
 
1340
            self.expires = (datetime.datetime.utcnow()
 
1341
                            + datetime.timedelta(milliseconds =
 
1342
                                                 time_to_die))
1107
1343
            self.disable_initiator_tag = (gobject.timeout_add
1108
1344
                                          (time_to_die, self.disable))
1109
1345
    
 
1346
    # ExtendedTimeout - property
 
1347
    @dbus_service_property(_interface, signature="t",
 
1348
                           access="readwrite")
 
1349
    def ExtendedTimeout_dbus_property(self, value=None):
 
1350
        if value is None:       # get
 
1351
            return dbus.UInt64(self.extended_timeout_milliseconds())
 
1352
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
 
1353
    
1110
1354
    # Interval - property
1111
1355
    @dbus_service_property(_interface, signature="t",
1112
1356
                           access="readwrite")
1114
1358
        if value is None:       # get
1115
1359
            return dbus.UInt64(self.interval_milliseconds())
1116
1360
        self.interval = datetime.timedelta(0, 0, 0, value)
1117
 
        # Emit D-Bus signal
1118
 
        self.PropertyChanged(dbus.String("Interval"),
1119
 
                             dbus.UInt64(value, variant_level=1))
1120
1361
        if getattr(self, "checker_initiator_tag", None) is None:
1121
1362
            return
1122
1363
        # Reschedule checker run
1124
1365
        self.checker_initiator_tag = (gobject.timeout_add
1125
1366
                                      (value, self.start_checker))
1126
1367
        self.start_checker()    # Start one now, too
1127
 
 
 
1368
    
1128
1369
    # Checker - property
1129
1370
    @dbus_service_property(_interface, signature="s",
1130
1371
                           access="readwrite")
1132
1373
        if value is None:       # get
1133
1374
            return dbus.String(self.checker_command)
1134
1375
        self.checker_command = value
1135
 
        # Emit D-Bus signal
1136
 
        self.PropertyChanged(dbus.String("Checker"),
1137
 
                             dbus.String(self.checker_command,
1138
 
                                         variant_level=1))
1139
1376
    
1140
1377
    # CheckerRunning - property
1141
1378
    @dbus_service_property(_interface, signature="b",
1168
1405
        self._pipe.send(('init', fpr, address))
1169
1406
        if not self._pipe.recv():
1170
1407
            raise KeyError()
1171
 
 
 
1408
    
1172
1409
    def __getattribute__(self, name):
1173
1410
        if(name == '_pipe'):
1174
1411
            return super(ProxyClient, self).__getattribute__(name)
1181
1418
                self._pipe.send(('funcall', name, args, kwargs))
1182
1419
                return self._pipe.recv()[1]
1183
1420
            return func
1184
 
 
 
1421
    
1185
1422
    def __setattr__(self, name, value):
1186
1423
        if(name == '_pipe'):
1187
1424
            return super(ProxyClient, self).__setattr__(name, value)
1188
1425
        self._pipe.send(('setattr', name, value))
1189
1426
 
1190
1427
 
 
1428
class ClientDBusTransitional(ClientDBus):
 
1429
    __metaclass__ = AlternateDBusNamesMetaclass
 
1430
 
 
1431
 
1191
1432
class ClientHandler(socketserver.BaseRequestHandler, object):
1192
1433
    """A class to handle client connections.
1193
1434
    
1200
1441
                        unicode(self.client_address))
1201
1442
            logger.debug("Pipe FD: %d",
1202
1443
                         self.server.child_pipe.fileno())
1203
 
 
 
1444
            
1204
1445
            session = (gnutls.connection
1205
1446
                       .ClientSession(self.request,
1206
1447
                                      gnutls.connection
1207
1448
                                      .X509Credentials()))
1208
 
 
 
1449
            
1209
1450
            # Note: gnutls.connection.X509Credentials is really a
1210
1451
            # generic GnuTLS certificate credentials object so long as
1211
1452
            # no X.509 keys are added to it.  Therefore, we can use it
1212
1453
            # here despite using OpenPGP certificates.
1213
 
 
 
1454
            
1214
1455
            #priority = ':'.join(("NONE", "+VERS-TLS1.1",
1215
1456
            #                      "+AES-256-CBC", "+SHA1",
1216
1457
            #                      "+COMP-NULL", "+CTYPE-OPENPGP",
1222
1463
            (gnutls.library.functions
1223
1464
             .gnutls_priority_set_direct(session._c_object,
1224
1465
                                         priority, None))
1225
 
 
 
1466
            
1226
1467
            # Start communication using the Mandos protocol
1227
1468
            # Get protocol number
1228
1469
            line = self.request.makefile().readline()
1233
1474
            except (ValueError, IndexError, RuntimeError) as error:
1234
1475
                logger.error("Unknown protocol version: %s", error)
1235
1476
                return
1236
 
 
 
1477
            
1237
1478
            # Start GnuTLS connection
1238
1479
            try:
1239
1480
                session.handshake()
1243
1484
                # established.  Just abandon the request.
1244
1485
                return
1245
1486
            logger.debug("Handshake succeeded")
1246
 
 
 
1487
            
1247
1488
            approval_required = False
1248
1489
            try:
1249
1490
                try:
1254
1495
                    logger.warning("Bad certificate: %s", error)
1255
1496
                    return
1256
1497
                logger.debug("Fingerprint: %s", fpr)
1257
 
 
 
1498
                if self.server.use_dbus:
 
1499
                    # Emit D-Bus signal
 
1500
                    client.NewRequest(str(self.client_address))
 
1501
                
1258
1502
                try:
1259
1503
                    client = ProxyClient(child_pipe, fpr,
1260
1504
                                         self.client_address)
1272
1516
                                       client.name)
1273
1517
                        if self.server.use_dbus:
1274
1518
                            # Emit D-Bus signal
1275
 
                            client.Rejected("Disabled")                    
 
1519
                            client.Rejected("Disabled")
1276
1520
                        return
1277
1521
                    
1278
1522
                    if client._approved or not client.approval_delay:
1295
1539
                        return
1296
1540
                    
1297
1541
                    #wait until timeout or approved
1298
 
                    #x = float(client._timedelta_to_milliseconds(delay))
1299
1542
                    time = datetime.datetime.now()
1300
1543
                    client.changedstate.acquire()
1301
 
                    client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
 
1544
                    (client.changedstate.wait
 
1545
                     (float(client._timedelta_to_milliseconds(delay)
 
1546
                            / 1000)))
1302
1547
                    client.changedstate.release()
1303
1548
                    time2 = datetime.datetime.now()
1304
1549
                    if (time2 - time) >= delay:
1326
1571
                                 sent, len(client.secret)
1327
1572
                                 - (sent_size + sent))
1328
1573
                    sent_size += sent
1329
 
 
 
1574
                
1330
1575
                logger.info("Sending secret to %s", client.name)
1331
 
                # bump the timeout as if seen
1332
 
                client.checked_ok()
 
1576
                # bump the timeout using extended_timeout
 
1577
                client.checked_ok(client.extended_timeout)
1333
1578
                if self.server.use_dbus:
1334
1579
                    # Emit D-Bus signal
1335
1580
                    client.GotSecret()
1402
1647
        # Convert the buffer to a Python bytestring
1403
1648
        fpr = ctypes.string_at(buf, buf_len.value)
1404
1649
        # Convert the bytestring to hexadecimal notation
1405
 
        hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
 
1650
        hex_fpr = binascii.hexlify(fpr).upper()
1406
1651
        return hex_fpr
1407
1652
 
1408
1653
 
1414
1659
        except:
1415
1660
            self.handle_error(request, address)
1416
1661
        self.close_request(request)
1417
 
            
 
1662
    
1418
1663
    def process_request(self, request, address):
1419
1664
        """Start a new process to process the request."""
1420
 
        multiprocessing.Process(target = self.sub_process_main,
1421
 
                                args = (request, address)).start()
 
1665
        proc = multiprocessing.Process(target = self.sub_process_main,
 
1666
                                       args = (request,
 
1667
                                               address))
 
1668
        proc.start()
 
1669
        return proc
 
1670
 
1422
1671
 
1423
1672
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1424
1673
    """ adds a pipe to the MixIn """
1428
1677
        This function creates a new pipe in self.pipe
1429
1678
        """
1430
1679
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
1431
 
 
1432
 
        super(MultiprocessingMixInWithPipe,
1433
 
              self).process_request(request, client_address)
 
1680
        
 
1681
        proc = MultiprocessingMixIn.process_request(self, request,
 
1682
                                                    client_address)
1434
1683
        self.child_pipe.close()
1435
 
        self.add_pipe(parent_pipe)
1436
 
 
1437
 
    def add_pipe(self, parent_pipe):
 
1684
        self.add_pipe(parent_pipe, proc)
 
1685
    
 
1686
    def add_pipe(self, parent_pipe, proc):
1438
1687
        """Dummy function; override as necessary"""
1439
1688
        raise NotImplementedError
1440
1689
 
 
1690
 
1441
1691
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1442
1692
                     socketserver.TCPServer, object):
1443
1693
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1517
1767
        self.enabled = False
1518
1768
        self.clients = clients
1519
1769
        if self.clients is None:
1520
 
            self.clients = set()
 
1770
            self.clients = {}
1521
1771
        self.use_dbus = use_dbus
1522
1772
        self.gnutls_priority = gnutls_priority
1523
1773
        IPv6_TCPServer.__init__(self, server_address,
1527
1777
    def server_activate(self):
1528
1778
        if self.enabled:
1529
1779
            return socketserver.TCPServer.server_activate(self)
 
1780
    
1530
1781
    def enable(self):
1531
1782
        self.enabled = True
1532
 
    def add_pipe(self, parent_pipe):
 
1783
    
 
1784
    def add_pipe(self, parent_pipe, proc):
1533
1785
        # Call "handle_ipc" for both data and EOF events
1534
1786
        gobject.io_add_watch(parent_pipe.fileno(),
1535
1787
                             gobject.IO_IN | gobject.IO_HUP,
1536
1788
                             functools.partial(self.handle_ipc,
1537
 
                                               parent_pipe = parent_pipe))
1538
 
        
 
1789
                                               parent_pipe =
 
1790
                                               parent_pipe,
 
1791
                                               proc = proc))
 
1792
    
1539
1793
    def handle_ipc(self, source, condition, parent_pipe=None,
1540
 
                   client_object=None):
 
1794
                   proc = None, client_object=None):
1541
1795
        condition_names = {
1542
1796
            gobject.IO_IN: "IN",   # There is data to read.
1543
1797
            gobject.IO_OUT: "OUT", # Data can be written (without
1552
1806
                                       for cond, name in
1553
1807
                                       condition_names.iteritems()
1554
1808
                                       if cond & condition)
1555
 
        # error or the other end of multiprocessing.Pipe has closed
 
1809
        # error, or the other end of multiprocessing.Pipe has closed
1556
1810
        if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
 
1811
            # Wait for other process to exit
 
1812
            proc.join()
1557
1813
            return False
1558
1814
        
1559
1815
        # Read a request from the child
1564
1820
            fpr = request[1]
1565
1821
            address = request[2]
1566
1822
            
1567
 
            for c in self.clients:
 
1823
            for c in self.clients.itervalues():
1568
1824
                if c.fingerprint == fpr:
1569
1825
                    client = c
1570
1826
                    break
1573
1829
                            "dress: %s", fpr, address)
1574
1830
                if self.use_dbus:
1575
1831
                    # Emit D-Bus signal
1576
 
                    mandos_dbus_service.ClientNotFound(fpr, address[0])
 
1832
                    mandos_dbus_service.ClientNotFound(fpr,
 
1833
                                                       address[0])
1577
1834
                parent_pipe.send(False)
1578
1835
                return False
1579
1836
            
1580
1837
            gobject.io_add_watch(parent_pipe.fileno(),
1581
1838
                                 gobject.IO_IN | gobject.IO_HUP,
1582
1839
                                 functools.partial(self.handle_ipc,
1583
 
                                                   parent_pipe = parent_pipe,
1584
 
                                                   client_object = client))
 
1840
                                                   parent_pipe =
 
1841
                                                   parent_pipe,
 
1842
                                                   proc = proc,
 
1843
                                                   client_object =
 
1844
                                                   client))
1585
1845
            parent_pipe.send(True)
1586
 
            # remove the old hook in favor of the new above hook on same fileno
 
1846
            # remove the old hook in favor of the new above hook on
 
1847
            # same fileno
1587
1848
            return False
1588
1849
        if command == 'funcall':
1589
1850
            funcname = request[1]
1590
1851
            args = request[2]
1591
1852
            kwargs = request[3]
1592
1853
            
1593
 
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1594
 
 
 
1854
            parent_pipe.send(('data', getattr(client_object,
 
1855
                                              funcname)(*args,
 
1856
                                                         **kwargs)))
 
1857
        
1595
1858
        if command == 'getattr':
1596
1859
            attrname = request[1]
1597
1860
            if callable(client_object.__getattribute__(attrname)):
1598
1861
                parent_pipe.send(('function',))
1599
1862
            else:
1600
 
                parent_pipe.send(('data', client_object.__getattribute__(attrname)))
 
1863
                parent_pipe.send(('data', client_object
 
1864
                                  .__getattribute__(attrname)))
1601
1865
        
1602
1866
        if command == 'setattr':
1603
1867
            attrname = request[1]
1604
1868
            value = request[2]
1605
1869
            setattr(client_object, attrname, value)
1606
 
 
 
1870
        
1607
1871
        return True
1608
1872
 
1609
1873
 
1646
1910
    return timevalue
1647
1911
 
1648
1912
 
1649
 
def if_nametoindex(interface):
1650
 
    """Call the C function if_nametoindex(), or equivalent
1651
 
    
1652
 
    Note: This function cannot accept a unicode string."""
1653
 
    global if_nametoindex
1654
 
    try:
1655
 
        if_nametoindex = (ctypes.cdll.LoadLibrary
1656
 
                          (ctypes.util.find_library("c"))
1657
 
                          .if_nametoindex)
1658
 
    except (OSError, AttributeError):
1659
 
        logger.warning("Doing if_nametoindex the hard way")
1660
 
        def if_nametoindex(interface):
1661
 
            "Get an interface index the hard way, i.e. using fcntl()"
1662
 
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1663
 
            with contextlib.closing(socket.socket()) as s:
1664
 
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1665
 
                                    struct.pack(str("16s16x"),
1666
 
                                                interface))
1667
 
            interface_index = struct.unpack(str("I"),
1668
 
                                            ifreq[16:20])[0]
1669
 
            return interface_index
1670
 
    return if_nametoindex(interface)
1671
 
 
1672
 
 
1673
1913
def daemon(nochdir = False, noclose = False):
1674
1914
    """See daemon(3).  Standard BSD Unix function.
1675
1915
    
1730
1970
                        " system bus interface")
1731
1971
    parser.add_argument("--no-ipv6", action="store_false",
1732
1972
                        dest="use_ipv6", help="Do not use IPv6")
 
1973
    parser.add_argument("--no-restore", action="store_false",
 
1974
                        dest="restore", help="Do not restore stored"
 
1975
                        " state")
 
1976
    parser.add_argument("--statedir", metavar="DIR",
 
1977
                        help="Directory to save/restore state in")
 
1978
    
1733
1979
    options = parser.parse_args()
1734
1980
    
1735
1981
    if options.check:
1748
1994
                        "use_dbus": "True",
1749
1995
                        "use_ipv6": "True",
1750
1996
                        "debuglevel": "",
 
1997
                        "restore": "True",
 
1998
                        "statedir": "/var/lib/mandos"
1751
1999
                        }
1752
2000
    
1753
2001
    # Parse config file for server-global settings
1770
2018
    # options, if set.
1771
2019
    for option in ("interface", "address", "port", "debug",
1772
2020
                   "priority", "servicename", "configdir",
1773
 
                   "use_dbus", "use_ipv6", "debuglevel"):
 
2021
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
 
2022
                   "statedir"):
1774
2023
        value = getattr(options, option)
1775
2024
        if value is not None:
1776
2025
            server_settings[option] = value
1788
2037
    debuglevel = server_settings["debuglevel"]
1789
2038
    use_dbus = server_settings["use_dbus"]
1790
2039
    use_ipv6 = server_settings["use_ipv6"]
1791
 
 
 
2040
    stored_state_path = os.path.join(server_settings["statedir"],
 
2041
                                     stored_state_file)
 
2042
    
 
2043
    if debug:
 
2044
        initlogger(logging.DEBUG)
 
2045
    else:
 
2046
        if not debuglevel:
 
2047
            initlogger()
 
2048
        else:
 
2049
            level = getattr(logging, debuglevel.upper())
 
2050
            initlogger(level)
 
2051
    
1792
2052
    if server_settings["servicename"] != "Mandos":
1793
2053
        syslogger.setFormatter(logging.Formatter
1794
2054
                               ('Mandos (%s) [%%(process)d]:'
1796
2056
                                % server_settings["servicename"]))
1797
2057
    
1798
2058
    # Parse config file with clients
1799
 
    client_defaults = { "timeout": "1h",
1800
 
                        "interval": "5m",
 
2059
    client_defaults = { "timeout": "5m",
 
2060
                        "extended_timeout": "15m",
 
2061
                        "interval": "2m",
1801
2062
                        "checker": "fping -q -- %%(host)s",
1802
2063
                        "host": "",
1803
2064
                        "approval_delay": "0s",
1847
2108
        if error[0] != errno.EPERM:
1848
2109
            raise error
1849
2110
    
1850
 
    if not debug and not debuglevel:
1851
 
        syslogger.setLevel(logging.WARNING)
1852
 
        console.setLevel(logging.WARNING)
1853
 
    if debuglevel:
1854
 
        level = getattr(logging, debuglevel.upper())
1855
 
        syslogger.setLevel(level)
1856
 
        console.setLevel(level)
1857
 
 
1858
2111
    if debug:
1859
2112
        # Enable all possible GnuTLS debugging
1860
2113
        
1891
2144
    # End of Avahi example code
1892
2145
    if use_dbus:
1893
2146
        try:
1894
 
            bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
 
2147
            bus_name = dbus.service.BusName("se.recompile.Mandos",
1895
2148
                                            bus, do_not_queue=True)
 
2149
            old_bus_name = (dbus.service.BusName
 
2150
                            ("se.bsnet.fukt.Mandos", bus,
 
2151
                             do_not_queue=True))
1896
2152
        except dbus.exceptions.NameExistsException as e:
1897
2153
            logger.error(unicode(e) + ", disabling D-Bus")
1898
2154
            use_dbus = False
1899
2155
            server_settings["use_dbus"] = False
1900
2156
            tcp_server.use_dbus = False
1901
2157
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1902
 
    service = AvahiService(name = server_settings["servicename"],
1903
 
                           servicetype = "_mandos._tcp",
1904
 
                           protocol = protocol, bus = bus)
 
2158
    service = AvahiServiceToSyslog(name =
 
2159
                                   server_settings["servicename"],
 
2160
                                   servicetype = "_mandos._tcp",
 
2161
                                   protocol = protocol, bus = bus)
1905
2162
    if server_settings["interface"]:
1906
2163
        service.interface = (if_nametoindex
1907
2164
                             (str(server_settings["interface"])))
1911
2168
    
1912
2169
    client_class = Client
1913
2170
    if use_dbus:
1914
 
        client_class = functools.partial(ClientDBus, bus = bus)
1915
 
    def client_config_items(config, section):
1916
 
        special_settings = {
1917
 
            "approved_by_default":
1918
 
                lambda: config.getboolean(section,
1919
 
                                          "approved_by_default"),
1920
 
            }
1921
 
        for name, value in config.items(section):
 
2171
        client_class = functools.partial(ClientDBusTransitional,
 
2172
                                         bus = bus)
 
2173
    
 
2174
    special_settings = {
 
2175
        # Some settings need to be accessd by special methods;
 
2176
        # booleans need .getboolean(), etc.  Here is a list of them:
 
2177
        "approved_by_default":
 
2178
            lambda section:
 
2179
            client_config.getboolean(section, "approved_by_default"),
 
2180
        }
 
2181
    # Construct a new dict of client settings of this form:
 
2182
    # { client_name: {setting_name: value, ...}, ...}
 
2183
    # with exceptions for any special settings as defined above
 
2184
    client_settings = dict((clientname,
 
2185
                           dict((setting,
 
2186
                                 (value
 
2187
                                  if setting not in special_settings
 
2188
                                  else special_settings[setting]
 
2189
                                  (clientname)))
 
2190
                                for setting, value in
 
2191
                                client_config.items(clientname)))
 
2192
                          for clientname in client_config.sections())
 
2193
    
 
2194
    old_client_settings = {}
 
2195
    clients_data = []
 
2196
    
 
2197
    # Get client data and settings from last running state.
 
2198
    if server_settings["restore"]:
 
2199
        try:
 
2200
            with open(stored_state_path, "rb") as stored_state:
 
2201
                clients_data, old_client_settings = (pickle.load
 
2202
                                                     (stored_state))
 
2203
            os.remove(stored_state_path)
 
2204
        except IOError as e:
 
2205
            logger.warning("Could not load persistent state: {0}"
 
2206
                           .format(e))
 
2207
            if e.errno != errno.ENOENT:
 
2208
                raise
 
2209
    
 
2210
    with Crypto() as crypt:
 
2211
        for client in clients_data:
 
2212
            client_name = client["name"]
 
2213
            
 
2214
            # Decide which value to use after restoring saved state.
 
2215
            # We have three different values: Old config file,
 
2216
            # new config file, and saved state.
 
2217
            # New config value takes precedence if it differs from old
 
2218
            # config value, otherwise use saved state.
 
2219
            for name, value in client_settings[client_name].items():
 
2220
                try:
 
2221
                    # For each value in new config, check if it
 
2222
                    # differs from the old config value (Except for
 
2223
                    # the "secret" attribute)
 
2224
                    if (name != "secret" and
 
2225
                        value != old_client_settings[client_name]
 
2226
                        [name]):
 
2227
                        setattr(client, name, value)
 
2228
                except KeyError:
 
2229
                    pass
 
2230
            
 
2231
            # Clients who has passed its expire date can still be
 
2232
            # enabled if its last checker was sucessful.  Clients
 
2233
            # whose checker failed before we stored its state is
 
2234
            # assumed to have failed all checkers during downtime.
 
2235
            if client["enabled"] and client["last_checked_ok"]:
 
2236
                if ((datetime.datetime.utcnow()
 
2237
                     - client["last_checked_ok"])
 
2238
                    > client["interval"]):
 
2239
                    if client["last_checker_status"] != 0:
 
2240
                        client["enabled"] = False
 
2241
                    else:
 
2242
                        client["expires"] = (datetime.datetime
 
2243
                                             .utcnow()
 
2244
                                             + client["timeout"])
 
2245
            
 
2246
            client["changedstate"] = (multiprocessing_manager
 
2247
                                      .Condition
 
2248
                                      (multiprocessing_manager
 
2249
                                       .Lock()))
 
2250
            if use_dbus:
 
2251
                new_client = (ClientDBusTransitional.__new__
 
2252
                              (ClientDBusTransitional))
 
2253
                tcp_server.clients[client_name] = new_client
 
2254
                new_client.bus = bus
 
2255
                for name, value in client.iteritems():
 
2256
                    setattr(new_client, name, value)
 
2257
                client_object_name = unicode(client_name).translate(
 
2258
                    {ord("."): ord("_"),
 
2259
                     ord("-"): ord("_")})
 
2260
                new_client.dbus_object_path = (dbus.ObjectPath
 
2261
                                               ("/clients/"
 
2262
                                                + client_object_name))
 
2263
                DBusObjectWithProperties.__init__(new_client,
 
2264
                                                  new_client.bus,
 
2265
                                                  new_client
 
2266
                                                  .dbus_object_path)
 
2267
            else:
 
2268
                tcp_server.clients[client_name] = (Client.__new__
 
2269
                                                   (Client))
 
2270
                for name, value in client.iteritems():
 
2271
                    setattr(tcp_server.clients[client_name],
 
2272
                            name, value)
 
2273
            
1922
2274
            try:
1923
 
                yield (name, special_settings[name]())
1924
 
            except KeyError:
1925
 
                yield (name, value)
1926
 
    
1927
 
    tcp_server.clients.update(set(
1928
 
            client_class(name = section,
1929
 
                         config= dict(client_config_items(
1930
 
                        client_config, section)))
1931
 
            for section in client_config.sections()))
 
2275
                tcp_server.clients[client_name].secret = (
 
2276
                    crypt.decrypt(tcp_server.clients[client_name]
 
2277
                                  .encrypted_secret,
 
2278
                                  client_settings[client_name]
 
2279
                                  ["secret"]))
 
2280
            except CryptoError:
 
2281
                # If decryption fails, we use secret from new settings
 
2282
                tcp_server.clients[client_name].secret = (
 
2283
                    client_settings[client_name]["secret"])
 
2284
    
 
2285
    # Create/remove clients based on new changes made to config
 
2286
    for clientname in set(old_client_settings) - set(client_settings):
 
2287
        del tcp_server.clients[clientname]
 
2288
    for clientname in set(client_settings) - set(old_client_settings):
 
2289
        tcp_server.clients[clientname] = (client_class(name
 
2290
                                                       = clientname,
 
2291
                                                       config =
 
2292
                                                       client_settings
 
2293
                                                       [clientname]))
 
2294
    
1932
2295
    if not tcp_server.clients:
1933
2296
        logger.warning("No clients defined")
1934
2297
        
1947
2310
        del pidfilename
1948
2311
        
1949
2312
        signal.signal(signal.SIGINT, signal.SIG_IGN)
1950
 
 
 
2313
    
1951
2314
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1952
2315
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1953
2316
    
1956
2319
            """A D-Bus proxy object"""
1957
2320
            def __init__(self):
1958
2321
                dbus.service.Object.__init__(self, bus, "/")
1959
 
            _interface = "se.bsnet.fukt.Mandos"
 
2322
            _interface = "se.recompile.Mandos"
1960
2323
            
1961
2324
            @dbus.service.signal(_interface, signature="o")
1962
2325
            def ClientAdded(self, objpath):
1977
2340
            def GetAllClients(self):
1978
2341
                "D-Bus method"
1979
2342
                return dbus.Array(c.dbus_object_path
1980
 
                                  for c in tcp_server.clients)
 
2343
                                  for c in
 
2344
                                  tcp_server.clients.itervalues())
1981
2345
            
1982
2346
            @dbus.service.method(_interface,
1983
2347
                                 out_signature="a{oa{sv}}")
1985
2349
                "D-Bus method"
1986
2350
                return dbus.Dictionary(
1987
2351
                    ((c.dbus_object_path, c.GetAll(""))
1988
 
                     for c in tcp_server.clients),
 
2352
                     for c in tcp_server.clients.itervalues()),
1989
2353
                    signature="oa{sv}")
1990
2354
            
1991
2355
            @dbus.service.method(_interface, in_signature="o")
1992
2356
            def RemoveClient(self, object_path):
1993
2357
                "D-Bus method"
1994
 
                for c in tcp_server.clients:
 
2358
                for c in tcp_server.clients.itervalues():
1995
2359
                    if c.dbus_object_path == object_path:
1996
 
                        tcp_server.clients.remove(c)
 
2360
                        del tcp_server.clients[c.name]
1997
2361
                        c.remove_from_connection()
1998
2362
                        # Don't signal anything except ClientRemoved
1999
2363
                        c.disable(quiet=True)
2004
2368
            
2005
2369
            del _interface
2006
2370
        
2007
 
        mandos_dbus_service = MandosDBusService()
 
2371
        class MandosDBusServiceTransitional(MandosDBusService):
 
2372
            __metaclass__ = AlternateDBusNamesMetaclass
 
2373
        mandos_dbus_service = MandosDBusServiceTransitional()
2008
2374
    
2009
2375
    def cleanup():
2010
2376
        "Cleanup function; run on exit"
2011
2377
        service.cleanup()
2012
2378
        
 
2379
        multiprocessing.active_children()
 
2380
        if not (tcp_server.clients or client_settings):
 
2381
            return
 
2382
        
 
2383
        # Store client before exiting. Secrets are encrypted with key
 
2384
        # based on what config file has. If config file is
 
2385
        # removed/edited, old secret will thus be unrecovable.
 
2386
        clients = []
 
2387
        with Crypto() as crypt:
 
2388
            for client in tcp_server.clients.itervalues():
 
2389
                key = client_settings[client.name]["secret"]
 
2390
                client.encrypted_secret = crypt.encrypt(client.secret,
 
2391
                                                        key)
 
2392
                client_dict = {}
 
2393
                
 
2394
                # A list of attributes that will not be stored when
 
2395
                # shutting down.
 
2396
                exclude = set(("bus", "changedstate", "secret"))
 
2397
                for name, typ in (inspect.getmembers
 
2398
                                  (dbus.service.Object)):
 
2399
                    exclude.add(name)
 
2400
                
 
2401
                client_dict["encrypted_secret"] = (client
 
2402
                                                   .encrypted_secret)
 
2403
                for attr in client.client_structure:
 
2404
                    if attr not in exclude:
 
2405
                        client_dict[attr] = getattr(client, attr)
 
2406
                
 
2407
                clients.append(client_dict)
 
2408
                del client_settings[client.name]["secret"]
 
2409
        
 
2410
        try:
 
2411
            with os.fdopen(os.open(stored_state_path,
 
2412
                                   os.O_CREAT|os.O_WRONLY|os.O_TRUNC,
 
2413
                                   0600), "wb") as stored_state:
 
2414
                pickle.dump((clients, client_settings), stored_state)
 
2415
        except (IOError, OSError) as e:
 
2416
            logger.warning("Could not save persistent state: {0}"
 
2417
                           .format(e))
 
2418
            if e.errno not in (errno.ENOENT, errno.EACCES):
 
2419
                raise
 
2420
        
 
2421
        # Delete all clients, and settings from config
2013
2422
        while tcp_server.clients:
2014
 
            client = tcp_server.clients.pop()
 
2423
            name, client = tcp_server.clients.popitem()
2015
2424
            if use_dbus:
2016
2425
                client.remove_from_connection()
2017
 
            client.disable_hook = None
2018
2426
            # Don't signal anything except ClientRemoved
2019
2427
            client.disable(quiet=True)
2020
2428
            if use_dbus:
2021
2429
                # Emit D-Bus signal
2022
 
                mandos_dbus_service.ClientRemoved(client.dbus_object_path,
 
2430
                mandos_dbus_service.ClientRemoved(client
 
2431
                                                  .dbus_object_path,
2023
2432
                                                  client.name)
 
2433
        client_settings.clear()
2024
2434
    
2025
2435
    atexit.register(cleanup)
2026
2436
    
2027
 
    for client in tcp_server.clients:
 
2437
    for client in tcp_server.clients.itervalues():
2028
2438
        if use_dbus:
2029
2439
            # Emit D-Bus signal
2030
2440
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
2031
 
        client.enable()
 
2441
        # Need to initiate checking of clients
 
2442
        if client.enabled:
 
2443
            client.init_checker()
2032
2444
    
2033
2445
    tcp_server.enable()
2034
2446
    tcp_server.server_activate()
2074
2486
    # Must run before the D-Bus bus name gets deregistered
2075
2487
    cleanup()
2076
2488
 
 
2489
 
2077
2490
if __name__ == '__main__':
2078
2491
    main()