/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-03-08 19:09:03 UTC
  • Revision ID: teddy@fukt.bsnet.se-20110308190903-j499ebtb9bpk31ar
* INSTALL: Updated.

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@recompile.se>.
 
31
# Contact the authors at <mandos@fukt.bsnet.se>.
32
32
33
33
 
34
34
from __future__ import (division, absolute_import, print_function,
36
36
 
37
37
import SocketServer as socketserver
38
38
import socket
39
 
import argparse
 
39
import optparse
40
40
import datetime
41
41
import errno
42
42
import gnutls.crypto
62
62
import functools
63
63
import cPickle as pickle
64
64
import multiprocessing
65
 
import types
66
 
import binascii
67
 
import tempfile
68
65
 
69
66
import dbus
70
67
import dbus.service
75
72
import ctypes.util
76
73
import xml.dom.minidom
77
74
import inspect
78
 
import GnuPGInterface
79
75
 
80
76
try:
81
77
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
86
82
        SO_BINDTODEVICE = None
87
83
 
88
84
 
89
 
version = "1.4.1"
90
 
stored_state_file = "clients.pickle"
 
85
version = "1.2.3"
91
86
 
92
 
logger = logging.getLogger()
 
87
#logger = logging.getLogger('mandos')
 
88
logger = logging.Logger('mandos')
93
89
syslogger = (logging.handlers.SysLogHandler
94
90
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
95
91
              address = str("/dev/log")))
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
 
 
 
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)
209
102
 
210
103
class AvahiError(Exception):
211
104
    def __init__(self, value, *args, **kwargs):
258
151
        self.group = None       # our entry group
259
152
        self.server = None
260
153
        self.bus = bus
261
 
        self.entry_group_state_changed_match = None
262
154
    def rename(self):
263
155
        """Derived from the Avahi example code"""
264
156
        if self.rename_count >= self.max_renames:
266
158
                            " after %i retries, exiting.",
267
159
                            self.rename_count)
268
160
            raise AvahiServiceError("Too many renames")
269
 
        self.name = unicode(self.server
270
 
                            .GetAlternativeServiceName(self.name))
 
161
        self.name = unicode(self.server.GetAlternativeServiceName(self.name))
271
162
        logger.info("Changing Zeroconf service name to %r ...",
272
163
                    self.name)
 
164
        syslogger.setFormatter(logging.Formatter
 
165
                               ('Mandos (%s) [%%(process)d]:'
 
166
                                ' %%(levelname)s: %%(message)s'
 
167
                                % self.name))
273
168
        self.remove()
274
169
        try:
275
170
            self.add()
276
 
        except dbus.exceptions.DBusException as error:
 
171
        except dbus.exceptions.DBusException, error:
277
172
            logger.critical("DBusException: %s", error)
278
173
            self.cleanup()
279
174
            os._exit(1)
280
175
        self.rename_count += 1
281
176
    def remove(self):
282
177
        """Derived from the Avahi example code"""
283
 
        if self.entry_group_state_changed_match is not None:
284
 
            self.entry_group_state_changed_match.remove()
285
 
            self.entry_group_state_changed_match = None
286
178
        if self.group is not None:
287
179
            self.group.Reset()
288
180
    def add(self):
289
181
        """Derived from the Avahi example code"""
290
 
        self.remove()
291
182
        if self.group is None:
292
183
            self.group = dbus.Interface(
293
184
                self.bus.get_object(avahi.DBUS_NAME,
294
185
                                    self.server.EntryGroupNew()),
295
186
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
296
 
        self.entry_group_state_changed_match = (
297
 
            self.group.connect_to_signal(
298
 
                'StateChanged', self.entry_group_state_changed))
 
187
            self.group.connect_to_signal('StateChanged',
 
188
                                         self
 
189
                                         .entry_group_state_changed)
299
190
        logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
300
191
                     self.name, self.type)
301
192
        self.group.AddService(
324
215
    def cleanup(self):
325
216
        """Derived from the Avahi example code"""
326
217
        if self.group is not None:
327
 
            try:
328
 
                self.group.Free()
329
 
            except (dbus.exceptions.UnknownMethodException,
330
 
                    dbus.exceptions.DBusException):
331
 
                pass
 
218
            self.group.Free()
332
219
            self.group = None
333
 
        self.remove()
334
 
    def server_state_changed(self, state, error=None):
 
220
    def server_state_changed(self, state):
335
221
        """Derived from the Avahi example code"""
336
222
        logger.debug("Avahi server state change: %i", state)
337
 
        bad_states = { avahi.SERVER_INVALID:
338
 
                           "Zeroconf server invalid",
339
 
                       avahi.SERVER_REGISTERING: None,
340
 
                       avahi.SERVER_COLLISION:
341
 
                           "Zeroconf server name collision",
342
 
                       avahi.SERVER_FAILURE:
343
 
                           "Zeroconf server failure" }
344
 
        if state in bad_states:
345
 
            if bad_states[state] is not None:
346
 
                if error is None:
347
 
                    logger.error(bad_states[state])
348
 
                else:
349
 
                    logger.error(bad_states[state] + ": %r", error)
350
 
            self.cleanup()
 
223
        if state == avahi.SERVER_COLLISION:
 
224
            logger.error("Zeroconf server name collision")
 
225
            self.remove()
351
226
        elif state == avahi.SERVER_RUNNING:
352
227
            self.add()
353
 
        else:
354
 
            if error is None:
355
 
                logger.debug("Unknown state: %r", state)
356
 
            else:
357
 
                logger.debug("Unknown state: %r: %r", state, error)
358
228
    def activate(self):
359
229
        """Derived from the Avahi example code"""
360
230
        if self.server is None:
361
231
            self.server = dbus.Interface(
362
232
                self.bus.get_object(avahi.DBUS_NAME,
363
 
                                    avahi.DBUS_PATH_SERVER,
364
 
                                    follow_name_owner_changes=True),
 
233
                                    avahi.DBUS_PATH_SERVER),
365
234
                avahi.DBUS_INTERFACE_SERVER)
366
235
        self.server.connect_to_signal("StateChanged",
367
236
                                 self.server_state_changed)
368
237
        self.server_state_changed(self.server.GetState())
369
238
 
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
379
239
 
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
 
        
386
240
class Client(object):
387
241
    """A representation of a client host served by this server.
388
242
    
389
243
    Attributes:
390
 
    approved:   bool(); 'None' if not yet approved/disapproved
 
244
    _approved:   bool(); 'None' if not yet approved/disapproved
391
245
    approval_delay: datetime.timedelta(); Time to wait for approval
392
246
    approval_duration: datetime.timedelta(); Duration of one approval
393
247
    checker:    subprocess.Popen(); a running checker process used
400
254
                     instance %(name)s can be used in the command.
401
255
    checker_initiator_tag: a gobject event source tag, or None
402
256
    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
405
257
    current_checker_command: string; current running checker_command
 
258
    disable_hook:  If set, called by disable() as disable_hook(self)
406
259
    disable_initiator_tag: a gobject event source tag, or None
407
260
    enabled:    bool()
408
261
    fingerprint: string (40 or 32 hexadecimal digits); used to
411
264
    interval:   datetime.timedelta(); How often to start a new checker
412
265
    last_approval_request: datetime.datetime(); (UTC) or None
413
266
    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.
418
 
    last_enabled: datetime.datetime(); (UTC) or None
 
267
    last_enabled: datetime.datetime(); (UTC)
419
268
    name:       string; from the config file, used in log messages and
420
269
                        D-Bus identifiers
421
270
    secret:     bytestring; sent verbatim (over TLS) to client
422
271
    timeout:    datetime.timedelta(); How long from last_checked_ok
423
272
                                      until this client is disabled
424
 
    extended_timeout:   extra long timeout when password has been sent
425
273
    runtime_expansions: Allowed attributes for runtime expansion.
426
 
    expires:    datetime.datetime(); time (UTC) when a client will be
427
 
                disabled, or None
428
274
    """
429
275
    
430
276
    runtime_expansions = ("approval_delay", "approval_duration",
432
278
                          "host", "interval", "last_checked_ok",
433
279
                          "last_enabled", "name", "timeout")
434
280
    
 
281
    @staticmethod
 
282
    def _timedelta_to_milliseconds(td):
 
283
        "Convert a datetime.timedelta() to milliseconds"
 
284
        return ((td.days * 24 * 60 * 60 * 1000)
 
285
                + (td.seconds * 1000)
 
286
                + (td.microseconds // 1000))
 
287
    
435
288
    def timeout_milliseconds(self):
436
289
        "Return the 'timeout' attribute in milliseconds"
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)
 
290
        return self._timedelta_to_milliseconds(self.timeout)
442
291
    
443
292
    def interval_milliseconds(self):
444
293
        "Return the 'interval' attribute in milliseconds"
445
 
        return timedelta_to_milliseconds(self.interval)
446
 
    
 
294
        return self._timedelta_to_milliseconds(self.interval)
 
295
 
447
296
    def approval_delay_milliseconds(self):
448
 
        return timedelta_to_milliseconds(self.approval_delay)
 
297
        return self._timedelta_to_milliseconds(self.approval_delay)
449
298
    
450
 
    def __init__(self, name = None, config=None):
 
299
    def __init__(self, name = None, disable_hook=None, config=None):
451
300
        """Note: the 'checker' key in 'config' sets the
452
301
        'checker_command' attribute and *not* the 'checker'
453
302
        attribute."""
473
322
                            % self.name)
474
323
        self.host = config.get("host", "")
475
324
        self.created = datetime.datetime.utcnow()
476
 
        self.enabled = config.get("enabled", True)
 
325
        self.enabled = False
477
326
        self.last_approval_request = None
478
 
        if self.enabled:
479
 
            self.last_enabled = datetime.datetime.utcnow()
480
 
        else:
481
 
            self.last_enabled = None
 
327
        self.last_enabled = None
482
328
        self.last_checked_ok = None
483
 
        self.last_checker_status = None
484
329
        self.timeout = string_to_delta(config["timeout"])
485
 
        self.extended_timeout = string_to_delta(config
486
 
                                                ["extended_timeout"])
487
330
        self.interval = string_to_delta(config["interval"])
 
331
        self.disable_hook = disable_hook
488
332
        self.checker = None
489
333
        self.checker_initiator_tag = None
490
334
        self.disable_initiator_tag = None
491
 
        if self.enabled:
492
 
            self.expires = datetime.datetime.utcnow() + self.timeout
493
 
        else:
494
 
            self.expires = None
495
335
        self.checker_callback_tag = None
496
336
        self.checker_command = config["checker"]
497
337
        self.current_checker_command = None
498
 
        self.approved = None
 
338
        self.last_connect = None
 
339
        self._approved = None
499
340
        self.approved_by_default = config.get("approved_by_default",
500
341
                                              True)
501
342
        self.approvals_pending = 0
503
344
            config["approval_delay"])
504
345
        self.approval_duration = string_to_delta(
505
346
            config["approval_duration"])
506
 
        self.changedstate = (multiprocessing_manager
507
 
                             .Condition(multiprocessing_manager
508
 
                                        .Lock()))
509
 
        self.client_structure = [attr for attr in
510
 
                                 self.__dict__.iterkeys()
511
 
                                 if not attr.startswith("_")]
512
 
        self.client_structure.append("client_structure")
513
 
        
514
 
        for name, t in inspect.getmembers(type(self),
515
 
                                          lambda obj:
516
 
                                              isinstance(obj,
517
 
                                                         property)):
518
 
            if not name.startswith("_"):
519
 
                self.client_structure.append(name)
 
347
        self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
520
348
    
521
 
    # Send notice to process children that client state has changed
522
349
    def send_changedstate(self):
523
 
        with self.changedstate:
524
 
            self.changedstate.notify_all()
525
 
    
 
350
        self.changedstate.acquire()
 
351
        self.changedstate.notify_all()
 
352
        self.changedstate.release()
 
353
        
526
354
    def enable(self):
527
355
        """Start this client's checker and timeout hooks"""
528
356
        if getattr(self, "enabled", False):
529
357
            # Already enabled
530
358
            return
531
359
        self.send_changedstate()
532
 
        self.expires = datetime.datetime.utcnow() + self.timeout
533
 
        self.enabled = True
534
360
        self.last_enabled = datetime.datetime.utcnow()
535
 
        self.init_checker()
 
361
        # Schedule a new checker to be started an 'interval' from now,
 
362
        # and every interval from then on.
 
363
        self.checker_initiator_tag = (gobject.timeout_add
 
364
                                      (self.interval_milliseconds(),
 
365
                                       self.start_checker))
 
366
        # Schedule a disable() when 'timeout' has passed
 
367
        self.disable_initiator_tag = (gobject.timeout_add
 
368
                                   (self.timeout_milliseconds(),
 
369
                                    self.disable))
 
370
        self.enabled = True
 
371
        # Also start a new checker *right now*.
 
372
        self.start_checker()
536
373
    
537
374
    def disable(self, quiet=True):
538
375
        """Disable this client."""
545
382
        if getattr(self, "disable_initiator_tag", False):
546
383
            gobject.source_remove(self.disable_initiator_tag)
547
384
            self.disable_initiator_tag = None
548
 
        self.expires = None
549
385
        if getattr(self, "checker_initiator_tag", False):
550
386
            gobject.source_remove(self.checker_initiator_tag)
551
387
            self.checker_initiator_tag = None
552
388
        self.stop_checker()
 
389
        if self.disable_hook:
 
390
            self.disable_hook(self)
553
391
        self.enabled = False
554
392
        # Do not run this again if called by a gobject.timeout_add
555
393
        return False
556
394
    
557
395
    def __del__(self):
 
396
        self.disable_hook = None
558
397
        self.disable()
559
398
    
560
 
    def init_checker(self):
561
 
        # Schedule a new checker to be started an 'interval' from now,
562
 
        # and every interval from then on.
563
 
        self.checker_initiator_tag = (gobject.timeout_add
564
 
                                      (self.interval_milliseconds(),
565
 
                                       self.start_checker))
566
 
        # Schedule a disable() when 'timeout' has passed
567
 
        self.disable_initiator_tag = (gobject.timeout_add
568
 
                                   (self.timeout_milliseconds(),
569
 
                                    self.disable))
570
 
        # Also start a new checker *right now*.
571
 
        self.start_checker()
572
 
    
573
399
    def checker_callback(self, pid, condition, command):
574
400
        """The checker has completed, so take appropriate actions."""
575
401
        self.checker_callback_tag = None
576
402
        self.checker = None
577
403
        if os.WIFEXITED(condition):
578
 
            self.last_checker_status =  os.WEXITSTATUS(condition)
579
 
            if self.last_checker_status == 0:
 
404
            exitstatus = os.WEXITSTATUS(condition)
 
405
            if exitstatus == 0:
580
406
                logger.info("Checker for %(name)s succeeded",
581
407
                            vars(self))
582
408
                self.checked_ok()
584
410
                logger.info("Checker for %(name)s failed",
585
411
                            vars(self))
586
412
        else:
587
 
            self.last_checker_status = -1
588
413
            logger.warning("Checker for %(name)s crashed?",
589
414
                           vars(self))
590
415
    
591
 
    def checked_ok(self, timeout=None):
 
416
    def checked_ok(self):
592
417
        """Bump up the timeout for this client.
593
418
        
594
419
        This should only be called when the client has been seen,
595
420
        alive and well.
596
421
        """
597
 
        if timeout is None:
598
 
            timeout = self.timeout
599
422
        self.last_checked_ok = datetime.datetime.utcnow()
600
 
        if self.disable_initiator_tag is not None:
601
 
            gobject.source_remove(self.disable_initiator_tag)
602
 
        if getattr(self, "enabled", False):
603
 
            self.disable_initiator_tag = (gobject.timeout_add
604
 
                                          (timedelta_to_milliseconds
605
 
                                           (timeout), self.disable))
606
 
            self.expires = datetime.datetime.utcnow() + timeout
 
423
        gobject.source_remove(self.disable_initiator_tag)
 
424
        self.disable_initiator_tag = (gobject.timeout_add
 
425
                                      (self.timeout_milliseconds(),
 
426
                                       self.disable))
607
427
    
608
428
    def need_approval(self):
609
429
        self.last_approval_request = datetime.datetime.utcnow()
625
445
        # If a checker exists, make sure it is not a zombie
626
446
        try:
627
447
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
628
 
        except (AttributeError, OSError) as error:
 
448
        except (AttributeError, OSError), error:
629
449
            if (isinstance(error, OSError)
630
450
                and error.errno != errno.ECHILD):
631
451
                raise error
649
469
                                       'replace')))
650
470
                    for attr in
651
471
                    self.runtime_expansions)
652
 
                
 
472
 
653
473
                try:
654
474
                    command = self.checker_command % escaped_attrs
655
 
                except TypeError as error:
 
475
                except TypeError, error:
656
476
                    logger.error('Could not format string "%s":'
657
477
                                 ' %s', self.checker_command, error)
658
478
                    return True # Try again later
677
497
                if pid:
678
498
                    gobject.source_remove(self.checker_callback_tag)
679
499
                    self.checker_callback(pid, status, command)
680
 
            except OSError as error:
 
500
            except OSError, error:
681
501
                logger.error("Failed to start subprocess: %s",
682
502
                             error)
683
503
        # Re-run this periodically if run by gobject.timeout_add
696
516
            #time.sleep(0.5)
697
517
            #if self.checker.poll() is None:
698
518
            #    os.kill(self.checker.pid, signal.SIGKILL)
699
 
        except OSError as error:
 
519
        except OSError, error:
700
520
            if error.errno != errno.ESRCH: # No such process
701
521
                raise
702
522
        self.checker = None
703
523
 
704
 
 
705
524
def dbus_service_property(dbus_interface, signature="v",
706
525
                          access="readwrite", byte_arrays=False):
707
526
    """Decorators for marking methods of a DBusObjectWithProperties to
753
572
 
754
573
class DBusObjectWithProperties(dbus.service.Object):
755
574
    """A D-Bus object with properties.
756
 
    
 
575
 
757
576
    Classes inheriting from this can use the dbus_service_property
758
577
    decorator to expose methods as D-Bus properties.  It exposes the
759
578
    standard Get(), Set(), and GetAll() methods on the D-Bus.
766
585
    def _get_all_dbus_properties(self):
767
586
        """Returns a generator of (name, attribute) pairs
768
587
        """
769
 
        return ((prop.__get__(self)._dbus_name, prop.__get__(self))
770
 
                for cls in self.__class__.__mro__
 
588
        return ((prop._dbus_name, prop)
771
589
                for name, prop in
772
 
                inspect.getmembers(cls, self._is_dbus_property))
 
590
                inspect.getmembers(self, self._is_dbus_property))
773
591
    
774
592
    def _get_dbus_property(self, interface_name, property_name):
775
593
        """Returns a bound method if one exists which is a D-Bus
776
594
        property with the specified name and interface.
777
595
        """
778
 
        for cls in  self.__class__.__mro__:
779
 
            for name, value in (inspect.getmembers
780
 
                                (cls, self._is_dbus_property)):
781
 
                if (value._dbus_name == property_name
782
 
                    and value._dbus_interface == interface_name):
783
 
                    return value.__get__(self)
784
 
        
 
596
        for name in (property_name,
 
597
                     property_name + "_dbus_property"):
 
598
            prop = getattr(self, name, None)
 
599
            if (prop is None
 
600
                or not self._is_dbus_property(prop)
 
601
                or prop._dbus_name != property_name
 
602
                or (interface_name and prop._dbus_interface
 
603
                    and interface_name != prop._dbus_interface)):
 
604
                continue
 
605
            return prop
785
606
        # No such property
786
607
        raise DBusPropertyNotFound(self.dbus_object_path + ":"
787
608
                                   + interface_name + "."
821
642
    def GetAll(self, interface_name):
822
643
        """Standard D-Bus property GetAll() method, see D-Bus
823
644
        standard.
824
 
        
 
645
 
825
646
        Note: Will not include properties with access="write".
826
647
        """
827
 
        properties = {}
 
648
        all = {}
828
649
        for name, prop in self._get_all_dbus_properties():
829
650
            if (interface_name
830
651
                and interface_name != prop._dbus_interface):
835
656
                continue
836
657
            value = prop()
837
658
            if not hasattr(value, "variant_level"):
838
 
                properties[name] = value
 
659
                all[name] = value
839
660
                continue
840
 
            properties[name] = type(value)(value, variant_level=
841
 
                                           value.variant_level+1)
842
 
        return dbus.Dictionary(properties, signature="sv")
 
661
            all[name] = type(value)(value, variant_level=
 
662
                                    value.variant_level+1)
 
663
        return dbus.Dictionary(all, signature="sv")
843
664
    
844
665
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
845
666
                         out_signature="s",
883
704
            xmlstring = document.toxml("utf-8")
884
705
            document.unlink()
885
706
        except (AttributeError, xml.dom.DOMException,
886
 
                xml.parsers.expat.ExpatError) as error:
 
707
                xml.parsers.expat.ExpatError), error:
887
708
            logger.error("Failed to override Introspection method",
888
709
                         error)
889
710
        return xmlstring
890
711
 
891
712
 
892
 
def datetime_to_dbus (dt, variant_level=0):
893
 
    """Convert a UTC datetime.datetime() to a D-Bus type."""
894
 
    if dt is None:
895
 
        return dbus.String("", variant_level = variant_level)
896
 
    return dbus.String(dt.isoformat(),
897
 
                       variant_level=variant_level)
898
 
 
899
 
 
900
 
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
901
 
                                  .__metaclass__):
902
 
    """Applied to an empty subclass of a D-Bus object, this metaclass
903
 
    will add additional D-Bus attributes matching a certain pattern.
904
 
    """
905
 
    def __new__(mcs, name, bases, attr):
906
 
        # Go through all the base classes which could have D-Bus
907
 
        # methods, signals, or properties in them
908
 
        for base in (b for b in bases
909
 
                     if issubclass(b, dbus.service.Object)):
910
 
            # Go though all attributes of the base class
911
 
            for attrname, attribute in inspect.getmembers(base):
912
 
                # Ignore non-D-Bus attributes, and D-Bus attributes
913
 
                # with the wrong interface name
914
 
                if (not hasattr(attribute, "_dbus_interface")
915
 
                    or not attribute._dbus_interface
916
 
                    .startswith("se.recompile.Mandos")):
917
 
                    continue
918
 
                # Create an alternate D-Bus interface name based on
919
 
                # the current name
920
 
                alt_interface = (attribute._dbus_interface
921
 
                                 .replace("se.recompile.Mandos",
922
 
                                          "se.bsnet.fukt.Mandos"))
923
 
                # Is this a D-Bus signal?
924
 
                if getattr(attribute, "_dbus_is_signal", False):
925
 
                    # Extract the original non-method function by
926
 
                    # black magic
927
 
                    nonmethod_func = (dict(
928
 
                            zip(attribute.func_code.co_freevars,
929
 
                                attribute.__closure__))["func"]
930
 
                                      .cell_contents)
931
 
                    # Create a new, but exactly alike, function
932
 
                    # object, and decorate it to be a new D-Bus signal
933
 
                    # with the alternate D-Bus interface name
934
 
                    new_function = (dbus.service.signal
935
 
                                    (alt_interface,
936
 
                                     attribute._dbus_signature)
937
 
                                    (types.FunctionType(
938
 
                                nonmethod_func.func_code,
939
 
                                nonmethod_func.func_globals,
940
 
                                nonmethod_func.func_name,
941
 
                                nonmethod_func.func_defaults,
942
 
                                nonmethod_func.func_closure)))
943
 
                    # Define a creator of a function to call both the
944
 
                    # old and new functions, so both the old and new
945
 
                    # signals gets sent when the function is called
946
 
                    def fixscope(func1, func2):
947
 
                        """This function is a scope container to pass
948
 
                        func1 and func2 to the "call_both" function
949
 
                        outside of its arguments"""
950
 
                        def call_both(*args, **kwargs):
951
 
                            """This function will emit two D-Bus
952
 
                            signals by calling func1 and func2"""
953
 
                            func1(*args, **kwargs)
954
 
                            func2(*args, **kwargs)
955
 
                        return call_both
956
 
                    # Create the "call_both" function and add it to
957
 
                    # the class
958
 
                    attr[attrname] = fixscope(attribute,
959
 
                                              new_function)
960
 
                # Is this a D-Bus method?
961
 
                elif getattr(attribute, "_dbus_is_method", False):
962
 
                    # Create a new, but exactly alike, function
963
 
                    # object.  Decorate it to be a new D-Bus method
964
 
                    # with the alternate D-Bus interface name.  Add it
965
 
                    # to the class.
966
 
                    attr[attrname] = (dbus.service.method
967
 
                                      (alt_interface,
968
 
                                       attribute._dbus_in_signature,
969
 
                                       attribute._dbus_out_signature)
970
 
                                      (types.FunctionType
971
 
                                       (attribute.func_code,
972
 
                                        attribute.func_globals,
973
 
                                        attribute.func_name,
974
 
                                        attribute.func_defaults,
975
 
                                        attribute.func_closure)))
976
 
                # Is this a D-Bus property?
977
 
                elif getattr(attribute, "_dbus_is_property", False):
978
 
                    # Create a new, but exactly alike, function
979
 
                    # object, and decorate it to be a new D-Bus
980
 
                    # property with the alternate D-Bus interface
981
 
                    # name.  Add it to the class.
982
 
                    attr[attrname] = (dbus_service_property
983
 
                                      (alt_interface,
984
 
                                       attribute._dbus_signature,
985
 
                                       attribute._dbus_access,
986
 
                                       attribute
987
 
                                       ._dbus_get_args_options
988
 
                                       ["byte_arrays"])
989
 
                                      (types.FunctionType
990
 
                                       (attribute.func_code,
991
 
                                        attribute.func_globals,
992
 
                                        attribute.func_name,
993
 
                                        attribute.func_defaults,
994
 
                                        attribute.func_closure)))
995
 
        return type.__new__(mcs, name, bases, attr)
996
 
 
997
 
 
998
713
class ClientDBus(Client, DBusObjectWithProperties):
999
714
    """A Client class using D-Bus
1000
715
    
1009
724
    # dbus.service.Object doesn't use super(), so we can't either.
1010
725
    
1011
726
    def __init__(self, bus = None, *args, **kwargs):
 
727
        self._approvals_pending = 0
1012
728
        self.bus = bus
1013
729
        Client.__init__(self, *args, **kwargs)
1014
 
        
1015
 
        self._approvals_pending = 0
1016
730
        # Only now, when this client is initialized, can it show up on
1017
731
        # the D-Bus
1018
732
        client_object_name = unicode(self.name).translate(
1023
737
        DBusObjectWithProperties.__init__(self, self.bus,
1024
738
                                          self.dbus_object_path)
1025
739
        
1026
 
    def notifychangeproperty(transform_func,
1027
 
                             dbus_name, type_func=lambda x: x,
1028
 
                             variant_level=1):
1029
 
        """ Modify a variable so that it's a property which announces
1030
 
        its changes to DBus.
1031
 
        
1032
 
        transform_fun: Function that takes a value and a variant_level
1033
 
                       and transforms it to a D-Bus type.
1034
 
        dbus_name: D-Bus name of the variable
1035
 
        type_func: Function that transform the value before sending it
1036
 
                   to the D-Bus.  Default: no transform
1037
 
        variant_level: D-Bus variant level.  Default: 1
1038
 
        """
1039
 
        attrname = "_{0}".format(dbus_name)
1040
 
        def setter(self, value):
1041
 
            if hasattr(self, "dbus_object_path"):
1042
 
                if (not hasattr(self, attrname) or
1043
 
                    type_func(getattr(self, attrname, None))
1044
 
                    != type_func(value)):
1045
 
                    dbus_value = transform_func(type_func(value),
1046
 
                                                variant_level
1047
 
                                                =variant_level)
1048
 
                    self.PropertyChanged(dbus.String(dbus_name),
1049
 
                                         dbus_value)
1050
 
            setattr(self, attrname, value)
1051
 
        
1052
 
        return property(lambda self: getattr(self, attrname), setter)
1053
 
    
1054
 
    
1055
 
    expires = notifychangeproperty(datetime_to_dbus, "Expires")
1056
 
    approvals_pending = notifychangeproperty(dbus.Boolean,
1057
 
                                             "ApprovalPending",
1058
 
                                             type_func = bool)
1059
 
    enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1060
 
    last_enabled = notifychangeproperty(datetime_to_dbus,
1061
 
                                        "LastEnabled")
1062
 
    checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1063
 
                                   type_func = lambda checker:
1064
 
                                       checker is not None)
1065
 
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
1066
 
                                           "LastCheckedOK")
1067
 
    last_approval_request = notifychangeproperty(
1068
 
        datetime_to_dbus, "LastApprovalRequest")
1069
 
    approved_by_default = notifychangeproperty(dbus.Boolean,
1070
 
                                               "ApprovedByDefault")
1071
 
    approval_delay = notifychangeproperty(dbus.UInt16,
1072
 
                                          "ApprovalDelay",
1073
 
                                          type_func =
1074
 
                                          timedelta_to_milliseconds)
1075
 
    approval_duration = notifychangeproperty(
1076
 
        dbus.UInt16, "ApprovalDuration",
1077
 
        type_func = timedelta_to_milliseconds)
1078
 
    host = notifychangeproperty(dbus.String, "Host")
1079
 
    timeout = notifychangeproperty(dbus.UInt16, "Timeout",
1080
 
                                   type_func =
1081
 
                                   timedelta_to_milliseconds)
1082
 
    extended_timeout = notifychangeproperty(
1083
 
        dbus.UInt16, "ExtendedTimeout",
1084
 
        type_func = timedelta_to_milliseconds)
1085
 
    interval = notifychangeproperty(dbus.UInt16,
1086
 
                                    "Interval",
1087
 
                                    type_func =
1088
 
                                    timedelta_to_milliseconds)
1089
 
    checker_command = notifychangeproperty(dbus.String, "Checker")
1090
 
    
1091
 
    del notifychangeproperty
 
740
    def _get_approvals_pending(self):
 
741
        return self._approvals_pending
 
742
    def _set_approvals_pending(self, value):
 
743
        old_value = self._approvals_pending
 
744
        self._approvals_pending = value
 
745
        bval = bool(value)
 
746
        if (hasattr(self, "dbus_object_path")
 
747
            and bval is not bool(old_value)):
 
748
            dbus_bool = dbus.Boolean(bval, variant_level=1)
 
749
            self.PropertyChanged(dbus.String("ApprovalPending"),
 
750
                                 dbus_bool)
 
751
 
 
752
    approvals_pending = property(_get_approvals_pending,
 
753
                                 _set_approvals_pending)
 
754
    del _get_approvals_pending, _set_approvals_pending
 
755
    
 
756
    @staticmethod
 
757
    def _datetime_to_dbus(dt, variant_level=0):
 
758
        """Convert a UTC datetime.datetime() to a D-Bus type."""
 
759
        return dbus.String(dt.isoformat(),
 
760
                           variant_level=variant_level)
 
761
    
 
762
    def enable(self):
 
763
        oldstate = getattr(self, "enabled", False)
 
764
        r = Client.enable(self)
 
765
        if oldstate != self.enabled:
 
766
            # Emit D-Bus signals
 
767
            self.PropertyChanged(dbus.String("Enabled"),
 
768
                                 dbus.Boolean(True, variant_level=1))
 
769
            self.PropertyChanged(
 
770
                dbus.String("LastEnabled"),
 
771
                self._datetime_to_dbus(self.last_enabled,
 
772
                                       variant_level=1))
 
773
        return r
 
774
    
 
775
    def disable(self, quiet = False):
 
776
        oldstate = getattr(self, "enabled", False)
 
777
        r = Client.disable(self, quiet=quiet)
 
778
        if not quiet and oldstate != self.enabled:
 
779
            # Emit D-Bus signal
 
780
            self.PropertyChanged(dbus.String("Enabled"),
 
781
                                 dbus.Boolean(False, variant_level=1))
 
782
        return r
1092
783
    
1093
784
    def __del__(self, *args, **kwargs):
1094
785
        try:
1103
794
                         *args, **kwargs):
1104
795
        self.checker_callback_tag = None
1105
796
        self.checker = None
 
797
        # Emit D-Bus signal
 
798
        self.PropertyChanged(dbus.String("CheckerRunning"),
 
799
                             dbus.Boolean(False, variant_level=1))
1106
800
        if os.WIFEXITED(condition):
1107
801
            exitstatus = os.WEXITSTATUS(condition)
1108
802
            # Emit D-Bus signal
1118
812
        return Client.checker_callback(self, pid, condition, command,
1119
813
                                       *args, **kwargs)
1120
814
    
 
815
    def checked_ok(self, *args, **kwargs):
 
816
        r = Client.checked_ok(self, *args, **kwargs)
 
817
        # Emit D-Bus signal
 
818
        self.PropertyChanged(
 
819
            dbus.String("LastCheckedOK"),
 
820
            (self._datetime_to_dbus(self.last_checked_ok,
 
821
                                    variant_level=1)))
 
822
        return r
 
823
    
 
824
    def need_approval(self, *args, **kwargs):
 
825
        r = Client.need_approval(self, *args, **kwargs)
 
826
        # Emit D-Bus signal
 
827
        self.PropertyChanged(
 
828
            dbus.String("LastApprovalRequest"),
 
829
            (self._datetime_to_dbus(self.last_approval_request,
 
830
                                    variant_level=1)))
 
831
        return r
 
832
    
1121
833
    def start_checker(self, *args, **kwargs):
1122
834
        old_checker = self.checker
1123
835
        if self.checker is not None:
1130
842
            and old_checker_pid != self.checker.pid):
1131
843
            # Emit D-Bus signal
1132
844
            self.CheckerStarted(self.current_checker_command)
 
845
            self.PropertyChanged(
 
846
                dbus.String("CheckerRunning"),
 
847
                dbus.Boolean(True, variant_level=1))
1133
848
        return r
1134
849
    
 
850
    def stop_checker(self, *args, **kwargs):
 
851
        old_checker = getattr(self, "checker", None)
 
852
        r = Client.stop_checker(self, *args, **kwargs)
 
853
        if (old_checker is not None
 
854
            and getattr(self, "checker", None) is None):
 
855
            self.PropertyChanged(dbus.String("CheckerRunning"),
 
856
                                 dbus.Boolean(False, variant_level=1))
 
857
        return r
 
858
 
1135
859
    def _reset_approved(self):
1136
 
        self.approved = None
 
860
        self._approved = None
1137
861
        return False
1138
862
    
1139
863
    def approve(self, value=True):
1140
864
        self.send_changedstate()
1141
 
        self.approved = value
1142
 
        gobject.timeout_add(timedelta_to_milliseconds
 
865
        self._approved = value
 
866
        gobject.timeout_add(self._timedelta_to_milliseconds
1143
867
                            (self.approval_duration),
1144
868
                            self._reset_approved)
1145
869
    
1146
870
    
1147
871
    ## D-Bus methods, signals & properties
1148
 
    _interface = "se.recompile.Mandos.Client"
 
872
    _interface = "se.bsnet.fukt.Mandos.Client"
1149
873
    
1150
874
    ## Signals
1151
875
    
1188
912
        "D-Bus signal"
1189
913
        return self.need_approval()
1190
914
    
1191
 
    # NeRwequest - signal
1192
 
    @dbus.service.signal(_interface, signature="s")
1193
 
    def NewRequest(self, ip):
1194
 
        """D-Bus signal
1195
 
        Is sent after a client request a password.
1196
 
        """
1197
 
        pass
1198
 
    
1199
915
    ## Methods
1200
916
    
1201
917
    # Approve - method
1206
922
    # CheckedOK - method
1207
923
    @dbus.service.method(_interface)
1208
924
    def CheckedOK(self):
1209
 
        self.checked_ok()
 
925
        return self.checked_ok()
1210
926
    
1211
927
    # Enable - method
1212
928
    @dbus.service.method(_interface)
1245
961
        if value is None:       # get
1246
962
            return dbus.Boolean(self.approved_by_default)
1247
963
        self.approved_by_default = bool(value)
 
964
        # Emit D-Bus signal
 
965
        self.PropertyChanged(dbus.String("ApprovedByDefault"),
 
966
                             dbus.Boolean(value, variant_level=1))
1248
967
    
1249
968
    # ApprovalDelay - property
1250
969
    @dbus_service_property(_interface, signature="t",
1253
972
        if value is None:       # get
1254
973
            return dbus.UInt64(self.approval_delay_milliseconds())
1255
974
        self.approval_delay = datetime.timedelta(0, 0, 0, value)
 
975
        # Emit D-Bus signal
 
976
        self.PropertyChanged(dbus.String("ApprovalDelay"),
 
977
                             dbus.UInt64(value, variant_level=1))
1256
978
    
1257
979
    # ApprovalDuration - property
1258
980
    @dbus_service_property(_interface, signature="t",
1259
981
                           access="readwrite")
1260
982
    def ApprovalDuration_dbus_property(self, value=None):
1261
983
        if value is None:       # get
1262
 
            return dbus.UInt64(timedelta_to_milliseconds(
 
984
            return dbus.UInt64(self._timedelta_to_milliseconds(
1263
985
                    self.approval_duration))
1264
986
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
 
987
        # Emit D-Bus signal
 
988
        self.PropertyChanged(dbus.String("ApprovalDuration"),
 
989
                             dbus.UInt64(value, variant_level=1))
1265
990
    
1266
991
    # Name - property
1267
992
    @dbus_service_property(_interface, signature="s", access="read")
1280
1005
        if value is None:       # get
1281
1006
            return dbus.String(self.host)
1282
1007
        self.host = value
 
1008
        # Emit D-Bus signal
 
1009
        self.PropertyChanged(dbus.String("Host"),
 
1010
                             dbus.String(value, variant_level=1))
1283
1011
    
1284
1012
    # Created - property
1285
1013
    @dbus_service_property(_interface, signature="s", access="read")
1286
1014
    def Created_dbus_property(self):
1287
 
        return datetime_to_dbus(self.created)
 
1015
        return dbus.String(self._datetime_to_dbus(self.created))
1288
1016
    
1289
1017
    # LastEnabled - property
1290
1018
    @dbus_service_property(_interface, signature="s", access="read")
1291
1019
    def LastEnabled_dbus_property(self):
1292
 
        return datetime_to_dbus(self.last_enabled)
 
1020
        if self.last_enabled is None:
 
1021
            return dbus.String("")
 
1022
        return dbus.String(self._datetime_to_dbus(self.last_enabled))
1293
1023
    
1294
1024
    # Enabled - property
1295
1025
    @dbus_service_property(_interface, signature="b",
1309
1039
        if value is not None:
1310
1040
            self.checked_ok()
1311
1041
            return
1312
 
        return datetime_to_dbus(self.last_checked_ok)
1313
 
    
1314
 
    # Expires - property
1315
 
    @dbus_service_property(_interface, signature="s", access="read")
1316
 
    def Expires_dbus_property(self):
1317
 
        return datetime_to_dbus(self.expires)
 
1042
        if self.last_checked_ok is None:
 
1043
            return dbus.String("")
 
1044
        return dbus.String(self._datetime_to_dbus(self
 
1045
                                                  .last_checked_ok))
1318
1046
    
1319
1047
    # LastApprovalRequest - property
1320
1048
    @dbus_service_property(_interface, signature="s", access="read")
1321
1049
    def LastApprovalRequest_dbus_property(self):
1322
 
        return datetime_to_dbus(self.last_approval_request)
 
1050
        if self.last_approval_request is None:
 
1051
            return dbus.String("")
 
1052
        return dbus.String(self.
 
1053
                           _datetime_to_dbus(self
 
1054
                                             .last_approval_request))
1323
1055
    
1324
1056
    # Timeout - property
1325
1057
    @dbus_service_property(_interface, signature="t",
1328
1060
        if value is None:       # get
1329
1061
            return dbus.UInt64(self.timeout_milliseconds())
1330
1062
        self.timeout = datetime.timedelta(0, 0, 0, value)
 
1063
        # Emit D-Bus signal
 
1064
        self.PropertyChanged(dbus.String("Timeout"),
 
1065
                             dbus.UInt64(value, variant_level=1))
1331
1066
        if getattr(self, "disable_initiator_tag", None) is None:
1332
1067
            return
1333
1068
        # Reschedule timeout
1334
1069
        gobject.source_remove(self.disable_initiator_tag)
1335
1070
        self.disable_initiator_tag = None
1336
 
        self.expires = None
1337
 
        time_to_die = timedelta_to_milliseconds((self
1338
 
                                                 .last_checked_ok
1339
 
                                                 + self.timeout)
1340
 
                                                - datetime.datetime
1341
 
                                                .utcnow())
 
1071
        time_to_die = (self.
 
1072
                       _timedelta_to_milliseconds((self
 
1073
                                                   .last_checked_ok
 
1074
                                                   + self.timeout)
 
1075
                                                  - datetime.datetime
 
1076
                                                  .utcnow()))
1342
1077
        if time_to_die <= 0:
1343
1078
            # The timeout has passed
1344
1079
            self.disable()
1345
1080
        else:
1346
 
            self.expires = (datetime.datetime.utcnow()
1347
 
                            + datetime.timedelta(milliseconds =
1348
 
                                                 time_to_die))
1349
1081
            self.disable_initiator_tag = (gobject.timeout_add
1350
1082
                                          (time_to_die, self.disable))
1351
1083
    
1352
 
    # ExtendedTimeout - property
1353
 
    @dbus_service_property(_interface, signature="t",
1354
 
                           access="readwrite")
1355
 
    def ExtendedTimeout_dbus_property(self, value=None):
1356
 
        if value is None:       # get
1357
 
            return dbus.UInt64(self.extended_timeout_milliseconds())
1358
 
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1359
 
    
1360
1084
    # Interval - property
1361
1085
    @dbus_service_property(_interface, signature="t",
1362
1086
                           access="readwrite")
1364
1088
        if value is None:       # get
1365
1089
            return dbus.UInt64(self.interval_milliseconds())
1366
1090
        self.interval = datetime.timedelta(0, 0, 0, value)
 
1091
        # Emit D-Bus signal
 
1092
        self.PropertyChanged(dbus.String("Interval"),
 
1093
                             dbus.UInt64(value, variant_level=1))
1367
1094
        if getattr(self, "checker_initiator_tag", None) is None:
1368
1095
            return
1369
 
        if self.enabled:
1370
 
            # Reschedule checker run
1371
 
            gobject.source_remove(self.checker_initiator_tag)
1372
 
            self.checker_initiator_tag = (gobject.timeout_add
1373
 
                                          (value, self.start_checker))
1374
 
            self.start_checker()    # Start one now, too
1375
 
    
 
1096
        # Reschedule checker run
 
1097
        gobject.source_remove(self.checker_initiator_tag)
 
1098
        self.checker_initiator_tag = (gobject.timeout_add
 
1099
                                      (value, self.start_checker))
 
1100
        self.start_checker()    # Start one now, too
 
1101
 
1376
1102
    # Checker - property
1377
1103
    @dbus_service_property(_interface, signature="s",
1378
1104
                           access="readwrite")
1380
1106
        if value is None:       # get
1381
1107
            return dbus.String(self.checker_command)
1382
1108
        self.checker_command = value
 
1109
        # Emit D-Bus signal
 
1110
        self.PropertyChanged(dbus.String("Checker"),
 
1111
                             dbus.String(self.checker_command,
 
1112
                                         variant_level=1))
1383
1113
    
1384
1114
    # CheckerRunning - property
1385
1115
    @dbus_service_property(_interface, signature="b",
1412
1142
        self._pipe.send(('init', fpr, address))
1413
1143
        if not self._pipe.recv():
1414
1144
            raise KeyError()
1415
 
    
 
1145
 
1416
1146
    def __getattribute__(self, name):
1417
 
        if name == '_pipe':
 
1147
        if(name == '_pipe'):
1418
1148
            return super(ProxyClient, self).__getattribute__(name)
1419
1149
        self._pipe.send(('getattr', name))
1420
1150
        data = self._pipe.recv()
1425
1155
                self._pipe.send(('funcall', name, args, kwargs))
1426
1156
                return self._pipe.recv()[1]
1427
1157
            return func
1428
 
    
 
1158
 
1429
1159
    def __setattr__(self, name, value):
1430
 
        if name == '_pipe':
 
1160
        if(name == '_pipe'):
1431
1161
            return super(ProxyClient, self).__setattr__(name, value)
1432
1162
        self._pipe.send(('setattr', name, value))
1433
1163
 
1434
1164
 
1435
 
class ClientDBusTransitional(ClientDBus):
1436
 
    __metaclass__ = AlternateDBusNamesMetaclass
1437
 
 
1438
 
 
1439
1165
class ClientHandler(socketserver.BaseRequestHandler, object):
1440
1166
    """A class to handle client connections.
1441
1167
    
1448
1174
                        unicode(self.client_address))
1449
1175
            logger.debug("Pipe FD: %d",
1450
1176
                         self.server.child_pipe.fileno())
1451
 
            
 
1177
 
1452
1178
            session = (gnutls.connection
1453
1179
                       .ClientSession(self.request,
1454
1180
                                      gnutls.connection
1455
1181
                                      .X509Credentials()))
1456
 
            
 
1182
 
1457
1183
            # Note: gnutls.connection.X509Credentials is really a
1458
1184
            # generic GnuTLS certificate credentials object so long as
1459
1185
            # no X.509 keys are added to it.  Therefore, we can use it
1460
1186
            # here despite using OpenPGP certificates.
1461
 
            
 
1187
 
1462
1188
            #priority = ':'.join(("NONE", "+VERS-TLS1.1",
1463
1189
            #                      "+AES-256-CBC", "+SHA1",
1464
1190
            #                      "+COMP-NULL", "+CTYPE-OPENPGP",
1470
1196
            (gnutls.library.functions
1471
1197
             .gnutls_priority_set_direct(session._c_object,
1472
1198
                                         priority, None))
1473
 
            
 
1199
 
1474
1200
            # Start communication using the Mandos protocol
1475
1201
            # Get protocol number
1476
1202
            line = self.request.makefile().readline()
1478
1204
            try:
1479
1205
                if int(line.strip().split()[0]) > 1:
1480
1206
                    raise RuntimeError
1481
 
            except (ValueError, IndexError, RuntimeError) as error:
 
1207
            except (ValueError, IndexError, RuntimeError), error:
1482
1208
                logger.error("Unknown protocol version: %s", error)
1483
1209
                return
1484
 
            
 
1210
 
1485
1211
            # Start GnuTLS connection
1486
1212
            try:
1487
1213
                session.handshake()
1488
 
            except gnutls.errors.GNUTLSError as error:
 
1214
            except gnutls.errors.GNUTLSError, error:
1489
1215
                logger.warning("Handshake failed: %s", error)
1490
1216
                # Do not run session.bye() here: the session is not
1491
1217
                # established.  Just abandon the request.
1492
1218
                return
1493
1219
            logger.debug("Handshake succeeded")
1494
 
            
 
1220
 
1495
1221
            approval_required = False
1496
1222
            try:
1497
1223
                try:
1498
1224
                    fpr = self.fingerprint(self.peer_certificate
1499
1225
                                           (session))
1500
 
                except (TypeError,
1501
 
                        gnutls.errors.GNUTLSError) as error:
 
1226
                except (TypeError, gnutls.errors.GNUTLSError), error:
1502
1227
                    logger.warning("Bad certificate: %s", error)
1503
1228
                    return
1504
1229
                logger.debug("Fingerprint: %s", fpr)
1505
 
                
 
1230
 
1506
1231
                try:
1507
1232
                    client = ProxyClient(child_pipe, fpr,
1508
1233
                                         self.client_address)
1509
1234
                except KeyError:
1510
1235
                    return
1511
1236
                
1512
 
                if self.server.use_dbus:
1513
 
                    # Emit D-Bus signal
1514
 
                    client.NewRequest(str(self.client_address))
1515
 
                
1516
1237
                if client.approval_delay:
1517
1238
                    delay = client.approval_delay
1518
1239
                    client.approvals_pending += 1
1520
1241
                
1521
1242
                while True:
1522
1243
                    if not client.enabled:
1523
 
                        logger.info("Client %s is disabled",
 
1244
                        logger.warning("Client %s is disabled",
1524
1245
                                       client.name)
1525
1246
                        if self.server.use_dbus:
1526
1247
                            # Emit D-Bus signal
1527
 
                            client.Rejected("Disabled")
 
1248
                            client.Rejected("Disabled")                    
1528
1249
                        return
1529
1250
                    
1530
 
                    if client.approved or not client.approval_delay:
 
1251
                    if client._approved or not client.approval_delay:
1531
1252
                        #We are approved or approval is disabled
1532
1253
                        break
1533
 
                    elif client.approved is None:
 
1254
                    elif client._approved is None:
1534
1255
                        logger.info("Client %s needs approval",
1535
1256
                                    client.name)
1536
1257
                        if self.server.use_dbus:
1547
1268
                        return
1548
1269
                    
1549
1270
                    #wait until timeout or approved
 
1271
                    #x = float(client._timedelta_to_milliseconds(delay))
1550
1272
                    time = datetime.datetime.now()
1551
1273
                    client.changedstate.acquire()
1552
 
                    (client.changedstate.wait
1553
 
                     (float(client.timedelta_to_milliseconds(delay)
1554
 
                            / 1000)))
 
1274
                    client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1555
1275
                    client.changedstate.release()
1556
1276
                    time2 = datetime.datetime.now()
1557
1277
                    if (time2 - time) >= delay:
1572
1292
                while sent_size < len(client.secret):
1573
1293
                    try:
1574
1294
                        sent = session.send(client.secret[sent_size:])
1575
 
                    except gnutls.errors.GNUTLSError as error:
 
1295
                    except (gnutls.errors.GNUTLSError), error:
1576
1296
                        logger.warning("gnutls send failed")
1577
1297
                        return
1578
1298
                    logger.debug("Sent: %d, remaining: %d",
1579
1299
                                 sent, len(client.secret)
1580
1300
                                 - (sent_size + sent))
1581
1301
                    sent_size += sent
1582
 
                
 
1302
 
1583
1303
                logger.info("Sending secret to %s", client.name)
1584
 
                # bump the timeout using extended_timeout
1585
 
                client.checked_ok(client.extended_timeout)
 
1304
                # bump the timeout as if seen
 
1305
                client.checked_ok()
1586
1306
                if self.server.use_dbus:
1587
1307
                    # Emit D-Bus signal
1588
1308
                    client.GotSecret()
1592
1312
                    client.approvals_pending -= 1
1593
1313
                try:
1594
1314
                    session.bye()
1595
 
                except gnutls.errors.GNUTLSError as error:
 
1315
                except (gnutls.errors.GNUTLSError), error:
1596
1316
                    logger.warning("GnuTLS bye failed")
1597
1317
    
1598
1318
    @staticmethod
1655
1375
        # Convert the buffer to a Python bytestring
1656
1376
        fpr = ctypes.string_at(buf, buf_len.value)
1657
1377
        # Convert the bytestring to hexadecimal notation
1658
 
        hex_fpr = binascii.hexlify(fpr).upper()
 
1378
        hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
1659
1379
        return hex_fpr
1660
1380
 
1661
1381
 
1667
1387
        except:
1668
1388
            self.handle_error(request, address)
1669
1389
        self.close_request(request)
1670
 
    
 
1390
            
1671
1391
    def process_request(self, request, address):
1672
1392
        """Start a new process to process the request."""
1673
 
        proc = multiprocessing.Process(target = self.sub_process_main,
1674
 
                                       args = (request,
1675
 
                                               address))
1676
 
        proc.start()
1677
 
        return proc
1678
 
 
 
1393
        multiprocessing.Process(target = self.sub_process_main,
 
1394
                                args = (request, address)).start()
1679
1395
 
1680
1396
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1681
1397
    """ adds a pipe to the MixIn """
1685
1401
        This function creates a new pipe in self.pipe
1686
1402
        """
1687
1403
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
1688
 
        
1689
 
        proc = MultiprocessingMixIn.process_request(self, request,
1690
 
                                                    client_address)
 
1404
 
 
1405
        super(MultiprocessingMixInWithPipe,
 
1406
              self).process_request(request, client_address)
1691
1407
        self.child_pipe.close()
1692
 
        self.add_pipe(parent_pipe, proc)
1693
 
    
1694
 
    def add_pipe(self, parent_pipe, proc):
 
1408
        self.add_pipe(parent_pipe)
 
1409
 
 
1410
    def add_pipe(self, parent_pipe):
1695
1411
        """Dummy function; override as necessary"""
1696
1412
        raise NotImplementedError
1697
1413
 
1698
 
 
1699
1414
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1700
1415
                     socketserver.TCPServer, object):
1701
1416
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1727
1442
                                           SO_BINDTODEVICE,
1728
1443
                                           str(self.interface
1729
1444
                                               + '\0'))
1730
 
                except socket.error as error:
 
1445
                except socket.error, error:
1731
1446
                    if error[0] == errno.EPERM:
1732
1447
                        logger.error("No permission to"
1733
1448
                                     " bind to interface %s",
1775
1490
        self.enabled = False
1776
1491
        self.clients = clients
1777
1492
        if self.clients is None:
1778
 
            self.clients = {}
 
1493
            self.clients = set()
1779
1494
        self.use_dbus = use_dbus
1780
1495
        self.gnutls_priority = gnutls_priority
1781
1496
        IPv6_TCPServer.__init__(self, server_address,
1785
1500
    def server_activate(self):
1786
1501
        if self.enabled:
1787
1502
            return socketserver.TCPServer.server_activate(self)
1788
 
    
1789
1503
    def enable(self):
1790
1504
        self.enabled = True
1791
 
    
1792
 
    def add_pipe(self, parent_pipe, proc):
 
1505
    def add_pipe(self, parent_pipe):
1793
1506
        # Call "handle_ipc" for both data and EOF events
1794
1507
        gobject.io_add_watch(parent_pipe.fileno(),
1795
1508
                             gobject.IO_IN | gobject.IO_HUP,
1796
1509
                             functools.partial(self.handle_ipc,
1797
 
                                               parent_pipe =
1798
 
                                               parent_pipe,
1799
 
                                               proc = proc))
1800
 
    
 
1510
                                               parent_pipe = parent_pipe))
 
1511
        
1801
1512
    def handle_ipc(self, source, condition, parent_pipe=None,
1802
 
                   proc = None, client_object=None):
 
1513
                   client_object=None):
1803
1514
        condition_names = {
1804
1515
            gobject.IO_IN: "IN",   # There is data to read.
1805
1516
            gobject.IO_OUT: "OUT", # Data can be written (without
1814
1525
                                       for cond, name in
1815
1526
                                       condition_names.iteritems()
1816
1527
                                       if cond & condition)
1817
 
        # error, or the other end of multiprocessing.Pipe has closed
 
1528
        # error or the other end of multiprocessing.Pipe has closed
1818
1529
        if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1819
 
            # Wait for other process to exit
1820
 
            proc.join()
1821
1530
            return False
1822
1531
        
1823
1532
        # Read a request from the child
1828
1537
            fpr = request[1]
1829
1538
            address = request[2]
1830
1539
            
1831
 
            for c in self.clients.itervalues():
 
1540
            for c in self.clients:
1832
1541
                if c.fingerprint == fpr:
1833
1542
                    client = c
1834
1543
                    break
1835
1544
            else:
1836
 
                logger.info("Client not found for fingerprint: %s, ad"
1837
 
                            "dress: %s", fpr, address)
 
1545
                logger.warning("Client not found for fingerprint: %s, ad"
 
1546
                               "dress: %s", fpr, address)
1838
1547
                if self.use_dbus:
1839
1548
                    # Emit D-Bus signal
1840
 
                    mandos_dbus_service.ClientNotFound(fpr,
1841
 
                                                       address[0])
 
1549
                    mandos_dbus_service.ClientNotFound(fpr, address[0])
1842
1550
                parent_pipe.send(False)
1843
1551
                return False
1844
1552
            
1845
1553
            gobject.io_add_watch(parent_pipe.fileno(),
1846
1554
                                 gobject.IO_IN | gobject.IO_HUP,
1847
1555
                                 functools.partial(self.handle_ipc,
1848
 
                                                   parent_pipe =
1849
 
                                                   parent_pipe,
1850
 
                                                   proc = proc,
1851
 
                                                   client_object =
1852
 
                                                   client))
 
1556
                                                   parent_pipe = parent_pipe,
 
1557
                                                   client_object = client))
1853
1558
            parent_pipe.send(True)
1854
 
            # remove the old hook in favor of the new above hook on
1855
 
            # same fileno
 
1559
            # remove the old hook in favor of the new above hook on same fileno
1856
1560
            return False
1857
1561
        if command == 'funcall':
1858
1562
            funcname = request[1]
1859
1563
            args = request[2]
1860
1564
            kwargs = request[3]
1861
1565
            
1862
 
            parent_pipe.send(('data', getattr(client_object,
1863
 
                                              funcname)(*args,
1864
 
                                                         **kwargs)))
1865
 
        
 
1566
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
 
1567
 
1866
1568
        if command == 'getattr':
1867
1569
            attrname = request[1]
1868
1570
            if callable(client_object.__getattribute__(attrname)):
1869
1571
                parent_pipe.send(('function',))
1870
1572
            else:
1871
 
                parent_pipe.send(('data', client_object
1872
 
                                  .__getattribute__(attrname)))
 
1573
                parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1873
1574
        
1874
1575
        if command == 'setattr':
1875
1576
            attrname = request[1]
1876
1577
            value = request[2]
1877
1578
            setattr(client_object, attrname, value)
1878
 
        
 
1579
 
1879
1580
        return True
1880
1581
 
1881
1582
 
1912
1613
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1913
1614
            else:
1914
1615
                raise ValueError("Unknown suffix %r" % suffix)
1915
 
        except (ValueError, IndexError) as e:
 
1616
        except (ValueError, IndexError), e:
1916
1617
            raise ValueError(*(e.args))
1917
1618
        timevalue += delta
1918
1619
    return timevalue
1919
1620
 
1920
1621
 
 
1622
def if_nametoindex(interface):
 
1623
    """Call the C function if_nametoindex(), or equivalent
 
1624
    
 
1625
    Note: This function cannot accept a unicode string."""
 
1626
    global if_nametoindex
 
1627
    try:
 
1628
        if_nametoindex = (ctypes.cdll.LoadLibrary
 
1629
                          (ctypes.util.find_library("c"))
 
1630
                          .if_nametoindex)
 
1631
    except (OSError, AttributeError):
 
1632
        logger.warning("Doing if_nametoindex the hard way")
 
1633
        def if_nametoindex(interface):
 
1634
            "Get an interface index the hard way, i.e. using fcntl()"
 
1635
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
 
1636
            with contextlib.closing(socket.socket()) as s:
 
1637
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
 
1638
                                    struct.pack(str("16s16x"),
 
1639
                                                interface))
 
1640
            interface_index = struct.unpack(str("I"),
 
1641
                                            ifreq[16:20])[0]
 
1642
            return interface_index
 
1643
    return if_nametoindex(interface)
 
1644
 
 
1645
 
1921
1646
def daemon(nochdir = False, noclose = False):
1922
1647
    """See daemon(3).  Standard BSD Unix function.
1923
1648
    
1948
1673
    ##################################################################
1949
1674
    # Parsing of options, both command line and config file
1950
1675
    
1951
 
    parser = argparse.ArgumentParser()
1952
 
    parser.add_argument("-v", "--version", action="version",
1953
 
                        version = "%%(prog)s %s" % version,
1954
 
                        help="show version number and exit")
1955
 
    parser.add_argument("-i", "--interface", metavar="IF",
1956
 
                        help="Bind to interface IF")
1957
 
    parser.add_argument("-a", "--address",
1958
 
                        help="Address to listen for requests on")
1959
 
    parser.add_argument("-p", "--port", type=int,
1960
 
                        help="Port number to receive requests on")
1961
 
    parser.add_argument("--check", action="store_true",
1962
 
                        help="Run self-test")
1963
 
    parser.add_argument("--debug", action="store_true",
1964
 
                        help="Debug mode; run in foreground and log"
1965
 
                        " to terminal")
1966
 
    parser.add_argument("--debuglevel", metavar="LEVEL",
1967
 
                        help="Debug level for stdout output")
1968
 
    parser.add_argument("--priority", help="GnuTLS"
1969
 
                        " priority string (see GnuTLS documentation)")
1970
 
    parser.add_argument("--servicename",
1971
 
                        metavar="NAME", help="Zeroconf service name")
1972
 
    parser.add_argument("--configdir",
1973
 
                        default="/etc/mandos", metavar="DIR",
1974
 
                        help="Directory to search for configuration"
1975
 
                        " files")
1976
 
    parser.add_argument("--no-dbus", action="store_false",
1977
 
                        dest="use_dbus", help="Do not provide D-Bus"
1978
 
                        " system bus interface")
1979
 
    parser.add_argument("--no-ipv6", action="store_false",
1980
 
                        dest="use_ipv6", help="Do not use IPv6")
1981
 
    parser.add_argument("--no-restore", action="store_false",
1982
 
                        dest="restore", help="Do not restore stored"
1983
 
                        " state")
1984
 
    parser.add_argument("--statedir", metavar="DIR",
1985
 
                        help="Directory to save/restore state in")
1986
 
    
1987
 
    options = parser.parse_args()
 
1676
    parser = optparse.OptionParser(version = "%%prog %s" % version)
 
1677
    parser.add_option("-i", "--interface", type="string",
 
1678
                      metavar="IF", help="Bind to interface IF")
 
1679
    parser.add_option("-a", "--address", type="string",
 
1680
                      help="Address to listen for requests on")
 
1681
    parser.add_option("-p", "--port", type="int",
 
1682
                      help="Port number to receive requests on")
 
1683
    parser.add_option("--check", action="store_true",
 
1684
                      help="Run self-test")
 
1685
    parser.add_option("--debug", action="store_true",
 
1686
                      help="Debug mode; run in foreground and log to"
 
1687
                      " terminal")
 
1688
    parser.add_option("--debuglevel", type="string", metavar="LEVEL",
 
1689
                      help="Debug level for stdout output")
 
1690
    parser.add_option("--priority", type="string", help="GnuTLS"
 
1691
                      " priority string (see GnuTLS documentation)")
 
1692
    parser.add_option("--servicename", type="string",
 
1693
                      metavar="NAME", help="Zeroconf service name")
 
1694
    parser.add_option("--configdir", type="string",
 
1695
                      default="/etc/mandos", metavar="DIR",
 
1696
                      help="Directory to search for configuration"
 
1697
                      " files")
 
1698
    parser.add_option("--no-dbus", action="store_false",
 
1699
                      dest="use_dbus", help="Do not provide D-Bus"
 
1700
                      " system bus interface")
 
1701
    parser.add_option("--no-ipv6", action="store_false",
 
1702
                      dest="use_ipv6", help="Do not use IPv6")
 
1703
    options = parser.parse_args()[0]
1988
1704
    
1989
1705
    if options.check:
1990
1706
        import doctest
2002
1718
                        "use_dbus": "True",
2003
1719
                        "use_ipv6": "True",
2004
1720
                        "debuglevel": "",
2005
 
                        "restore": "True",
2006
 
                        "statedir": "/var/lib/mandos"
2007
1721
                        }
2008
1722
    
2009
1723
    # Parse config file for server-global settings
2026
1740
    # options, if set.
2027
1741
    for option in ("interface", "address", "port", "debug",
2028
1742
                   "priority", "servicename", "configdir",
2029
 
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
2030
 
                   "statedir"):
 
1743
                   "use_dbus", "use_ipv6", "debuglevel"):
2031
1744
        value = getattr(options, option)
2032
1745
        if value is not None:
2033
1746
            server_settings[option] = value
2045
1758
    debuglevel = server_settings["debuglevel"]
2046
1759
    use_dbus = server_settings["use_dbus"]
2047
1760
    use_ipv6 = server_settings["use_ipv6"]
2048
 
    stored_state_path = os.path.join(server_settings["statedir"],
2049
 
                                     stored_state_file)
2050
 
    
2051
 
    if debug:
2052
 
        initlogger(logging.DEBUG)
2053
 
    else:
2054
 
        if not debuglevel:
2055
 
            initlogger()
2056
 
        else:
2057
 
            level = getattr(logging, debuglevel.upper())
2058
 
            initlogger(level)
2059
 
    
 
1761
 
2060
1762
    if server_settings["servicename"] != "Mandos":
2061
1763
        syslogger.setFormatter(logging.Formatter
2062
1764
                               ('Mandos (%s) [%%(process)d]:'
2064
1766
                                % server_settings["servicename"]))
2065
1767
    
2066
1768
    # Parse config file with clients
2067
 
    client_defaults = { "timeout": "5m",
2068
 
                        "extended_timeout": "15m",
2069
 
                        "interval": "2m",
 
1769
    client_defaults = { "timeout": "1h",
 
1770
                        "interval": "5m",
2070
1771
                        "checker": "fping -q -- %%(host)s",
2071
1772
                        "host": "",
2072
1773
                        "approval_delay": "0s",
2112
1813
    try:
2113
1814
        os.setgid(gid)
2114
1815
        os.setuid(uid)
2115
 
    except OSError as error:
 
1816
    except OSError, error:
2116
1817
        if error[0] != errno.EPERM:
2117
1818
            raise error
2118
1819
    
 
1820
    if not debug and not debuglevel:
 
1821
        syslogger.setLevel(logging.WARNING)
 
1822
        console.setLevel(logging.WARNING)
 
1823
    if debuglevel:
 
1824
        level = getattr(logging, debuglevel.upper())
 
1825
        syslogger.setLevel(level)
 
1826
        console.setLevel(level)
 
1827
 
2119
1828
    if debug:
2120
1829
        # Enable all possible GnuTLS debugging
2121
1830
        
2152
1861
    # End of Avahi example code
2153
1862
    if use_dbus:
2154
1863
        try:
2155
 
            bus_name = dbus.service.BusName("se.recompile.Mandos",
 
1864
            bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
2156
1865
                                            bus, do_not_queue=True)
2157
 
            old_bus_name = (dbus.service.BusName
2158
 
                            ("se.bsnet.fukt.Mandos", bus,
2159
 
                             do_not_queue=True))
2160
 
        except dbus.exceptions.NameExistsException as e:
 
1866
        except dbus.exceptions.NameExistsException, e:
2161
1867
            logger.error(unicode(e) + ", disabling D-Bus")
2162
1868
            use_dbus = False
2163
1869
            server_settings["use_dbus"] = False
2164
1870
            tcp_server.use_dbus = False
2165
1871
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2166
 
    service = AvahiServiceToSyslog(name =
2167
 
                                   server_settings["servicename"],
2168
 
                                   servicetype = "_mandos._tcp",
2169
 
                                   protocol = protocol, bus = bus)
 
1872
    service = AvahiService(name = server_settings["servicename"],
 
1873
                           servicetype = "_mandos._tcp",
 
1874
                           protocol = protocol, bus = bus)
2170
1875
    if server_settings["interface"]:
2171
1876
        service.interface = (if_nametoindex
2172
1877
                             (str(server_settings["interface"])))
2176
1881
    
2177
1882
    client_class = Client
2178
1883
    if use_dbus:
2179
 
        client_class = functools.partial(ClientDBusTransitional,
2180
 
                                         bus = bus)
2181
 
    
2182
 
    special_settings = {
2183
 
        # Some settings need to be accessd by special methods;
2184
 
        # booleans need .getboolean(), etc.  Here is a list of them:
2185
 
        "approved_by_default":
2186
 
            lambda section:
2187
 
            client_config.getboolean(section, "approved_by_default"),
2188
 
        "enabled":
2189
 
            lambda section:
2190
 
            client_config.getboolean(section, "enabled"),
2191
 
        }
2192
 
    # Construct a new dict of client settings of this form:
2193
 
    # { client_name: {setting_name: value, ...}, ...}
2194
 
    # with exceptions for any special settings as defined above
2195
 
    client_settings = dict((clientname,
2196
 
                           dict((setting,
2197
 
                                 (value
2198
 
                                  if setting not in special_settings
2199
 
                                  else special_settings[setting]
2200
 
                                  (clientname)))
2201
 
                                for setting, value in
2202
 
                                client_config.items(clientname)))
2203
 
                          for clientname in client_config.sections())
2204
 
    
2205
 
    old_client_settings = {}
2206
 
    clients_data = []
2207
 
    
2208
 
    # Get client data and settings from last running state.
2209
 
    if server_settings["restore"]:
2210
 
        try:
2211
 
            with open(stored_state_path, "rb") as stored_state:
2212
 
                clients_data, old_client_settings = (pickle.load
2213
 
                                                     (stored_state))
2214
 
            os.remove(stored_state_path)
2215
 
        except IOError as e:
2216
 
            logger.warning("Could not load persistent state: {0}"
2217
 
                           .format(e))
2218
 
            if e.errno != errno.ENOENT:
2219
 
                raise
2220
 
    
2221
 
    with Crypto() as crypt:
2222
 
        for client in clients_data:
2223
 
            client_name = client["name"]
2224
 
            
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
 
                        setattr(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 sucessful.  Clients
2244
 
            # whose checker failed before we stored its state is
2245
 
            # assumed to have failed all checkers during downtime.
2246
 
            if client["enabled"] and client["last_checked_ok"]:
2247
 
                if ((datetime.datetime.utcnow()
2248
 
                     - client["last_checked_ok"])
2249
 
                    > client["interval"]):
2250
 
                    if client["last_checker_status"] != 0:
2251
 
                        client["enabled"] = False
2252
 
                    else:
2253
 
                        client["expires"] = (datetime.datetime
2254
 
                                             .utcnow()
2255
 
                                             + client["timeout"])
2256
 
            
2257
 
            client["changedstate"] = (multiprocessing_manager
2258
 
                                      .Condition
2259
 
                                      (multiprocessing_manager
2260
 
                                       .Lock()))
2261
 
            if use_dbus:
2262
 
                new_client = (ClientDBusTransitional.__new__
2263
 
                              (ClientDBusTransitional))
2264
 
                tcp_server.clients[client_name] = new_client
2265
 
                new_client.bus = bus
2266
 
                for name, value in client.iteritems():
2267
 
                    setattr(new_client, name, value)
2268
 
                client_object_name = unicode(client_name).translate(
2269
 
                    {ord("."): ord("_"),
2270
 
                     ord("-"): ord("_")})
2271
 
                new_client.dbus_object_path = (dbus.ObjectPath
2272
 
                                               ("/clients/"
2273
 
                                                + client_object_name))
2274
 
                DBusObjectWithProperties.__init__(new_client,
2275
 
                                                  new_client.bus,
2276
 
                                                  new_client
2277
 
                                                  .dbus_object_path)
2278
 
            else:
2279
 
                tcp_server.clients[client_name] = (Client.__new__
2280
 
                                                   (Client))
2281
 
                for name, value in client.iteritems():
2282
 
                    setattr(tcp_server.clients[client_name],
2283
 
                            name, value)
2284
 
            
 
1884
        client_class = functools.partial(ClientDBus, bus = bus)
 
1885
    def client_config_items(config, section):
 
1886
        special_settings = {
 
1887
            "approved_by_default":
 
1888
                lambda: config.getboolean(section,
 
1889
                                          "approved_by_default"),
 
1890
            }
 
1891
        for name, value in config.items(section):
2285
1892
            try:
2286
 
                tcp_server.clients[client_name].secret = (
2287
 
                    crypt.decrypt(tcp_server.clients[client_name]
2288
 
                                  .encrypted_secret,
2289
 
                                  client_settings[client_name]
2290
 
                                  ["secret"]))
2291
 
            except CryptoError:
2292
 
                # If decryption fails, we use secret from new settings
2293
 
                tcp_server.clients[client_name].secret = (
2294
 
                    client_settings[client_name]["secret"])
2295
 
    
2296
 
    # Create/remove clients based on new changes made to config
2297
 
    for clientname in set(old_client_settings) - set(client_settings):
2298
 
        del tcp_server.clients[clientname]
2299
 
    for clientname in set(client_settings) - set(old_client_settings):
2300
 
        tcp_server.clients[clientname] = (client_class(name
2301
 
                                                       = clientname,
2302
 
                                                       config =
2303
 
                                                       client_settings
2304
 
                                                       [clientname]))
2305
 
    
 
1893
                yield (name, special_settings[name]())
 
1894
            except KeyError:
 
1895
                yield (name, value)
 
1896
    
 
1897
    tcp_server.clients.update(set(
 
1898
            client_class(name = section,
 
1899
                         config= dict(client_config_items(
 
1900
                        client_config, section)))
 
1901
            for section in client_config.sections()))
2306
1902
    if not tcp_server.clients:
2307
1903
        logger.warning("No clients defined")
2308
1904
        
2321
1917
        del pidfilename
2322
1918
        
2323
1919
        signal.signal(signal.SIGINT, signal.SIG_IGN)
2324
 
    
 
1920
 
2325
1921
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2326
1922
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2327
1923
    
2330
1926
            """A D-Bus proxy object"""
2331
1927
            def __init__(self):
2332
1928
                dbus.service.Object.__init__(self, bus, "/")
2333
 
            _interface = "se.recompile.Mandos"
 
1929
            _interface = "se.bsnet.fukt.Mandos"
2334
1930
            
2335
1931
            @dbus.service.signal(_interface, signature="o")
2336
1932
            def ClientAdded(self, objpath):
2351
1947
            def GetAllClients(self):
2352
1948
                "D-Bus method"
2353
1949
                return dbus.Array(c.dbus_object_path
2354
 
                                  for c in
2355
 
                                  tcp_server.clients.itervalues())
 
1950
                                  for c in tcp_server.clients)
2356
1951
            
2357
1952
            @dbus.service.method(_interface,
2358
1953
                                 out_signature="a{oa{sv}}")
2360
1955
                "D-Bus method"
2361
1956
                return dbus.Dictionary(
2362
1957
                    ((c.dbus_object_path, c.GetAll(""))
2363
 
                     for c in tcp_server.clients.itervalues()),
 
1958
                     for c in tcp_server.clients),
2364
1959
                    signature="oa{sv}")
2365
1960
            
2366
1961
            @dbus.service.method(_interface, in_signature="o")
2367
1962
            def RemoveClient(self, object_path):
2368
1963
                "D-Bus method"
2369
 
                for c in tcp_server.clients.itervalues():
 
1964
                for c in tcp_server.clients:
2370
1965
                    if c.dbus_object_path == object_path:
2371
 
                        del tcp_server.clients[c.name]
 
1966
                        tcp_server.clients.remove(c)
2372
1967
                        c.remove_from_connection()
2373
1968
                        # Don't signal anything except ClientRemoved
2374
1969
                        c.disable(quiet=True)
2379
1974
            
2380
1975
            del _interface
2381
1976
        
2382
 
        class MandosDBusServiceTransitional(MandosDBusService):
2383
 
            __metaclass__ = AlternateDBusNamesMetaclass
2384
 
        mandos_dbus_service = MandosDBusServiceTransitional()
 
1977
        mandos_dbus_service = MandosDBusService()
2385
1978
    
2386
1979
    def cleanup():
2387
1980
        "Cleanup function; run on exit"
2388
1981
        service.cleanup()
2389
1982
        
2390
 
        multiprocessing.active_children()
2391
 
        if not (tcp_server.clients or client_settings):
2392
 
            return
2393
 
        
2394
 
        # Store client before exiting. Secrets are encrypted with key
2395
 
        # based on what config file has. If config file is
2396
 
        # removed/edited, old secret will thus be unrecovable.
2397
 
        clients = []
2398
 
        with Crypto() as crypt:
2399
 
            for client in tcp_server.clients.itervalues():
2400
 
                key = client_settings[client.name]["secret"]
2401
 
                client.encrypted_secret = crypt.encrypt(client.secret,
2402
 
                                                        key)
2403
 
                client_dict = {}
2404
 
                
2405
 
                # A list of attributes that will not be stored when
2406
 
                # shutting down.
2407
 
                exclude = set(("bus", "changedstate", "secret"))
2408
 
                for name, typ in (inspect.getmembers
2409
 
                                  (dbus.service.Object)):
2410
 
                    exclude.add(name)
2411
 
                
2412
 
                client_dict["encrypted_secret"] = (client
2413
 
                                                   .encrypted_secret)
2414
 
                for attr in client.client_structure:
2415
 
                    if attr not in exclude:
2416
 
                        client_dict[attr] = getattr(client, attr)
2417
 
                
2418
 
                clients.append(client_dict)
2419
 
                del client_settings[client.name]["secret"]
2420
 
        
2421
 
        try:
2422
 
            with os.fdopen(os.open(stored_state_path,
2423
 
                                   os.O_CREAT|os.O_WRONLY|os.O_TRUNC,
2424
 
                                   0600), "wb") as stored_state:
2425
 
                pickle.dump((clients, client_settings), stored_state)
2426
 
        except (IOError, OSError) as e:
2427
 
            logger.warning("Could not save persistent state: {0}"
2428
 
                           .format(e))
2429
 
            if e.errno not in (errno.ENOENT, errno.EACCES):
2430
 
                raise
2431
 
        
2432
 
        # Delete all clients, and settings from config
2433
1983
        while tcp_server.clients:
2434
 
            name, client = tcp_server.clients.popitem()
 
1984
            client = tcp_server.clients.pop()
2435
1985
            if use_dbus:
2436
1986
                client.remove_from_connection()
 
1987
            client.disable_hook = None
2437
1988
            # Don't signal anything except ClientRemoved
2438
1989
            client.disable(quiet=True)
2439
1990
            if use_dbus:
2440
1991
                # Emit D-Bus signal
2441
 
                mandos_dbus_service.ClientRemoved(client
2442
 
                                                  .dbus_object_path,
 
1992
                mandos_dbus_service.ClientRemoved(client.dbus_object_path,
2443
1993
                                                  client.name)
2444
 
        client_settings.clear()
2445
1994
    
2446
1995
    atexit.register(cleanup)
2447
1996
    
2448
 
    for client in tcp_server.clients.itervalues():
 
1997
    for client in tcp_server.clients:
2449
1998
        if use_dbus:
2450
1999
            # Emit D-Bus signal
2451
2000
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
2452
 
        # Need to initiate checking of clients
2453
 
        if client.enabled:
2454
 
            client.init_checker()
 
2001
        client.enable()
2455
2002
    
2456
2003
    tcp_server.enable()
2457
2004
    tcp_server.server_activate()
2472
2019
        # From the Avahi example code
2473
2020
        try:
2474
2021
            service.activate()
2475
 
        except dbus.exceptions.DBusException as error:
 
2022
        except dbus.exceptions.DBusException, error:
2476
2023
            logger.critical("DBusException: %s", error)
2477
2024
            cleanup()
2478
2025
            sys.exit(1)
2485
2032
        
2486
2033
        logger.debug("Starting main loop")
2487
2034
        main_loop.run()
2488
 
    except AvahiError as error:
 
2035
    except AvahiError, error:
2489
2036
        logger.critical("AvahiError: %s", error)
2490
2037
        cleanup()
2491
2038
        sys.exit(1)
2497
2044
    # Must run before the D-Bus bus name gets deregistered
2498
2045
    cleanup()
2499
2046
 
2500
 
 
2501
2047
if __name__ == '__main__':
2502
2048
    main()