/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Björn Påhlsson
  • Date: 2011-11-24 19:27:53 UTC
  • mto: (518.2.5 persistent-state-gpgme)
  • mto: This revision was merged to the branch mainline in revision 524.
  • Revision ID: belorn@fukt.bsnet.se-20111124192753-y5jxlc1h3tcxpubc
First run of python-lint. Fixed some *obviously* bad code and turned
them into good code.

Show diffs side-by-side

added added

removed removed

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