/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Björn Påhlsson
  • Date: 2010-09-02 18:53:38 UTC
  • mto: (237.4.3 mandos-release)
  • mto: This revision was merged to the branch mainline in revision 421.
  • Revision ID: belorn@fukt.bsnet.se-20100902185338-d1022sv517txn3sb
early commit to ease todays coding

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