/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: teddy at bsnet
  • Date: 2011-11-12 18:15:29 UTC
  • mfrom: (521.1.1 doc-filename-class)
  • Revision ID: teddy@fukt.bsnet.se-20111112181529-v4emlw0v5fugv2tk
Merge doc markup fixes.

Show diffs side-by-side

added added

removed removed

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