/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos

todo

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