105
126
    max_renames: integer; maximum number of renames
 
106
127
    rename_count: integer; counter so we only rename after collisions
 
107
128
                  a sensible number of times
 
 
129
    group: D-Bus Entry Group
 
 
131
    bus: dbus.SystemBus()
 
109
133
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
 
110
 
                 type = None, port = None, TXT = None, domain = "",
 
111
 
                 host = "", max_renames = 32768):
 
 
134
                 servicetype = None, port = None, TXT = None,
 
 
135
                 domain = u"", host = u"", max_renames = 32768,
 
 
136
                 protocol = avahi.PROTO_UNSPEC, bus = None):
 
112
137
        self.interface = interface
 
 
139
        self.type = servicetype
 
 
141
        self.TXT = TXT if TXT is not None else []
 
120
142
        self.domain = domain
 
122
144
        self.rename_count = 0
 
123
145
        self.max_renames = max_renames
 
 
146
        self.protocol = protocol
 
 
147
        self.group = None       # our entry group
 
124
150
    def rename(self):
 
125
151
        """Derived from the Avahi example code"""
 
126
152
        if self.rename_count >= self.max_renames:
 
127
153
            logger.critical(u"No suitable Zeroconf service name found"
 
128
154
                            u" after %i retries, exiting.",
 
130
 
            raise AvahiServiceError("Too many renames")
 
131
 
        self.name = server.GetAlternativeServiceName(self.name)
 
 
156
            raise AvahiServiceError(u"Too many renames")
 
 
157
        self.name = self.server.GetAlternativeServiceName(self.name)
 
132
158
        logger.info(u"Changing Zeroconf service name to %r ...",
 
134
 
        syslogger.setFormatter(logging.Formatter\
 
135
 
                               ('Mandos (%s): %%(levelname)s:'
 
136
 
                               ' %%(message)s' % self.name))
 
 
160
        syslogger.setFormatter(logging.Formatter
 
 
161
                               (u'Mandos (%s) [%%(process)d]:'
 
 
162
                                u' %%(levelname)s: %%(message)s'
 
139
166
        self.rename_count += 1
 
140
167
    def remove(self):
 
141
168
        """Derived from the Avahi example code"""
 
142
 
        if group is not None:
 
 
169
        if self.group is not None:
 
145
172
        """Derived from the Avahi example code"""
 
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)
 
 
173
        if self.group is None:
 
 
174
            self.group = dbus.Interface(
 
 
175
                self.bus.get_object(avahi.DBUS_NAME,
 
 
176
                                    self.server.EntryGroupNew()),
 
 
177
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
 
 
178
            self.group.connect_to_signal('StateChanged',
 
 
180
                                         .entry_group_state_changed)
 
154
181
        logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
 
155
 
                     service.name, service.type)
 
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))
 
166
 
# From the Avahi example code:
 
167
 
group = None                            # our entry group
 
168
 
# End of Avahi example code
 
 
182
                     self.name, self.type)
 
 
183
        self.group.AddService(
 
 
186
            dbus.UInt32(0),     # flags
 
 
187
            self.name, self.type,
 
 
188
            self.domain, self.host,
 
 
189
            dbus.UInt16(self.port),
 
 
190
            avahi.string_array_to_txt_array(self.TXT))
 
 
192
    def entry_group_state_changed(self, state, error):
 
 
193
        """Derived from the Avahi example code"""
 
 
194
        logger.debug(u"Avahi state change: %i", state)
 
 
196
        if state == avahi.ENTRY_GROUP_ESTABLISHED:
 
 
197
            logger.debug(u"Zeroconf service established.")
 
 
198
        elif state == avahi.ENTRY_GROUP_COLLISION:
 
 
199
            logger.warning(u"Zeroconf service name collision.")
 
 
201
        elif state == avahi.ENTRY_GROUP_FAILURE:
 
 
202
            logger.critical(u"Avahi: Error in group state changed %s",
 
 
204
            raise AvahiGroupError(u"State changed: %s"
 
 
207
        """Derived from the Avahi example code"""
 
 
208
        if self.group is not None:
 
 
211
    def server_state_changed(self, state):
 
 
212
        """Derived from the Avahi example code"""
 
 
213
        if state == avahi.SERVER_COLLISION:
 
 
214
            logger.error(u"Zeroconf server name collision")
 
 
216
        elif state == avahi.SERVER_RUNNING:
 
 
219
        """Derived from the Avahi example code"""
 
 
220
        if self.server is None:
 
 
221
            self.server = dbus.Interface(
 
 
222
                self.bus.get_object(avahi.DBUS_NAME,
 
 
223
                                    avahi.DBUS_PATH_SERVER),
 
 
224
                avahi.DBUS_INTERFACE_SERVER)
 
 
225
        self.server.connect_to_signal(u"StateChanged",
 
 
226
                                 self.server_state_changed)
 
 
227
        self.server_state_changed(self.server.GetState())
 
171
230
class Client(object):
 
172
231
    """A representation of a client host served by this server.
 
174
 
    name:      string; from the config file, used in log messages
 
 
234
    name:       string; from the config file, used in log messages and
 
175
236
    fingerprint: string (40 or 32 hexadecimal digits); used to
 
176
237
                 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.
 
 
238
    secret:     bytestring; sent verbatim (over TLS) to client
 
 
239
    host:       string; available for use by the checker command
 
 
240
    created:    datetime.datetime(); (UTC) object creation
 
 
241
    last_enabled: datetime.datetime(); (UTC)
 
 
243
    last_checked_ok: datetime.datetime(); (UTC) or None
 
 
244
    timeout:    datetime.timedelta(); How long from last_checked_ok
 
 
245
                                      until this client is invalid
 
 
246
    interval:   datetime.timedelta(); How often to start a new checker
 
 
247
    disable_hook:  If set, called by disable() as disable_hook(self)
 
 
248
    checker:    subprocess.Popen(); a running checker process used
 
 
249
                                    to see if the client lives.
 
 
250
                                    'None' if no process is running.
 
188
251
    checker_initiator_tag: a gobject event source tag, or None
 
189
 
    stop_initiator_tag:    - '' -
 
 
252
    disable_initiator_tag: - '' -
 
190
253
    checker_callback_tag:  - '' -
 
191
254
    checker_command: string; External command which is run to check if
 
192
255
                     client lives.  %() expansions are done at
 
193
256
                     runtime with vars(self) as dict, so that for
 
194
257
                     instance %(name)s can be used in the command.
 
196
 
    _timeout: Real variable for 'timeout'
 
197
 
    _interval: Real variable for 'interval'
 
198
 
    _timeout_milliseconds: Used when calling gobject.timeout_add()
 
199
 
    _interval_milliseconds: - '' -
 
 
258
    current_checker_command: string; current running checker_command
 
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
 
209
 
    timeout = property(lambda self: self._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
 
219
 
                                       + (self.interval.microseconds
 
221
 
    interval = property(lambda self: self._interval,
 
224
 
    def __init__(self, name = None, stop_hook=None, config={}):
 
 
262
    def _timedelta_to_milliseconds(td):
 
 
263
        "Convert a datetime.timedelta() to milliseconds"
 
 
264
        return ((td.days * 24 * 60 * 60 * 1000)
 
 
265
                + (td.seconds * 1000)
 
 
266
                + (td.microseconds // 1000))
 
 
268
    def timeout_milliseconds(self):
 
 
269
        "Return the 'timeout' attribute in milliseconds"
 
 
270
        return self._timedelta_to_milliseconds(self.timeout)
 
 
272
    def interval_milliseconds(self):
 
 
273
        "Return the 'interval' attribute in milliseconds"
 
 
274
        return self._timedelta_to_milliseconds(self.interval)
 
 
276
    def __init__(self, name = None, disable_hook=None, config=None):
 
225
277
        """Note: the 'checker' key in 'config' sets the
 
226
278
        'checker_command' attribute and *not* the 'checker'
 
229
283
        logger.debug(u"Creating client %r", self.name)
 
230
284
        # Uppercase and remove spaces from fingerprint for later
 
231
285
        # comparison purposes with return value from the fingerprint()
 
233
 
        self.fingerprint = config["fingerprint"].upper()\
 
 
287
        self.fingerprint = (config[u"fingerprint"].upper()
 
235
289
        logger.debug(u"  Fingerprint: %s", self.fingerprint)
 
236
 
        if "secret" in config:
 
237
 
            self.secret = config["secret"].decode(u"base64")
 
238
 
        elif "secfile" in config:
 
239
 
            sf = open(config["secfile"])
 
240
 
            self.secret = sf.read()
 
 
290
        if u"secret" in config:
 
 
291
            self.secret = config[u"secret"].decode(u"base64")
 
 
292
        elif u"secfile" in config:
 
 
293
            with closing(open(os.path.expanduser
 
 
295
                               (config[u"secfile"])),
 
 
297
                self.secret = secfile.read()
 
243
299
            raise TypeError(u"No secret or secfile for client %s"
 
245
 
        self.host = config.get("host", "")
 
246
 
        self.created = datetime.datetime.now()
 
 
301
        self.host = config.get(u"host", u"")
 
 
302
        self.created = datetime.datetime.utcnow()
 
 
304
        self.last_enabled = None
 
247
305
        self.last_checked_ok = None
 
248
 
        self.timeout = string_to_delta(config["timeout"])
 
249
 
        self.interval = string_to_delta(config["interval"])
 
250
 
        self.stop_hook = stop_hook
 
 
306
        self.timeout = string_to_delta(config[u"timeout"])
 
 
307
        self.interval = string_to_delta(config[u"interval"])
 
 
308
        self.disable_hook = disable_hook
 
251
309
        self.checker = None
 
252
310
        self.checker_initiator_tag = None
 
253
 
        self.stop_initiator_tag = None
 
 
311
        self.disable_initiator_tag = None
 
254
312
        self.checker_callback_tag = None
 
255
 
        self.check_command = config["checker"]
 
 
313
        self.checker_command = config[u"checker"]
 
 
314
        self.current_checker_command = None
 
 
315
        self.last_connect = None
 
257
318
        """Start this client's checker and timeout hooks"""
 
 
319
        if getattr(self, u"enabled", False):
 
 
322
        self.last_enabled = datetime.datetime.utcnow()
 
258
323
        # Schedule a new checker to be started an 'interval' from now,
 
259
324
        # and every interval from then on.
 
260
 
        self.checker_initiator_tag = gobject.timeout_add\
 
261
 
                                     (self._interval_milliseconds,
 
 
325
        self.checker_initiator_tag = (gobject.timeout_add
 
 
326
                                      (self.interval_milliseconds(),
 
 
328
        # Schedule a disable() when 'timeout' has passed
 
 
329
        self.disable_initiator_tag = (gobject.timeout_add
 
 
330
                                   (self.timeout_milliseconds(),
 
263
333
        # Also start a new checker *right now*.
 
264
334
        self.start_checker()
 
265
 
        # Schedule a stop() when 'timeout' has passed
 
266
 
        self.stop_initiator_tag = gobject.timeout_add\
 
267
 
                                  (self._timeout_milliseconds,
 
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)
 
 
336
    def disable(self, quiet=True):
 
 
337
        """Disable this client."""
 
 
338
        if not getattr(self, "enabled", False):
 
279
 
        if getattr(self, "stop_initiator_tag", False):
 
280
 
            gobject.source_remove(self.stop_initiator_tag)
 
281
 
            self.stop_initiator_tag = None
 
282
 
        if getattr(self, "checker_initiator_tag", False):
 
 
341
            logger.info(u"Disabling client %s", self.name)
 
 
342
        if getattr(self, u"disable_initiator_tag", False):
 
 
343
            gobject.source_remove(self.disable_initiator_tag)
 
 
344
            self.disable_initiator_tag = None
 
 
345
        if getattr(self, u"checker_initiator_tag", False):
 
283
346
            gobject.source_remove(self.checker_initiator_tag)
 
284
347
            self.checker_initiator_tag = None
 
285
348
        self.stop_checker()
 
 
349
        if self.disable_hook:
 
 
350
            self.disable_hook(self)
 
288
352
        # Do not run this again if called by a gobject.timeout_add
 
290
355
    def __del__(self):
 
291
 
        self.stop_hook = None
 
293
 
    def checker_callback(self, pid, condition):
 
 
356
        self.disable_hook = None
 
 
359
    def checker_callback(self, pid, condition, command):
 
294
360
        """The checker has completed, so take appropriate actions."""
 
295
 
        now = datetime.datetime.now()
 
296
361
        self.checker_callback_tag = None
 
297
362
        self.checker = None
 
298
 
        if os.WIFEXITED(condition) \
 
299
 
               and (os.WEXITSTATUS(condition) == 0):
 
300
 
            logger.info(u"Checker for %(name)s succeeded",
 
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,
 
307
 
        elif not os.WIFEXITED(condition):
 
 
363
        if os.WIFEXITED(condition):
 
 
364
            exitstatus = os.WEXITSTATUS(condition)
 
 
366
                logger.info(u"Checker for %(name)s succeeded",
 
 
370
                logger.info(u"Checker for %(name)s failed",
 
308
373
            logger.warning(u"Checker for %(name)s crashed?",
 
311
 
            logger.info(u"Checker for %(name)s failed",
 
 
376
    def checked_ok(self):
 
 
377
        """Bump up the timeout for this client.
 
 
379
        This should only be called when the client has been seen,
 
 
382
        self.last_checked_ok = datetime.datetime.utcnow()
 
 
383
        gobject.source_remove(self.disable_initiator_tag)
 
 
384
        self.disable_initiator_tag = (gobject.timeout_add
 
 
385
                                      (self.timeout_milliseconds(),
 
313
388
    def start_checker(self):
 
314
389
        """Start a new checker subprocess if one is not running.
 
315
391
        If a checker already exists, leave it running and do
 
317
393
        # The reason for not killing a running checker is that if we
 
 
346
441
                # always replaced by /dev/null.)
 
347
442
                self.checker = subprocess.Popen(command,
 
350
 
                self.checker_callback_tag = gobject.child_watch_add\
 
352
 
                                             self.checker_callback)
 
 
444
                                                shell=True, cwd=u"/")
 
 
445
                self.checker_callback_tag = (gobject.child_watch_add
 
 
447
                                              self.checker_callback,
 
 
449
                # The checker may have completed before the gobject
 
 
450
                # watch was added.  Check for this.
 
 
451
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
 
 
453
                    gobject.source_remove(self.checker_callback_tag)
 
 
454
                    self.checker_callback(pid, status, command)
 
353
455
            except OSError, error:
 
354
456
                logger.error(u"Failed to start subprocess: %s",
 
356
458
        # Re-run this periodically if run by gobject.timeout_add
 
358
461
    def stop_checker(self):
 
359
462
        """Force the checker process, if any, to stop."""
 
360
463
        if self.checker_callback_tag:
 
361
464
            gobject.source_remove(self.checker_callback_tag)
 
362
465
            self.checker_callback_tag = None
 
363
 
        if getattr(self, "checker", None) is None:
 
 
466
        if getattr(self, u"checker", None) is None:
 
365
468
        logger.debug(u"Stopping checker for %(name)s", vars(self))
 
367
470
            os.kill(self.checker.pid, signal.SIGTERM)
 
369
472
            #if self.checker.poll() is None:
 
370
473
            #    os.kill(self.checker.pid, signal.SIGKILL)
 
371
474
        except OSError, error:
 
372
475
            if error.errno != errno.ESRCH: # No such process
 
374
477
        self.checker = None
 
375
479
    def still_valid(self):
 
376
480
        """Has the timeout not yet passed for this client?"""
 
377
 
        now = datetime.datetime.now()
 
 
481
        if not getattr(self, u"enabled", False):
 
 
483
        now = datetime.datetime.utcnow()
 
378
484
        if self.last_checked_ok is None:
 
379
485
            return now < (self.created + self.timeout)
 
381
487
            return now < (self.last_checked_ok + self.timeout)
 
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:
 
398
 
    return ctypes.string_at(cert.data, cert.size)
 
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\
 
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)
 
438
 
class tcp_handler(SocketServer.BaseRequestHandler, object):
 
439
 
    """A TCP request handler class.
 
440
 
    Instantiated by IPv6_TCPServer for each request to handle it.
 
 
490
def dbus_service_property(dbus_interface, signature=u"v",
 
 
491
                          access=u"readwrite", byte_arrays=False):
 
 
492
    """Decorators for marking methods of a DBusObjectWithProperties to
 
 
493
    become properties on the D-Bus.
 
 
495
    The decorated method will be called with no arguments by "Get"
 
 
496
    and with one argument by "Set".
 
 
498
    The parameters, where they are supported, are the same as
 
 
499
    dbus.service.method, except there is only "signature", since the
 
 
500
    type from Get() and the type sent to Set() is the same.
 
 
503
        func._dbus_is_property = True
 
 
504
        func._dbus_interface = dbus_interface
 
 
505
        func._dbus_signature = signature
 
 
506
        func._dbus_access = access
 
 
507
        func._dbus_name = func.__name__
 
 
508
        if func._dbus_name.endswith(u"_dbus_property"):
 
 
509
            func._dbus_name = func._dbus_name[:-14]
 
 
510
        func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
 
 
515
class DBusPropertyException(dbus.exceptions.DBusException):
 
 
516
    """A base class for D-Bus property-related exceptions
 
 
518
    def __unicode__(self):
 
 
519
        return unicode(str(self))
 
 
522
class DBusPropertyAccessException(DBusPropertyException):
 
 
523
    """A property's access permissions disallows an operation.
 
 
528
class DBusPropertyNotFound(DBusPropertyException):
 
 
529
    """An attempt was made to access a non-existing property.
 
 
534
class DBusObjectWithProperties(dbus.service.Object):
 
 
535
    """A D-Bus object with properties.
 
 
537
    Classes inheriting from this can use the dbus_service_property
 
 
538
    decorator to expose methods as D-Bus properties.  It exposes the
 
 
539
    standard Get(), Set(), and GetAll() methods on the D-Bus.
 
 
543
    def _is_dbus_property(obj):
 
 
544
        return getattr(obj, u"_dbus_is_property", False)
 
 
546
    def _get_all_dbus_properties(self):
 
 
547
        """Returns a generator of (name, attribute) pairs
 
 
549
        return ((prop._dbus_name, prop)
 
 
551
                inspect.getmembers(self, self._is_dbus_property))
 
 
553
    def _get_dbus_property(self, interface_name, property_name):
 
 
554
        """Returns a bound method if one exists which is a D-Bus
 
 
555
        property with the specified name and interface.
 
 
557
        for name in (property_name,
 
 
558
                     property_name + u"_dbus_property"):
 
 
559
            prop = getattr(self, name, None)
 
 
561
                or not self._is_dbus_property(prop)
 
 
562
                or prop._dbus_name != property_name
 
 
563
                or (interface_name and prop._dbus_interface
 
 
564
                    and interface_name != prop._dbus_interface)):
 
 
568
        raise DBusPropertyNotFound(self.dbus_object_path + u":"
 
 
569
                                   + interface_name + u"."
 
 
572
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
 
 
574
    def Get(self, interface_name, property_name):
 
 
575
        """Standard D-Bus property Get() method, see D-Bus standard.
 
 
577
        prop = self._get_dbus_property(interface_name, property_name)
 
 
578
        if prop._dbus_access == u"write":
 
 
579
            raise DBusPropertyAccessException(property_name)
 
 
581
        if not hasattr(value, u"variant_level"):
 
 
583
        return type(value)(value, variant_level=value.variant_level+1)
 
 
585
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
 
 
586
    def Set(self, interface_name, property_name, value):
 
 
587
        """Standard D-Bus property Set() method, see D-Bus standard.
 
 
589
        prop = self._get_dbus_property(interface_name, property_name)
 
 
590
        if prop._dbus_access == u"read":
 
 
591
            raise DBusPropertyAccessException(property_name)
 
 
592
        if prop._dbus_get_args_options[u"byte_arrays"]:
 
 
593
            value = dbus.ByteArray(''.join(unichr(byte)
 
 
597
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
 
 
598
                         out_signature=u"a{sv}")
 
 
599
    def GetAll(self, interface_name):
 
 
600
        """Standard D-Bus property GetAll() method, see D-Bus
 
 
603
        Note: Will not include properties with access="write".
 
 
606
        for name, prop in self._get_all_dbus_properties():
 
 
608
                and interface_name != prop._dbus_interface):
 
 
609
                # Interface non-empty but did not match
 
 
611
            # Ignore write-only properties
 
 
612
            if prop._dbus_access == u"write":
 
 
615
            if not hasattr(value, u"variant_level"):
 
 
618
            all[name] = type(value)(value, variant_level=
 
 
619
                                    value.variant_level+1)
 
 
620
        return dbus.Dictionary(all, signature=u"sv")
 
 
622
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
 
 
624
                         path_keyword='object_path',
 
 
625
                         connection_keyword='connection')
 
 
626
    def Introspect(self, object_path, connection):
 
 
627
        """Standard D-Bus method, overloaded to insert property tags.
 
 
629
        xmlstring = dbus.service.Object.Introspect(self, object_path,
 
 
632
            document = xml.dom.minidom.parseString(xmlstring)
 
 
633
            def make_tag(document, name, prop):
 
 
634
                e = document.createElement(u"property")
 
 
635
                e.setAttribute(u"name", name)
 
 
636
                e.setAttribute(u"type", prop._dbus_signature)
 
 
637
                e.setAttribute(u"access", prop._dbus_access)
 
 
639
            for if_tag in document.getElementsByTagName(u"interface"):
 
 
640
                for tag in (make_tag(document, name, prop)
 
 
642
                            in self._get_all_dbus_properties()
 
 
643
                            if prop._dbus_interface
 
 
644
                            == if_tag.getAttribute(u"name")):
 
 
645
                    if_tag.appendChild(tag)
 
 
646
                # Add the names to the return values for the
 
 
647
                # "org.freedesktop.DBus.Properties" methods
 
 
648
                if (if_tag.getAttribute(u"name")
 
 
649
                    == u"org.freedesktop.DBus.Properties"):
 
 
650
                    for cn in if_tag.getElementsByTagName(u"method"):
 
 
651
                        if cn.getAttribute(u"name") == u"Get":
 
 
652
                            for arg in cn.getElementsByTagName(u"arg"):
 
 
653
                                if (arg.getAttribute(u"direction")
 
 
655
                                    arg.setAttribute(u"name", u"value")
 
 
656
                        elif cn.getAttribute(u"name") == u"GetAll":
 
 
657
                            for arg in cn.getElementsByTagName(u"arg"):
 
 
658
                                if (arg.getAttribute(u"direction")
 
 
660
                                    arg.setAttribute(u"name", u"props")
 
 
661
            xmlstring = document.toxml(u"utf-8")
 
 
663
        except (AttributeError, xml.dom.DOMException,
 
 
664
                xml.parsers.expat.ExpatError), error:
 
 
665
            logger.error(u"Failed to override Introspection method",
 
 
670
class ClientDBus(Client, DBusObjectWithProperties):
 
 
671
    """A Client class using D-Bus
 
 
674
    dbus_object_path: dbus.ObjectPath
 
 
675
    bus: dbus.SystemBus()
 
 
677
    # dbus.service.Object doesn't use super(), so we can't either.
 
 
679
    def __init__(self, bus = None, *args, **kwargs):
 
 
681
        Client.__init__(self, *args, **kwargs)
 
 
682
        # Only now, when this client is initialized, can it show up on
 
 
684
        self.dbus_object_path = (dbus.ObjectPath
 
 
686
                                  + self.name.replace(u".", u"_")))
 
 
687
        DBusObjectWithProperties.__init__(self, self.bus,
 
 
688
                                          self.dbus_object_path)
 
 
691
    def _datetime_to_dbus(dt, variant_level=0):
 
 
692
        """Convert a UTC datetime.datetime() to a D-Bus type."""
 
 
693
        return dbus.String(dt.isoformat(),
 
 
694
                           variant_level=variant_level)
 
 
697
        oldstate = getattr(self, u"enabled", False)
 
 
698
        r = Client.enable(self)
 
 
699
        if oldstate != self.enabled:
 
 
701
            self.PropertyChanged(dbus.String(u"enabled"),
 
 
702
                                 dbus.Boolean(True, variant_level=1))
 
 
703
            self.PropertyChanged(
 
 
704
                dbus.String(u"last_enabled"),
 
 
705
                self._datetime_to_dbus(self.last_enabled,
 
 
709
    def disable(self, quiet = False):
 
 
710
        oldstate = getattr(self, u"enabled", False)
 
 
711
        r = Client.disable(self, quiet=quiet)
 
 
712
        if not quiet and oldstate != self.enabled:
 
 
714
            self.PropertyChanged(dbus.String(u"enabled"),
 
 
715
                                 dbus.Boolean(False, variant_level=1))
 
 
718
    def __del__(self, *args, **kwargs):
 
 
720
            self.remove_from_connection()
 
 
723
        if hasattr(DBusObjectWithProperties, u"__del__"):
 
 
724
            DBusObjectWithProperties.__del__(self, *args, **kwargs)
 
 
725
        Client.__del__(self, *args, **kwargs)
 
 
727
    def checker_callback(self, pid, condition, command,
 
 
729
        self.checker_callback_tag = None
 
 
732
        self.PropertyChanged(dbus.String(u"checker_running"),
 
 
733
                             dbus.Boolean(False, variant_level=1))
 
 
734
        if os.WIFEXITED(condition):
 
 
735
            exitstatus = os.WEXITSTATUS(condition)
 
 
737
            self.CheckerCompleted(dbus.Int16(exitstatus),
 
 
738
                                  dbus.Int64(condition),
 
 
739
                                  dbus.String(command))
 
 
742
            self.CheckerCompleted(dbus.Int16(-1),
 
 
743
                                  dbus.Int64(condition),
 
 
744
                                  dbus.String(command))
 
 
746
        return Client.checker_callback(self, pid, condition, command,
 
 
749
    def checked_ok(self, *args, **kwargs):
 
 
750
        r = Client.checked_ok(self, *args, **kwargs)
 
 
752
        self.PropertyChanged(
 
 
753
            dbus.String(u"last_checked_ok"),
 
 
754
            (self._datetime_to_dbus(self.last_checked_ok,
 
 
758
    def start_checker(self, *args, **kwargs):
 
 
759
        old_checker = self.checker
 
 
760
        if self.checker is not None:
 
 
761
            old_checker_pid = self.checker.pid
 
 
763
            old_checker_pid = None
 
 
764
        r = Client.start_checker(self, *args, **kwargs)
 
 
765
        # Only if new checker process was started
 
 
766
        if (self.checker is not None
 
 
767
            and old_checker_pid != self.checker.pid):
 
 
769
            self.CheckerStarted(self.current_checker_command)
 
 
770
            self.PropertyChanged(
 
 
771
                dbus.String(u"checker_running"),
 
 
772
                dbus.Boolean(True, variant_level=1))
 
 
775
    def stop_checker(self, *args, **kwargs):
 
 
776
        old_checker = getattr(self, u"checker", None)
 
 
777
        r = Client.stop_checker(self, *args, **kwargs)
 
 
778
        if (old_checker is not None
 
 
779
            and getattr(self, u"checker", None) is None):
 
 
780
            self.PropertyChanged(dbus.String(u"checker_running"),
 
 
781
                                 dbus.Boolean(False, variant_level=1))
 
 
784
    ## D-Bus methods & signals
 
 
785
    _interface = u"se.bsnet.fukt.Mandos.Client"
 
 
788
    @dbus.service.method(_interface)
 
 
790
        return self.checked_ok()
 
 
792
    # CheckerCompleted - signal
 
 
793
    @dbus.service.signal(_interface, signature=u"nxs")
 
 
794
    def CheckerCompleted(self, exitcode, waitstatus, command):
 
 
798
    # CheckerStarted - signal
 
 
799
    @dbus.service.signal(_interface, signature=u"s")
 
 
800
    def CheckerStarted(self, command):
 
 
804
    # PropertyChanged - signal
 
 
805
    @dbus.service.signal(_interface, signature=u"sv")
 
 
806
    def PropertyChanged(self, property, value):
 
 
811
    @dbus.service.signal(_interface)
 
 
817
    @dbus.service.signal(_interface)
 
 
823
    @dbus.service.method(_interface)
 
 
828
    # StartChecker - method
 
 
829
    @dbus.service.method(_interface)
 
 
830
    def StartChecker(self):
 
 
835
    @dbus.service.method(_interface)
 
 
840
    # StopChecker - method
 
 
841
    @dbus.service.method(_interface)
 
 
842
    def StopChecker(self):
 
 
846
    @dbus_service_property(_interface, signature=u"s", access=u"read")
 
 
847
    def name_dbus_property(self):
 
 
848
        return dbus.String(self.name)
 
 
850
    # fingerprint - property
 
 
851
    @dbus_service_property(_interface, signature=u"s", access=u"read")
 
 
852
    def fingerprint_dbus_property(self):
 
 
853
        return dbus.String(self.fingerprint)
 
 
856
    @dbus_service_property(_interface, signature=u"s",
 
 
858
    def host_dbus_property(self, value=None):
 
 
859
        if value is None:       # get
 
 
860
            return dbus.String(self.host)
 
 
863
        self.PropertyChanged(dbus.String(u"host"),
 
 
864
                             dbus.String(value, variant_level=1))
 
 
867
    @dbus_service_property(_interface, signature=u"s", access=u"read")
 
 
868
    def created_dbus_property(self):
 
 
869
        return dbus.String(self._datetime_to_dbus(self.created))
 
 
871
    # last_enabled - property
 
 
872
    @dbus_service_property(_interface, signature=u"s", access=u"read")
 
 
873
    def last_enabled_dbus_property(self):
 
 
874
        if self.last_enabled is None:
 
 
875
            return dbus.String(u"")
 
 
876
        return dbus.String(self._datetime_to_dbus(self.last_enabled))
 
 
879
    @dbus_service_property(_interface, signature=u"b",
 
 
881
    def enabled_dbus_property(self, value=None):
 
 
882
        if value is None:       # get
 
 
883
            return dbus.Boolean(self.enabled)
 
 
889
    # last_checked_ok - property
 
 
890
    @dbus_service_property(_interface, signature=u"s",
 
 
892
    def last_checked_ok_dbus_property(self, value=None):
 
 
893
        if value is not None:
 
 
896
        if self.last_checked_ok is None:
 
 
897
            return dbus.String(u"")
 
 
898
        return dbus.String(self._datetime_to_dbus(self
 
 
902
    @dbus_service_property(_interface, signature=u"t",
 
 
904
    def timeout_dbus_property(self, value=None):
 
 
905
        if value is None:       # get
 
 
906
            return dbus.UInt64(self.timeout_milliseconds())
 
 
907
        self.timeout = datetime.timedelta(0, 0, 0, value)
 
 
909
        self.PropertyChanged(dbus.String(u"timeout"),
 
 
910
                             dbus.UInt64(value, variant_level=1))
 
 
911
        if getattr(self, u"disable_initiator_tag", None) is None:
 
 
914
        gobject.source_remove(self.disable_initiator_tag)
 
 
915
        self.disable_initiator_tag = None
 
 
917
                       _timedelta_to_milliseconds((self
 
 
923
            # The timeout has passed
 
 
926
            self.disable_initiator_tag = (gobject.timeout_add
 
 
927
                                          (time_to_die, self.disable))
 
 
929
    # interval - property
 
 
930
    @dbus_service_property(_interface, signature=u"t",
 
 
932
    def interval_dbus_property(self, value=None):
 
 
933
        if value is None:       # get
 
 
934
            return dbus.UInt64(self.interval_milliseconds())
 
 
935
        self.interval = datetime.timedelta(0, 0, 0, value)
 
 
937
        self.PropertyChanged(dbus.String(u"interval"),
 
 
938
                             dbus.UInt64(value, variant_level=1))
 
 
939
        if getattr(self, u"checker_initiator_tag", None) is None:
 
 
941
        # Reschedule checker run
 
 
942
        gobject.source_remove(self.checker_initiator_tag)
 
 
943
        self.checker_initiator_tag = (gobject.timeout_add
 
 
944
                                      (value, self.start_checker))
 
 
945
        self.start_checker()    # Start one now, too
 
 
948
    @dbus_service_property(_interface, signature=u"s",
 
 
950
    def checker_dbus_property(self, value=None):
 
 
951
        if value is None:       # get
 
 
952
            return dbus.String(self.checker_command)
 
 
953
        self.checker_command = value
 
 
955
        self.PropertyChanged(dbus.String(u"checker"),
 
 
956
                             dbus.String(self.checker_command,
 
 
959
    # checker_running - property
 
 
960
    @dbus_service_property(_interface, signature=u"b",
 
 
962
    def checker_running_dbus_property(self, value=None):
 
 
963
        if value is None:       # get
 
 
964
            return dbus.Boolean(self.checker is not None)
 
 
970
    # object_path - property
 
 
971
    @dbus_service_property(_interface, signature=u"o", access=u"read")
 
 
972
    def object_path_dbus_property(self):
 
 
973
        return self.dbus_object_path # is already a dbus.ObjectPath
 
 
976
    @dbus_service_property(_interface, signature=u"ay",
 
 
977
                           access=u"write", byte_arrays=True)
 
 
978
    def secret_dbus_property(self, value):
 
 
979
        self.secret = str(value)
 
 
984
class ClientHandler(socketserver.BaseRequestHandler, object):
 
 
985
    """A class to handle client connections.
 
 
987
    Instantiated once for each connection to handle it.
 
441
988
    Note: This will run in its own forked process."""
 
443
990
    def handle(self):
 
444
991
        logger.info(u"TCP connection from: %s",
 
445
 
                     unicode(self.client_address))
 
446
 
        session = gnutls.connection.ClientSession\
 
447
 
                  (self.request, gnutls.connection.X509Credentials())
 
449
 
        line = self.request.makefile().readline()
 
450
 
        logger.debug(u"Protocol version: %r", line)
 
452
 
            if int(line.strip().split()[0]) > 1:
 
454
 
        except (ValueError, IndexError, RuntimeError), error:
 
455
 
            logger.error(u"Unknown protocol version: %s", error)
 
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.
 
463
 
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
 
464
 
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
 
466
 
        priority = "NORMAL"             # Fallback default, since this
 
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);
 
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.
 
481
 
            fpr = fingerprint(peer_certificate(session))
 
482
 
        except (TypeError, gnutls.errors.GNUTLSError), error:
 
483
 
            logger.warning(u"Bad certificate: %s", error)
 
486
 
        logger.debug(u"Fingerprint: %s", fpr)
 
488
 
        for c in self.server.clients:
 
489
 
            if c.fingerprint == fpr:
 
493
 
            logger.warning(u"Client not found for fingerprint: %s",
 
497
 
        # Have to check if client.still_valid(), since it is possible
 
498
 
        # that the client timed out while establishing the GnuTLS
 
500
 
        if not client.still_valid():
 
501
 
            logger.warning(u"Client %(name)s is invalid",
 
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))
 
515
 
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
 
516
 
    """IPv6 TCP server.  Accepts 'None' as address and/or port.
 
 
992
                    unicode(self.client_address))
 
 
993
        logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
 
 
994
        # Open IPC pipe to parent process
 
 
995
        with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
 
 
996
            session = (gnutls.connection
 
 
997
                       .ClientSession(self.request,
 
 
1001
            line = self.request.makefile().readline()
 
 
1002
            logger.debug(u"Protocol version: %r", line)
 
 
1004
                if int(line.strip().split()[0]) > 1:
 
 
1006
            except (ValueError, IndexError, RuntimeError), error:
 
 
1007
                logger.error(u"Unknown protocol version: %s", error)
 
 
1010
            # Note: gnutls.connection.X509Credentials is really a
 
 
1011
            # generic GnuTLS certificate credentials object so long as
 
 
1012
            # no X.509 keys are added to it.  Therefore, we can use it
 
 
1013
            # here despite using OpenPGP certificates.
 
 
1015
            #priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
 
 
1016
            #                      u"+AES-256-CBC", u"+SHA1",
 
 
1017
            #                      u"+COMP-NULL", u"+CTYPE-OPENPGP",
 
 
1019
            # Use a fallback default, since this MUST be set.
 
 
1020
            priority = self.server.gnutls_priority
 
 
1021
            if priority is None:
 
 
1022
                priority = u"NORMAL"
 
 
1023
            (gnutls.library.functions
 
 
1024
             .gnutls_priority_set_direct(session._c_object,
 
 
1029
            except gnutls.errors.GNUTLSError, error:
 
 
1030
                logger.warning(u"Handshake failed: %s", error)
 
 
1031
                # Do not run session.bye() here: the session is not
 
 
1032
                # established.  Just abandon the request.
 
 
1034
            logger.debug(u"Handshake succeeded")
 
 
1036
                fpr = self.fingerprint(self.peer_certificate(session))
 
 
1037
            except (TypeError, gnutls.errors.GNUTLSError), error:
 
 
1038
                logger.warning(u"Bad certificate: %s", error)
 
 
1041
            logger.debug(u"Fingerprint: %s", fpr)
 
 
1043
            for c in self.server.clients:
 
 
1044
                if c.fingerprint == fpr:
 
 
1048
                ipc.write(u"NOTFOUND %s %s\n"
 
 
1049
                          % (fpr, unicode(self.client_address)))
 
 
1052
            # Have to check if client.still_valid(), since it is
 
 
1053
            # possible that the client timed out while establishing
 
 
1054
            # the GnuTLS session.
 
 
1055
            if not client.still_valid():
 
 
1056
                ipc.write(u"INVALID %s\n" % client.name)
 
 
1059
            ipc.write(u"SENDING %s\n" % client.name)
 
 
1061
            while sent_size < len(client.secret):
 
 
1062
                sent = session.send(client.secret[sent_size:])
 
 
1063
                logger.debug(u"Sent: %d, remaining: %d",
 
 
1064
                             sent, len(client.secret)
 
 
1065
                             - (sent_size + sent))
 
 
1070
    def peer_certificate(session):
 
 
1071
        "Return the peer's OpenPGP certificate as a bytestring"
 
 
1072
        # If not an OpenPGP certificate...
 
 
1073
        if (gnutls.library.functions
 
 
1074
            .gnutls_certificate_type_get(session._c_object)
 
 
1075
            != gnutls.library.constants.GNUTLS_CRT_OPENPGP):
 
 
1076
            # ...do the normal thing
 
 
1077
            return session.peer_certificate
 
 
1078
        list_size = ctypes.c_uint(1)
 
 
1079
        cert_list = (gnutls.library.functions
 
 
1080
                     .gnutls_certificate_get_peers
 
 
1081
                     (session._c_object, ctypes.byref(list_size)))
 
 
1082
        if not bool(cert_list) and list_size.value != 0:
 
 
1083
            raise gnutls.errors.GNUTLSError(u"error getting peer"
 
 
1085
        if list_size.value == 0:
 
 
1088
        return ctypes.string_at(cert.data, cert.size)
 
 
1091
    def fingerprint(openpgp):
 
 
1092
        "Convert an OpenPGP bytestring to a hexdigit fingerprint"
 
 
1093
        # New GnuTLS "datum" with the OpenPGP public key
 
 
1094
        datum = (gnutls.library.types
 
 
1095
                 .gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
 
 
1098
                                 ctypes.c_uint(len(openpgp))))
 
 
1099
        # New empty GnuTLS certificate
 
 
1100
        crt = gnutls.library.types.gnutls_openpgp_crt_t()
 
 
1101
        (gnutls.library.functions
 
 
1102
         .gnutls_openpgp_crt_init(ctypes.byref(crt)))
 
 
1103
        # Import the OpenPGP public key into the certificate
 
 
1104
        (gnutls.library.functions
 
 
1105
         .gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
 
 
1106
                                    gnutls.library.constants
 
 
1107
                                    .GNUTLS_OPENPGP_FMT_RAW))
 
 
1108
        # Verify the self signature in the key
 
 
1109
        crtverify = ctypes.c_uint()
 
 
1110
        (gnutls.library.functions
 
 
1111
         .gnutls_openpgp_crt_verify_self(crt, 0,
 
 
1112
                                         ctypes.byref(crtverify)))
 
 
1113
        if crtverify.value != 0:
 
 
1114
            gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
 
 
1115
            raise (gnutls.errors.CertificateSecurityError
 
 
1117
        # New buffer for the fingerprint
 
 
1118
        buf = ctypes.create_string_buffer(20)
 
 
1119
        buf_len = ctypes.c_size_t()
 
 
1120
        # Get the fingerprint from the certificate into the buffer
 
 
1121
        (gnutls.library.functions
 
 
1122
         .gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
 
 
1123
                                             ctypes.byref(buf_len)))
 
 
1124
        # Deinit the certificate
 
 
1125
        gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
 
 
1126
        # Convert the buffer to a Python bytestring
 
 
1127
        fpr = ctypes.string_at(buf, buf_len.value)
 
 
1128
        # Convert the bytestring to hexadecimal notation
 
 
1129
        hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
 
 
1133
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
 
 
1134
    """Like socketserver.ForkingMixIn, but also pass a pipe."""
 
 
1135
    def process_request(self, request, client_address):
 
 
1136
        """Overrides and wraps the original process_request().
 
 
1138
        This function creates a new pipe in self.pipe
 
 
1140
        self.pipe = os.pipe()
 
 
1141
        super(ForkingMixInWithPipe,
 
 
1142
              self).process_request(request, client_address)
 
 
1143
        os.close(self.pipe[1])  # close write end
 
 
1144
        self.add_pipe(self.pipe[0])
 
 
1145
    def add_pipe(self, pipe):
 
 
1146
        """Dummy function; override as necessary"""
 
 
1150
class IPv6_TCPServer(ForkingMixInWithPipe,
 
 
1151
                     socketserver.TCPServer, object):
 
 
1152
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
 
518
 
        settings:       Server settings
 
519
 
        clients:        Set() of Client objects
 
 
1155
        enabled:        Boolean; whether this server is activated yet
 
 
1156
        interface:      None or a network interface name (string)
 
 
1157
        use_ipv6:       Boolean; to use IPv6 or not
 
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)
 
 
1159
    def __init__(self, server_address, RequestHandlerClass,
 
 
1160
                 interface=None, use_ipv6=True):
 
 
1161
        self.interface = interface
 
 
1163
            self.address_family = socket.AF_INET6
 
 
1164
        socketserver.TCPServer.__init__(self, server_address,
 
 
1165
                                        RequestHandlerClass)
 
530
1166
    def server_bind(self):
 
531
1167
        """This overrides the normal server_bind() function
 
532
1168
        to bind to an interface if one was specified, and also NOT to
 
533
1169
        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)
 
538
 
                self.socket.setsockopt(socket.SOL_SOCKET,
 
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"])
 
 
1170
        if self.interface is not None:
 
 
1171
            if SO_BINDTODEVICE is None:
 
 
1172
                logger.error(u"SO_BINDTODEVICE does not exist;"
 
 
1173
                             u" cannot bind to interface %s",
 
 
1177
                    self.socket.setsockopt(socket.SOL_SOCKET,
 
 
1181
                except socket.error, error:
 
 
1182
                    if error[0] == errno.EPERM:
 
 
1183
                        logger.error(u"No permission to"
 
 
1184
                                     u" bind to interface %s",
 
 
1186
                    elif error[0] == errno.ENOPROTOOPT:
 
 
1187
                        logger.error(u"SO_BINDTODEVICE not available;"
 
 
1188
                                     u" cannot bind to interface %s",
 
548
1192
        # Only bind(2) the socket if we really need to.
 
549
1193
        if self.server_address[0] or self.server_address[1]:
 
550
1194
            if not self.server_address[0]:
 
552
 
                self.server_address = (in6addr_any,
 
 
1195
                if self.address_family == socket.AF_INET6:
 
 
1196
                    any_address = u"::" # in6addr_any
 
 
1198
                    any_address = socket.INADDR_ANY
 
 
1199
                self.server_address = (any_address,
 
553
1200
                                       self.server_address[1])
 
554
1201
            elif not self.server_address[1]:
 
555
1202
                self.server_address = (self.server_address[0],
 
557
 
#                 if self.settings["interface"]:
 
 
1204
#                 if self.interface:
 
558
1205
#                     self.server_address = (self.server_address[0],
 
561
1208
#                                            if_nametoindex
 
564
 
            return super(type(self), self).server_bind()
 
 
1210
            return socketserver.TCPServer.server_bind(self)
 
 
1213
class MandosServer(IPv6_TCPServer):
 
 
1217
        clients:        set of Client objects
 
 
1218
        gnutls_priority GnuTLS priority string
 
 
1219
        use_dbus:       Boolean; to emit D-Bus signals or not
 
 
1221
    Assumes a gobject.MainLoop event loop.
 
 
1223
    def __init__(self, server_address, RequestHandlerClass,
 
 
1224
                 interface=None, use_ipv6=True, clients=None,
 
 
1225
                 gnutls_priority=None, use_dbus=True):
 
 
1226
        self.enabled = False
 
 
1227
        self.clients = clients
 
 
1228
        if self.clients is None:
 
 
1229
            self.clients = set()
 
 
1230
        self.use_dbus = use_dbus
 
 
1231
        self.gnutls_priority = gnutls_priority
 
 
1232
        IPv6_TCPServer.__init__(self, server_address,
 
 
1233
                                RequestHandlerClass,
 
 
1234
                                interface = interface,
 
 
1235
                                use_ipv6 = use_ipv6)
 
 
1236
    def server_activate(self):
 
 
1238
            return socketserver.TCPServer.server_activate(self)
 
 
1241
    def add_pipe(self, pipe):
 
 
1242
        # Call "handle_ipc" for both data and EOF events
 
 
1243
        gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
 
 
1245
    def handle_ipc(self, source, condition, file_objects={}):
 
 
1247
            gobject.IO_IN: u"IN",   # There is data to read.
 
 
1248
            gobject.IO_OUT: u"OUT", # Data can be written (without
 
 
1250
            gobject.IO_PRI: u"PRI", # There is urgent data to read.
 
 
1251
            gobject.IO_ERR: u"ERR", # Error condition.
 
 
1252
            gobject.IO_HUP: u"HUP"  # Hung up (the connection has been
 
 
1253
                                    # broken, usually for pipes and
 
 
1256
        conditions_string = ' | '.join(name
 
 
1258
                                       condition_names.iteritems()
 
 
1259
                                       if cond & condition)
 
 
1260
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
 
 
1263
        # Turn the pipe file descriptor into a Python file object
 
 
1264
        if source not in file_objects:
 
 
1265
            file_objects[source] = os.fdopen(source, u"r", 1)
 
 
1267
        # Read a line from the file object
 
 
1268
        cmdline = file_objects[source].readline()
 
 
1269
        if not cmdline:             # Empty line means end of file
 
 
1270
            # close the IPC pipe
 
 
1271
            file_objects[source].close()
 
 
1272
            del file_objects[source]
 
 
1274
            # Stop calling this function
 
 
1277
        logger.debug(u"IPC command: %r", cmdline)
 
 
1279
        # Parse and act on command
 
 
1280
        cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
 
 
1282
        if cmd == u"NOTFOUND":
 
 
1283
            fpr, address = args.split(None, 1)
 
 
1284
            logger.warning(u"Client not found for fingerprint: %s, ad"
 
 
1285
                           u"dress: %s", fpr, address)
 
 
1288
                mandos_dbus_service.ClientNotFound(fpr, address)
 
 
1289
        elif cmd == u"INVALID":
 
 
1290
            for client in self.clients:
 
 
1291
                if client.name == args:
 
 
1292
                    logger.warning(u"Client %s is invalid", args)
 
 
1298
                logger.error(u"Unknown client %s is invalid", args)
 
 
1299
        elif cmd == u"SENDING":
 
 
1300
            for client in self.clients:
 
 
1301
                if client.name == args:
 
 
1302
                    logger.info(u"Sending secret to %s", client.name)
 
 
1309
                logger.error(u"Sending secret to unknown client %s",
 
 
1312
            logger.error(u"Unknown IPC command: %r", cmdline)
 
 
1314
        # Keep calling this function
 
567
1318
def string_to_delta(interval):
 
568
1319
    """Parse a string and return a datetime.timedelta
 
570
 
    >>> string_to_delta('7d')
 
 
1321
    >>> string_to_delta(u'7d')
 
571
1322
    datetime.timedelta(7)
 
572
 
    >>> string_to_delta('60s')
 
 
1323
    >>> string_to_delta(u'60s')
 
573
1324
    datetime.timedelta(0, 60)
 
574
 
    >>> string_to_delta('60m')
 
 
1325
    >>> string_to_delta(u'60m')
 
575
1326
    datetime.timedelta(0, 3600)
 
576
 
    >>> string_to_delta('24h')
 
 
1327
    >>> string_to_delta(u'24h')
 
577
1328
    datetime.timedelta(1)
 
578
1329
    >>> string_to_delta(u'1w')
 
579
1330
    datetime.timedelta(7)
 
580
 
    >>> string_to_delta('5m 30s')
 
 
1331
    >>> string_to_delta(u'5m 30s')
 
581
1332
    datetime.timedelta(0, 330)
 
583
1334
    timevalue = datetime.timedelta(0)
 
584
1335
    for s in interval.split():
 
586
 
            suffix=unicode(s[-1])
 
 
1337
            suffix = unicode(s[-1])
 
588
1339
            if suffix == u"d":
 
589
1340
                delta = datetime.timedelta(value)
 
590
1341
            elif suffix == u"s":