/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2011-11-26 20:59:56 UTC
  • mto: (518.1.8 mandos-persistent)
  • mto: This revision was merged to the branch mainline in revision 524.
  • Revision ID: teddy@recompile.se-20111126205956-vft6g0z2i6my0165
Use GPG to encrypt instead of AES.

* Makefile (run-server): Use "--no-restore" option.
* debian/control (mandos/Depends): Added "python-gnupginterface".
* mandos: (CryptoError, Crypto): New; uses GPG.
  (Client.encrypt_secret, Client.decrypt_secret): Removed.
  (ClientHandler.fingerprint): Use binascii.hexlify().
  (main): Use Crypto class to decrypt.
  (main/cleanup): Use Crypto class to encrypt.  Handle EACCES.

Show diffs side-by-side

added added

removed removed

Lines of Context:
11
11
# "AvahiService" class, and some lines in "main".
12
12
13
13
# Everything else is
14
 
# Copyright © 2008,2009 Teddy Hogeborn
15
 
# Copyright © 2008,2009 Björn Påhlsson
 
14
# Copyright © 2008-2011 Teddy Hogeborn
 
15
# Copyright © 2008-2011 Björn Påhlsson
16
16
17
17
# This program is free software: you can redistribute it and/or modify
18
18
# it under the terms of the GNU General Public License as published by
28
28
# along with this program.  If not, see
29
29
# <http://www.gnu.org/licenses/>.
30
30
31
 
# Contact the authors at <mandos@fukt.bsnet.se>.
 
31
# Contact the authors at <mandos@recompile.se>.
32
32
33
33
 
34
 
from __future__ import division, with_statement, absolute_import
 
34
from __future__ import (division, absolute_import, print_function,
 
35
                        unicode_literals)
35
36
 
36
37
import SocketServer as socketserver
37
38
import socket
38
 
import optparse
 
39
import argparse
39
40
import datetime
40
41
import errno
41
42
import gnutls.crypto
55
56
import logging
56
57
import logging.handlers
57
58
import pwd
58
 
from contextlib import closing
 
59
import contextlib
59
60
import struct
60
61
import fcntl
61
62
import functools
 
63
import cPickle as pickle
 
64
import multiprocessing
 
65
import types
 
66
import binascii
 
67
import tempfile
62
68
 
63
69
import dbus
64
70
import dbus.service
69
75
import ctypes.util
70
76
import xml.dom.minidom
71
77
import inspect
 
78
import GnuPGInterface
72
79
 
73
80
try:
74
81
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
79
86
        SO_BINDTODEVICE = None
80
87
 
81
88
 
82
 
version = "1.0.12"
 
89
version = "1.4.1"
 
90
stored_state_path = "/var/lib/mandos/clients.pickle"
83
91
 
84
 
logger = logging.Logger(u'mandos')
 
92
logger = logging.getLogger()
85
93
syslogger = (logging.handlers.SysLogHandler
86
94
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
87
 
              address = "/dev/log"))
88
 
syslogger.setFormatter(logging.Formatter
89
 
                       (u'Mandos [%(process)d]: %(levelname)s:'
90
 
                        u' %(message)s'))
91
 
logger.addHandler(syslogger)
92
 
 
93
 
console = logging.StreamHandler()
94
 
console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:'
95
 
                                       u' %(levelname)s:'
96
 
                                       u' %(message)s'))
97
 
logger.addHandler(console)
 
95
              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
 
98
209
 
99
210
class AvahiError(Exception):
100
211
    def __init__(self, value, *args, **kwargs):
116
227
    Attributes:
117
228
    interface: integer; avahi.IF_UNSPEC or an interface index.
118
229
               Used to optionally bind to the specified interface.
119
 
    name: string; Example: u'Mandos'
120
 
    type: string; Example: u'_mandos._tcp'.
 
230
    name: string; Example: 'Mandos'
 
231
    type: string; Example: '_mandos._tcp'.
121
232
                  See <http://www.dns-sd.org/ServiceTypes.html>
122
233
    port: integer; what port to announce
123
234
    TXT: list of strings; TXT record for the service
132
243
    """
133
244
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
134
245
                 servicetype = None, port = None, TXT = None,
135
 
                 domain = u"", host = u"", max_renames = 32768,
 
246
                 domain = "", host = "", max_renames = 32768,
136
247
                 protocol = avahi.PROTO_UNSPEC, bus = None):
137
248
        self.interface = interface
138
249
        self.name = name
147
258
        self.group = None       # our entry group
148
259
        self.server = None
149
260
        self.bus = bus
 
261
        self.entry_group_state_changed_match = None
150
262
    def rename(self):
151
263
        """Derived from the Avahi example code"""
152
264
        if self.rename_count >= self.max_renames:
153
 
            logger.critical(u"No suitable Zeroconf service name found"
154
 
                            u" after %i retries, exiting.",
 
265
            logger.critical("No suitable Zeroconf service name found"
 
266
                            " after %i retries, exiting.",
155
267
                            self.rename_count)
156
 
            raise AvahiServiceError(u"Too many renames")
157
 
        self.name = self.server.GetAlternativeServiceName(self.name)
158
 
        logger.info(u"Changing Zeroconf service name to %r ...",
159
 
                    unicode(self.name))
160
 
        syslogger.setFormatter(logging.Formatter
161
 
                               (u'Mandos (%s) [%%(process)d]:'
162
 
                                u' %%(levelname)s: %%(message)s'
163
 
                                % self.name))
 
268
            raise AvahiServiceError("Too many renames")
 
269
        self.name = unicode(self.server
 
270
                            .GetAlternativeServiceName(self.name))
 
271
        logger.info("Changing Zeroconf service name to %r ...",
 
272
                    self.name)
164
273
        self.remove()
165
 
        self.add()
 
274
        try:
 
275
            self.add()
 
276
        except dbus.exceptions.DBusException as error:
 
277
            logger.critical("DBusException: %s", error)
 
278
            self.cleanup()
 
279
            os._exit(1)
166
280
        self.rename_count += 1
167
281
    def remove(self):
168
282
        """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
169
286
        if self.group is not None:
170
287
            self.group.Reset()
171
288
    def add(self):
172
289
        """Derived from the Avahi example code"""
 
290
        self.remove()
173
291
        if self.group is None:
174
292
            self.group = dbus.Interface(
175
293
                self.bus.get_object(avahi.DBUS_NAME,
176
294
                                    self.server.EntryGroupNew()),
177
295
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
178
 
            self.group.connect_to_signal('StateChanged',
179
 
                                         self
180
 
                                         .entry_group_state_changed)
181
 
        logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
 
296
        self.entry_group_state_changed_match = (
 
297
            self.group.connect_to_signal(
 
298
                'StateChanged', self.entry_group_state_changed))
 
299
        logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
182
300
                     self.name, self.type)
183
301
        self.group.AddService(
184
302
            self.interface,
191
309
        self.group.Commit()
192
310
    def entry_group_state_changed(self, state, error):
193
311
        """Derived from the Avahi example code"""
194
 
        logger.debug(u"Avahi state change: %i", state)
 
312
        logger.debug("Avahi entry group state change: %i", state)
195
313
        
196
314
        if state == avahi.ENTRY_GROUP_ESTABLISHED:
197
 
            logger.debug(u"Zeroconf service established.")
 
315
            logger.debug("Zeroconf service established.")
198
316
        elif state == avahi.ENTRY_GROUP_COLLISION:
199
 
            logger.warning(u"Zeroconf service name collision.")
 
317
            logger.info("Zeroconf service name collision.")
200
318
            self.rename()
201
319
        elif state == avahi.ENTRY_GROUP_FAILURE:
202
 
            logger.critical(u"Avahi: Error in group state changed %s",
 
320
            logger.critical("Avahi: Error in group state changed %s",
203
321
                            unicode(error))
204
 
            raise AvahiGroupError(u"State changed: %s"
 
322
            raise AvahiGroupError("State changed: %s"
205
323
                                  % unicode(error))
206
324
    def cleanup(self):
207
325
        """Derived from the Avahi example code"""
208
326
        if self.group is not None:
209
 
            self.group.Free()
 
327
            try:
 
328
                self.group.Free()
 
329
            except (dbus.exceptions.UnknownMethodException,
 
330
                    dbus.exceptions.DBusException):
 
331
                pass
210
332
            self.group = None
211
 
    def server_state_changed(self, state):
 
333
        self.remove()
 
334
    def server_state_changed(self, state, error=None):
212
335
        """Derived from the Avahi example code"""
213
 
        if state == avahi.SERVER_COLLISION:
214
 
            logger.error(u"Zeroconf server name collision")
215
 
            self.remove()
 
336
        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()
216
351
        elif state == avahi.SERVER_RUNNING:
217
352
            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)
218
358
    def activate(self):
219
359
        """Derived from the Avahi example code"""
220
360
        if self.server is None:
221
361
            self.server = dbus.Interface(
222
362
                self.bus.get_object(avahi.DBUS_NAME,
223
 
                                    avahi.DBUS_PATH_SERVER),
 
363
                                    avahi.DBUS_PATH_SERVER,
 
364
                                    follow_name_owner_changes=True),
224
365
                avahi.DBUS_INTERFACE_SERVER)
225
 
        self.server.connect_to_signal(u"StateChanged",
 
366
        self.server.connect_to_signal("StateChanged",
226
367
                                 self.server_state_changed)
227
368
        self.server_state_changed(self.server.GetState())
228
369
 
 
370
class AvahiServiceToSyslog(AvahiService):
 
371
    def rename(self):
 
372
        """Add the new name to the syslog messages"""
 
373
        ret = AvahiService.rename(self)
 
374
        syslogger.setFormatter(logging.Formatter
 
375
                               ('Mandos (%s) [%%(process)d]:'
 
376
                                ' %%(levelname)s: %%(message)s'
 
377
                                % self.name))
 
378
        return ret
229
379
 
 
380
def _timedelta_to_milliseconds(td):
 
381
    "Convert a datetime.timedelta() to milliseconds"
 
382
    return ((td.days * 24 * 60 * 60 * 1000)
 
383
            + (td.seconds * 1000)
 
384
            + (td.microseconds // 1000))
 
385
        
230
386
class Client(object):
231
387
    """A representation of a client host served by this server.
232
388
    
233
389
    Attributes:
234
 
    name:       string; from the config file, used in log messages and
235
 
                        D-Bus identifiers
236
 
    fingerprint: string (40 or 32 hexadecimal digits); used to
237
 
                 uniquely identify the client
238
 
    secret:     bytestring; sent verbatim (over TLS) to client
239
 
    host:       string; available for use by the checker command
240
 
    created:    datetime.datetime(); (UTC) object creation
241
 
    last_enabled: datetime.datetime(); (UTC)
242
 
    enabled:    bool()
243
 
    last_checked_ok: datetime.datetime(); (UTC) or None
244
 
    timeout:    datetime.timedelta(); How long from last_checked_ok
245
 
                                      until this client is invalid
246
 
    interval:   datetime.timedelta(); How often to start a new checker
247
 
    disable_hook:  If set, called by disable() as disable_hook(self)
 
390
    _approved:   bool(); 'None' if not yet approved/disapproved
 
391
    approval_delay: datetime.timedelta(); Time to wait for approval
 
392
    approval_duration: datetime.timedelta(); Duration of one approval
248
393
    checker:    subprocess.Popen(); a running checker process used
249
394
                                    to see if the client lives.
250
395
                                    'None' if no process is running.
251
 
    checker_initiator_tag: a gobject event source tag, or None
252
 
    disable_initiator_tag: - '' -
253
 
    checker_callback_tag:  - '' -
254
 
    checker_command: string; External command which is run to check if
255
 
                     client lives.  %() expansions are done at
 
396
    checker_callback_tag: a gobject event source tag, or None
 
397
    checker_command: string; External command which is run to check
 
398
                     if client lives.  %() expansions are done at
256
399
                     runtime with vars(self) as dict, so that for
257
400
                     instance %(name)s can be used in the command.
 
401
    checker_initiator_tag: a gobject event source tag, or None
 
402
    created:    datetime.datetime(); (UTC) object creation
 
403
    client_structure: Object describing what attributes a client has
 
404
                      and is used for storing the client at exit
258
405
    current_checker_command: string; current running checker_command
 
406
    disable_initiator_tag: a gobject event source tag, or None
 
407
    enabled:    bool()
 
408
    fingerprint: string (40 or 32 hexadecimal digits); used to
 
409
                 uniquely identify the client
 
410
    host:       string; available for use by the checker command
 
411
    interval:   datetime.timedelta(); How often to start a new checker
 
412
    last_approval_request: datetime.datetime(); (UTC) or None
 
413
    last_checked_ok: datetime.datetime(); (UTC) or None
 
414
 
 
415
    last_checker_status: integer between 0 and 255 reflecting exit
 
416
                         status of last checker. -1 reflects crashed
 
417
                         checker, or None.
 
418
    last_enabled: datetime.datetime(); (UTC)
 
419
    name:       string; from the config file, used in log messages and
 
420
                        D-Bus identifiers
 
421
    secret:     bytestring; sent verbatim (over TLS) to client
 
422
    timeout:    datetime.timedelta(); How long from last_checked_ok
 
423
                                      until this client is disabled
 
424
    extended_timeout:   extra long timeout when password has been sent
 
425
    runtime_expansions: Allowed attributes for runtime expansion.
 
426
    expires:    datetime.datetime(); time (UTC) when a client will be
 
427
                disabled, or None
259
428
    """
260
429
    
261
 
    @staticmethod
262
 
    def _timedelta_to_milliseconds(td):
263
 
        "Convert a datetime.timedelta() to milliseconds"
264
 
        return ((td.days * 24 * 60 * 60 * 1000)
265
 
                + (td.seconds * 1000)
266
 
                + (td.microseconds // 1000))
 
430
    runtime_expansions = ("approval_delay", "approval_duration",
 
431
                          "created", "enabled", "fingerprint",
 
432
                          "host", "interval", "last_checked_ok",
 
433
                          "last_enabled", "name", "timeout")
267
434
    
268
435
    def timeout_milliseconds(self):
269
436
        "Return the 'timeout' attribute in milliseconds"
270
 
        return self._timedelta_to_milliseconds(self.timeout)
 
437
        return _timedelta_to_milliseconds(self.timeout)
 
438
    
 
439
    def extended_timeout_milliseconds(self):
 
440
        "Return the 'extended_timeout' attribute in milliseconds"
 
441
        return _timedelta_to_milliseconds(self.extended_timeout)
271
442
    
272
443
    def interval_milliseconds(self):
273
444
        "Return the 'interval' attribute in milliseconds"
274
 
        return self._timedelta_to_milliseconds(self.interval)
275
 
    
276
 
    def __init__(self, name = None, disable_hook=None, config=None):
 
445
        return _timedelta_to_milliseconds(self.interval)
 
446
    
 
447
    def approval_delay_milliseconds(self):
 
448
        return _timedelta_to_milliseconds(self.approval_delay)
 
449
    
 
450
    def __init__(self, name = None, config=None):
277
451
        """Note: the 'checker' key in 'config' sets the
278
452
        'checker_command' attribute and *not* the 'checker'
279
453
        attribute."""
280
454
        self.name = name
281
455
        if config is None:
282
456
            config = {}
283
 
        logger.debug(u"Creating client %r", self.name)
 
457
        logger.debug("Creating client %r", self.name)
284
458
        # Uppercase and remove spaces from fingerprint for later
285
459
        # comparison purposes with return value from the fingerprint()
286
460
        # function
287
 
        self.fingerprint = (config[u"fingerprint"].upper()
288
 
                            .replace(u" ", u""))
289
 
        logger.debug(u"  Fingerprint: %s", self.fingerprint)
290
 
        if u"secret" in config:
291
 
            self.secret = config[u"secret"].decode(u"base64")
292
 
        elif u"secfile" in config:
293
 
            with closing(open(os.path.expanduser
294
 
                              (os.path.expandvars
295
 
                               (config[u"secfile"])),
296
 
                              "rb")) as secfile:
 
461
        self.fingerprint = (config["fingerprint"].upper()
 
462
                            .replace(" ", ""))
 
463
        logger.debug("  Fingerprint: %s", self.fingerprint)
 
464
        if "secret" in config:
 
465
            self.secret = config["secret"].decode("base64")
 
466
        elif "secfile" in config:
 
467
            with open(os.path.expanduser(os.path.expandvars
 
468
                                         (config["secfile"])),
 
469
                      "rb") as secfile:
297
470
                self.secret = secfile.read()
298
471
        else:
299
 
            raise TypeError(u"No secret or secfile for client %s"
 
472
            raise TypeError("No secret or secfile for client %s"
300
473
                            % self.name)
301
 
        self.host = config.get(u"host", u"")
 
474
        self.host = config.get("host", "")
302
475
        self.created = datetime.datetime.utcnow()
303
 
        self.enabled = False
304
 
        self.last_enabled = None
 
476
        self.enabled = True
 
477
        self.last_approval_request = None
 
478
        self.last_enabled = datetime.datetime.utcnow()
305
479
        self.last_checked_ok = None
306
 
        self.timeout = string_to_delta(config[u"timeout"])
307
 
        self.interval = string_to_delta(config[u"interval"])
308
 
        self.disable_hook = disable_hook
 
480
        self.last_checker_status = None
 
481
        self.timeout = string_to_delta(config["timeout"])
 
482
        self.extended_timeout = string_to_delta(config
 
483
                                                ["extended_timeout"])
 
484
        self.interval = string_to_delta(config["interval"])
309
485
        self.checker = None
310
486
        self.checker_initiator_tag = None
311
487
        self.disable_initiator_tag = None
 
488
        self.expires = datetime.datetime.utcnow() + self.timeout
312
489
        self.checker_callback_tag = None
313
 
        self.checker_command = config[u"checker"]
 
490
        self.checker_command = config["checker"]
314
491
        self.current_checker_command = None
315
 
        self.last_connect = None
 
492
        self._approved = None
 
493
        self.approved_by_default = config.get("approved_by_default",
 
494
                                              True)
 
495
        self.approvals_pending = 0
 
496
        self.approval_delay = string_to_delta(
 
497
            config["approval_delay"])
 
498
        self.approval_duration = string_to_delta(
 
499
            config["approval_duration"])
 
500
        self.changedstate = (multiprocessing_manager
 
501
                             .Condition(multiprocessing_manager
 
502
                                        .Lock()))
 
503
        self.client_structure = [attr for attr in
 
504
                                 self.__dict__.iterkeys()
 
505
                                 if not attr.startswith("_")]
 
506
        self.client_structure.append("client_structure")
 
507
        
 
508
        for name, t in inspect.getmembers(type(self),
 
509
                                          lambda obj:
 
510
                                              isinstance(obj,
 
511
                                                         property)):
 
512
            if not name.startswith("_"):
 
513
                self.client_structure.append(name)
 
514
    
 
515
    # Send notice to process children that client state has changed
 
516
    def send_changedstate(self):
 
517
        with self.changedstate:
 
518
            self.changedstate.notify_all()
316
519
    
317
520
    def enable(self):
318
521
        """Start this client's checker and timeout hooks"""
319
 
        if getattr(self, u"enabled", False):
 
522
        if getattr(self, "enabled", False):
320
523
            # Already enabled
321
524
            return
 
525
        self.send_changedstate()
 
526
        self.expires = datetime.datetime.utcnow() + self.timeout
 
527
        self.enabled = True
322
528
        self.last_enabled = datetime.datetime.utcnow()
 
529
        self.init_checker()
 
530
    
 
531
    def disable(self, quiet=True):
 
532
        """Disable this client."""
 
533
        if not getattr(self, "enabled", False):
 
534
            return False
 
535
        if not quiet:
 
536
            self.send_changedstate()
 
537
        if not quiet:
 
538
            logger.info("Disabling client %s", self.name)
 
539
        if getattr(self, "disable_initiator_tag", False):
 
540
            gobject.source_remove(self.disable_initiator_tag)
 
541
            self.disable_initiator_tag = None
 
542
        self.expires = None
 
543
        if getattr(self, "checker_initiator_tag", False):
 
544
            gobject.source_remove(self.checker_initiator_tag)
 
545
            self.checker_initiator_tag = None
 
546
        self.stop_checker()
 
547
        self.enabled = False
 
548
        # Do not run this again if called by a gobject.timeout_add
 
549
        return False
 
550
    
 
551
    def __del__(self):
 
552
        self.disable()
 
553
    
 
554
    def init_checker(self):
323
555
        # Schedule a new checker to be started an 'interval' from now,
324
556
        # and every interval from then on.
325
557
        self.checker_initiator_tag = (gobject.timeout_add
326
558
                                      (self.interval_milliseconds(),
327
559
                                       self.start_checker))
328
 
        # Also start a new checker *right now*.
329
 
        self.start_checker()
330
560
        # Schedule a disable() when 'timeout' has passed
331
561
        self.disable_initiator_tag = (gobject.timeout_add
332
562
                                   (self.timeout_milliseconds(),
333
563
                                    self.disable))
334
 
        self.enabled = True
335
 
    
336
 
    def disable(self):
337
 
        """Disable this client."""
338
 
        if not getattr(self, "enabled", False):
339
 
            return False
340
 
        logger.info(u"Disabling client %s", self.name)
341
 
        if getattr(self, u"disable_initiator_tag", False):
342
 
            gobject.source_remove(self.disable_initiator_tag)
343
 
            self.disable_initiator_tag = None
344
 
        if getattr(self, u"checker_initiator_tag", False):
345
 
            gobject.source_remove(self.checker_initiator_tag)
346
 
            self.checker_initiator_tag = None
347
 
        self.stop_checker()
348
 
        if self.disable_hook:
349
 
            self.disable_hook(self)
350
 
        self.enabled = False
351
 
        # Do not run this again if called by a gobject.timeout_add
352
 
        return False
353
 
    
354
 
    def __del__(self):
355
 
        self.disable_hook = None
356
 
        self.disable()
 
564
        # Also start a new checker *right now*.
 
565
        self.start_checker()
357
566
    
358
567
    def checker_callback(self, pid, condition, command):
359
568
        """The checker has completed, so take appropriate actions."""
360
569
        self.checker_callback_tag = None
361
570
        self.checker = None
362
571
        if os.WIFEXITED(condition):
363
 
            exitstatus = os.WEXITSTATUS(condition)
364
 
            if exitstatus == 0:
365
 
                logger.info(u"Checker for %(name)s succeeded",
 
572
            self.last_checker_status =  os.WEXITSTATUS(condition)
 
573
            if self.last_checker_status == 0:
 
574
                logger.info("Checker for %(name)s succeeded",
366
575
                            vars(self))
367
576
                self.checked_ok()
368
577
            else:
369
 
                logger.info(u"Checker for %(name)s failed",
 
578
                logger.info("Checker for %(name)s failed",
370
579
                            vars(self))
371
580
        else:
372
 
            logger.warning(u"Checker for %(name)s crashed?",
 
581
            self.last_checker_status = -1
 
582
            logger.warning("Checker for %(name)s crashed?",
373
583
                           vars(self))
374
584
    
375
 
    def checked_ok(self):
 
585
    def checked_ok(self, timeout=None):
376
586
        """Bump up the timeout for this client.
377
587
        
378
588
        This should only be called when the client has been seen,
379
589
        alive and well.
380
590
        """
 
591
        if timeout is None:
 
592
            timeout = self.timeout
381
593
        self.last_checked_ok = datetime.datetime.utcnow()
382
 
        gobject.source_remove(self.disable_initiator_tag)
383
 
        self.disable_initiator_tag = (gobject.timeout_add
384
 
                                      (self.timeout_milliseconds(),
385
 
                                       self.disable))
 
594
        if self.disable_initiator_tag is not None:
 
595
            gobject.source_remove(self.disable_initiator_tag)
 
596
        if getattr(self, "enabled", False):
 
597
            self.disable_initiator_tag = (gobject.timeout_add
 
598
                                          (_timedelta_to_milliseconds
 
599
                                           (timeout), self.disable))
 
600
            self.expires = datetime.datetime.utcnow() + timeout
 
601
    
 
602
    def need_approval(self):
 
603
        self.last_approval_request = datetime.datetime.utcnow()
386
604
    
387
605
    def start_checker(self):
388
606
        """Start a new checker subprocess if one is not running.
395
613
        # client would inevitably timeout, since no checker would get
396
614
        # a chance to run to completion.  If we instead leave running
397
615
        # checkers alone, the checker would have to take more time
398
 
        # than 'timeout' for the client to be declared invalid, which
399
 
        # is as it should be.
 
616
        # than 'timeout' for the client to be disabled, which is as it
 
617
        # should be.
400
618
        
401
619
        # If a checker exists, make sure it is not a zombie
402
620
        try:
403
621
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
404
 
        except (AttributeError, OSError), error:
 
622
        except (AttributeError, OSError) as error:
405
623
            if (isinstance(error, OSError)
406
624
                and error.errno != errno.ECHILD):
407
625
                raise error
408
626
        else:
409
627
            if pid:
410
 
                logger.warning(u"Checker was a zombie")
 
628
                logger.warning("Checker was a zombie")
411
629
                gobject.source_remove(self.checker_callback_tag)
412
630
                self.checker_callback(pid, status,
413
631
                                      self.current_checker_command)
418
636
                command = self.checker_command % self.host
419
637
            except TypeError:
420
638
                # Escape attributes for the shell
421
 
                escaped_attrs = dict((key,
422
 
                                      re.escape(unicode(str(val),
423
 
                                                        errors=
424
 
                                                        u'replace')))
425
 
                                     for key, val in
426
 
                                     vars(self).iteritems())
 
639
                escaped_attrs = dict(
 
640
                    (attr,
 
641
                     re.escape(unicode(str(getattr(self, attr, "")),
 
642
                                       errors=
 
643
                                       'replace')))
 
644
                    for attr in
 
645
                    self.runtime_expansions)
 
646
                
427
647
                try:
428
648
                    command = self.checker_command % escaped_attrs
429
 
                except TypeError, error:
430
 
                    logger.error(u'Could not format string "%s":'
431
 
                                 u' %s', self.checker_command, error)
 
649
                except TypeError as error:
 
650
                    logger.error('Could not format string "%s":'
 
651
                                 ' %s', self.checker_command, error)
432
652
                    return True # Try again later
433
653
            self.current_checker_command = command
434
654
            try:
435
 
                logger.info(u"Starting checker %r for %s",
 
655
                logger.info("Starting checker %r for %s",
436
656
                            command, self.name)
437
657
                # We don't need to redirect stdout and stderr, since
438
658
                # in normal mode, that is already done by daemon(),
440
660
                # always replaced by /dev/null.)
441
661
                self.checker = subprocess.Popen(command,
442
662
                                                close_fds=True,
443
 
                                                shell=True, cwd=u"/")
 
663
                                                shell=True, cwd="/")
444
664
                self.checker_callback_tag = (gobject.child_watch_add
445
665
                                             (self.checker.pid,
446
666
                                              self.checker_callback,
451
671
                if pid:
452
672
                    gobject.source_remove(self.checker_callback_tag)
453
673
                    self.checker_callback(pid, status, command)
454
 
            except OSError, error:
455
 
                logger.error(u"Failed to start subprocess: %s",
 
674
            except OSError as error:
 
675
                logger.error("Failed to start subprocess: %s",
456
676
                             error)
457
677
        # Re-run this periodically if run by gobject.timeout_add
458
678
        return True
462
682
        if self.checker_callback_tag:
463
683
            gobject.source_remove(self.checker_callback_tag)
464
684
            self.checker_callback_tag = None
465
 
        if getattr(self, u"checker", None) is None:
 
685
        if getattr(self, "checker", None) is None:
466
686
            return
467
 
        logger.debug(u"Stopping checker for %(name)s", vars(self))
 
687
        logger.debug("Stopping checker for %(name)s", vars(self))
468
688
        try:
469
689
            os.kill(self.checker.pid, signal.SIGTERM)
470
 
            #os.sleep(0.5)
 
690
            #time.sleep(0.5)
471
691
            #if self.checker.poll() is None:
472
692
            #    os.kill(self.checker.pid, signal.SIGKILL)
473
 
        except OSError, error:
 
693
        except OSError as error:
474
694
            if error.errno != errno.ESRCH: # No such process
475
695
                raise
476
696
        self.checker = None
477
 
    
478
 
    def still_valid(self):
479
 
        """Has the timeout not yet passed for this client?"""
480
 
        if not getattr(self, u"enabled", False):
481
 
            return False
482
 
        now = datetime.datetime.utcnow()
483
 
        if self.last_checked_ok is None:
484
 
            return now < (self.created + self.timeout)
485
 
        else:
486
 
            return now < (self.last_checked_ok + self.timeout)
487
 
 
488
 
 
489
 
def dbus_service_property(dbus_interface, signature=u"v",
490
 
                          access=u"readwrite", byte_arrays=False):
 
697
 
 
698
 
 
699
def dbus_service_property(dbus_interface, signature="v",
 
700
                          access="readwrite", byte_arrays=False):
491
701
    """Decorators for marking methods of a DBusObjectWithProperties to
492
702
    become properties on the D-Bus.
493
703
    
498
708
    dbus.service.method, except there is only "signature", since the
499
709
    type from Get() and the type sent to Set() is the same.
500
710
    """
 
711
    # Encoding deeply encoded byte arrays is not supported yet by the
 
712
    # "Set" method, so we fail early here:
 
713
    if byte_arrays and signature != "ay":
 
714
        raise ValueError("Byte arrays not supported for non-'ay'"
 
715
                         " signature %r" % signature)
501
716
    def decorator(func):
502
717
        func._dbus_is_property = True
503
718
        func._dbus_interface = dbus_interface
504
719
        func._dbus_signature = signature
505
720
        func._dbus_access = access
506
721
        func._dbus_name = func.__name__
507
 
        if func._dbus_name.endswith(u"_dbus_property"):
 
722
        if func._dbus_name.endswith("_dbus_property"):
508
723
            func._dbus_name = func._dbus_name[:-14]
509
 
        func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
 
724
        func._dbus_get_args_options = {'byte_arrays': byte_arrays }
510
725
        return func
511
726
    return decorator
512
727
 
532
747
 
533
748
class DBusObjectWithProperties(dbus.service.Object):
534
749
    """A D-Bus object with properties.
535
 
 
 
750
    
536
751
    Classes inheriting from this can use the dbus_service_property
537
752
    decorator to expose methods as D-Bus properties.  It exposes the
538
753
    standard Get(), Set(), and GetAll() methods on the D-Bus.
540
755
    
541
756
    @staticmethod
542
757
    def _is_dbus_property(obj):
543
 
        return getattr(obj, u"_dbus_is_property", False)
 
758
        return getattr(obj, "_dbus_is_property", False)
544
759
    
545
760
    def _get_all_dbus_properties(self):
546
761
        """Returns a generator of (name, attribute) pairs
547
762
        """
548
 
        return ((prop._dbus_name, prop)
 
763
        return ((prop.__get__(self)._dbus_name, prop.__get__(self))
 
764
                for cls in self.__class__.__mro__
549
765
                for name, prop in
550
 
                inspect.getmembers(self, self._is_dbus_property))
 
766
                inspect.getmembers(cls, self._is_dbus_property))
551
767
    
552
768
    def _get_dbus_property(self, interface_name, property_name):
553
769
        """Returns a bound method if one exists which is a D-Bus
554
770
        property with the specified name and interface.
555
771
        """
556
 
        for name in (property_name,
557
 
                     property_name + u"_dbus_property"):
558
 
            prop = getattr(self, name, None)
559
 
            if (prop is None
560
 
                or not self._is_dbus_property(prop)
561
 
                or prop._dbus_name != property_name
562
 
                or (interface_name and prop._dbus_interface
563
 
                    and interface_name != prop._dbus_interface)):
564
 
                continue
565
 
            return prop
 
772
        for cls in  self.__class__.__mro__:
 
773
            for name, value in (inspect.getmembers
 
774
                                (cls, self._is_dbus_property)):
 
775
                if (value._dbus_name == property_name
 
776
                    and value._dbus_interface == interface_name):
 
777
                    return value.__get__(self)
 
778
        
566
779
        # No such property
567
 
        raise DBusPropertyNotFound(self.dbus_object_path + u":"
568
 
                                   + interface_name + u"."
 
780
        raise DBusPropertyNotFound(self.dbus_object_path + ":"
 
781
                                   + interface_name + "."
569
782
                                   + property_name)
570
783
    
571
 
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
572
 
                         out_signature=u"v")
 
784
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
 
785
                         out_signature="v")
573
786
    def Get(self, interface_name, property_name):
574
787
        """Standard D-Bus property Get() method, see D-Bus standard.
575
788
        """
576
789
        prop = self._get_dbus_property(interface_name, property_name)
577
 
        if prop._dbus_access == u"write":
 
790
        if prop._dbus_access == "write":
578
791
            raise DBusPropertyAccessException(property_name)
579
792
        value = prop()
580
 
        if not hasattr(value, u"variant_level"):
 
793
        if not hasattr(value, "variant_level"):
581
794
            return value
582
795
        return type(value)(value, variant_level=value.variant_level+1)
583
796
    
584
 
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
 
797
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
585
798
    def Set(self, interface_name, property_name, value):
586
799
        """Standard D-Bus property Set() method, see D-Bus standard.
587
800
        """
588
801
        prop = self._get_dbus_property(interface_name, property_name)
589
 
        if prop._dbus_access == u"read":
 
802
        if prop._dbus_access == "read":
590
803
            raise DBusPropertyAccessException(property_name)
591
 
        if prop._dbus_get_args_options[u"byte_arrays"]:
 
804
        if prop._dbus_get_args_options["byte_arrays"]:
 
805
            # The byte_arrays option is not supported yet on
 
806
            # signatures other than "ay".
 
807
            if prop._dbus_signature != "ay":
 
808
                raise ValueError
592
809
            value = dbus.ByteArray(''.join(unichr(byte)
593
810
                                           for byte in value))
594
811
        prop(value)
595
812
    
596
 
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
597
 
                         out_signature=u"a{sv}")
 
813
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
 
814
                         out_signature="a{sv}")
598
815
    def GetAll(self, interface_name):
599
816
        """Standard D-Bus property GetAll() method, see D-Bus
600
817
        standard.
601
 
 
 
818
        
602
819
        Note: Will not include properties with access="write".
603
820
        """
604
 
        all = {}
 
821
        properties = {}
605
822
        for name, prop in self._get_all_dbus_properties():
606
823
            if (interface_name
607
824
                and interface_name != prop._dbus_interface):
608
825
                # Interface non-empty but did not match
609
826
                continue
610
827
            # Ignore write-only properties
611
 
            if prop._dbus_access == u"write":
 
828
            if prop._dbus_access == "write":
612
829
                continue
613
830
            value = prop()
614
 
            if not hasattr(value, u"variant_level"):
615
 
                all[name] = value
 
831
            if not hasattr(value, "variant_level"):
 
832
                properties[name] = value
616
833
                continue
617
 
            all[name] = type(value)(value, variant_level=
618
 
                                    value.variant_level+1)
619
 
        return dbus.Dictionary(all, signature=u"sv")
 
834
            properties[name] = type(value)(value, variant_level=
 
835
                                           value.variant_level+1)
 
836
        return dbus.Dictionary(properties, signature="sv")
620
837
    
621
838
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
622
 
                         out_signature=u"s",
 
839
                         out_signature="s",
623
840
                         path_keyword='object_path',
624
841
                         connection_keyword='connection')
625
842
    def Introspect(self, object_path, connection):
627
844
        """
628
845
        xmlstring = dbus.service.Object.Introspect(self, object_path,
629
846
                                                   connection)
630
 
        document = xml.dom.minidom.parseString(xmlstring)
631
 
        del xmlstring
632
 
        def make_tag(document, name, prop):
633
 
            e = document.createElement(u"property")
634
 
            e.setAttribute(u"name", name)
635
 
            e.setAttribute(u"type", prop._dbus_signature)
636
 
            e.setAttribute(u"access", prop._dbus_access)
637
 
            return e
638
 
        for if_tag in document.getElementsByTagName(u"interface"):
639
 
            for tag in (make_tag(document, name, prop)
640
 
                        for name, prop
641
 
                        in self._get_all_dbus_properties()
642
 
                        if prop._dbus_interface
643
 
                        == if_tag.getAttribute(u"name")):
644
 
                if_tag.appendChild(tag)
645
 
            # Add the names to the return values for the
646
 
            # "org.freedesktop.DBus.Properties" methods
647
 
            if (if_tag.getAttribute(u"name")
648
 
                == u"org.freedesktop.DBus.Properties"):
649
 
                for cn in if_tag.getElementsByTagName(u"method"):
650
 
                    if cn.getAttribute(u"name") == u"Get":
651
 
                        for arg in cn.getElementsByTagName(u"arg"):
652
 
                            if (arg.getAttribute(u"direction")
653
 
                                == u"out"):
654
 
                                arg.setAttribute(u"name", u"value")
655
 
                    elif cn.getAttribute(u"name") == u"GetAll":
656
 
                        for arg in cn.getElementsByTagName(u"arg"):
657
 
                            if (arg.getAttribute(u"direction")
658
 
                                == u"out"):
659
 
                                arg.setAttribute(u"name", u"props")
660
 
        xmlstring = document.toxml(u"utf-8")
661
 
        document.unlink()
 
847
        try:
 
848
            document = xml.dom.minidom.parseString(xmlstring)
 
849
            def make_tag(document, name, prop):
 
850
                e = document.createElement("property")
 
851
                e.setAttribute("name", name)
 
852
                e.setAttribute("type", prop._dbus_signature)
 
853
                e.setAttribute("access", prop._dbus_access)
 
854
                return e
 
855
            for if_tag in document.getElementsByTagName("interface"):
 
856
                for tag in (make_tag(document, name, prop)
 
857
                            for name, prop
 
858
                            in self._get_all_dbus_properties()
 
859
                            if prop._dbus_interface
 
860
                            == if_tag.getAttribute("name")):
 
861
                    if_tag.appendChild(tag)
 
862
                # Add the names to the return values for the
 
863
                # "org.freedesktop.DBus.Properties" methods
 
864
                if (if_tag.getAttribute("name")
 
865
                    == "org.freedesktop.DBus.Properties"):
 
866
                    for cn in if_tag.getElementsByTagName("method"):
 
867
                        if cn.getAttribute("name") == "Get":
 
868
                            for arg in cn.getElementsByTagName("arg"):
 
869
                                if (arg.getAttribute("direction")
 
870
                                    == "out"):
 
871
                                    arg.setAttribute("name", "value")
 
872
                        elif cn.getAttribute("name") == "GetAll":
 
873
                            for arg in cn.getElementsByTagName("arg"):
 
874
                                if (arg.getAttribute("direction")
 
875
                                    == "out"):
 
876
                                    arg.setAttribute("name", "props")
 
877
            xmlstring = document.toxml("utf-8")
 
878
            document.unlink()
 
879
        except (AttributeError, xml.dom.DOMException,
 
880
                xml.parsers.expat.ExpatError) as error:
 
881
            logger.error("Failed to override Introspection method",
 
882
                         error)
662
883
        return xmlstring
663
884
 
664
885
 
 
886
def datetime_to_dbus (dt, variant_level=0):
 
887
    """Convert a UTC datetime.datetime() to a D-Bus type."""
 
888
    if dt is None:
 
889
        return dbus.String("", variant_level = variant_level)
 
890
    return dbus.String(dt.isoformat(),
 
891
                       variant_level=variant_level)
 
892
 
 
893
 
 
894
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
 
895
                                  .__metaclass__):
 
896
    """Applied to an empty subclass of a D-Bus object, this metaclass
 
897
    will add additional D-Bus attributes matching a certain pattern.
 
898
    """
 
899
    def __new__(mcs, name, bases, attr):
 
900
        # Go through all the base classes which could have D-Bus
 
901
        # methods, signals, or properties in them
 
902
        for base in (b for b in bases
 
903
                     if issubclass(b, dbus.service.Object)):
 
904
            # Go though all attributes of the base class
 
905
            for attrname, attribute in inspect.getmembers(base):
 
906
                # Ignore non-D-Bus attributes, and D-Bus attributes
 
907
                # with the wrong interface name
 
908
                if (not hasattr(attribute, "_dbus_interface")
 
909
                    or not attribute._dbus_interface
 
910
                    .startswith("se.recompile.Mandos")):
 
911
                    continue
 
912
                # Create an alternate D-Bus interface name based on
 
913
                # the current name
 
914
                alt_interface = (attribute._dbus_interface
 
915
                                 .replace("se.recompile.Mandos",
 
916
                                          "se.bsnet.fukt.Mandos"))
 
917
                # Is this a D-Bus signal?
 
918
                if getattr(attribute, "_dbus_is_signal", False):
 
919
                    # Extract the original non-method function by
 
920
                    # black magic
 
921
                    nonmethod_func = (dict(
 
922
                            zip(attribute.func_code.co_freevars,
 
923
                                attribute.__closure__))["func"]
 
924
                                      .cell_contents)
 
925
                    # Create a new, but exactly alike, function
 
926
                    # object, and decorate it to be a new D-Bus signal
 
927
                    # with the alternate D-Bus interface name
 
928
                    new_function = (dbus.service.signal
 
929
                                    (alt_interface,
 
930
                                     attribute._dbus_signature)
 
931
                                    (types.FunctionType(
 
932
                                nonmethod_func.func_code,
 
933
                                nonmethod_func.func_globals,
 
934
                                nonmethod_func.func_name,
 
935
                                nonmethod_func.func_defaults,
 
936
                                nonmethod_func.func_closure)))
 
937
                    # Define a creator of a function to call both the
 
938
                    # old and new functions, so both the old and new
 
939
                    # signals gets sent when the function is called
 
940
                    def fixscope(func1, func2):
 
941
                        """This function is a scope container to pass
 
942
                        func1 and func2 to the "call_both" function
 
943
                        outside of its arguments"""
 
944
                        def call_both(*args, **kwargs):
 
945
                            """This function will emit two D-Bus
 
946
                            signals by calling func1 and func2"""
 
947
                            func1(*args, **kwargs)
 
948
                            func2(*args, **kwargs)
 
949
                        return call_both
 
950
                    # Create the "call_both" function and add it to
 
951
                    # the class
 
952
                    attr[attrname] = fixscope(attribute,
 
953
                                              new_function)
 
954
                # Is this a D-Bus method?
 
955
                elif getattr(attribute, "_dbus_is_method", False):
 
956
                    # Create a new, but exactly alike, function
 
957
                    # object.  Decorate it to be a new D-Bus method
 
958
                    # with the alternate D-Bus interface name.  Add it
 
959
                    # to the class.
 
960
                    attr[attrname] = (dbus.service.method
 
961
                                      (alt_interface,
 
962
                                       attribute._dbus_in_signature,
 
963
                                       attribute._dbus_out_signature)
 
964
                                      (types.FunctionType
 
965
                                       (attribute.func_code,
 
966
                                        attribute.func_globals,
 
967
                                        attribute.func_name,
 
968
                                        attribute.func_defaults,
 
969
                                        attribute.func_closure)))
 
970
                # Is this a D-Bus property?
 
971
                elif getattr(attribute, "_dbus_is_property", False):
 
972
                    # Create a new, but exactly alike, function
 
973
                    # object, and decorate it to be a new D-Bus
 
974
                    # property with the alternate D-Bus interface
 
975
                    # name.  Add it to the class.
 
976
                    attr[attrname] = (dbus_service_property
 
977
                                      (alt_interface,
 
978
                                       attribute._dbus_signature,
 
979
                                       attribute._dbus_access,
 
980
                                       attribute
 
981
                                       ._dbus_get_args_options
 
982
                                       ["byte_arrays"])
 
983
                                      (types.FunctionType
 
984
                                       (attribute.func_code,
 
985
                                        attribute.func_globals,
 
986
                                        attribute.func_name,
 
987
                                        attribute.func_defaults,
 
988
                                        attribute.func_closure)))
 
989
        return type.__new__(mcs, name, bases, attr)
 
990
 
 
991
 
665
992
class ClientDBus(Client, DBusObjectWithProperties):
666
993
    """A Client class using D-Bus
667
994
    
669
996
    dbus_object_path: dbus.ObjectPath
670
997
    bus: dbus.SystemBus()
671
998
    """
 
999
    
 
1000
    runtime_expansions = (Client.runtime_expansions
 
1001
                          + ("dbus_object_path",))
 
1002
    
672
1003
    # dbus.service.Object doesn't use super(), so we can't either.
673
1004
    
674
1005
    def __init__(self, bus = None, *args, **kwargs):
675
1006
        self.bus = bus
676
1007
        Client.__init__(self, *args, **kwargs)
 
1008
        
 
1009
        self._approvals_pending = 0
677
1010
        # Only now, when this client is initialized, can it show up on
678
1011
        # the D-Bus
 
1012
        client_object_name = unicode(self.name).translate(
 
1013
            {ord("."): ord("_"),
 
1014
             ord("-"): ord("_")})
679
1015
        self.dbus_object_path = (dbus.ObjectPath
680
 
                                 (u"/clients/"
681
 
                                  + self.name.replace(u".", u"_")))
 
1016
                                 ("/clients/" + client_object_name))
682
1017
        DBusObjectWithProperties.__init__(self, self.bus,
683
1018
                                          self.dbus_object_path)
684
 
    
685
 
    @staticmethod
686
 
    def _datetime_to_dbus(dt, variant_level=0):
687
 
        """Convert a UTC datetime.datetime() to a D-Bus type."""
688
 
        return dbus.String(dt.isoformat(),
689
 
                           variant_level=variant_level)
690
 
    
691
 
    def enable(self):
692
 
        oldstate = getattr(self, u"enabled", False)
693
 
        r = Client.enable(self)
694
 
        if oldstate != self.enabled:
695
 
            # Emit D-Bus signals
696
 
            self.PropertyChanged(dbus.String(u"enabled"),
697
 
                                 dbus.Boolean(True, variant_level=1))
698
 
            self.PropertyChanged(
699
 
                dbus.String(u"last_enabled"),
700
 
                self._datetime_to_dbus(self.last_enabled,
701
 
                                       variant_level=1))
702
 
        return r
703
 
    
704
 
    def disable(self, signal = True):
705
 
        oldstate = getattr(self, u"enabled", False)
706
 
        r = Client.disable(self)
707
 
        if signal and oldstate != self.enabled:
708
 
            # Emit D-Bus signal
709
 
            self.PropertyChanged(dbus.String(u"enabled"),
710
 
                                 dbus.Boolean(False, variant_level=1))
711
 
        return r
 
1019
        
 
1020
    def notifychangeproperty(transform_func,
 
1021
                             dbus_name, type_func=lambda x: x,
 
1022
                             variant_level=1):
 
1023
        """ Modify a variable so that it's a property which announces
 
1024
        its changes to DBus.
 
1025
        
 
1026
        transform_fun: Function that takes a value and a variant_level
 
1027
                       and transforms it to a D-Bus type.
 
1028
        dbus_name: D-Bus name of the variable
 
1029
        type_func: Function that transform the value before sending it
 
1030
                   to the D-Bus.  Default: no transform
 
1031
        variant_level: D-Bus variant level.  Default: 1
 
1032
        """
 
1033
        attrname = "_{0}".format(dbus_name)
 
1034
        def setter(self, value):
 
1035
            if hasattr(self, "dbus_object_path"):
 
1036
                if (not hasattr(self, attrname) or
 
1037
                    type_func(getattr(self, attrname, None))
 
1038
                    != type_func(value)):
 
1039
                    dbus_value = transform_func(type_func(value),
 
1040
                                                variant_level
 
1041
                                                =variant_level)
 
1042
                    self.PropertyChanged(dbus.String(dbus_name),
 
1043
                                         dbus_value)
 
1044
            setattr(self, attrname, value)
 
1045
        
 
1046
        return property(lambda self: getattr(self, attrname), setter)
 
1047
    
 
1048
    
 
1049
    expires = notifychangeproperty(datetime_to_dbus, "Expires")
 
1050
    approvals_pending = notifychangeproperty(dbus.Boolean,
 
1051
                                             "ApprovalPending",
 
1052
                                             type_func = bool)
 
1053
    enabled = notifychangeproperty(dbus.Boolean, "Enabled")
 
1054
    last_enabled = notifychangeproperty(datetime_to_dbus,
 
1055
                                        "LastEnabled")
 
1056
    checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
 
1057
                                   type_func = lambda checker:
 
1058
                                       checker is not None)
 
1059
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
 
1060
                                           "LastCheckedOK")
 
1061
    last_approval_request = notifychangeproperty(
 
1062
        datetime_to_dbus, "LastApprovalRequest")
 
1063
    approved_by_default = notifychangeproperty(dbus.Boolean,
 
1064
                                               "ApprovedByDefault")
 
1065
    approval_delay = notifychangeproperty(dbus.UInt16,
 
1066
                                          "ApprovalDelay",
 
1067
                                          type_func =
 
1068
                                          _timedelta_to_milliseconds)
 
1069
    approval_duration = notifychangeproperty(
 
1070
        dbus.UInt16, "ApprovalDuration",
 
1071
        type_func = _timedelta_to_milliseconds)
 
1072
    host = notifychangeproperty(dbus.String, "Host")
 
1073
    timeout = notifychangeproperty(dbus.UInt16, "Timeout",
 
1074
                                   type_func =
 
1075
                                   _timedelta_to_milliseconds)
 
1076
    extended_timeout = notifychangeproperty(
 
1077
        dbus.UInt16, "ExtendedTimeout",
 
1078
        type_func = _timedelta_to_milliseconds)
 
1079
    interval = notifychangeproperty(dbus.UInt16,
 
1080
                                    "Interval",
 
1081
                                    type_func =
 
1082
                                    _timedelta_to_milliseconds)
 
1083
    checker_command = notifychangeproperty(dbus.String, "Checker")
 
1084
    
 
1085
    del notifychangeproperty
712
1086
    
713
1087
    def __del__(self, *args, **kwargs):
714
1088
        try:
715
1089
            self.remove_from_connection()
716
1090
        except LookupError:
717
1091
            pass
718
 
        if hasattr(DBusObjectWithProperties, u"__del__"):
 
1092
        if hasattr(DBusObjectWithProperties, "__del__"):
719
1093
            DBusObjectWithProperties.__del__(self, *args, **kwargs)
720
1094
        Client.__del__(self, *args, **kwargs)
721
1095
    
723
1097
                         *args, **kwargs):
724
1098
        self.checker_callback_tag = None
725
1099
        self.checker = None
726
 
        # Emit D-Bus signal
727
 
        self.PropertyChanged(dbus.String(u"checker_running"),
728
 
                             dbus.Boolean(False, variant_level=1))
729
1100
        if os.WIFEXITED(condition):
730
1101
            exitstatus = os.WEXITSTATUS(condition)
731
1102
            # Emit D-Bus signal
741
1112
        return Client.checker_callback(self, pid, condition, command,
742
1113
                                       *args, **kwargs)
743
1114
    
744
 
    def checked_ok(self, *args, **kwargs):
745
 
        r = Client.checked_ok(self, *args, **kwargs)
746
 
        # Emit D-Bus signal
747
 
        self.PropertyChanged(
748
 
            dbus.String(u"last_checked_ok"),
749
 
            (self._datetime_to_dbus(self.last_checked_ok,
750
 
                                    variant_level=1)))
751
 
        return r
752
 
    
753
1115
    def start_checker(self, *args, **kwargs):
754
1116
        old_checker = self.checker
755
1117
        if self.checker is not None:
762
1124
            and old_checker_pid != self.checker.pid):
763
1125
            # Emit D-Bus signal
764
1126
            self.CheckerStarted(self.current_checker_command)
765
 
            self.PropertyChanged(
766
 
                dbus.String(u"checker_running"),
767
 
                dbus.Boolean(True, variant_level=1))
768
 
        return r
769
 
    
770
 
    def stop_checker(self, *args, **kwargs):
771
 
        old_checker = getattr(self, u"checker", None)
772
 
        r = Client.stop_checker(self, *args, **kwargs)
773
 
        if (old_checker is not None
774
 
            and getattr(self, u"checker", None) is None):
775
 
            self.PropertyChanged(dbus.String(u"checker_running"),
776
 
                                 dbus.Boolean(False, variant_level=1))
777
 
        return r
778
 
    
779
 
    ## D-Bus methods & signals
780
 
    _interface = u"se.bsnet.fukt.Mandos.Client"
781
 
    
782
 
    # CheckedOK - method
783
 
    @dbus.service.method(_interface)
784
 
    def CheckedOK(self):
785
 
        return self.checked_ok()
 
1127
        return r
 
1128
    
 
1129
    def _reset_approved(self):
 
1130
        self._approved = None
 
1131
        return False
 
1132
    
 
1133
    def approve(self, value=True):
 
1134
        self.send_changedstate()
 
1135
        self._approved = value
 
1136
        gobject.timeout_add(_timedelta_to_milliseconds
 
1137
                            (self.approval_duration),
 
1138
                            self._reset_approved)
 
1139
    
 
1140
    
 
1141
    ## D-Bus methods, signals & properties
 
1142
    _interface = "se.recompile.Mandos.Client"
 
1143
    
 
1144
    ## Signals
786
1145
    
787
1146
    # CheckerCompleted - signal
788
 
    @dbus.service.signal(_interface, signature=u"nxs")
 
1147
    @dbus.service.signal(_interface, signature="nxs")
789
1148
    def CheckerCompleted(self, exitcode, waitstatus, command):
790
1149
        "D-Bus signal"
791
1150
        pass
792
1151
    
793
1152
    # CheckerStarted - signal
794
 
    @dbus.service.signal(_interface, signature=u"s")
 
1153
    @dbus.service.signal(_interface, signature="s")
795
1154
    def CheckerStarted(self, command):
796
1155
        "D-Bus signal"
797
1156
        pass
798
1157
    
799
1158
    # PropertyChanged - signal
800
 
    @dbus.service.signal(_interface, signature=u"sv")
 
1159
    @dbus.service.signal(_interface, signature="sv")
801
1160
    def PropertyChanged(self, property, value):
802
1161
        "D-Bus signal"
803
1162
        pass
804
1163
    
805
 
    # ReceivedSecret - signal
 
1164
    # GotSecret - signal
806
1165
    @dbus.service.signal(_interface)
807
 
    def ReceivedSecret(self):
808
 
        "D-Bus signal"
 
1166
    def GotSecret(self):
 
1167
        """D-Bus signal
 
1168
        Is sent after a successful transfer of secret from the Mandos
 
1169
        server to mandos-client
 
1170
        """
809
1171
        pass
810
1172
    
811
1173
    # Rejected - signal
812
 
    @dbus.service.signal(_interface)
813
 
    def Rejected(self):
814
 
        "D-Bus signal"
815
 
        pass
 
1174
    @dbus.service.signal(_interface, signature="s")
 
1175
    def Rejected(self, reason):
 
1176
        "D-Bus signal"
 
1177
        pass
 
1178
    
 
1179
    # NeedApproval - signal
 
1180
    @dbus.service.signal(_interface, signature="tb")
 
1181
    def NeedApproval(self, timeout, default):
 
1182
        "D-Bus signal"
 
1183
        return self.need_approval()
 
1184
    
 
1185
    # NeRwequest - signal
 
1186
    @dbus.service.signal(_interface, signature="s")
 
1187
    def NewRequest(self, ip):
 
1188
        """D-Bus signal
 
1189
        Is sent after a client request a password.
 
1190
        """
 
1191
        pass
 
1192
    
 
1193
    ## Methods
 
1194
    
 
1195
    # Approve - method
 
1196
    @dbus.service.method(_interface, in_signature="b")
 
1197
    def Approve(self, value):
 
1198
        self.approve(value)
 
1199
    
 
1200
    # CheckedOK - method
 
1201
    @dbus.service.method(_interface)
 
1202
    def CheckedOK(self):
 
1203
        self.checked_ok()
816
1204
    
817
1205
    # Enable - method
818
1206
    @dbus.service.method(_interface)
837
1225
    def StopChecker(self):
838
1226
        self.stop_checker()
839
1227
    
840
 
    # name - property
841
 
    @dbus_service_property(_interface, signature=u"s", access=u"read")
842
 
    def name_dbus_property(self):
 
1228
    ## Properties
 
1229
    
 
1230
    # ApprovalPending - property
 
1231
    @dbus_service_property(_interface, signature="b", access="read")
 
1232
    def ApprovalPending_dbus_property(self):
 
1233
        return dbus.Boolean(bool(self.approvals_pending))
 
1234
    
 
1235
    # ApprovedByDefault - property
 
1236
    @dbus_service_property(_interface, signature="b",
 
1237
                           access="readwrite")
 
1238
    def ApprovedByDefault_dbus_property(self, value=None):
 
1239
        if value is None:       # get
 
1240
            return dbus.Boolean(self.approved_by_default)
 
1241
        self.approved_by_default = bool(value)
 
1242
    
 
1243
    # ApprovalDelay - property
 
1244
    @dbus_service_property(_interface, signature="t",
 
1245
                           access="readwrite")
 
1246
    def ApprovalDelay_dbus_property(self, value=None):
 
1247
        if value is None:       # get
 
1248
            return dbus.UInt64(self.approval_delay_milliseconds())
 
1249
        self.approval_delay = datetime.timedelta(0, 0, 0, value)
 
1250
    
 
1251
    # ApprovalDuration - property
 
1252
    @dbus_service_property(_interface, signature="t",
 
1253
                           access="readwrite")
 
1254
    def ApprovalDuration_dbus_property(self, value=None):
 
1255
        if value is None:       # get
 
1256
            return dbus.UInt64(_timedelta_to_milliseconds(
 
1257
                    self.approval_duration))
 
1258
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
 
1259
    
 
1260
    # Name - property
 
1261
    @dbus_service_property(_interface, signature="s", access="read")
 
1262
    def Name_dbus_property(self):
843
1263
        return dbus.String(self.name)
844
1264
    
845
 
    # fingerprint - property
846
 
    @dbus_service_property(_interface, signature=u"s", access=u"read")
847
 
    def fingerprint_dbus_property(self):
 
1265
    # Fingerprint - property
 
1266
    @dbus_service_property(_interface, signature="s", access="read")
 
1267
    def Fingerprint_dbus_property(self):
848
1268
        return dbus.String(self.fingerprint)
849
1269
    
850
 
    # host - property
851
 
    @dbus_service_property(_interface, signature=u"s",
852
 
                           access=u"readwrite")
853
 
    def host_dbus_property(self, value=None):
 
1270
    # Host - property
 
1271
    @dbus_service_property(_interface, signature="s",
 
1272
                           access="readwrite")
 
1273
    def Host_dbus_property(self, value=None):
854
1274
        if value is None:       # get
855
1275
            return dbus.String(self.host)
856
1276
        self.host = value
857
 
        # Emit D-Bus signal
858
 
        self.PropertyChanged(dbus.String(u"host"),
859
 
                             dbus.String(value, variant_level=1))
860
 
    
861
 
    # created - property
862
 
    @dbus_service_property(_interface, signature=u"s", access=u"read")
863
 
    def created_dbus_property(self):
864
 
        return dbus.String(self._datetime_to_dbus(self.created))
865
 
    
866
 
    # last_enabled - property
867
 
    @dbus_service_property(_interface, signature=u"s", access=u"read")
868
 
    def last_enabled_dbus_property(self):
869
 
        if self.last_enabled is None:
870
 
            return dbus.String(u"")
871
 
        return dbus.String(self._datetime_to_dbus(self.last_enabled))
872
 
    
873
 
    # enabled - property
874
 
    @dbus_service_property(_interface, signature=u"b",
875
 
                           access=u"readwrite")
876
 
    def enabled_dbus_property(self, value=None):
 
1277
    
 
1278
    # Created - property
 
1279
    @dbus_service_property(_interface, signature="s", access="read")
 
1280
    def Created_dbus_property(self):
 
1281
        return dbus.String(datetime_to_dbus(self.created))
 
1282
    
 
1283
    # LastEnabled - property
 
1284
    @dbus_service_property(_interface, signature="s", access="read")
 
1285
    def LastEnabled_dbus_property(self):
 
1286
        return datetime_to_dbus(self.last_enabled)
 
1287
    
 
1288
    # Enabled - property
 
1289
    @dbus_service_property(_interface, signature="b",
 
1290
                           access="readwrite")
 
1291
    def Enabled_dbus_property(self, value=None):
877
1292
        if value is None:       # get
878
1293
            return dbus.Boolean(self.enabled)
879
1294
        if value:
881
1296
        else:
882
1297
            self.disable()
883
1298
    
884
 
    # last_checked_ok - property
885
 
    @dbus_service_property(_interface, signature=u"s",
886
 
                           access=u"readwrite")
887
 
    def last_checked_ok_dbus_property(self, value=None):
 
1299
    # LastCheckedOK - property
 
1300
    @dbus_service_property(_interface, signature="s",
 
1301
                           access="readwrite")
 
1302
    def LastCheckedOK_dbus_property(self, value=None):
888
1303
        if value is not None:
889
1304
            self.checked_ok()
890
1305
            return
891
 
        if self.last_checked_ok is None:
892
 
            return dbus.String(u"")
893
 
        return dbus.String(self._datetime_to_dbus(self
894
 
                                                  .last_checked_ok))
895
 
    
896
 
    # timeout - property
897
 
    @dbus_service_property(_interface, signature=u"t",
898
 
                           access=u"readwrite")
899
 
    def timeout_dbus_property(self, value=None):
 
1306
        return datetime_to_dbus(self.last_checked_ok)
 
1307
    
 
1308
    # Expires - property
 
1309
    @dbus_service_property(_interface, signature="s", access="read")
 
1310
    def Expires_dbus_property(self):
 
1311
        return datetime_to_dbus(self.expires)
 
1312
    
 
1313
    # LastApprovalRequest - property
 
1314
    @dbus_service_property(_interface, signature="s", access="read")
 
1315
    def LastApprovalRequest_dbus_property(self):
 
1316
        return datetime_to_dbus(self.last_approval_request)
 
1317
    
 
1318
    # Timeout - property
 
1319
    @dbus_service_property(_interface, signature="t",
 
1320
                           access="readwrite")
 
1321
    def Timeout_dbus_property(self, value=None):
900
1322
        if value is None:       # get
901
1323
            return dbus.UInt64(self.timeout_milliseconds())
902
1324
        self.timeout = datetime.timedelta(0, 0, 0, value)
903
 
        # Emit D-Bus signal
904
 
        self.PropertyChanged(dbus.String(u"timeout"),
905
 
                             dbus.UInt64(value, variant_level=1))
906
 
        if getattr(self, u"disable_initiator_tag", None) is None:
 
1325
        if getattr(self, "disable_initiator_tag", None) is None:
907
1326
            return
908
1327
        # Reschedule timeout
909
1328
        gobject.source_remove(self.disable_initiator_tag)
910
1329
        self.disable_initiator_tag = None
911
 
        time_to_die = (self.
912
 
                       _timedelta_to_milliseconds((self
913
 
                                                   .last_checked_ok
914
 
                                                   + self.timeout)
915
 
                                                  - datetime.datetime
916
 
                                                  .utcnow()))
 
1330
        self.expires = None
 
1331
        time_to_die = _timedelta_to_milliseconds((self
 
1332
                                                  .last_checked_ok
 
1333
                                                  + self.timeout)
 
1334
                                                 - datetime.datetime
 
1335
                                                 .utcnow())
917
1336
        if time_to_die <= 0:
918
1337
            # The timeout has passed
919
1338
            self.disable()
920
1339
        else:
 
1340
            self.expires = (datetime.datetime.utcnow()
 
1341
                            + datetime.timedelta(milliseconds =
 
1342
                                                 time_to_die))
921
1343
            self.disable_initiator_tag = (gobject.timeout_add
922
1344
                                          (time_to_die, self.disable))
923
1345
    
924
 
    # interval - property
925
 
    @dbus_service_property(_interface, signature=u"t",
926
 
                           access=u"readwrite")
927
 
    def interval_dbus_property(self, value=None):
 
1346
    # ExtendedTimeout - property
 
1347
    @dbus_service_property(_interface, signature="t",
 
1348
                           access="readwrite")
 
1349
    def ExtendedTimeout_dbus_property(self, value=None):
 
1350
        if value is None:       # get
 
1351
            return dbus.UInt64(self.extended_timeout_milliseconds())
 
1352
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
 
1353
    
 
1354
    # Interval - property
 
1355
    @dbus_service_property(_interface, signature="t",
 
1356
                           access="readwrite")
 
1357
    def Interval_dbus_property(self, value=None):
928
1358
        if value is None:       # get
929
1359
            return dbus.UInt64(self.interval_milliseconds())
930
1360
        self.interval = datetime.timedelta(0, 0, 0, value)
931
 
        # Emit D-Bus signal
932
 
        self.PropertyChanged(dbus.String(u"interval"),
933
 
                             dbus.UInt64(value, variant_level=1))
934
 
        if getattr(self, u"checker_initiator_tag", None) is None:
 
1361
        if getattr(self, "checker_initiator_tag", None) is None:
935
1362
            return
936
1363
        # Reschedule checker run
937
1364
        gobject.source_remove(self.checker_initiator_tag)
938
1365
        self.checker_initiator_tag = (gobject.timeout_add
939
1366
                                      (value, self.start_checker))
940
1367
        self.start_checker()    # Start one now, too
941
 
 
942
 
    # checker - property
943
 
    @dbus_service_property(_interface, signature=u"s",
944
 
                           access=u"readwrite")
945
 
    def checker_dbus_property(self, value=None):
 
1368
    
 
1369
    # Checker - property
 
1370
    @dbus_service_property(_interface, signature="s",
 
1371
                           access="readwrite")
 
1372
    def Checker_dbus_property(self, value=None):
946
1373
        if value is None:       # get
947
1374
            return dbus.String(self.checker_command)
948
1375
        self.checker_command = value
949
 
        # Emit D-Bus signal
950
 
        self.PropertyChanged(dbus.String(u"checker"),
951
 
                             dbus.String(self.checker_command,
952
 
                                         variant_level=1))
953
1376
    
954
 
    # checker_running - property
955
 
    @dbus_service_property(_interface, signature=u"b",
956
 
                           access=u"readwrite")
957
 
    def checker_running_dbus_property(self, value=None):
 
1377
    # CheckerRunning - property
 
1378
    @dbus_service_property(_interface, signature="b",
 
1379
                           access="readwrite")
 
1380
    def CheckerRunning_dbus_property(self, value=None):
958
1381
        if value is None:       # get
959
1382
            return dbus.Boolean(self.checker is not None)
960
1383
        if value:
962
1385
        else:
963
1386
            self.stop_checker()
964
1387
    
965
 
    # object_path - property
966
 
    @dbus_service_property(_interface, signature=u"o", access=u"read")
967
 
    def object_path_dbus_property(self):
 
1388
    # ObjectPath - property
 
1389
    @dbus_service_property(_interface, signature="o", access="read")
 
1390
    def ObjectPath_dbus_property(self):
968
1391
        return self.dbus_object_path # is already a dbus.ObjectPath
969
1392
    
970
 
    # secret = property
971
 
    @dbus_service_property(_interface, signature=u"ay",
972
 
                           access=u"write", byte_arrays=True)
973
 
    def secret_dbus_property(self, value):
 
1393
    # Secret = property
 
1394
    @dbus_service_property(_interface, signature="ay",
 
1395
                           access="write", byte_arrays=True)
 
1396
    def Secret_dbus_property(self, value):
974
1397
        self.secret = str(value)
975
1398
    
976
1399
    del _interface
977
1400
 
978
1401
 
 
1402
class ProxyClient(object):
 
1403
    def __init__(self, child_pipe, fpr, address):
 
1404
        self._pipe = child_pipe
 
1405
        self._pipe.send(('init', fpr, address))
 
1406
        if not self._pipe.recv():
 
1407
            raise KeyError()
 
1408
    
 
1409
    def __getattribute__(self, name):
 
1410
        if(name == '_pipe'):
 
1411
            return super(ProxyClient, self).__getattribute__(name)
 
1412
        self._pipe.send(('getattr', name))
 
1413
        data = self._pipe.recv()
 
1414
        if data[0] == 'data':
 
1415
            return data[1]
 
1416
        if data[0] == 'function':
 
1417
            def func(*args, **kwargs):
 
1418
                self._pipe.send(('funcall', name, args, kwargs))
 
1419
                return self._pipe.recv()[1]
 
1420
            return func
 
1421
    
 
1422
    def __setattr__(self, name, value):
 
1423
        if(name == '_pipe'):
 
1424
            return super(ProxyClient, self).__setattr__(name, value)
 
1425
        self._pipe.send(('setattr', name, value))
 
1426
 
 
1427
 
 
1428
class ClientDBusTransitional(ClientDBus):
 
1429
    __metaclass__ = AlternateDBusNamesMetaclass
 
1430
 
 
1431
 
979
1432
class ClientHandler(socketserver.BaseRequestHandler, object):
980
1433
    """A class to handle client connections.
981
1434
    
983
1436
    Note: This will run in its own forked process."""
984
1437
    
985
1438
    def handle(self):
986
 
        logger.info(u"TCP connection from: %s",
987
 
                    unicode(self.client_address))
988
 
        logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
989
 
        # Open IPC pipe to parent process
990
 
        with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
 
1439
        with contextlib.closing(self.server.child_pipe) as child_pipe:
 
1440
            logger.info("TCP connection from: %s",
 
1441
                        unicode(self.client_address))
 
1442
            logger.debug("Pipe FD: %d",
 
1443
                         self.server.child_pipe.fileno())
 
1444
            
991
1445
            session = (gnutls.connection
992
1446
                       .ClientSession(self.request,
993
1447
                                      gnutls.connection
994
1448
                                      .X509Credentials()))
995
1449
            
996
 
            line = self.request.makefile().readline()
997
 
            logger.debug(u"Protocol version: %r", line)
998
 
            try:
999
 
                if int(line.strip().split()[0]) > 1:
1000
 
                    raise RuntimeError
1001
 
            except (ValueError, IndexError, RuntimeError), error:
1002
 
                logger.error(u"Unknown protocol version: %s", error)
1003
 
                return
1004
 
            
1005
1450
            # Note: gnutls.connection.X509Credentials is really a
1006
1451
            # generic GnuTLS certificate credentials object so long as
1007
1452
            # no X.509 keys are added to it.  Therefore, we can use it
1008
1453
            # here despite using OpenPGP certificates.
1009
1454
            
1010
 
            #priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1011
 
            #                      u"+AES-256-CBC", u"+SHA1",
1012
 
            #                      u"+COMP-NULL", u"+CTYPE-OPENPGP",
1013
 
            #                      u"+DHE-DSS"))
 
1455
            #priority = ':'.join(("NONE", "+VERS-TLS1.1",
 
1456
            #                      "+AES-256-CBC", "+SHA1",
 
1457
            #                      "+COMP-NULL", "+CTYPE-OPENPGP",
 
1458
            #                      "+DHE-DSS"))
1014
1459
            # Use a fallback default, since this MUST be set.
1015
1460
            priority = self.server.gnutls_priority
1016
1461
            if priority is None:
1017
 
                priority = u"NORMAL"
 
1462
                priority = "NORMAL"
1018
1463
            (gnutls.library.functions
1019
1464
             .gnutls_priority_set_direct(session._c_object,
1020
1465
                                         priority, None))
1021
1466
            
 
1467
            # Start communication using the Mandos protocol
 
1468
            # Get protocol number
 
1469
            line = self.request.makefile().readline()
 
1470
            logger.debug("Protocol version: %r", line)
 
1471
            try:
 
1472
                if int(line.strip().split()[0]) > 1:
 
1473
                    raise RuntimeError
 
1474
            except (ValueError, IndexError, RuntimeError) as error:
 
1475
                logger.error("Unknown protocol version: %s", error)
 
1476
                return
 
1477
            
 
1478
            # Start GnuTLS connection
1022
1479
            try:
1023
1480
                session.handshake()
1024
 
            except gnutls.errors.GNUTLSError, error:
1025
 
                logger.warning(u"Handshake failed: %s", error)
 
1481
            except gnutls.errors.GNUTLSError as error:
 
1482
                logger.warning("Handshake failed: %s", error)
1026
1483
                # Do not run session.bye() here: the session is not
1027
1484
                # established.  Just abandon the request.
1028
1485
                return
1029
 
            logger.debug(u"Handshake succeeded")
 
1486
            logger.debug("Handshake succeeded")
 
1487
            
 
1488
            approval_required = False
1030
1489
            try:
1031
 
                fpr = self.fingerprint(self.peer_certificate(session))
1032
 
            except (TypeError, gnutls.errors.GNUTLSError), error:
1033
 
                logger.warning(u"Bad certificate: %s", error)
1034
 
                session.bye()
1035
 
                return
1036
 
            logger.debug(u"Fingerprint: %s", fpr)
 
1490
                try:
 
1491
                    fpr = self.fingerprint(self.peer_certificate
 
1492
                                           (session))
 
1493
                except (TypeError,
 
1494
                        gnutls.errors.GNUTLSError) as error:
 
1495
                    logger.warning("Bad certificate: %s", error)
 
1496
                    return
 
1497
                logger.debug("Fingerprint: %s", fpr)
 
1498
                if self.server.use_dbus:
 
1499
                    # Emit D-Bus signal
 
1500
                    client.NewRequest(str(self.client_address))
 
1501
                
 
1502
                try:
 
1503
                    client = ProxyClient(child_pipe, fpr,
 
1504
                                         self.client_address)
 
1505
                except KeyError:
 
1506
                    return
 
1507
                
 
1508
                if client.approval_delay:
 
1509
                    delay = client.approval_delay
 
1510
                    client.approvals_pending += 1
 
1511
                    approval_required = True
 
1512
                
 
1513
                while True:
 
1514
                    if not client.enabled:
 
1515
                        logger.info("Client %s is disabled",
 
1516
                                       client.name)
 
1517
                        if self.server.use_dbus:
 
1518
                            # Emit D-Bus signal
 
1519
                            client.Rejected("Disabled")
 
1520
                        return
 
1521
                    
 
1522
                    if client._approved or not client.approval_delay:
 
1523
                        #We are approved or approval is disabled
 
1524
                        break
 
1525
                    elif client._approved is None:
 
1526
                        logger.info("Client %s needs approval",
 
1527
                                    client.name)
 
1528
                        if self.server.use_dbus:
 
1529
                            # Emit D-Bus signal
 
1530
                            client.NeedApproval(
 
1531
                                client.approval_delay_milliseconds(),
 
1532
                                client.approved_by_default)
 
1533
                    else:
 
1534
                        logger.warning("Client %s was not approved",
 
1535
                                       client.name)
 
1536
                        if self.server.use_dbus:
 
1537
                            # Emit D-Bus signal
 
1538
                            client.Rejected("Denied")
 
1539
                        return
 
1540
                    
 
1541
                    #wait until timeout or approved
 
1542
                    time = datetime.datetime.now()
 
1543
                    client.changedstate.acquire()
 
1544
                    (client.changedstate.wait
 
1545
                     (float(client._timedelta_to_milliseconds(delay)
 
1546
                            / 1000)))
 
1547
                    client.changedstate.release()
 
1548
                    time2 = datetime.datetime.now()
 
1549
                    if (time2 - time) >= delay:
 
1550
                        if not client.approved_by_default:
 
1551
                            logger.warning("Client %s timed out while"
 
1552
                                           " waiting for approval",
 
1553
                                           client.name)
 
1554
                            if self.server.use_dbus:
 
1555
                                # Emit D-Bus signal
 
1556
                                client.Rejected("Approval timed out")
 
1557
                            return
 
1558
                        else:
 
1559
                            break
 
1560
                    else:
 
1561
                        delay -= time2 - time
 
1562
                
 
1563
                sent_size = 0
 
1564
                while sent_size < len(client.secret):
 
1565
                    try:
 
1566
                        sent = session.send(client.secret[sent_size:])
 
1567
                    except gnutls.errors.GNUTLSError as error:
 
1568
                        logger.warning("gnutls send failed")
 
1569
                        return
 
1570
                    logger.debug("Sent: %d, remaining: %d",
 
1571
                                 sent, len(client.secret)
 
1572
                                 - (sent_size + sent))
 
1573
                    sent_size += sent
 
1574
                
 
1575
                logger.info("Sending secret to %s", client.name)
 
1576
                # bump the timeout using extended_timeout
 
1577
                client.checked_ok(client.extended_timeout)
 
1578
                if self.server.use_dbus:
 
1579
                    # Emit D-Bus signal
 
1580
                    client.GotSecret()
1037
1581
            
1038
 
            for c in self.server.clients:
1039
 
                if c.fingerprint == fpr:
1040
 
                    client = c
1041
 
                    break
1042
 
            else:
1043
 
                ipc.write(u"NOTFOUND %s %s\n"
1044
 
                          % (fpr, unicode(self.client_address)))
1045
 
                session.bye()
1046
 
                return
1047
 
            # Have to check if client.still_valid(), since it is
1048
 
            # possible that the client timed out while establishing
1049
 
            # the GnuTLS session.
1050
 
            if not client.still_valid():
1051
 
                ipc.write(u"INVALID %s\n" % client.name)
1052
 
                session.bye()
1053
 
                return
1054
 
            ipc.write(u"SENDING %s\n" % client.name)
1055
 
            sent_size = 0
1056
 
            while sent_size < len(client.secret):
1057
 
                sent = session.send(client.secret[sent_size:])
1058
 
                logger.debug(u"Sent: %d, remaining: %d",
1059
 
                             sent, len(client.secret)
1060
 
                             - (sent_size + sent))
1061
 
                sent_size += sent
1062
 
            session.bye()
 
1582
            finally:
 
1583
                if approval_required:
 
1584
                    client.approvals_pending -= 1
 
1585
                try:
 
1586
                    session.bye()
 
1587
                except gnutls.errors.GNUTLSError as error:
 
1588
                    logger.warning("GnuTLS bye failed")
1063
1589
    
1064
1590
    @staticmethod
1065
1591
    def peer_certificate(session):
1075
1601
                     .gnutls_certificate_get_peers
1076
1602
                     (session._c_object, ctypes.byref(list_size)))
1077
1603
        if not bool(cert_list) and list_size.value != 0:
1078
 
            raise gnutls.errors.GNUTLSError(u"error getting peer"
1079
 
                                            u" certificate")
 
1604
            raise gnutls.errors.GNUTLSError("error getting peer"
 
1605
                                            " certificate")
1080
1606
        if list_size.value == 0:
1081
1607
            return None
1082
1608
        cert = cert_list[0]
1108
1634
        if crtverify.value != 0:
1109
1635
            gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1110
1636
            raise (gnutls.errors.CertificateSecurityError
1111
 
                   (u"Verify failed"))
 
1637
                   ("Verify failed"))
1112
1638
        # New buffer for the fingerprint
1113
1639
        buf = ctypes.create_string_buffer(20)
1114
1640
        buf_len = ctypes.c_size_t()
1121
1647
        # Convert the buffer to a Python bytestring
1122
1648
        fpr = ctypes.string_at(buf, buf_len.value)
1123
1649
        # Convert the bytestring to hexadecimal notation
1124
 
        hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
 
1650
        hex_fpr = binascii.hexlify(fpr).upper()
1125
1651
        return hex_fpr
1126
1652
 
1127
1653
 
1128
 
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
1129
 
    """Like socketserver.ForkingMixIn, but also pass a pipe."""
 
1654
class MultiprocessingMixIn(object):
 
1655
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
 
1656
    def sub_process_main(self, request, address):
 
1657
        try:
 
1658
            self.finish_request(request, address)
 
1659
        except:
 
1660
            self.handle_error(request, address)
 
1661
        self.close_request(request)
 
1662
    
 
1663
    def process_request(self, request, address):
 
1664
        """Start a new process to process the request."""
 
1665
        proc = multiprocessing.Process(target = self.sub_process_main,
 
1666
                                       args = (request,
 
1667
                                               address))
 
1668
        proc.start()
 
1669
        return proc
 
1670
 
 
1671
 
 
1672
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
 
1673
    """ adds a pipe to the MixIn """
1130
1674
    def process_request(self, request, client_address):
1131
1675
        """Overrides and wraps the original process_request().
1132
1676
        
1133
1677
        This function creates a new pipe in self.pipe
1134
1678
        """
1135
 
        self.pipe = os.pipe()
1136
 
        super(ForkingMixInWithPipe,
1137
 
              self).process_request(request, client_address)
1138
 
        os.close(self.pipe[1])  # close write end
1139
 
        self.add_pipe(self.pipe[0])
1140
 
    def add_pipe(self, pipe):
 
1679
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
 
1680
        
 
1681
        proc = MultiprocessingMixIn.process_request(self, request,
 
1682
                                                    client_address)
 
1683
        self.child_pipe.close()
 
1684
        self.add_pipe(parent_pipe, proc)
 
1685
    
 
1686
    def add_pipe(self, parent_pipe, proc):
1141
1687
        """Dummy function; override as necessary"""
1142
 
        os.close(pipe)
1143
 
 
1144
 
 
1145
 
class IPv6_TCPServer(ForkingMixInWithPipe,
 
1688
        raise NotImplementedError
 
1689
 
 
1690
 
 
1691
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1146
1692
                     socketserver.TCPServer, object):
1147
1693
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1148
1694
    
1164
1710
        bind to an address or port if they were not specified."""
1165
1711
        if self.interface is not None:
1166
1712
            if SO_BINDTODEVICE is None:
1167
 
                logger.error(u"SO_BINDTODEVICE does not exist;"
1168
 
                             u" cannot bind to interface %s",
 
1713
                logger.error("SO_BINDTODEVICE does not exist;"
 
1714
                             " cannot bind to interface %s",
1169
1715
                             self.interface)
1170
1716
            else:
1171
1717
                try:
1172
1718
                    self.socket.setsockopt(socket.SOL_SOCKET,
1173
1719
                                           SO_BINDTODEVICE,
1174
1720
                                           str(self.interface
1175
 
                                               + u'\0'))
1176
 
                except socket.error, error:
 
1721
                                               + '\0'))
 
1722
                except socket.error as error:
1177
1723
                    if error[0] == errno.EPERM:
1178
 
                        logger.error(u"No permission to"
1179
 
                                     u" bind to interface %s",
 
1724
                        logger.error("No permission to"
 
1725
                                     " bind to interface %s",
1180
1726
                                     self.interface)
1181
1727
                    elif error[0] == errno.ENOPROTOOPT:
1182
 
                        logger.error(u"SO_BINDTODEVICE not available;"
1183
 
                                     u" cannot bind to interface %s",
 
1728
                        logger.error("SO_BINDTODEVICE not available;"
 
1729
                                     " cannot bind to interface %s",
1184
1730
                                     self.interface)
1185
1731
                    else:
1186
1732
                        raise
1188
1734
        if self.server_address[0] or self.server_address[1]:
1189
1735
            if not self.server_address[0]:
1190
1736
                if self.address_family == socket.AF_INET6:
1191
 
                    any_address = u"::" # in6addr_any
 
1737
                    any_address = "::" # in6addr_any
1192
1738
                else:
1193
1739
                    any_address = socket.INADDR_ANY
1194
1740
                self.server_address = (any_address,
1221
1767
        self.enabled = False
1222
1768
        self.clients = clients
1223
1769
        if self.clients is None:
1224
 
            self.clients = set()
 
1770
            self.clients = {}
1225
1771
        self.use_dbus = use_dbus
1226
1772
        self.gnutls_priority = gnutls_priority
1227
1773
        IPv6_TCPServer.__init__(self, server_address,
1231
1777
    def server_activate(self):
1232
1778
        if self.enabled:
1233
1779
            return socketserver.TCPServer.server_activate(self)
 
1780
    
1234
1781
    def enable(self):
1235
1782
        self.enabled = True
1236
 
    def add_pipe(self, pipe):
 
1783
    
 
1784
    def add_pipe(self, parent_pipe, proc):
1237
1785
        # Call "handle_ipc" for both data and EOF events
1238
 
        gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1239
 
                             self.handle_ipc)
1240
 
    def handle_ipc(self, source, condition, file_objects={}):
 
1786
        gobject.io_add_watch(parent_pipe.fileno(),
 
1787
                             gobject.IO_IN | gobject.IO_HUP,
 
1788
                             functools.partial(self.handle_ipc,
 
1789
                                               parent_pipe =
 
1790
                                               parent_pipe,
 
1791
                                               proc = proc))
 
1792
    
 
1793
    def handle_ipc(self, source, condition, parent_pipe=None,
 
1794
                   proc = None, client_object=None):
1241
1795
        condition_names = {
1242
 
            gobject.IO_IN: u"IN",   # There is data to read.
1243
 
            gobject.IO_OUT: u"OUT", # Data can be written (without
 
1796
            gobject.IO_IN: "IN",   # There is data to read.
 
1797
            gobject.IO_OUT: "OUT", # Data can be written (without
1244
1798
                                    # blocking).
1245
 
            gobject.IO_PRI: u"PRI", # There is urgent data to read.
1246
 
            gobject.IO_ERR: u"ERR", # Error condition.
1247
 
            gobject.IO_HUP: u"HUP"  # Hung up (the connection has been
 
1799
            gobject.IO_PRI: "PRI", # There is urgent data to read.
 
1800
            gobject.IO_ERR: "ERR", # Error condition.
 
1801
            gobject.IO_HUP: "HUP"  # Hung up (the connection has been
1248
1802
                                    # broken, usually for pipes and
1249
1803
                                    # sockets).
1250
1804
            }
1252
1806
                                       for cond, name in
1253
1807
                                       condition_names.iteritems()
1254
1808
                                       if cond & condition)
1255
 
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1256
 
                     conditions_string)
1257
 
        
1258
 
        # Turn the pipe file descriptor into a Python file object
1259
 
        if source not in file_objects:
1260
 
            file_objects[source] = os.fdopen(source, u"r", 1)
1261
 
        
1262
 
        # Read a line from the file object
1263
 
        cmdline = file_objects[source].readline()
1264
 
        if not cmdline:             # Empty line means end of file
1265
 
            # close the IPC pipe
1266
 
            file_objects[source].close()
1267
 
            del file_objects[source]
1268
 
            
1269
 
            # Stop calling this function
1270
 
            return False
1271
 
        
1272
 
        logger.debug(u"IPC command: %r", cmdline)
1273
 
        
1274
 
        # Parse and act on command
1275
 
        cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1276
 
        
1277
 
        if cmd == u"NOTFOUND":
1278
 
            logger.warning(u"Client not found for fingerprint: %s",
1279
 
                           args)
1280
 
            if self.use_dbus:
1281
 
                # Emit D-Bus signal
1282
 
                mandos_dbus_service.ClientNotFound(args)
1283
 
        elif cmd == u"INVALID":
1284
 
            for client in self.clients:
1285
 
                if client.name == args:
1286
 
                    logger.warning(u"Client %s is invalid", args)
1287
 
                    if self.use_dbus:
1288
 
                        # Emit D-Bus signal
1289
 
                        client.Rejected()
1290
 
                    break
1291
 
            else:
1292
 
                logger.error(u"Unknown client %s is invalid", args)
1293
 
        elif cmd == u"SENDING":
1294
 
            for client in self.clients:
1295
 
                if client.name == args:
1296
 
                    logger.info(u"Sending secret to %s", client.name)
1297
 
                    client.checked_ok()
1298
 
                    if self.use_dbus:
1299
 
                        # Emit D-Bus signal
1300
 
                        client.ReceivedSecret()
1301
 
                    break
1302
 
            else:
1303
 
                logger.error(u"Sending secret to unknown client %s",
1304
 
                             args)
1305
 
        else:
1306
 
            logger.error(u"Unknown IPC command: %r", cmdline)
1307
 
        
1308
 
        # Keep calling this function
 
1809
        # error, or the other end of multiprocessing.Pipe has closed
 
1810
        if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
 
1811
            # Wait for other process to exit
 
1812
            proc.join()
 
1813
            return False
 
1814
        
 
1815
        # Read a request from the child
 
1816
        request = parent_pipe.recv()
 
1817
        command = request[0]
 
1818
        
 
1819
        if command == 'init':
 
1820
            fpr = request[1]
 
1821
            address = request[2]
 
1822
            
 
1823
            for c in self.clients.itervalues():
 
1824
                if c.fingerprint == fpr:
 
1825
                    client = c
 
1826
                    break
 
1827
            else:
 
1828
                logger.info("Client not found for fingerprint: %s, ad"
 
1829
                            "dress: %s", fpr, address)
 
1830
                if self.use_dbus:
 
1831
                    # Emit D-Bus signal
 
1832
                    mandos_dbus_service.ClientNotFound(fpr,
 
1833
                                                       address[0])
 
1834
                parent_pipe.send(False)
 
1835
                return False
 
1836
            
 
1837
            gobject.io_add_watch(parent_pipe.fileno(),
 
1838
                                 gobject.IO_IN | gobject.IO_HUP,
 
1839
                                 functools.partial(self.handle_ipc,
 
1840
                                                   parent_pipe =
 
1841
                                                   parent_pipe,
 
1842
                                                   proc = proc,
 
1843
                                                   client_object =
 
1844
                                                   client))
 
1845
            parent_pipe.send(True)
 
1846
            # remove the old hook in favor of the new above hook on
 
1847
            # same fileno
 
1848
            return False
 
1849
        if command == 'funcall':
 
1850
            funcname = request[1]
 
1851
            args = request[2]
 
1852
            kwargs = request[3]
 
1853
            
 
1854
            parent_pipe.send(('data', getattr(client_object,
 
1855
                                              funcname)(*args,
 
1856
                                                         **kwargs)))
 
1857
        
 
1858
        if command == 'getattr':
 
1859
            attrname = request[1]
 
1860
            if callable(client_object.__getattribute__(attrname)):
 
1861
                parent_pipe.send(('function',))
 
1862
            else:
 
1863
                parent_pipe.send(('data', client_object
 
1864
                                  .__getattribute__(attrname)))
 
1865
        
 
1866
        if command == 'setattr':
 
1867
            attrname = request[1]
 
1868
            value = request[2]
 
1869
            setattr(client_object, attrname, value)
 
1870
        
1309
1871
        return True
1310
1872
 
1311
1873
 
1312
1874
def string_to_delta(interval):
1313
1875
    """Parse a string and return a datetime.timedelta
1314
1876
    
1315
 
    >>> string_to_delta(u'7d')
 
1877
    >>> string_to_delta('7d')
1316
1878
    datetime.timedelta(7)
1317
 
    >>> string_to_delta(u'60s')
 
1879
    >>> string_to_delta('60s')
1318
1880
    datetime.timedelta(0, 60)
1319
 
    >>> string_to_delta(u'60m')
 
1881
    >>> string_to_delta('60m')
1320
1882
    datetime.timedelta(0, 3600)
1321
 
    >>> string_to_delta(u'24h')
 
1883
    >>> string_to_delta('24h')
1322
1884
    datetime.timedelta(1)
1323
 
    >>> string_to_delta(u'1w')
 
1885
    >>> string_to_delta('1w')
1324
1886
    datetime.timedelta(7)
1325
 
    >>> string_to_delta(u'5m 30s')
 
1887
    >>> string_to_delta('5m 30s')
1326
1888
    datetime.timedelta(0, 330)
1327
1889
    """
1328
1890
    timevalue = datetime.timedelta(0)
1330
1892
        try:
1331
1893
            suffix = unicode(s[-1])
1332
1894
            value = int(s[:-1])
1333
 
            if suffix == u"d":
 
1895
            if suffix == "d":
1334
1896
                delta = datetime.timedelta(value)
1335
 
            elif suffix == u"s":
 
1897
            elif suffix == "s":
1336
1898
                delta = datetime.timedelta(0, value)
1337
 
            elif suffix == u"m":
 
1899
            elif suffix == "m":
1338
1900
                delta = datetime.timedelta(0, 0, 0, 0, value)
1339
 
            elif suffix == u"h":
 
1901
            elif suffix == "h":
1340
1902
                delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1341
 
            elif suffix == u"w":
 
1903
            elif suffix == "w":
1342
1904
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1343
1905
            else:
1344
 
                raise ValueError
1345
 
        except (ValueError, IndexError):
1346
 
            raise ValueError
 
1906
                raise ValueError("Unknown suffix %r" % suffix)
 
1907
        except (ValueError, IndexError) as e:
 
1908
            raise ValueError(*(e.args))
1347
1909
        timevalue += delta
1348
1910
    return timevalue
1349
1911
 
1350
1912
 
1351
 
def if_nametoindex(interface):
1352
 
    """Call the C function if_nametoindex(), or equivalent
1353
 
    
1354
 
    Note: This function cannot accept a unicode string."""
1355
 
    global if_nametoindex
1356
 
    try:
1357
 
        if_nametoindex = (ctypes.cdll.LoadLibrary
1358
 
                          (ctypes.util.find_library(u"c"))
1359
 
                          .if_nametoindex)
1360
 
    except (OSError, AttributeError):
1361
 
        logger.warning(u"Doing if_nametoindex the hard way")
1362
 
        def if_nametoindex(interface):
1363
 
            "Get an interface index the hard way, i.e. using fcntl()"
1364
 
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1365
 
            with closing(socket.socket()) as s:
1366
 
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1367
 
                                    struct.pack(str(u"16s16x"),
1368
 
                                                interface))
1369
 
            interface_index = struct.unpack(str(u"I"),
1370
 
                                            ifreq[16:20])[0]
1371
 
            return interface_index
1372
 
    return if_nametoindex(interface)
1373
 
 
1374
 
 
1375
1913
def daemon(nochdir = False, noclose = False):
1376
1914
    """See daemon(3).  Standard BSD Unix function.
1377
1915
    
1380
1918
        sys.exit()
1381
1919
    os.setsid()
1382
1920
    if not nochdir:
1383
 
        os.chdir(u"/")
 
1921
        os.chdir("/")
1384
1922
    if os.fork():
1385
1923
        sys.exit()
1386
1924
    if not noclose:
1388
1926
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1389
1927
        if not stat.S_ISCHR(os.fstat(null).st_mode):
1390
1928
            raise OSError(errno.ENODEV,
1391
 
                          u"/dev/null not a character device")
 
1929
                          "%s not a character device"
 
1930
                          % os.path.devnull)
1392
1931
        os.dup2(null, sys.stdin.fileno())
1393
1932
        os.dup2(null, sys.stdout.fileno())
1394
1933
        os.dup2(null, sys.stderr.fileno())
1401
1940
    ##################################################################
1402
1941
    # Parsing of options, both command line and config file
1403
1942
    
1404
 
    parser = optparse.OptionParser(version = "%%prog %s" % version)
1405
 
    parser.add_option("-i", u"--interface", type=u"string",
1406
 
                      metavar="IF", help=u"Bind to interface IF")
1407
 
    parser.add_option("-a", u"--address", type=u"string",
1408
 
                      help=u"Address to listen for requests on")
1409
 
    parser.add_option("-p", u"--port", type=u"int",
1410
 
                      help=u"Port number to receive requests on")
1411
 
    parser.add_option("--check", action=u"store_true",
1412
 
                      help=u"Run self-test")
1413
 
    parser.add_option("--debug", action=u"store_true",
1414
 
                      help=u"Debug mode; run in foreground and log to"
1415
 
                      u" terminal")
1416
 
    parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1417
 
                      u" priority string (see GnuTLS documentation)")
1418
 
    parser.add_option("--servicename", type=u"string",
1419
 
                      metavar=u"NAME", help=u"Zeroconf service name")
1420
 
    parser.add_option("--configdir", type=u"string",
1421
 
                      default=u"/etc/mandos", metavar=u"DIR",
1422
 
                      help=u"Directory to search for configuration"
1423
 
                      u" files")
1424
 
    parser.add_option("--no-dbus", action=u"store_false",
1425
 
                      dest=u"use_dbus", help=u"Do not provide D-Bus"
1426
 
                      u" system bus interface")
1427
 
    parser.add_option("--no-ipv6", action=u"store_false",
1428
 
                      dest=u"use_ipv6", help=u"Do not use IPv6")
1429
 
    options = parser.parse_args()[0]
 
1943
    parser = argparse.ArgumentParser()
 
1944
    parser.add_argument("-v", "--version", action="version",
 
1945
                        version = "%%(prog)s %s" % version,
 
1946
                        help="show version number and exit")
 
1947
    parser.add_argument("-i", "--interface", metavar="IF",
 
1948
                        help="Bind to interface IF")
 
1949
    parser.add_argument("-a", "--address",
 
1950
                        help="Address to listen for requests on")
 
1951
    parser.add_argument("-p", "--port", type=int,
 
1952
                        help="Port number to receive requests on")
 
1953
    parser.add_argument("--check", action="store_true",
 
1954
                        help="Run self-test")
 
1955
    parser.add_argument("--debug", action="store_true",
 
1956
                        help="Debug mode; run in foreground and log"
 
1957
                        " to terminal")
 
1958
    parser.add_argument("--debuglevel", metavar="LEVEL",
 
1959
                        help="Debug level for stdout output")
 
1960
    parser.add_argument("--priority", help="GnuTLS"
 
1961
                        " priority string (see GnuTLS documentation)")
 
1962
    parser.add_argument("--servicename",
 
1963
                        metavar="NAME", help="Zeroconf service name")
 
1964
    parser.add_argument("--configdir",
 
1965
                        default="/etc/mandos", metavar="DIR",
 
1966
                        help="Directory to search for configuration"
 
1967
                        " files")
 
1968
    parser.add_argument("--no-dbus", action="store_false",
 
1969
                        dest="use_dbus", help="Do not provide D-Bus"
 
1970
                        " system bus interface")
 
1971
    parser.add_argument("--no-ipv6", action="store_false",
 
1972
                        dest="use_ipv6", help="Do not use IPv6")
 
1973
    parser.add_argument("--no-restore", action="store_false",
 
1974
                        dest="restore", help="Do not restore stored"
 
1975
                        " state", default=True)
 
1976
    
 
1977
    options = parser.parse_args()
1430
1978
    
1431
1979
    if options.check:
1432
1980
        import doctest
1434
1982
        sys.exit()
1435
1983
    
1436
1984
    # Default values for config file for server-global settings
1437
 
    server_defaults = { u"interface": u"",
1438
 
                        u"address": u"",
1439
 
                        u"port": u"",
1440
 
                        u"debug": u"False",
1441
 
                        u"priority":
1442
 
                        u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1443
 
                        u"servicename": u"Mandos",
1444
 
                        u"use_dbus": u"True",
1445
 
                        u"use_ipv6": u"True",
 
1985
    server_defaults = { "interface": "",
 
1986
                        "address": "",
 
1987
                        "port": "",
 
1988
                        "debug": "False",
 
1989
                        "priority":
 
1990
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
 
1991
                        "servicename": "Mandos",
 
1992
                        "use_dbus": "True",
 
1993
                        "use_ipv6": "True",
 
1994
                        "debuglevel": "",
1446
1995
                        }
1447
1996
    
1448
1997
    # Parse config file for server-global settings
1449
1998
    server_config = configparser.SafeConfigParser(server_defaults)
1450
1999
    del server_defaults
1451
2000
    server_config.read(os.path.join(options.configdir,
1452
 
                                    u"mandos.conf"))
 
2001
                                    "mandos.conf"))
1453
2002
    # Convert the SafeConfigParser object to a dict
1454
2003
    server_settings = server_config.defaults()
1455
2004
    # Use the appropriate methods on the non-string config options
1456
 
    for option in (u"debug", u"use_dbus", u"use_ipv6"):
1457
 
        server_settings[option] = server_config.getboolean(u"DEFAULT",
 
2005
    for option in ("debug", "use_dbus", "use_ipv6"):
 
2006
        server_settings[option] = server_config.getboolean("DEFAULT",
1458
2007
                                                           option)
1459
2008
    if server_settings["port"]:
1460
 
        server_settings["port"] = server_config.getint(u"DEFAULT",
1461
 
                                                       u"port")
 
2009
        server_settings["port"] = server_config.getint("DEFAULT",
 
2010
                                                       "port")
1462
2011
    del server_config
1463
2012
    
1464
2013
    # Override the settings from the config file with command line
1465
2014
    # options, if set.
1466
 
    for option in (u"interface", u"address", u"port", u"debug",
1467
 
                   u"priority", u"servicename", u"configdir",
1468
 
                   u"use_dbus", u"use_ipv6"):
 
2015
    for option in ("interface", "address", "port", "debug",
 
2016
                   "priority", "servicename", "configdir",
 
2017
                   "use_dbus", "use_ipv6", "debuglevel", "restore"):
1469
2018
        value = getattr(options, option)
1470
2019
        if value is not None:
1471
2020
            server_settings[option] = value
1479
2028
    ##################################################################
1480
2029
    
1481
2030
    # For convenience
1482
 
    debug = server_settings[u"debug"]
1483
 
    use_dbus = server_settings[u"use_dbus"]
1484
 
    use_ipv6 = server_settings[u"use_ipv6"]
1485
 
    
1486
 
    if not debug:
1487
 
        syslogger.setLevel(logging.WARNING)
1488
 
        console.setLevel(logging.WARNING)
1489
 
    
1490
 
    if server_settings[u"servicename"] != u"Mandos":
 
2031
    debug = server_settings["debug"]
 
2032
    debuglevel = server_settings["debuglevel"]
 
2033
    use_dbus = server_settings["use_dbus"]
 
2034
    use_ipv6 = server_settings["use_ipv6"]
 
2035
    
 
2036
    if debug:
 
2037
        initlogger(logging.DEBUG)
 
2038
    else:
 
2039
        if not debuglevel:
 
2040
            initlogger()
 
2041
        else:
 
2042
            level = getattr(logging, debuglevel.upper())
 
2043
            initlogger(level)
 
2044
    
 
2045
    if server_settings["servicename"] != "Mandos":
1491
2046
        syslogger.setFormatter(logging.Formatter
1492
 
                               (u'Mandos (%s) [%%(process)d]:'
1493
 
                                u' %%(levelname)s: %%(message)s'
1494
 
                                % server_settings[u"servicename"]))
 
2047
                               ('Mandos (%s) [%%(process)d]:'
 
2048
                                ' %%(levelname)s: %%(message)s'
 
2049
                                % server_settings["servicename"]))
1495
2050
    
1496
2051
    # Parse config file with clients
1497
 
    client_defaults = { u"timeout": u"1h",
1498
 
                        u"interval": u"5m",
1499
 
                        u"checker": u"fping -q -- %%(host)s",
1500
 
                        u"host": u"",
 
2052
    client_defaults = { "timeout": "5m",
 
2053
                        "extended_timeout": "15m",
 
2054
                        "interval": "2m",
 
2055
                        "checker": "fping -q -- %%(host)s",
 
2056
                        "host": "",
 
2057
                        "approval_delay": "0s",
 
2058
                        "approval_duration": "1s",
1501
2059
                        }
1502
2060
    client_config = configparser.SafeConfigParser(client_defaults)
1503
 
    client_config.read(os.path.join(server_settings[u"configdir"],
1504
 
                                    u"clients.conf"))
 
2061
    client_config.read(os.path.join(server_settings["configdir"],
 
2062
                                    "clients.conf"))
1505
2063
    
1506
2064
    global mandos_dbus_service
1507
2065
    mandos_dbus_service = None
1508
2066
    
1509
 
    tcp_server = MandosServer((server_settings[u"address"],
1510
 
                               server_settings[u"port"]),
 
2067
    tcp_server = MandosServer((server_settings["address"],
 
2068
                               server_settings["port"]),
1511
2069
                              ClientHandler,
1512
 
                              interface=server_settings[u"interface"],
 
2070
                              interface=(server_settings["interface"]
 
2071
                                         or None),
1513
2072
                              use_ipv6=use_ipv6,
1514
2073
                              gnutls_priority=
1515
 
                              server_settings[u"priority"],
 
2074
                              server_settings["priority"],
1516
2075
                              use_dbus=use_dbus)
1517
 
    pidfilename = u"/var/run/mandos.pid"
1518
 
    try:
1519
 
        pidfile = open(pidfilename, u"w")
1520
 
    except IOError:
1521
 
        logger.error(u"Could not open file %r", pidfilename)
 
2076
    if not debug:
 
2077
        pidfilename = "/var/run/mandos.pid"
 
2078
        try:
 
2079
            pidfile = open(pidfilename, "w")
 
2080
        except IOError:
 
2081
            logger.error("Could not open file %r", pidfilename)
1522
2082
    
1523
2083
    try:
1524
 
        uid = pwd.getpwnam(u"_mandos").pw_uid
1525
 
        gid = pwd.getpwnam(u"_mandos").pw_gid
 
2084
        uid = pwd.getpwnam("_mandos").pw_uid
 
2085
        gid = pwd.getpwnam("_mandos").pw_gid
1526
2086
    except KeyError:
1527
2087
        try:
1528
 
            uid = pwd.getpwnam(u"mandos").pw_uid
1529
 
            gid = pwd.getpwnam(u"mandos").pw_gid
 
2088
            uid = pwd.getpwnam("mandos").pw_uid
 
2089
            gid = pwd.getpwnam("mandos").pw_gid
1530
2090
        except KeyError:
1531
2091
            try:
1532
 
                uid = pwd.getpwnam(u"nobody").pw_uid
1533
 
                gid = pwd.getpwnam(u"nobody").pw_gid
 
2092
                uid = pwd.getpwnam("nobody").pw_uid
 
2093
                gid = pwd.getpwnam("nobody").pw_gid
1534
2094
            except KeyError:
1535
2095
                uid = 65534
1536
2096
                gid = 65534
1537
2097
    try:
1538
2098
        os.setgid(gid)
1539
2099
        os.setuid(uid)
1540
 
    except OSError, error:
 
2100
    except OSError as error:
1541
2101
        if error[0] != errno.EPERM:
1542
2102
            raise error
1543
2103
    
1544
 
    # Enable all possible GnuTLS debugging
1545
2104
    if debug:
 
2105
        # Enable all possible GnuTLS debugging
 
2106
        
1546
2107
        # "Use a log level over 10 to enable all debugging options."
1547
2108
        # - GnuTLS manual
1548
2109
        gnutls.library.functions.gnutls_global_set_log_level(11)
1549
2110
        
1550
2111
        @gnutls.library.types.gnutls_log_func
1551
2112
        def debug_gnutls(level, string):
1552
 
            logger.debug(u"GnuTLS: %s", string[:-1])
 
2113
            logger.debug("GnuTLS: %s", string[:-1])
1553
2114
        
1554
2115
        (gnutls.library.functions
1555
2116
         .gnutls_global_set_log_function(debug_gnutls))
 
2117
        
 
2118
        # Redirect stdin so all checkers get /dev/null
 
2119
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
2120
        os.dup2(null, sys.stdin.fileno())
 
2121
        if null > 2:
 
2122
            os.close(null)
 
2123
    else:
 
2124
        # No console logging
 
2125
        logger.removeHandler(console)
 
2126
    
 
2127
    # Need to fork before connecting to D-Bus
 
2128
    if not debug:
 
2129
        # Close all input and output, do double fork, etc.
 
2130
        daemon()
1556
2131
    
1557
2132
    global main_loop
1558
2133
    # From the Avahi example code
1561
2136
    bus = dbus.SystemBus()
1562
2137
    # End of Avahi example code
1563
2138
    if use_dbus:
1564
 
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
 
2139
        try:
 
2140
            bus_name = dbus.service.BusName("se.recompile.Mandos",
 
2141
                                            bus, do_not_queue=True)
 
2142
            old_bus_name = (dbus.service.BusName
 
2143
                            ("se.bsnet.fukt.Mandos", bus,
 
2144
                             do_not_queue=True))
 
2145
        except dbus.exceptions.NameExistsException as e:
 
2146
            logger.error(unicode(e) + ", disabling D-Bus")
 
2147
            use_dbus = False
 
2148
            server_settings["use_dbus"] = False
 
2149
            tcp_server.use_dbus = False
1565
2150
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1566
 
    service = AvahiService(name = server_settings[u"servicename"],
1567
 
                           servicetype = u"_mandos._tcp",
1568
 
                           protocol = protocol, bus = bus)
 
2151
    service = AvahiServiceToSyslog(name =
 
2152
                                   server_settings["servicename"],
 
2153
                                   servicetype = "_mandos._tcp",
 
2154
                                   protocol = protocol, bus = bus)
1569
2155
    if server_settings["interface"]:
1570
2156
        service.interface = (if_nametoindex
1571
 
                             (str(server_settings[u"interface"])))
 
2157
                             (str(server_settings["interface"])))
 
2158
    
 
2159
    global multiprocessing_manager
 
2160
    multiprocessing_manager = multiprocessing.Manager()
1572
2161
    
1573
2162
    client_class = Client
1574
2163
    if use_dbus:
1575
 
        client_class = functools.partial(ClientDBus, bus = bus)
1576
 
    tcp_server.clients.update(set(
1577
 
            client_class(name = section,
1578
 
                         config= dict(client_config.items(section)))
1579
 
            for section in client_config.sections()))
 
2164
        client_class = functools.partial(ClientDBusTransitional,
 
2165
                                         bus = bus)
 
2166
    
 
2167
    special_settings = {
 
2168
        # Some settings need to be accessd by special methods;
 
2169
        # booleans need .getboolean(), etc.  Here is a list of them:
 
2170
        "approved_by_default":
 
2171
            lambda section:
 
2172
            client_config.getboolean(section, "approved_by_default"),
 
2173
        }
 
2174
    # Construct a new dict of client settings of this form:
 
2175
    # { client_name: {setting_name: value, ...}, ...}
 
2176
    # with exceptions for any special settings as defined above
 
2177
    client_settings = dict((clientname,
 
2178
                           dict((setting,
 
2179
                                 (value
 
2180
                                  if setting not in special_settings
 
2181
                                  else special_settings[setting]
 
2182
                                  (clientname)))
 
2183
                                for setting, value in
 
2184
                                client_config.items(clientname)))
 
2185
                          for clientname in client_config.sections())
 
2186
    
 
2187
    old_client_settings = {}
 
2188
    clients_data = []
 
2189
    
 
2190
    # Get client data and settings from last running state.
 
2191
    if server_settings["restore"]:
 
2192
        try:
 
2193
            with open(stored_state_path, "rb") as stored_state:
 
2194
                clients_data, old_client_settings = (pickle.load
 
2195
                                                     (stored_state))
 
2196
            os.remove(stored_state_path)
 
2197
        except IOError as e:
 
2198
            logger.warning("Could not load persistent state: {0}"
 
2199
                           .format(e))
 
2200
            if e.errno != errno.ENOENT:
 
2201
                raise
 
2202
    
 
2203
    with Crypto() as crypt:
 
2204
        for client in clients_data:
 
2205
            client_name = client["name"]
 
2206
            
 
2207
            # Decide which value to use after restoring saved state.
 
2208
            # We have three different values: Old config file,
 
2209
            # new config file, and saved state.
 
2210
            # New config value takes precedence if it differs from old
 
2211
            # config value, otherwise use saved state.
 
2212
            for name, value in client_settings[client_name].items():
 
2213
                try:
 
2214
                    # For each value in new config, check if it
 
2215
                    # differs from the old config value (Except for
 
2216
                    # the "secret" attribute)
 
2217
                    if (name != "secret" and
 
2218
                        value != old_client_settings[client_name]
 
2219
                        [name]):
 
2220
                        setattr(client, name, value)
 
2221
                except KeyError:
 
2222
                    pass
 
2223
            
 
2224
            # Clients who has passed its expire date can still be
 
2225
            # enabled if its last checker was sucessful.  Clients
 
2226
            # whose checker failed before we stored its state is
 
2227
            # assumed to have failed all checkers during downtime.
 
2228
            if client["enabled"] and client["last_checked_ok"]:
 
2229
                if ((datetime.datetime.utcnow()
 
2230
                     - client["last_checked_ok"])
 
2231
                    > client["interval"]):
 
2232
                    if client["last_checker_status"] != 0:
 
2233
                        client["enabled"] = False
 
2234
                    else:
 
2235
                        client["expires"] = (datetime.datetime
 
2236
                                             .utcnow()
 
2237
                                             + client["timeout"])
 
2238
            
 
2239
            client["changedstate"] = (multiprocessing_manager
 
2240
                                      .Condition
 
2241
                                      (multiprocessing_manager
 
2242
                                       .Lock()))
 
2243
            if use_dbus:
 
2244
                new_client = (ClientDBusTransitional.__new__
 
2245
                              (ClientDBusTransitional))
 
2246
                tcp_server.clients[client_name] = new_client
 
2247
                new_client.bus = bus
 
2248
                for name, value in client.iteritems():
 
2249
                    setattr(new_client, name, value)
 
2250
                client_object_name = unicode(client_name).translate(
 
2251
                    {ord("."): ord("_"),
 
2252
                     ord("-"): ord("_")})
 
2253
                new_client.dbus_object_path = (dbus.ObjectPath
 
2254
                                               ("/clients/"
 
2255
                                                + client_object_name))
 
2256
                DBusObjectWithProperties.__init__(new_client,
 
2257
                                                  new_client.bus,
 
2258
                                                  new_client
 
2259
                                                  .dbus_object_path)
 
2260
            else:
 
2261
                tcp_server.clients[client_name] = (Client.__new__
 
2262
                                                   (Client))
 
2263
                for name, value in client.iteritems():
 
2264
                    setattr(tcp_server.clients[client_name],
 
2265
                            name, value)
 
2266
            
 
2267
            try:
 
2268
                tcp_server.clients[client_name].secret = (
 
2269
                    crypt.decrypt(tcp_server.clients[client_name]
 
2270
                                  .encrypted_secret,
 
2271
                                  client_settings[client_name]
 
2272
                                  ["secret"]))
 
2273
            except CryptoError:
 
2274
                # If decryption fails, we use secret from new settings
 
2275
                tcp_server.clients[client_name].secret = (
 
2276
                    client_settings[client_name]["secret"])
 
2277
    
 
2278
    # Create/remove clients based on new changes made to config
 
2279
    for clientname in set(old_client_settings) - set(client_settings):
 
2280
        del tcp_server.clients[clientname]
 
2281
    for clientname in set(client_settings) - set(old_client_settings):
 
2282
        tcp_server.clients[clientname] = (client_class(name
 
2283
                                                       = clientname,
 
2284
                                                       config =
 
2285
                                                       client_settings
 
2286
                                                       [clientname]))
 
2287
    
1580
2288
    if not tcp_server.clients:
1581
 
        logger.warning(u"No clients defined")
1582
 
    
1583
 
    if debug:
1584
 
        # Redirect stdin so all checkers get /dev/null
1585
 
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1586
 
        os.dup2(null, sys.stdin.fileno())
1587
 
        if null > 2:
1588
 
            os.close(null)
1589
 
    else:
1590
 
        # No console logging
1591
 
        logger.removeHandler(console)
1592
 
        # Close all input and output, do double fork, etc.
1593
 
        daemon()
1594
 
    
1595
 
    try:
1596
 
        with closing(pidfile):
1597
 
            pid = os.getpid()
1598
 
            pidfile.write(str(pid) + "\n")
1599
 
        del pidfile
1600
 
    except IOError:
1601
 
        logger.error(u"Could not write to file %r with PID %d",
1602
 
                     pidfilename, pid)
1603
 
    except NameError:
1604
 
        # "pidfile" was never created
1605
 
        pass
1606
 
    del pidfilename
1607
 
    
1608
 
    def cleanup():
1609
 
        "Cleanup function; run on exit"
1610
 
        service.cleanup()
 
2289
        logger.warning("No clients defined")
1611
2290
        
1612
 
        while tcp_server.clients:
1613
 
            client = tcp_server.clients.pop()
1614
 
            client.disable_hook = None
1615
 
            client.disable()
1616
 
    
1617
 
    atexit.register(cleanup)
1618
 
    
1619
2291
    if not debug:
 
2292
        try:
 
2293
            with pidfile:
 
2294
                pid = os.getpid()
 
2295
                pidfile.write(str(pid) + "\n".encode("utf-8"))
 
2296
            del pidfile
 
2297
        except IOError:
 
2298
            logger.error("Could not write to file %r with PID %d",
 
2299
                         pidfilename, pid)
 
2300
        except NameError:
 
2301
            # "pidfile" was never created
 
2302
            pass
 
2303
        del pidfilename
 
2304
        
1620
2305
        signal.signal(signal.SIGINT, signal.SIG_IGN)
 
2306
    
1621
2307
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1622
2308
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1623
2309
    
1625
2311
        class MandosDBusService(dbus.service.Object):
1626
2312
            """A D-Bus proxy object"""
1627
2313
            def __init__(self):
1628
 
                dbus.service.Object.__init__(self, bus, u"/")
1629
 
            _interface = u"se.bsnet.fukt.Mandos"
1630
 
            
1631
 
            @dbus.service.signal(_interface, signature=u"oa{sv}")
1632
 
            def ClientAdded(self, objpath, properties):
1633
 
                "D-Bus signal"
1634
 
                pass
1635
 
            
1636
 
            @dbus.service.signal(_interface, signature=u"s")
1637
 
            def ClientNotFound(self, fingerprint):
1638
 
                "D-Bus signal"
1639
 
                pass
1640
 
            
1641
 
            @dbus.service.signal(_interface, signature=u"os")
 
2314
                dbus.service.Object.__init__(self, bus, "/")
 
2315
            _interface = "se.recompile.Mandos"
 
2316
            
 
2317
            @dbus.service.signal(_interface, signature="o")
 
2318
            def ClientAdded(self, objpath):
 
2319
                "D-Bus signal"
 
2320
                pass
 
2321
            
 
2322
            @dbus.service.signal(_interface, signature="ss")
 
2323
            def ClientNotFound(self, fingerprint, address):
 
2324
                "D-Bus signal"
 
2325
                pass
 
2326
            
 
2327
            @dbus.service.signal(_interface, signature="os")
1642
2328
            def ClientRemoved(self, objpath, name):
1643
2329
                "D-Bus signal"
1644
2330
                pass
1645
2331
            
1646
 
            @dbus.service.method(_interface, out_signature=u"ao")
 
2332
            @dbus.service.method(_interface, out_signature="ao")
1647
2333
            def GetAllClients(self):
1648
2334
                "D-Bus method"
1649
2335
                return dbus.Array(c.dbus_object_path
1650
 
                                  for c in tcp_server.clients)
 
2336
                                  for c in
 
2337
                                  tcp_server.clients.itervalues())
1651
2338
            
1652
2339
            @dbus.service.method(_interface,
1653
 
                                 out_signature=u"a{oa{sv}}")
 
2340
                                 out_signature="a{oa{sv}}")
1654
2341
            def GetAllClientsWithProperties(self):
1655
2342
                "D-Bus method"
1656
2343
                return dbus.Dictionary(
1657
 
                    ((c.dbus_object_path, c.GetAll(u""))
1658
 
                     for c in tcp_server.clients),
1659
 
                    signature=u"oa{sv}")
 
2344
                    ((c.dbus_object_path, c.GetAll(""))
 
2345
                     for c in tcp_server.clients.itervalues()),
 
2346
                    signature="oa{sv}")
1660
2347
            
1661
 
            @dbus.service.method(_interface, in_signature=u"o")
 
2348
            @dbus.service.method(_interface, in_signature="o")
1662
2349
            def RemoveClient(self, object_path):
1663
2350
                "D-Bus method"
1664
 
                for c in tcp_server.clients:
 
2351
                for c in tcp_server.clients.itervalues():
1665
2352
                    if c.dbus_object_path == object_path:
1666
 
                        tcp_server.clients.remove(c)
 
2353
                        del tcp_server.clients[c.name]
1667
2354
                        c.remove_from_connection()
1668
2355
                        # Don't signal anything except ClientRemoved
1669
 
                        c.disable(signal=False)
 
2356
                        c.disable(quiet=True)
1670
2357
                        # Emit D-Bus signal
1671
2358
                        self.ClientRemoved(object_path, c.name)
1672
2359
                        return
1673
 
                raise KeyError
 
2360
                raise KeyError(object_path)
1674
2361
            
1675
2362
            del _interface
1676
2363
        
1677
 
        mandos_dbus_service = MandosDBusService()
1678
 
    
1679
 
    for client in tcp_server.clients:
 
2364
        class MandosDBusServiceTransitional(MandosDBusService):
 
2365
            __metaclass__ = AlternateDBusNamesMetaclass
 
2366
        mandos_dbus_service = MandosDBusServiceTransitional()
 
2367
    
 
2368
    def cleanup():
 
2369
        "Cleanup function; run on exit"
 
2370
        service.cleanup()
 
2371
        
 
2372
        multiprocessing.active_children()
 
2373
        if not (tcp_server.clients or client_settings):
 
2374
            return
 
2375
        
 
2376
        # Store client before exiting. Secrets are encrypted with key
 
2377
        # based on what config file has. If config file is
 
2378
        # removed/edited, old secret will thus be unrecovable.
 
2379
        clients = []
 
2380
        with Crypto() as crypt:
 
2381
            for client in tcp_server.clients.itervalues():
 
2382
                key = client_settings[client.name]["secret"]
 
2383
                client.encrypted_secret = crypt.encrypt(client.secret,
 
2384
                                                        key)
 
2385
                client_dict = {}
 
2386
                
 
2387
                # A list of attributes that will not be stored when
 
2388
                # shutting down.
 
2389
                exclude = set(("bus", "changedstate", "secret"))
 
2390
                for name, typ in (inspect.getmembers
 
2391
                                  (dbus.service.Object)):
 
2392
                    exclude.add(name)
 
2393
                
 
2394
                client_dict["encrypted_secret"] = (client
 
2395
                                                   .encrypted_secret)
 
2396
                for attr in client.client_structure:
 
2397
                    if attr not in exclude:
 
2398
                        client_dict[attr] = getattr(client, attr)
 
2399
                
 
2400
                clients.append(client_dict)
 
2401
                del client_settings[client.name]["secret"]
 
2402
        
 
2403
        try:
 
2404
            with os.fdopen(os.open(stored_state_path,
 
2405
                                   os.O_CREAT|os.O_WRONLY|os.O_TRUNC,
 
2406
                                   0600), "wb") as stored_state:
 
2407
                pickle.dump((clients, client_settings), stored_state)
 
2408
        except (IOError, OSError) as e:
 
2409
            logger.warning("Could not save persistent state: {0}"
 
2410
                           .format(e))
 
2411
            if e.errno not in (errno.ENOENT, errno.EACCES):
 
2412
                raise
 
2413
        
 
2414
        # Delete all clients, and settings from config
 
2415
        while tcp_server.clients:
 
2416
            name, client = tcp_server.clients.popitem()
 
2417
            if use_dbus:
 
2418
                client.remove_from_connection()
 
2419
            # Don't signal anything except ClientRemoved
 
2420
            client.disable(quiet=True)
 
2421
            if use_dbus:
 
2422
                # Emit D-Bus signal
 
2423
                mandos_dbus_service.ClientRemoved(client
 
2424
                                                  .dbus_object_path,
 
2425
                                                  client.name)
 
2426
        client_settings.clear()
 
2427
    
 
2428
    atexit.register(cleanup)
 
2429
    
 
2430
    for client in tcp_server.clients.itervalues():
1680
2431
        if use_dbus:
1681
2432
            # Emit D-Bus signal
1682
 
            mandos_dbus_service.ClientAdded(client.dbus_object_path,
1683
 
                                            client.GetAll(u""))
1684
 
        client.enable()
 
2433
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
 
2434
        # Need to initiate checking of clients
 
2435
        if client.enabled:
 
2436
            client.init_checker()
1685
2437
    
1686
2438
    tcp_server.enable()
1687
2439
    tcp_server.server_activate()
1689
2441
    # Find out what port we got
1690
2442
    service.port = tcp_server.socket.getsockname()[1]
1691
2443
    if use_ipv6:
1692
 
        logger.info(u"Now listening on address %r, port %d,"
 
2444
        logger.info("Now listening on address %r, port %d,"
1693
2445
                    " flowinfo %d, scope_id %d"
1694
2446
                    % tcp_server.socket.getsockname())
1695
2447
    else:                       # IPv4
1696
 
        logger.info(u"Now listening on address %r, port %d"
 
2448
        logger.info("Now listening on address %r, port %d"
1697
2449
                    % tcp_server.socket.getsockname())
1698
2450
    
1699
2451
    #service.interface = tcp_server.socket.getsockname()[3]
1702
2454
        # From the Avahi example code
1703
2455
        try:
1704
2456
            service.activate()
1705
 
        except dbus.exceptions.DBusException, error:
1706
 
            logger.critical(u"DBusException: %s", error)
 
2457
        except dbus.exceptions.DBusException as error:
 
2458
            logger.critical("DBusException: %s", error)
 
2459
            cleanup()
1707
2460
            sys.exit(1)
1708
2461
        # End of Avahi example code
1709
2462
        
1712
2465
                             (tcp_server.handle_request
1713
2466
                              (*args[2:], **kwargs) or True))
1714
2467
        
1715
 
        logger.debug(u"Starting main loop")
 
2468
        logger.debug("Starting main loop")
1716
2469
        main_loop.run()
1717
 
    except AvahiError, error:
1718
 
        logger.critical(u"AvahiError: %s", error)
 
2470
    except AvahiError as error:
 
2471
        logger.critical("AvahiError: %s", error)
 
2472
        cleanup()
1719
2473
        sys.exit(1)
1720
2474
    except KeyboardInterrupt:
1721
2475
        if debug:
1722
 
            print >> sys.stderr
1723
 
        logger.debug(u"Server received KeyboardInterrupt")
1724
 
    logger.debug(u"Server exiting")
 
2476
            print("", file=sys.stderr)
 
2477
        logger.debug("Server received KeyboardInterrupt")
 
2478
    logger.debug("Server exiting")
 
2479
    # Must run before the D-Bus bus name gets deregistered
 
2480
    cleanup()
 
2481
 
1725
2482
 
1726
2483
if __name__ == '__main__':
1727
2484
    main()