/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

Reorder TODO entries

Show diffs side-by-side

added added

removed removed

Lines of Context:
63
63
import cPickle as pickle
64
64
import multiprocessing
65
65
import types
 
66
import binascii
 
67
import tempfile
66
68
 
67
69
import dbus
68
70
import dbus.service
73
75
import ctypes.util
74
76
import xml.dom.minidom
75
77
import inspect
 
78
import GnuPGInterface
76
79
 
77
80
try:
78
81
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
82
85
    except ImportError:
83
86
        SO_BINDTODEVICE = None
84
87
 
85
 
 
86
 
version = "1.3.1"
87
 
 
88
 
#logger = logging.getLogger('mandos')
89
 
logger = logging.Logger('mandos')
 
88
version = "1.4.1"
 
89
stored_state_file = "clients.pickle"
 
90
 
 
91
logger = logging.getLogger()
90
92
syslogger = (logging.handlers.SysLogHandler
91
93
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
92
94
              address = str("/dev/log")))
93
 
syslogger.setFormatter(logging.Formatter
94
 
                       ('Mandos [%(process)d]: %(levelname)s:'
95
 
                        ' %(message)s'))
96
 
logger.addHandler(syslogger)
97
 
 
98
 
console = logging.StreamHandler()
99
 
console.setFormatter(logging.Formatter('%(name)s [%(process)d]:'
100
 
                                       ' %(levelname)s:'
101
 
                                       ' %(message)s'))
102
 
logger.addHandler(console)
 
95
 
 
96
try:
 
97
    if_nametoindex = (ctypes.cdll.LoadLibrary
 
98
                      (ctypes.util.find_library("c"))
 
99
                      .if_nametoindex)
 
100
except (OSError, AttributeError):
 
101
    def if_nametoindex(interface):
 
102
        "Get an interface index the hard way, i.e. using fcntl()"
 
103
        SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
 
104
        with contextlib.closing(socket.socket()) as s:
 
105
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
 
106
                                struct.pack(str("16s16x"),
 
107
                                            interface))
 
108
        interface_index = struct.unpack(str("I"),
 
109
                                        ifreq[16:20])[0]
 
110
        return interface_index
 
111
 
 
112
 
 
113
def initlogger(debug, level=logging.WARNING):
 
114
    """init logger and add loglevel"""
 
115
    
 
116
    syslogger.setFormatter(logging.Formatter
 
117
                           ('Mandos [%(process)d]: %(levelname)s:'
 
118
                            ' %(message)s'))
 
119
    logger.addHandler(syslogger)
 
120
    
 
121
    if debug:
 
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 PGPError(Exception):
 
132
    """Exception if encryption/decryption fails"""
 
133
    pass
 
134
 
 
135
 
 
136
class PGPEngine(object):
 
137
    """A simple class for OpenPGP symmetric encryption & decryption"""
 
138
    def __init__(self):
 
139
        self.gnupg = GnuPGInterface.GnuPG()
 
140
        self.tempdir = tempfile.mkdtemp(prefix="mandos-")
 
141
        self.gnupg = GnuPGInterface.GnuPG()
 
142
        self.gnupg.options.meta_interactive = False
 
143
        self.gnupg.options.homedir = self.tempdir
 
144
        self.gnupg.options.extra_args.extend(['--force-mdc',
 
145
                                              '--quiet'])
 
146
    
 
147
    def __enter__(self):
 
148
        return self
 
149
    
 
150
    def __exit__ (self, exc_type, exc_value, traceback):
 
151
        self._cleanup()
 
152
        return False
 
153
    
 
154
    def __del__(self):
 
155
        self._cleanup()
 
156
    
 
157
    def _cleanup(self):
 
158
        if self.tempdir is not None:
 
159
            # Delete contents of tempdir
 
160
            for root, dirs, files in os.walk(self.tempdir,
 
161
                                             topdown = False):
 
162
                for filename in files:
 
163
                    os.remove(os.path.join(root, filename))
 
164
                for dirname in dirs:
 
165
                    os.rmdir(os.path.join(root, dirname))
 
166
            # Remove tempdir
 
167
            os.rmdir(self.tempdir)
 
168
            self.tempdir = None
 
169
    
 
170
    def password_encode(self, password):
 
171
        # Passphrase can not be empty and can not contain newlines or
 
172
        # NUL bytes.  So we prefix it and hex encode it.
 
173
        return b"mandos" + binascii.hexlify(password)
 
174
    
 
175
    def encrypt(self, data, password):
 
176
        self.gnupg.passphrase = self.password_encode(password)
 
177
        with open(os.devnull) as devnull:
 
178
            try:
 
179
                proc = self.gnupg.run(['--symmetric'],
 
180
                                      create_fhs=['stdin', 'stdout'],
 
181
                                      attach_fhs={'stderr': devnull})
 
182
                with contextlib.closing(proc.handles['stdin']) as f:
 
183
                    f.write(data)
 
184
                with contextlib.closing(proc.handles['stdout']) as f:
 
185
                    ciphertext = f.read()
 
186
                proc.wait()
 
187
            except IOError as e:
 
188
                raise PGPError(e)
 
189
        self.gnupg.passphrase = None
 
190
        return ciphertext
 
191
    
 
192
    def decrypt(self, data, password):
 
193
        self.gnupg.passphrase = self.password_encode(password)
 
194
        with open(os.devnull) as devnull:
 
195
            try:
 
196
                proc = self.gnupg.run(['--decrypt'],
 
197
                                      create_fhs=['stdin', 'stdout'],
 
198
                                      attach_fhs={'stderr': devnull})
 
199
                with contextlib.closing(proc.handles['stdin'] ) as f:
 
200
                    f.write(data)
 
201
                with contextlib.closing(proc.handles['stdout']) as f:
 
202
                    decrypted_plaintext = f.read()
 
203
                proc.wait()
 
204
            except IOError as e:
 
205
                raise PGPError(e)
 
206
        self.gnupg.passphrase = None
 
207
        return decrypted_plaintext
 
208
 
 
209
 
103
210
 
104
211
class AvahiError(Exception):
105
212
    def __init__(self, value, *args, **kwargs):
164
271
                            .GetAlternativeServiceName(self.name))
165
272
        logger.info("Changing Zeroconf service name to %r ...",
166
273
                    self.name)
167
 
        syslogger.setFormatter(logging.Formatter
168
 
                               ('Mandos (%s) [%%(process)d]:'
169
 
                                ' %%(levelname)s: %%(message)s'
170
 
                                % self.name))
171
274
        self.remove()
172
275
        try:
173
276
            self.add()
193
296
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
194
297
        self.entry_group_state_changed_match = (
195
298
            self.group.connect_to_signal(
196
 
                'StateChanged', self .entry_group_state_changed))
 
299
                'StateChanged', self.entry_group_state_changed))
197
300
        logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
198
301
                     self.name, self.type)
199
302
        self.group.AddService(
225
328
            try:
226
329
                self.group.Free()
227
330
            except (dbus.exceptions.UnknownMethodException,
228
 
                    dbus.exceptions.DBusException) as e:
 
331
                    dbus.exceptions.DBusException):
229
332
                pass
230
333
            self.group = None
231
334
        self.remove()
265
368
                                 self.server_state_changed)
266
369
        self.server_state_changed(self.server.GetState())
267
370
 
 
371
class AvahiServiceToSyslog(AvahiService):
 
372
    def rename(self):
 
373
        """Add the new name to the syslog messages"""
 
374
        ret = AvahiService.rename(self)
 
375
        syslogger.setFormatter(logging.Formatter
 
376
                               ('Mandos (%s) [%%(process)d]:'
 
377
                                ' %%(levelname)s: %%(message)s'
 
378
                                % self.name))
 
379
        return ret
268
380
 
269
 
def _timedelta_to_milliseconds(td):
 
381
def timedelta_to_milliseconds(td):
270
382
    "Convert a datetime.timedelta() to milliseconds"
271
383
    return ((td.days * 24 * 60 * 60 * 1000)
272
384
            + (td.seconds * 1000)
276
388
    """A representation of a client host served by this server.
277
389
    
278
390
    Attributes:
279
 
    _approved:   bool(); 'None' if not yet approved/disapproved
 
391
    approved:   bool(); 'None' if not yet approved/disapproved
280
392
    approval_delay: datetime.timedelta(); Time to wait for approval
281
393
    approval_duration: datetime.timedelta(); Duration of one approval
282
394
    checker:    subprocess.Popen(); a running checker process used
289
401
                     instance %(name)s can be used in the command.
290
402
    checker_initiator_tag: a gobject event source tag, or None
291
403
    created:    datetime.datetime(); (UTC) object creation
 
404
    client_structure: Object describing what attributes a client has
 
405
                      and is used for storing the client at exit
292
406
    current_checker_command: string; current running checker_command
293
 
    disable_hook:  If set, called by disable() as disable_hook(self)
294
407
    disable_initiator_tag: a gobject event source tag, or None
295
408
    enabled:    bool()
296
409
    fingerprint: string (40 or 32 hexadecimal digits); used to
299
412
    interval:   datetime.timedelta(); How often to start a new checker
300
413
    last_approval_request: datetime.datetime(); (UTC) or None
301
414
    last_checked_ok: datetime.datetime(); (UTC) or None
302
 
    last_enabled: datetime.datetime(); (UTC)
 
415
    last_checker_status: integer between 0 and 255 reflecting exit
 
416
                         status of last checker. -1 reflects crashed
 
417
                         checker, or None.
 
418
    last_enabled: datetime.datetime(); (UTC) or None
303
419
    name:       string; from the config file, used in log messages and
304
420
                        D-Bus identifiers
305
421
    secret:     bytestring; sent verbatim (over TLS) to client
315
431
                          "created", "enabled", "fingerprint",
316
432
                          "host", "interval", "last_checked_ok",
317
433
                          "last_enabled", "name", "timeout")
 
434
    client_defaults = { "timeout": "5m",
 
435
                        "extended_timeout": "15m",
 
436
                        "interval": "2m",
 
437
                        "checker": "fping -q -- %%(host)s",
 
438
                        "host": "",
 
439
                        "approval_delay": "0s",
 
440
                        "approval_duration": "1s",
 
441
                        "approved_by_default": "True",
 
442
                        "enabled": "True",
 
443
                        }
318
444
    
319
445
    def timeout_milliseconds(self):
320
446
        "Return the 'timeout' attribute in milliseconds"
321
 
        return _timedelta_to_milliseconds(self.timeout)
 
447
        return timedelta_to_milliseconds(self.timeout)
322
448
    
323
449
    def extended_timeout_milliseconds(self):
324
450
        "Return the 'extended_timeout' attribute in milliseconds"
325
 
        return _timedelta_to_milliseconds(self.extended_timeout)
 
451
        return timedelta_to_milliseconds(self.extended_timeout)
326
452
    
327
453
    def interval_milliseconds(self):
328
454
        "Return the 'interval' attribute in milliseconds"
329
 
        return _timedelta_to_milliseconds(self.interval)
 
455
        return timedelta_to_milliseconds(self.interval)
330
456
    
331
457
    def approval_delay_milliseconds(self):
332
 
        return _timedelta_to_milliseconds(self.approval_delay)
333
 
    
334
 
    def __init__(self, name = None, disable_hook=None, config=None):
 
458
        return timedelta_to_milliseconds(self.approval_delay)
 
459
 
 
460
    @staticmethod
 
461
    def config_parser(config):
 
462
        """ Construct a new dict of client settings of this form:
 
463
        { client_name: {setting_name: value, ...}, ...}
 
464
        with exceptions for any special settings as defined above"""
 
465
        settings = {}
 
466
        for client_name in config.sections():
 
467
            section = dict(config.items(client_name))
 
468
            client = settings[client_name] = {}
 
469
            
 
470
            client["host"] = section["host"]
 
471
            # Reformat values from string types to Python types
 
472
            client["approved_by_default"] = config.getboolean(
 
473
                client_name, "approved_by_default")
 
474
            client["enabled"] = config.getboolean(client_name, "enabled")
 
475
            
 
476
            client["fingerprint"] = (section["fingerprint"].upper()
 
477
                                     .replace(" ", ""))
 
478
            if "secret" in section:
 
479
                client["secret"] = section["secret"].decode("base64")
 
480
            elif "secfile" in section:
 
481
                with open(os.path.expanduser(os.path.expandvars
 
482
                                             (section["secfile"])),
 
483
                          "rb") as secfile:
 
484
                    client["secret"] = secfile.read()
 
485
            else:
 
486
                raise TypeError("No secret or secfile for section %s"
 
487
                                % section)
 
488
            client["timeout"] = string_to_delta(section["timeout"])
 
489
            client["extended_timeout"] = string_to_delta(
 
490
                section["extended_timeout"])
 
491
            client["interval"] = string_to_delta(section["interval"])
 
492
            client["approval_delay"] = string_to_delta(
 
493
                section["approval_delay"])
 
494
            client["approval_duration"] = string_to_delta(
 
495
                section["approval_duration"])
 
496
            client["checker_command"] = section["checker"]
 
497
            client["last_approval_request"] = None
 
498
            client["last_checked_ok"] = None
 
499
            client["last_checker_status"] = None
 
500
            if client["enabled"]:
 
501
                client["last_enabled"] = datetime.datetime.utcnow()
 
502
                client["expires"] = (datetime.datetime.utcnow()
 
503
                                     + client["timeout"])
 
504
            else:
 
505
                client["last_enabled"] = None
 
506
                client["expires"] = None
 
507
 
 
508
        return settings
 
509
        
 
510
        
 
511
    def __init__(self, settings, name = None):
335
512
        """Note: the 'checker' key in 'config' sets the
336
513
        'checker_command' attribute and *not* the 'checker'
337
514
        attribute."""
338
515
        self.name = name
339
 
        if config is None:
340
 
            config = {}
 
516
        # adding all client settings
 
517
        for setting, value in settings.iteritems():
 
518
            setattr(self, setting, value)
 
519
        
341
520
        logger.debug("Creating client %r", self.name)
342
521
        # Uppercase and remove spaces from fingerprint for later
343
522
        # comparison purposes with return value from the fingerprint()
344
523
        # function
345
 
        self.fingerprint = (config["fingerprint"].upper()
346
 
                            .replace(" ", ""))
347
524
        logger.debug("  Fingerprint: %s", self.fingerprint)
348
 
        if "secret" in config:
349
 
            self.secret = config["secret"].decode("base64")
350
 
        elif "secfile" in config:
351
 
            with open(os.path.expanduser(os.path.expandvars
352
 
                                         (config["secfile"])),
353
 
                      "rb") as secfile:
354
 
                self.secret = secfile.read()
355
 
        else:
356
 
            raise TypeError("No secret or secfile for client %s"
357
 
                            % self.name)
358
 
        self.host = config.get("host", "")
359
 
        self.created = datetime.datetime.utcnow()
360
 
        self.enabled = False
361
 
        self.last_approval_request = None
362
 
        self.last_enabled = None
363
 
        self.last_checked_ok = None
364
 
        self.timeout = string_to_delta(config["timeout"])
365
 
        self.extended_timeout = string_to_delta(config
366
 
                                                ["extended_timeout"])
367
 
        self.interval = string_to_delta(config["interval"])
368
 
        self.disable_hook = disable_hook
 
525
        self.created = settings.get("created", datetime.datetime.utcnow())
 
526
 
 
527
        # attributes specific for this server instance
369
528
        self.checker = None
370
529
        self.checker_initiator_tag = None
371
530
        self.disable_initiator_tag = None
372
 
        self.expires = None
373
531
        self.checker_callback_tag = None
374
 
        self.checker_command = config["checker"]
375
532
        self.current_checker_command = None
376
 
        self.last_connect = None
377
 
        self._approved = None
378
 
        self.approved_by_default = config.get("approved_by_default",
379
 
                                              True)
 
533
        self.approved = None
380
534
        self.approvals_pending = 0
381
 
        self.approval_delay = string_to_delta(
382
 
            config["approval_delay"])
383
 
        self.approval_duration = string_to_delta(
384
 
            config["approval_duration"])
385
535
        self.changedstate = (multiprocessing_manager
386
536
                             .Condition(multiprocessing_manager
387
537
                                        .Lock()))
 
538
        self.client_structure = [attr for attr in
 
539
                                 self.__dict__.iterkeys()
 
540
                                 if not attr.startswith("_")]
 
541
        self.client_structure.append("client_structure")
 
542
        
 
543
        for name, t in inspect.getmembers(type(self),
 
544
                                          lambda obj:
 
545
                                              isinstance(obj,
 
546
                                                         property)):
 
547
            if not name.startswith("_"):
 
548
                self.client_structure.append(name)
388
549
    
 
550
    # Send notice to process children that client state has changed
389
551
    def send_changedstate(self):
390
 
        self.changedstate.acquire()
391
 
        self.changedstate.notify_all()
392
 
        self.changedstate.release()
 
552
        with self.changedstate:
 
553
            self.changedstate.notify_all()
393
554
    
394
555
    def enable(self):
395
556
        """Start this client's checker and timeout hooks"""
397
558
            # Already enabled
398
559
            return
399
560
        self.send_changedstate()
400
 
        # Schedule a new checker to be started an 'interval' from now,
401
 
        # and every interval from then on.
402
 
        self.checker_initiator_tag = (gobject.timeout_add
403
 
                                      (self.interval_milliseconds(),
404
 
                                       self.start_checker))
405
 
        # Schedule a disable() when 'timeout' has passed
406
561
        self.expires = datetime.datetime.utcnow() + self.timeout
407
 
        self.disable_initiator_tag = (gobject.timeout_add
408
 
                                   (self.timeout_milliseconds(),
409
 
                                    self.disable))
410
562
        self.enabled = True
411
563
        self.last_enabled = datetime.datetime.utcnow()
412
 
        # Also start a new checker *right now*.
413
 
        self.start_checker()
 
564
        self.init_checker()
414
565
    
415
566
    def disable(self, quiet=True):
416
567
        """Disable this client."""
428
579
            gobject.source_remove(self.checker_initiator_tag)
429
580
            self.checker_initiator_tag = None
430
581
        self.stop_checker()
431
 
        if self.disable_hook:
432
 
            self.disable_hook(self)
433
582
        self.enabled = False
434
583
        # Do not run this again if called by a gobject.timeout_add
435
584
        return False
436
585
    
437
586
    def __del__(self):
438
 
        self.disable_hook = None
439
587
        self.disable()
440
588
    
 
589
    def init_checker(self):
 
590
        # Schedule a new checker to be started an 'interval' from now,
 
591
        # and every interval from then on.
 
592
        self.checker_initiator_tag = (gobject.timeout_add
 
593
                                      (self.interval_milliseconds(),
 
594
                                       self.start_checker))
 
595
        # Schedule a disable() when 'timeout' has passed
 
596
        self.disable_initiator_tag = (gobject.timeout_add
 
597
                                   (self.timeout_milliseconds(),
 
598
                                    self.disable))
 
599
        # Also start a new checker *right now*.
 
600
        self.start_checker()
 
601
    
441
602
    def checker_callback(self, pid, condition, command):
442
603
        """The checker has completed, so take appropriate actions."""
443
604
        self.checker_callback_tag = None
444
605
        self.checker = None
445
606
        if os.WIFEXITED(condition):
446
 
            exitstatus = os.WEXITSTATUS(condition)
447
 
            if exitstatus == 0:
 
607
            self.last_checker_status = os.WEXITSTATUS(condition)
 
608
            if self.last_checker_status == 0:
448
609
                logger.info("Checker for %(name)s succeeded",
449
610
                            vars(self))
450
611
                self.checked_ok()
452
613
                logger.info("Checker for %(name)s failed",
453
614
                            vars(self))
454
615
        else:
 
616
            self.last_checker_status = -1
455
617
            logger.warning("Checker for %(name)s crashed?",
456
618
                           vars(self))
457
619
    
464
626
        if timeout is None:
465
627
            timeout = self.timeout
466
628
        self.last_checked_ok = datetime.datetime.utcnow()
467
 
        gobject.source_remove(self.disable_initiator_tag)
468
 
        self.expires = datetime.datetime.utcnow() + timeout
469
 
        self.disable_initiator_tag = (gobject.timeout_add
470
 
                                      (_timedelta_to_milliseconds
471
 
                                       (timeout), self.disable))
 
629
        if self.disable_initiator_tag is not None:
 
630
            gobject.source_remove(self.disable_initiator_tag)
 
631
        if getattr(self, "enabled", False):
 
632
            self.disable_initiator_tag = (gobject.timeout_add
 
633
                                          (timedelta_to_milliseconds
 
634
                                           (timeout), self.disable))
 
635
            self.expires = datetime.datetime.utcnow() + timeout
472
636
    
473
637
    def need_approval(self):
474
638
        self.last_approval_request = datetime.datetime.utcnow()
689
853
        
690
854
        Note: Will not include properties with access="write".
691
855
        """
692
 
        all = {}
 
856
        properties = {}
693
857
        for name, prop in self._get_all_dbus_properties():
694
858
            if (interface_name
695
859
                and interface_name != prop._dbus_interface):
700
864
                continue
701
865
            value = prop()
702
866
            if not hasattr(value, "variant_level"):
703
 
                all[name] = value
 
867
                properties[name] = value
704
868
                continue
705
 
            all[name] = type(value)(value, variant_level=
706
 
                                    value.variant_level+1)
707
 
        return dbus.Dictionary(all, signature="sv")
 
869
            properties[name] = type(value)(value, variant_level=
 
870
                                           value.variant_level+1)
 
871
        return dbus.Dictionary(properties, signature="sv")
708
872
    
709
873
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
710
874
                         out_signature="s",
761
925
    return dbus.String(dt.isoformat(),
762
926
                       variant_level=variant_level)
763
927
 
 
928
 
764
929
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
765
930
                                  .__metaclass__):
766
931
    """Applied to an empty subclass of a D-Bus object, this metaclass
858
1023
                                        attribute.func_closure)))
859
1024
        return type.__new__(mcs, name, bases, attr)
860
1025
 
 
1026
 
861
1027
class ClientDBus(Client, DBusObjectWithProperties):
862
1028
    """A Client class using D-Bus
863
1029
    
872
1038
    # dbus.service.Object doesn't use super(), so we can't either.
873
1039
    
874
1040
    def __init__(self, bus = None, *args, **kwargs):
875
 
        self._approvals_pending = 0
876
1041
        self.bus = bus
877
1042
        Client.__init__(self, *args, **kwargs)
 
1043
        self._approvals_pending = 0
 
1044
        
 
1045
        self._approvals_pending = 0
878
1046
        # Only now, when this client is initialized, can it show up on
879
1047
        # the D-Bus
880
1048
        client_object_name = unicode(self.name).translate(
890
1058
                             variant_level=1):
891
1059
        """ Modify a variable so that it's a property which announces
892
1060
        its changes to DBus.
893
 
 
894
 
        transform_fun: Function that takes a value and transforms it
895
 
                       to a D-Bus type.
 
1061
        
 
1062
        transform_fun: Function that takes a value and a variant_level
 
1063
                       and transforms it to a D-Bus type.
896
1064
        dbus_name: D-Bus name of the variable
897
1065
        type_func: Function that transform the value before sending it
898
1066
                   to the D-Bus.  Default: no transform
905
1073
                    type_func(getattr(self, attrname, None))
906
1074
                    != type_func(value)):
907
1075
                    dbus_value = transform_func(type_func(value),
908
 
                                                variant_level)
 
1076
                                                variant_level
 
1077
                                                =variant_level)
909
1078
                    self.PropertyChanged(dbus.String(dbus_name),
910
1079
                                         dbus_value)
911
1080
            setattr(self, attrname, value)
929
1098
        datetime_to_dbus, "LastApprovalRequest")
930
1099
    approved_by_default = notifychangeproperty(dbus.Boolean,
931
1100
                                               "ApprovedByDefault")
932
 
    approval_delay = notifychangeproperty(dbus.UInt16,
 
1101
    approval_delay = notifychangeproperty(dbus.UInt64,
933
1102
                                          "ApprovalDelay",
934
1103
                                          type_func =
935
 
                                          _timedelta_to_milliseconds)
 
1104
                                          timedelta_to_milliseconds)
936
1105
    approval_duration = notifychangeproperty(
937
 
        dbus.UInt16, "ApprovalDuration",
938
 
        type_func = _timedelta_to_milliseconds)
 
1106
        dbus.UInt64, "ApprovalDuration",
 
1107
        type_func = timedelta_to_milliseconds)
939
1108
    host = notifychangeproperty(dbus.String, "Host")
940
 
    timeout = notifychangeproperty(dbus.UInt16, "Timeout",
 
1109
    timeout = notifychangeproperty(dbus.UInt64, "Timeout",
941
1110
                                   type_func =
942
 
                                   _timedelta_to_milliseconds)
 
1111
                                   timedelta_to_milliseconds)
943
1112
    extended_timeout = notifychangeproperty(
944
 
        dbus.UInt16, "ExtendedTimeout",
945
 
        type_func = _timedelta_to_milliseconds)
946
 
    interval = notifychangeproperty(dbus.UInt16,
 
1113
        dbus.UInt64, "ExtendedTimeout",
 
1114
        type_func = timedelta_to_milliseconds)
 
1115
    interval = notifychangeproperty(dbus.UInt64,
947
1116
                                    "Interval",
948
1117
                                    type_func =
949
 
                                    _timedelta_to_milliseconds)
 
1118
                                    timedelta_to_milliseconds)
950
1119
    checker_command = notifychangeproperty(dbus.String, "Checker")
951
1120
    
952
1121
    del notifychangeproperty
994
1163
        return r
995
1164
    
996
1165
    def _reset_approved(self):
997
 
        self._approved = None
 
1166
        self.approved = None
998
1167
        return False
999
1168
    
1000
1169
    def approve(self, value=True):
1001
1170
        self.send_changedstate()
1002
 
        self._approved = value
1003
 
        gobject.timeout_add(_timedelta_to_milliseconds
 
1171
        self.approved = value
 
1172
        gobject.timeout_add(timedelta_to_milliseconds
1004
1173
                            (self.approval_duration),
1005
1174
                            self._reset_approved)
1006
1175
    
1049
1218
        "D-Bus signal"
1050
1219
        return self.need_approval()
1051
1220
    
 
1221
    # NeRwequest - signal
 
1222
    @dbus.service.signal(_interface, signature="s")
 
1223
    def NewRequest(self, ip):
 
1224
        """D-Bus signal
 
1225
        Is sent after a client request a password.
 
1226
        """
 
1227
        pass
 
1228
    
1052
1229
    ## Methods
1053
1230
    
1054
1231
    # Approve - method
1112
1289
                           access="readwrite")
1113
1290
    def ApprovalDuration_dbus_property(self, value=None):
1114
1291
        if value is None:       # get
1115
 
            return dbus.UInt64(_timedelta_to_milliseconds(
 
1292
            return dbus.UInt64(timedelta_to_milliseconds(
1116
1293
                    self.approval_duration))
1117
1294
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
1118
1295
    
1132
1309
    def Host_dbus_property(self, value=None):
1133
1310
        if value is None:       # get
1134
1311
            return dbus.String(self.host)
1135
 
        self.host = value
 
1312
        self.host = unicode(value)
1136
1313
    
1137
1314
    # Created - property
1138
1315
    @dbus_service_property(_interface, signature="s", access="read")
1139
1316
    def Created_dbus_property(self):
1140
 
        return dbus.String(datetime_to_dbus(self.created))
 
1317
        return datetime_to_dbus(self.created)
1141
1318
    
1142
1319
    # LastEnabled - property
1143
1320
    @dbus_service_property(_interface, signature="s", access="read")
1187
1364
        gobject.source_remove(self.disable_initiator_tag)
1188
1365
        self.disable_initiator_tag = None
1189
1366
        self.expires = None
1190
 
        time_to_die = (self.
1191
 
                       _timedelta_to_milliseconds((self
1192
 
                                                   .last_checked_ok
1193
 
                                                   + self.timeout)
1194
 
                                                  - datetime.datetime
1195
 
                                                  .utcnow()))
 
1367
        time_to_die = timedelta_to_milliseconds((self
 
1368
                                                 .last_checked_ok
 
1369
                                                 + self.timeout)
 
1370
                                                - datetime.datetime
 
1371
                                                .utcnow())
1196
1372
        if time_to_die <= 0:
1197
1373
            # The timeout has passed
1198
1374
            self.disable()
1220
1396
        self.interval = datetime.timedelta(0, 0, 0, value)
1221
1397
        if getattr(self, "checker_initiator_tag", None) is None:
1222
1398
            return
1223
 
        # Reschedule checker run
1224
 
        gobject.source_remove(self.checker_initiator_tag)
1225
 
        self.checker_initiator_tag = (gobject.timeout_add
1226
 
                                      (value, self.start_checker))
1227
 
        self.start_checker()    # Start one now, too
 
1399
        if self.enabled:
 
1400
            # Reschedule checker run
 
1401
            gobject.source_remove(self.checker_initiator_tag)
 
1402
            self.checker_initiator_tag = (gobject.timeout_add
 
1403
                                          (value, self.start_checker))
 
1404
            self.start_checker()    # Start one now, too
1228
1405
    
1229
1406
    # Checker - property
1230
1407
    @dbus_service_property(_interface, signature="s",
1232
1409
    def Checker_dbus_property(self, value=None):
1233
1410
        if value is None:       # get
1234
1411
            return dbus.String(self.checker_command)
1235
 
        self.checker_command = value
 
1412
        self.checker_command = unicode(value)
1236
1413
    
1237
1414
    # CheckerRunning - property
1238
1415
    @dbus_service_property(_interface, signature="b",
1267
1444
            raise KeyError()
1268
1445
    
1269
1446
    def __getattribute__(self, name):
1270
 
        if(name == '_pipe'):
 
1447
        if name == '_pipe':
1271
1448
            return super(ProxyClient, self).__getattribute__(name)
1272
1449
        self._pipe.send(('getattr', name))
1273
1450
        data = self._pipe.recv()
1280
1457
            return func
1281
1458
    
1282
1459
    def __setattr__(self, name, value):
1283
 
        if(name == '_pipe'):
 
1460
        if name == '_pipe':
1284
1461
            return super(ProxyClient, self).__setattr__(name, value)
1285
1462
        self._pipe.send(('setattr', name, value))
1286
1463
 
 
1464
 
1287
1465
class ClientDBusTransitional(ClientDBus):
1288
1466
    __metaclass__ = AlternateDBusNamesMetaclass
1289
1467
 
 
1468
 
1290
1469
class ClientHandler(socketserver.BaseRequestHandler, object):
1291
1470
    """A class to handle client connections.
1292
1471
    
1360
1539
                except KeyError:
1361
1540
                    return
1362
1541
                
 
1542
                if self.server.use_dbus:
 
1543
                    # Emit D-Bus signal
 
1544
                    client.NewRequest(str(self.client_address))
 
1545
                
1363
1546
                if client.approval_delay:
1364
1547
                    delay = client.approval_delay
1365
1548
                    client.approvals_pending += 1
1374
1557
                            client.Rejected("Disabled")
1375
1558
                        return
1376
1559
                    
1377
 
                    if client._approved or not client.approval_delay:
 
1560
                    if client.approved or not client.approval_delay:
1378
1561
                        #We are approved or approval is disabled
1379
1562
                        break
1380
 
                    elif client._approved is None:
 
1563
                    elif client.approved is None:
1381
1564
                        logger.info("Client %s needs approval",
1382
1565
                                    client.name)
1383
1566
                        if self.server.use_dbus:
1394
1577
                        return
1395
1578
                    
1396
1579
                    #wait until timeout or approved
1397
 
                    #x = float(client
1398
 
                    #          ._timedelta_to_milliseconds(delay))
1399
1580
                    time = datetime.datetime.now()
1400
1581
                    client.changedstate.acquire()
1401
1582
                    (client.changedstate.wait
1402
 
                     (float(client._timedelta_to_milliseconds(delay)
 
1583
                     (float(client.timedelta_to_milliseconds(delay)
1403
1584
                            / 1000)))
1404
1585
                    client.changedstate.release()
1405
1586
                    time2 = datetime.datetime.now()
1430
1611
                    sent_size += sent
1431
1612
                
1432
1613
                logger.info("Sending secret to %s", client.name)
1433
 
                # bump the timeout as if seen
 
1614
                # bump the timeout using extended_timeout
1434
1615
                client.checked_ok(client.extended_timeout)
1435
1616
                if self.server.use_dbus:
1436
1617
                    # Emit D-Bus signal
1504
1685
        # Convert the buffer to a Python bytestring
1505
1686
        fpr = ctypes.string_at(buf, buf_len.value)
1506
1687
        # Convert the bytestring to hexadecimal notation
1507
 
        hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
 
1688
        hex_fpr = binascii.hexlify(fpr).upper()
1508
1689
        return hex_fpr
1509
1690
 
1510
1691
 
1513
1694
    def sub_process_main(self, request, address):
1514
1695
        try:
1515
1696
            self.finish_request(request, address)
1516
 
        except:
 
1697
        except Exception:
1517
1698
            self.handle_error(request, address)
1518
1699
        self.close_request(request)
1519
 
            
 
1700
    
1520
1701
    def process_request(self, request, address):
1521
1702
        """Start a new process to process the request."""
1522
 
        multiprocessing.Process(target = self.sub_process_main,
1523
 
                                args = (request, address)).start()
 
1703
        proc = multiprocessing.Process(target = self.sub_process_main,
 
1704
                                       args = (request,
 
1705
                                               address))
 
1706
        proc.start()
 
1707
        return proc
1524
1708
 
1525
1709
 
1526
1710
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1532
1716
        """
1533
1717
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
1534
1718
        
1535
 
        super(MultiprocessingMixInWithPipe,
1536
 
              self).process_request(request, client_address)
 
1719
        proc = MultiprocessingMixIn.process_request(self, request,
 
1720
                                                    client_address)
1537
1721
        self.child_pipe.close()
1538
 
        self.add_pipe(parent_pipe)
 
1722
        self.add_pipe(parent_pipe, proc)
1539
1723
    
1540
 
    def add_pipe(self, parent_pipe):
 
1724
    def add_pipe(self, parent_pipe, proc):
1541
1725
        """Dummy function; override as necessary"""
1542
1726
        raise NotImplementedError
1543
1727
 
1621
1805
        self.enabled = False
1622
1806
        self.clients = clients
1623
1807
        if self.clients is None:
1624
 
            self.clients = set()
 
1808
            self.clients = {}
1625
1809
        self.use_dbus = use_dbus
1626
1810
        self.gnutls_priority = gnutls_priority
1627
1811
        IPv6_TCPServer.__init__(self, server_address,
1631
1815
    def server_activate(self):
1632
1816
        if self.enabled:
1633
1817
            return socketserver.TCPServer.server_activate(self)
 
1818
    
1634
1819
    def enable(self):
1635
1820
        self.enabled = True
1636
 
    def add_pipe(self, parent_pipe):
 
1821
    
 
1822
    def add_pipe(self, parent_pipe, proc):
1637
1823
        # Call "handle_ipc" for both data and EOF events
1638
1824
        gobject.io_add_watch(parent_pipe.fileno(),
1639
1825
                             gobject.IO_IN | gobject.IO_HUP,
1640
1826
                             functools.partial(self.handle_ipc,
1641
1827
                                               parent_pipe =
1642
 
                                               parent_pipe))
1643
 
        
 
1828
                                               parent_pipe,
 
1829
                                               proc = proc))
 
1830
    
1644
1831
    def handle_ipc(self, source, condition, parent_pipe=None,
1645
 
                   client_object=None):
 
1832
                   proc = None, client_object=None):
1646
1833
        condition_names = {
1647
1834
            gobject.IO_IN: "IN",   # There is data to read.
1648
1835
            gobject.IO_OUT: "OUT", # Data can be written (without
1657
1844
                                       for cond, name in
1658
1845
                                       condition_names.iteritems()
1659
1846
                                       if cond & condition)
1660
 
        # error or the other end of multiprocessing.Pipe has closed
 
1847
        # error, or the other end of multiprocessing.Pipe has closed
1661
1848
        if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
 
1849
            # Wait for other process to exit
 
1850
            proc.join()
1662
1851
            return False
1663
1852
        
1664
1853
        # Read a request from the child
1669
1858
            fpr = request[1]
1670
1859
            address = request[2]
1671
1860
            
1672
 
            for c in self.clients:
 
1861
            for c in self.clients.itervalues():
1673
1862
                if c.fingerprint == fpr:
1674
1863
                    client = c
1675
1864
                    break
1688
1877
                                 functools.partial(self.handle_ipc,
1689
1878
                                                   parent_pipe =
1690
1879
                                                   parent_pipe,
 
1880
                                                   proc = proc,
1691
1881
                                                   client_object =
1692
1882
                                                   client))
1693
1883
            parent_pipe.send(True)
1758
1948
    return timevalue
1759
1949
 
1760
1950
 
1761
 
def if_nametoindex(interface):
1762
 
    """Call the C function if_nametoindex(), or equivalent
1763
 
    
1764
 
    Note: This function cannot accept a unicode string."""
1765
 
    global if_nametoindex
1766
 
    try:
1767
 
        if_nametoindex = (ctypes.cdll.LoadLibrary
1768
 
                          (ctypes.util.find_library("c"))
1769
 
                          .if_nametoindex)
1770
 
    except (OSError, AttributeError):
1771
 
        logger.warning("Doing if_nametoindex the hard way")
1772
 
        def if_nametoindex(interface):
1773
 
            "Get an interface index the hard way, i.e. using fcntl()"
1774
 
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1775
 
            with contextlib.closing(socket.socket()) as s:
1776
 
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1777
 
                                    struct.pack(str("16s16x"),
1778
 
                                                interface))
1779
 
            interface_index = struct.unpack(str("I"),
1780
 
                                            ifreq[16:20])[0]
1781
 
            return interface_index
1782
 
    return if_nametoindex(interface)
1783
 
 
1784
 
 
1785
1951
def daemon(nochdir = False, noclose = False):
1786
1952
    """See daemon(3).  Standard BSD Unix function.
1787
1953
    
1842
2008
                        " system bus interface")
1843
2009
    parser.add_argument("--no-ipv6", action="store_false",
1844
2010
                        dest="use_ipv6", help="Do not use IPv6")
 
2011
    parser.add_argument("--no-restore", action="store_false",
 
2012
                        dest="restore", help="Do not restore stored"
 
2013
                        " state")
 
2014
    parser.add_argument("--statedir", metavar="DIR",
 
2015
                        help="Directory to save/restore state in")
 
2016
    
1845
2017
    options = parser.parse_args()
1846
2018
    
1847
2019
    if options.check:
1860
2032
                        "use_dbus": "True",
1861
2033
                        "use_ipv6": "True",
1862
2034
                        "debuglevel": "",
 
2035
                        "restore": "True",
 
2036
                        "statedir": "/var/lib/mandos"
1863
2037
                        }
1864
2038
    
1865
2039
    # Parse config file for server-global settings
1882
2056
    # options, if set.
1883
2057
    for option in ("interface", "address", "port", "debug",
1884
2058
                   "priority", "servicename", "configdir",
1885
 
                   "use_dbus", "use_ipv6", "debuglevel"):
 
2059
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
 
2060
                   "statedir"):
1886
2061
        value = getattr(options, option)
1887
2062
        if value is not None:
1888
2063
            server_settings[option] = value
1900
2075
    debuglevel = server_settings["debuglevel"]
1901
2076
    use_dbus = server_settings["use_dbus"]
1902
2077
    use_ipv6 = server_settings["use_ipv6"]
 
2078
    stored_state_path = os.path.join(server_settings["statedir"],
 
2079
                                     stored_state_file)
 
2080
    
 
2081
    if debug:
 
2082
        initlogger(debug, logging.DEBUG)
 
2083
    else:
 
2084
        if not debuglevel:
 
2085
            initlogger(debug)
 
2086
        else:
 
2087
            level = getattr(logging, debuglevel.upper())
 
2088
            initlogger(debug, level)
1903
2089
    
1904
2090
    if server_settings["servicename"] != "Mandos":
1905
2091
        syslogger.setFormatter(logging.Formatter
1908
2094
                                % server_settings["servicename"]))
1909
2095
    
1910
2096
    # Parse config file with clients
1911
 
    client_defaults = { "timeout": "5m",
1912
 
                        "extended_timeout": "15m",
1913
 
                        "interval": "2m",
1914
 
                        "checker": "fping -q -- %%(host)s",
1915
 
                        "host": "",
1916
 
                        "approval_delay": "0s",
1917
 
                        "approval_duration": "1s",
1918
 
                        }
1919
 
    client_config = configparser.SafeConfigParser(client_defaults)
 
2097
    client_config = configparser.SafeConfigParser(Client.client_defaults)
1920
2098
    client_config.read(os.path.join(server_settings["configdir"],
1921
2099
                                    "clients.conf"))
1922
2100
    
1960
2138
        if error[0] != errno.EPERM:
1961
2139
            raise error
1962
2140
    
1963
 
    if not debug and not debuglevel:
1964
 
        syslogger.setLevel(logging.WARNING)
1965
 
        console.setLevel(logging.WARNING)
1966
 
    if debuglevel:
1967
 
        level = getattr(logging, debuglevel.upper())
1968
 
        syslogger.setLevel(level)
1969
 
        console.setLevel(level)
1970
 
    
1971
2141
    if debug:
1972
2142
        # Enable all possible GnuTLS debugging
1973
2143
        
1987
2157
        os.dup2(null, sys.stdin.fileno())
1988
2158
        if null > 2:
1989
2159
            os.close(null)
1990
 
    else:
1991
 
        # No console logging
1992
 
        logger.removeHandler(console)
1993
2160
    
1994
2161
    # Need to fork before connecting to D-Bus
1995
2162
    if not debug:
1996
2163
        # Close all input and output, do double fork, etc.
1997
2164
        daemon()
1998
2165
    
 
2166
    gobject.threads_init()
 
2167
    
1999
2168
    global main_loop
2000
2169
    # From the Avahi example code
2001
2170
    DBusGMainLoop(set_as_default=True )
2015
2184
            server_settings["use_dbus"] = False
2016
2185
            tcp_server.use_dbus = False
2017
2186
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2018
 
    service = AvahiService(name = server_settings["servicename"],
2019
 
                           servicetype = "_mandos._tcp",
2020
 
                           protocol = protocol, bus = bus)
 
2187
    service = AvahiServiceToSyslog(name =
 
2188
                                   server_settings["servicename"],
 
2189
                                   servicetype = "_mandos._tcp",
 
2190
                                   protocol = protocol, bus = bus)
2021
2191
    if server_settings["interface"]:
2022
2192
        service.interface = (if_nametoindex
2023
2193
                             (str(server_settings["interface"])))
2029
2199
    if use_dbus:
2030
2200
        client_class = functools.partial(ClientDBusTransitional,
2031
2201
                                         bus = bus)
2032
 
    def client_config_items(config, section):
2033
 
        special_settings = {
2034
 
            "approved_by_default":
2035
 
                lambda: config.getboolean(section,
2036
 
                                          "approved_by_default"),
2037
 
            }
2038
 
        for name, value in config.items(section):
 
2202
    
 
2203
    client_settings = Client.config_parser(client_config)
 
2204
    old_client_settings = {}
 
2205
    clients_data = {}
 
2206
    
 
2207
    # Get client data and settings from last running state.
 
2208
    if server_settings["restore"]:
 
2209
        try:
 
2210
            with open(stored_state_path, "rb") as stored_state:
 
2211
                clients_data, old_client_settings = (pickle.load
 
2212
                                                     (stored_state))
 
2213
            os.remove(stored_state_path)
 
2214
        except IOError as e:
 
2215
            logger.warning("Could not load persistent state: {0}"
 
2216
                           .format(e))
 
2217
            if e.errno != errno.ENOENT:
 
2218
                raise
 
2219
        except EOFError as e:
 
2220
            logger.warning("Could not load persistent state: "
 
2221
                           "EOFError: {0}".format(e))
 
2222
    
 
2223
    with PGPEngine() as pgp:
 
2224
        for client_name, client in clients_data.iteritems():
 
2225
            # Decide which value to use after restoring saved state.
 
2226
            # We have three different values: Old config file,
 
2227
            # new config file, and saved state.
 
2228
            # New config value takes precedence if it differs from old
 
2229
            # config value, otherwise use saved state.
 
2230
            for name, value in client_settings[client_name].items():
 
2231
                try:
 
2232
                    # For each value in new config, check if it
 
2233
                    # differs from the old config value (Except for
 
2234
                    # the "secret" attribute)
 
2235
                    if (name != "secret" and
 
2236
                        value != old_client_settings[client_name]
 
2237
                        [name]):
 
2238
                        client[name] = value
 
2239
                except KeyError:
 
2240
                    pass
 
2241
            
 
2242
            # Clients who has passed its expire date can still be
 
2243
            # enabled if its last checker was successful.  Clients
 
2244
            # whose checker failed before we stored its state is
 
2245
            # assumed to have failed all checkers during downtime.
 
2246
            if client["enabled"]:
 
2247
                if datetime.datetime.utcnow() >= client["expires"]:
 
2248
                    if not client["last_checked_ok"]:
 
2249
                        logger.warning(
 
2250
                            "disabling client {0} - Client never "
 
2251
                            "performed a successfull checker"
 
2252
                            .format(client["name"]))
 
2253
                        client["enabled"] = False
 
2254
                    elif client["last_checker_status"] != 0:
 
2255
                        logger.warning(
 
2256
                            "disabling client {0} - Client "
 
2257
                            "last checker failed with error code {1}"
 
2258
                            .format(client["name"],
 
2259
                                    client["last_checker_status"]))
 
2260
                        client["enabled"] = False
 
2261
                    else:
 
2262
                        client["expires"] = (datetime.datetime
 
2263
                                             .utcnow()
 
2264
                                             + client["timeout"])
 
2265
                    
2039
2266
            try:
2040
 
                yield (name, special_settings[name]())
2041
 
            except KeyError:
2042
 
                yield (name, value)
2043
 
    
2044
 
    tcp_server.clients.update(set(
2045
 
            client_class(name = section,
2046
 
                         config= dict(client_config_items(
2047
 
                        client_config, section)))
2048
 
            for section in client_config.sections()))
 
2267
                client["secret"] = (
 
2268
                    pgp.decrypt(client["encrypted_secret"],
 
2269
                                client_settings[client_name]
 
2270
                                ["secret"]))
 
2271
            except PGPError:
 
2272
                # If decryption fails, we use secret from new settings
 
2273
                logger.debug("Failed to decrypt {0} old secret"
 
2274
                             .format(client_name))
 
2275
                client["secret"] = (
 
2276
                    client_settings[client_name]["secret"])
 
2277
 
 
2278
    
 
2279
    # Add/remove clients based on new changes made to config
 
2280
    for client_name in set(old_client_settings) - set(client_settings):
 
2281
        del clients_data[client_name]
 
2282
    for client_name in set(client_settings) - set(old_client_settings):
 
2283
        clients_data[client_name] = client_settings[client_name]
 
2284
 
 
2285
    # Create clients all clients
 
2286
    for client_name, client in clients_data.iteritems():
 
2287
        tcp_server.clients[client_name] = client_class(
 
2288
            name = client_name, settings = client)
 
2289
    
2049
2290
    if not tcp_server.clients:
2050
2291
        logger.warning("No clients defined")
2051
2292
        
2062
2303
            # "pidfile" was never created
2063
2304
            pass
2064
2305
        del pidfilename
2065
 
        
2066
2306
        signal.signal(signal.SIGINT, signal.SIG_IGN)
2067
2307
    
2068
2308
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2094
2334
            def GetAllClients(self):
2095
2335
                "D-Bus method"
2096
2336
                return dbus.Array(c.dbus_object_path
2097
 
                                  for c in tcp_server.clients)
 
2337
                                  for c in
 
2338
                                  tcp_server.clients.itervalues())
2098
2339
            
2099
2340
            @dbus.service.method(_interface,
2100
2341
                                 out_signature="a{oa{sv}}")
2102
2343
                "D-Bus method"
2103
2344
                return dbus.Dictionary(
2104
2345
                    ((c.dbus_object_path, c.GetAll(""))
2105
 
                     for c in tcp_server.clients),
 
2346
                     for c in tcp_server.clients.itervalues()),
2106
2347
                    signature="oa{sv}")
2107
2348
            
2108
2349
            @dbus.service.method(_interface, in_signature="o")
2109
2350
            def RemoveClient(self, object_path):
2110
2351
                "D-Bus method"
2111
 
                for c in tcp_server.clients:
 
2352
                for c in tcp_server.clients.itervalues():
2112
2353
                    if c.dbus_object_path == object_path:
2113
 
                        tcp_server.clients.remove(c)
 
2354
                        del tcp_server.clients[c.name]
2114
2355
                        c.remove_from_connection()
2115
2356
                        # Don't signal anything except ClientRemoved
2116
2357
                        c.disable(quiet=True)
2129
2370
        "Cleanup function; run on exit"
2130
2371
        service.cleanup()
2131
2372
        
 
2373
        multiprocessing.active_children()
 
2374
        if not (tcp_server.clients or client_settings):
 
2375
            return
 
2376
        
 
2377
        # Store client before exiting. Secrets are encrypted with key
 
2378
        # based on what config file has. If config file is
 
2379
        # removed/edited, old secret will thus be unrecovable.
 
2380
        clients = {}
 
2381
        with PGPEngine() as pgp:
 
2382
            for client in tcp_server.clients.itervalues():
 
2383
                key = client_settings[client.name]["secret"]
 
2384
                client.encrypted_secret = pgp.encrypt(client.secret,
 
2385
                                                      key)
 
2386
                client_dict = {}
 
2387
                
 
2388
                # A list of attributes that can not be pickled
 
2389
                # + secret.
 
2390
                exclude = set(("bus", "changedstate", "secret",
 
2391
                               "checker"))
 
2392
                for name, typ in (inspect.getmembers
 
2393
                                  (dbus.service.Object)):
 
2394
                    exclude.add(name)
 
2395
                
 
2396
                client_dict["encrypted_secret"] = (client
 
2397
                                                   .encrypted_secret)
 
2398
                for attr in client.client_structure:
 
2399
                    if attr not in exclude:
 
2400
                        client_dict[attr] = getattr(client, attr)
 
2401
                
 
2402
                clients[client.name] = client_dict
 
2403
                del client_settings[client.name]["secret"]
 
2404
        
 
2405
        try:
 
2406
            tempfd, tempname = tempfile.mkstemp(suffix=".pickle",
 
2407
                                                prefix="clients-",
 
2408
                                                dir=os.path.dirname
 
2409
                                                (stored_state_path))
 
2410
            with os.fdopen(tempfd, "wb") as stored_state:
 
2411
                pickle.dump((clients, client_settings), stored_state)
 
2412
            os.rename(tempname, stored_state_path)
 
2413
        except (IOError, OSError) as e:
 
2414
            logger.warning("Could not save persistent state: {0}"
 
2415
                           .format(e))
 
2416
            if not debug:
 
2417
                try:
 
2418
                    os.remove(tempname)
 
2419
                except NameError:
 
2420
                    pass
 
2421
            if e.errno not in set((errno.ENOENT, errno.EACCES,
 
2422
                                   errno.EEXIST)):
 
2423
                raise e
 
2424
        
 
2425
        # Delete all clients, and settings from config
2132
2426
        while tcp_server.clients:
2133
 
            client = tcp_server.clients.pop()
 
2427
            name, client = tcp_server.clients.popitem()
2134
2428
            if use_dbus:
2135
2429
                client.remove_from_connection()
2136
 
            client.disable_hook = None
2137
2430
            # Don't signal anything except ClientRemoved
2138
2431
            client.disable(quiet=True)
2139
2432
            if use_dbus:
2141
2434
                mandos_dbus_service.ClientRemoved(client
2142
2435
                                                  .dbus_object_path,
2143
2436
                                                  client.name)
 
2437
        client_settings.clear()
2144
2438
    
2145
2439
    atexit.register(cleanup)
2146
2440
    
2147
 
    for client in tcp_server.clients:
 
2441
    for client in tcp_server.clients.itervalues():
2148
2442
        if use_dbus:
2149
2443
            # Emit D-Bus signal
2150
2444
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
2151
 
        client.enable()
 
2445
        # Need to initiate checking of clients
 
2446
        if client.enabled:
 
2447
            client.init_checker()
2152
2448
    
2153
2449
    tcp_server.enable()
2154
2450
    tcp_server.server_activate()
2194
2490
    # Must run before the D-Bus bus name gets deregistered
2195
2491
    cleanup()
2196
2492
 
2197
 
 
2198
2493
if __name__ == '__main__':
2199
2494
    main()