/mandos/release

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2011-11-29 20:58:01 UTC
  • mto: (237.12.13 mandos-persistent)
  • mto: This revision was merged to the branch mainline in revision 290.
  • Revision ID: teddy@recompile.se-20111129205801-oufxx7hwq0nq2zau
* mandos (main): Bug fix: Syntax fix when restoring settings.

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