104
126
    max_renames: integer; maximum number of renames
 
105
127
    rename_count: integer; counter so we only rename after collisions
 
106
128
                  a sensible number of times
 
 
129
    group: D-Bus Entry Group
 
 
131
    bus: dbus.SystemBus()
 
108
133
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
 
109
 
                 type = None, port = None, TXT = None, domain = "",
 
110
 
                 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):
 
111
137
        self.interface = interface
 
 
139
        self.type = servicetype
 
 
141
        self.TXT = TXT if TXT is not None else []
 
119
142
        self.domain = domain
 
121
144
        self.rename_count = 0
 
122
145
        self.max_renames = max_renames
 
 
146
        self.protocol = protocol
 
 
147
        self.group = None       # our entry group
 
123
150
    def rename(self):
 
124
151
        """Derived from the Avahi example code"""
 
125
152
        if self.rename_count >= self.max_renames:
 
126
 
            logger.critical(u"No suitable service name found after %i"
 
127
 
                            u" retries, exiting.", rename_count)
 
128
 
            raise AvahiServiceError("Too many renames")
 
129
 
        self.name = server.GetAlternativeServiceName(self.name)
 
130
 
        logger.info(u"Changing name to %r ...", str(self.name))
 
131
 
        syslogger.setFormatter(logging.Formatter\
 
132
 
                               ('Mandos (%s): %%(levelname)s:'
 
133
 
                               ' %%(message)s' % self.name))
 
 
153
            logger.critical(u"No suitable Zeroconf service name found"
 
 
154
                            u" after %i retries, exiting.",
 
 
156
            raise AvahiServiceError(u"Too many renames")
 
 
157
        self.name = self.server.GetAlternativeServiceName(self.name)
 
 
158
        logger.info(u"Changing Zeroconf service name to %r ...",
 
 
160
        syslogger.setFormatter(logging.Formatter
 
 
161
                               (u'Mandos (%s) [%%(process)d]:'
 
 
162
                                u' %%(levelname)s: %%(message)s'
 
136
166
        self.rename_count += 1
 
137
167
    def remove(self):
 
138
168
        """Derived from the Avahi example code"""
 
139
 
        if group is not None:
 
 
169
        if self.group is not None:
 
142
172
        """Derived from the Avahi example code"""
 
145
 
            group = dbus.Interface\
 
146
 
                    (bus.get_object(avahi.DBUS_NAME,
 
147
 
                                    server.EntryGroupNew()),
 
148
 
                     avahi.DBUS_INTERFACE_ENTRY_GROUP)
 
149
 
            group.connect_to_signal('StateChanged',
 
150
 
                                    entry_group_state_changed)
 
151
 
        logger.debug(u"Adding service '%s' of type '%s' ...",
 
152
 
                     service.name, service.type)
 
154
 
                self.interface,         # interface
 
155
 
                avahi.PROTO_INET6,      # protocol
 
156
 
                dbus.UInt32(0),         # flags
 
157
 
                self.name, self.type,
 
158
 
                self.domain, self.host,
 
159
 
                dbus.UInt16(self.port),
 
160
 
                avahi.string_array_to_txt_array(self.TXT))
 
163
 
# From the Avahi example code:
 
164
 
group = None                            # our entry group
 
165
 
# End of Avahi example code
 
 
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)
 
 
181
        logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
 
 
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())
 
168
230
class Client(object):
 
169
231
    """A representation of a client host served by this server.
 
171
 
    name:      string; from the config file, used in log messages
 
 
234
    name:       string; from the config file, used in log messages and
 
172
236
    fingerprint: string (40 or 32 hexadecimal digits); used to
 
173
237
                 uniquely identify the client
 
174
 
    secret:    bytestring; sent verbatim (over TLS) to client
 
175
 
    host:      string; available for use by the checker command
 
176
 
    created:   datetime.datetime(); object creation, not client host
 
177
 
    last_checked_ok: datetime.datetime() or None if not yet checked OK
 
178
 
    timeout:   datetime.timedelta(); How long from last_checked_ok
 
179
 
                                     until this client is invalid
 
180
 
    interval:  datetime.timedelta(); How often to start a new checker
 
181
 
    stop_hook: If set, called by stop() as stop_hook(self)
 
182
 
    checker:   subprocess.Popen(); a running checker process used
 
183
 
                                   to see if the client lives.
 
184
 
                                   '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.
 
185
251
    checker_initiator_tag: a gobject event source tag, or None
 
186
 
    stop_initiator_tag:    - '' -
 
 
252
    disable_initiator_tag: - '' -
 
187
253
    checker_callback_tag:  - '' -
 
188
254
    checker_command: string; External command which is run to check if
 
189
255
                     client lives.  %() expansions are done at
 
190
256
                     runtime with vars(self) as dict, so that for
 
191
257
                     instance %(name)s can be used in the command.
 
193
 
    _timeout: Real variable for 'timeout'
 
194
 
    _interval: Real variable for 'interval'
 
195
 
    _timeout_milliseconds: Used when calling gobject.timeout_add()
 
196
 
    _interval_milliseconds: - '' -
 
 
258
    current_checker_command: string; current running checker_command
 
198
 
    def _set_timeout(self, timeout):
 
199
 
        "Setter function for 'timeout' attribute"
 
200
 
        self._timeout = timeout
 
201
 
        self._timeout_milliseconds = ((self.timeout.days
 
202
 
                                       * 24 * 60 * 60 * 1000)
 
203
 
                                      + (self.timeout.seconds * 1000)
 
204
 
                                      + (self.timeout.microseconds
 
206
 
    timeout = property(lambda self: self._timeout,
 
209
 
    def _set_interval(self, interval):
 
210
 
        "Setter function for 'interval' attribute"
 
211
 
        self._interval = interval
 
212
 
        self._interval_milliseconds = ((self.interval.days
 
213
 
                                        * 24 * 60 * 60 * 1000)
 
214
 
                                       + (self.interval.seconds
 
216
 
                                       + (self.interval.microseconds
 
218
 
    interval = property(lambda self: self._interval,
 
221
 
    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):
 
222
277
        """Note: the 'checker' key in 'config' sets the
 
223
278
        'checker_command' attribute and *not* the 'checker'
 
226
283
        logger.debug(u"Creating client %r", self.name)
 
227
284
        # Uppercase and remove spaces from fingerprint for later
 
228
285
        # comparison purposes with return value from the fingerprint()
 
230
 
        self.fingerprint = config["fingerprint"].upper()\
 
 
287
        self.fingerprint = (config[u"fingerprint"].upper()
 
232
289
        logger.debug(u"  Fingerprint: %s", self.fingerprint)
 
233
 
        if "secret" in config:
 
234
 
            self.secret = config["secret"].decode(u"base64")
 
235
 
        elif "secfile" in config:
 
236
 
            sf = open(config["secfile"])
 
237
 
            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()
 
240
299
            raise TypeError(u"No secret or secfile for client %s"
 
242
 
        self.host = config.get("host", "")
 
243
 
        self.created = datetime.datetime.now()
 
 
301
        self.host = config.get(u"host", u"")
 
 
302
        self.created = datetime.datetime.utcnow()
 
 
304
        self.last_enabled = None
 
244
305
        self.last_checked_ok = None
 
245
 
        self.timeout = string_to_delta(config["timeout"])
 
246
 
        self.interval = string_to_delta(config["interval"])
 
247
 
        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
 
248
309
        self.checker = None
 
249
310
        self.checker_initiator_tag = None
 
250
 
        self.stop_initiator_tag = None
 
 
311
        self.disable_initiator_tag = None
 
251
312
        self.checker_callback_tag = None
 
252
 
        self.check_command = config["checker"]
 
 
313
        self.checker_command = config[u"checker"]
 
 
314
        self.current_checker_command = None
 
 
315
        self.last_connect = None
 
254
318
        """Start this client's checker and timeout hooks"""
 
 
319
        if getattr(self, u"enabled", False):
 
 
322
        self.last_enabled = datetime.datetime.utcnow()
 
255
323
        # Schedule a new checker to be started an 'interval' from now,
 
256
324
        # and every interval from then on.
 
257
 
        self.checker_initiator_tag = gobject.timeout_add\
 
258
 
                                     (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(),
 
260
333
        # Also start a new checker *right now*.
 
261
334
        self.start_checker()
 
262
 
        # Schedule a stop() when 'timeout' has passed
 
263
 
        self.stop_initiator_tag = gobject.timeout_add\
 
264
 
                                  (self._timeout_milliseconds,
 
268
 
        The possibility that a client might be restarted is left open,
 
269
 
        but not currently used."""
 
270
 
        # If this client doesn't have a secret, it is already stopped.
 
271
 
        if hasattr(self, "secret") and self.secret:
 
272
 
            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):
 
276
 
        if getattr(self, "stop_initiator_tag", False):
 
277
 
            gobject.source_remove(self.stop_initiator_tag)
 
278
 
            self.stop_initiator_tag = None
 
279
 
        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):
 
280
346
            gobject.source_remove(self.checker_initiator_tag)
 
281
347
            self.checker_initiator_tag = None
 
282
348
        self.stop_checker()
 
 
349
        if self.disable_hook:
 
 
350
            self.disable_hook(self)
 
285
352
        # Do not run this again if called by a gobject.timeout_add
 
287
355
    def __del__(self):
 
288
 
        self.stop_hook = None
 
290
 
    def checker_callback(self, pid, condition):
 
 
356
        self.disable_hook = None
 
 
359
    def checker_callback(self, pid, condition, command):
 
291
360
        """The checker has completed, so take appropriate actions."""
 
292
 
        now = datetime.datetime.now()
 
293
361
        self.checker_callback_tag = None
 
294
362
        self.checker = None
 
295
 
        if os.WIFEXITED(condition) \
 
296
 
               and (os.WEXITSTATUS(condition) == 0):
 
297
 
            logger.info(u"Checker for %(name)s succeeded",
 
299
 
            self.last_checked_ok = now
 
300
 
            gobject.source_remove(self.stop_initiator_tag)
 
301
 
            self.stop_initiator_tag = gobject.timeout_add\
 
302
 
                                      (self._timeout_milliseconds,
 
304
 
        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",
 
305
373
            logger.warning(u"Checker for %(name)s crashed?",
 
308
 
            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(),
 
310
388
    def start_checker(self):
 
311
389
        """Start a new checker subprocess if one is not running.
 
312
391
        If a checker already exists, leave it running and do
 
314
393
        # The reason for not killing a running checker is that if we
 
 
319
398
        # checkers alone, the checker would have to take more time
 
320
399
        # than 'timeout' for the client to be declared invalid, which
 
321
400
        # is as it should be.
 
 
402
        # If a checker exists, make sure it is not a zombie
 
 
404
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
 
 
405
        except (AttributeError, OSError), error:
 
 
406
            if (isinstance(error, OSError)
 
 
407
                and error.errno != errno.ECHILD):
 
 
411
                logger.warning(u"Checker was a zombie")
 
 
412
                gobject.source_remove(self.checker_callback_tag)
 
 
413
                self.checker_callback(pid, status,
 
 
414
                                      self.current_checker_command)
 
 
415
        # Start a new checker if needed
 
322
416
        if self.checker is None:
 
324
 
                # In case check_command has exactly one % operator
 
325
 
                command = self.check_command % self.host
 
 
418
                # In case checker_command has exactly one % operator
 
 
419
                command = self.checker_command % self.host
 
326
420
            except TypeError:
 
327
421
                # Escape attributes for the shell
 
328
 
                escaped_attrs = dict((key, re.escape(str(val)))
 
 
422
                escaped_attrs = dict((key,
 
 
423
                                      re.escape(unicode(str(val),
 
330
427
                                     vars(self).iteritems())
 
332
 
                    command = self.check_command % escaped_attrs
 
 
429
                    command = self.checker_command % escaped_attrs
 
333
430
                except TypeError, error:
 
334
431
                    logger.error(u'Could not format string "%s":'
 
335
 
                                 u' %s', self.check_command, error)
 
 
432
                                 u' %s', self.checker_command, error)
 
336
433
                    return True # Try again later
 
 
434
            self.current_checker_command = command
 
338
436
                logger.info(u"Starting checker %r for %s",
 
339
437
                            command, self.name)
 
 
438
                # We don't need to redirect stdout and stderr, since
 
 
439
                # in normal mode, that is already done by daemon(),
 
 
440
                # and in debug mode we don't want to.  (Stdin is
 
 
441
                # always replaced by /dev/null.)
 
340
442
                self.checker = subprocess.Popen(command,
 
343
 
                self.checker_callback_tag = gobject.child_watch_add\
 
345
 
                                             self.checker_callback)
 
346
 
            except subprocess.OSError, error:
 
 
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)
 
 
455
            except OSError, error:
 
347
456
                logger.error(u"Failed to start subprocess: %s",
 
349
458
        # Re-run this periodically if run by gobject.timeout_add
 
351
461
    def stop_checker(self):
 
352
462
        """Force the checker process, if any, to stop."""
 
353
463
        if self.checker_callback_tag:
 
354
464
            gobject.source_remove(self.checker_callback_tag)
 
355
465
            self.checker_callback_tag = None
 
356
 
        if getattr(self, "checker", None) is None:
 
 
466
        if getattr(self, u"checker", None) is None:
 
358
468
        logger.debug(u"Stopping checker for %(name)s", vars(self))
 
360
470
            os.kill(self.checker.pid, signal.SIGTERM)
 
362
472
            #if self.checker.poll() is None:
 
363
473
            #    os.kill(self.checker.pid, signal.SIGKILL)
 
364
474
        except OSError, error:
 
365
475
            if error.errno != errno.ESRCH: # No such process
 
367
477
        self.checker = None
 
368
479
    def still_valid(self):
 
369
480
        """Has the timeout not yet passed for this client?"""
 
370
 
        now = datetime.datetime.now()
 
 
481
        if not getattr(self, u"enabled", False):
 
 
483
        now = datetime.datetime.utcnow()
 
371
484
        if self.last_checked_ok is None:
 
372
485
            return now < (self.created + self.timeout)
 
374
487
            return now < (self.last_checked_ok + self.timeout)
 
377
 
def peer_certificate(session):
 
378
 
    "Return the peer's OpenPGP certificate as a bytestring"
 
379
 
    # If not an OpenPGP certificate...
 
380
 
    if gnutls.library.functions.gnutls_certificate_type_get\
 
381
 
            (session._c_object) \
 
382
 
           != gnutls.library.constants.GNUTLS_CRT_OPENPGP:
 
383
 
        # ...do the normal thing
 
384
 
        return session.peer_certificate
 
385
 
    list_size = ctypes.c_uint()
 
386
 
    cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
 
387
 
        (session._c_object, ctypes.byref(list_size))
 
388
 
    if list_size.value == 0:
 
391
 
    return ctypes.string_at(cert.data, cert.size)
 
394
 
def fingerprint(openpgp):
 
395
 
    "Convert an OpenPGP bytestring to a hexdigit fingerprint string"
 
396
 
    # New GnuTLS "datum" with the OpenPGP public key
 
397
 
    datum = gnutls.library.types.gnutls_datum_t\
 
398
 
        (ctypes.cast(ctypes.c_char_p(openpgp),
 
399
 
                     ctypes.POINTER(ctypes.c_ubyte)),
 
400
 
         ctypes.c_uint(len(openpgp)))
 
401
 
    # New empty GnuTLS certificate
 
402
 
    crt = gnutls.library.types.gnutls_openpgp_crt_t()
 
403
 
    gnutls.library.functions.gnutls_openpgp_crt_init\
 
405
 
    # Import the OpenPGP public key into the certificate
 
406
 
    gnutls.library.functions.gnutls_openpgp_crt_import\
 
407
 
                    (crt, ctypes.byref(datum),
 
408
 
                     gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
 
409
 
    # New buffer for the fingerprint
 
410
 
    buffer = ctypes.create_string_buffer(20)
 
411
 
    buffer_length = ctypes.c_size_t()
 
412
 
    # Get the fingerprint from the certificate into the buffer
 
413
 
    gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
 
414
 
        (crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
 
415
 
    # Deinit the certificate
 
416
 
    gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
 
417
 
    # Convert the buffer to a Python bytestring
 
418
 
    fpr = ctypes.string_at(buffer, buffer_length.value)
 
419
 
    # Convert the bytestring to hexadecimal notation
 
420
 
    hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
 
424
 
class tcp_handler(SocketServer.BaseRequestHandler, object):
 
425
 
    """A TCP request handler class.
 
426
 
    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.
 
427
988
    Note: This will run in its own forked process."""
 
429
990
    def handle(self):
 
430
991
        logger.info(u"TCP connection from: %s",
 
431
 
                     unicode(self.client_address))
 
432
 
        session = gnutls.connection.ClientSession\
 
433
 
                  (self.request, gnutls.connection.X509Credentials())
 
435
 
        line = self.request.makefile().readline()
 
436
 
        logger.debug(u"Protocol version: %r", line)
 
438
 
            if int(line.strip().split()[0]) > 1:
 
440
 
        except (ValueError, IndexError, RuntimeError), error:
 
441
 
            logger.error(u"Unknown protocol version: %s", error)
 
444
 
        # Note: gnutls.connection.X509Credentials is really a generic
 
445
 
        # GnuTLS certificate credentials object so long as no X.509
 
446
 
        # keys are added to it.  Therefore, we can use it here despite
 
447
 
        # using OpenPGP certificates.
 
449
 
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
 
450
 
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
 
452
 
        priority = "NORMAL"             # Fallback default, since this
 
454
 
        if self.server.settings["priority"]:
 
455
 
            priority = self.server.settings["priority"]
 
456
 
        gnutls.library.functions.gnutls_priority_set_direct\
 
457
 
            (session._c_object, priority, None);
 
461
 
        except gnutls.errors.GNUTLSError, error:
 
462
 
            logger.warning(u"Handshake failed: %s", error)
 
463
 
            # Do not run session.bye() here: the session is not
 
464
 
            # established.  Just abandon the request.
 
467
 
            fpr = fingerprint(peer_certificate(session))
 
468
 
        except (TypeError, gnutls.errors.GNUTLSError), error:
 
469
 
            logger.warning(u"Bad certificate: %s", error)
 
472
 
        logger.debug(u"Fingerprint: %s", fpr)
 
474
 
        for c in self.server.clients:
 
475
 
            if c.fingerprint == fpr:
 
479
 
            logger.warning(u"Client not found for fingerprint: %s",
 
483
 
        # Have to check if client.still_valid(), since it is possible
 
484
 
        # that the client timed out while establishing the GnuTLS
 
486
 
        if not client.still_valid():
 
487
 
            logger.warning(u"Client %(name)s is invalid",
 
492
 
        while sent_size < len(client.secret):
 
493
 
            sent = session.send(client.secret[sent_size:])
 
494
 
            logger.debug(u"Sent: %d, remaining: %d",
 
495
 
                         sent, len(client.secret)
 
496
 
                         - (sent_size + sent))
 
501
 
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
 
502
 
    """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
 
504
 
        settings:       Server settings
 
505
 
        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
 
507
 
    address_family = socket.AF_INET6
 
508
 
    def __init__(self, *args, **kwargs):
 
509
 
        if "settings" in kwargs:
 
510
 
            self.settings = kwargs["settings"]
 
511
 
            del kwargs["settings"]
 
512
 
        if "clients" in kwargs:
 
513
 
            self.clients = kwargs["clients"]
 
514
 
            del kwargs["clients"]
 
515
 
        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)
 
516
1166
    def server_bind(self):
 
517
1167
        """This overrides the normal server_bind() function
 
518
1168
        to bind to an interface if one was specified, and also NOT to
 
519
1169
        bind to an address or port if they were not specified."""
 
520
 
        if self.settings["interface"]:
 
521
 
            # 25 is from /usr/include/asm-i486/socket.h
 
522
 
            SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
 
524
 
                self.socket.setsockopt(socket.SOL_SOCKET,
 
526
 
                                       self.settings["interface"])
 
527
 
            except socket.error, error:
 
528
 
                if error[0] == errno.EPERM:
 
529
 
                    logger.error(u"No permission to"
 
530
 
                                 u" bind to interface %s",
 
531
 
                                 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",
 
534
1192
        # Only bind(2) the socket if we really need to.
 
535
1193
        if self.server_address[0] or self.server_address[1]:
 
536
1194
            if not self.server_address[0]:
 
538
 
                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,
 
539
1200
                                       self.server_address[1])
 
540
1201
            elif not self.server_address[1]:
 
541
1202
                self.server_address = (self.server_address[0],
 
543
 
#                 if self.settings["interface"]:
 
 
1204
#                 if self.interface:
 
544
1205
#                     self.server_address = (self.server_address[0],
 
547
1208
#                                            if_nametoindex
 
550
 
            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
 
553
1318
def string_to_delta(interval):
 
554
1319
    """Parse a string and return a datetime.timedelta
 
556
 
    >>> string_to_delta('7d')
 
 
1321
    >>> string_to_delta(u'7d')
 
557
1322
    datetime.timedelta(7)
 
558
 
    >>> string_to_delta('60s')
 
 
1323
    >>> string_to_delta(u'60s')
 
559
1324
    datetime.timedelta(0, 60)
 
560
 
    >>> string_to_delta('60m')
 
 
1325
    >>> string_to_delta(u'60m')
 
561
1326
    datetime.timedelta(0, 3600)
 
562
 
    >>> string_to_delta('24h')
 
 
1327
    >>> string_to_delta(u'24h')
 
563
1328
    datetime.timedelta(1)
 
564
1329
    >>> string_to_delta(u'1w')
 
565
1330
    datetime.timedelta(7)
 
 
1331
    >>> string_to_delta(u'5m 30s')
 
 
1332
    datetime.timedelta(0, 330)
 
568
 
        suffix=unicode(interval[-1])
 
569
 
        value=int(interval[:-1])
 
571
 
            delta = datetime.timedelta(value)
 
573
 
            delta = datetime.timedelta(0, value)
 
575
 
            delta = datetime.timedelta(0, 0, 0, 0, value)
 
577
 
            delta = datetime.timedelta(0, 0, 0, 0, 0, value)
 
579
 
            delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
 
582
 
    except (ValueError, IndexError):
 
587
 
def server_state_changed(state):
 
588
 
    """Derived from the Avahi example code"""
 
589
 
    if state == avahi.SERVER_COLLISION:
 
590
 
        logger.error(u"Server name collision")
 
592
 
    elif state == avahi.SERVER_RUNNING:
 
596
 
def entry_group_state_changed(state, error):
 
597
 
    """Derived from the Avahi example code"""
 
598
 
    logger.debug(u"state change: %i", state)
 
600
 
    if state == avahi.ENTRY_GROUP_ESTABLISHED:
 
601
 
        logger.debug(u"Service established.")
 
602
 
    elif state == avahi.ENTRY_GROUP_COLLISION:
 
603
 
        logger.warning(u"Service name collision.")
 
605
 
    elif state == avahi.ENTRY_GROUP_FAILURE:
 
606
 
        logger.critical(u"Error in group state changed %s",
 
608
 
        raise AvahiGroupError("State changed: %s", str(error))
 
 
1334
    timevalue = datetime.timedelta(0)
 
 
1335
    for s in interval.split():
 
 
1337
            suffix = unicode(s[-1])
 
 
1340
                delta = datetime.timedelta(value)
 
 
1341
            elif suffix == u"s":
 
 
1342
                delta = datetime.timedelta(0, value)
 
 
1343
            elif suffix == u"m":
 
 
1344
                delta = datetime.timedelta(0, 0, 0, 0, value)
 
 
1345
            elif suffix == u"h":
 
 
1346
                delta = datetime.timedelta(0, 0, 0, 0, 0, value)
 
 
1347
            elif suffix == u"w":
 
 
1348
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
 
 
1350
                raise ValueError(u"Unknown suffix %r" % suffix)
 
 
1351
        except (ValueError, IndexError), e:
 
 
1352
            raise ValueError(e.message)
 
610
1357
def if_nametoindex(interface):
 
611
 
    """Call the C function if_nametoindex(), or equivalent"""
 
 
1358
    """Call the C function if_nametoindex(), or equivalent
 
 
1360
    Note: This function cannot accept a unicode string."""
 
612
1361
    global if_nametoindex
 
614
 
        if "ctypes.util" not in sys.modules:
 
616
 
        if_nametoindex = ctypes.cdll.LoadLibrary\
 
617
 
            (ctypes.util.find_library("c")).if_nametoindex
 
 
1363
        if_nametoindex = (ctypes.cdll.LoadLibrary
 
 
1364
                          (ctypes.util.find_library(u"c"))
 
618
1366
    except (OSError, AttributeError):
 
619
 
        if "struct" not in sys.modules:
 
621
 
        if "fcntl" not in sys.modules:
 
 
1367
        logger.warning(u"Doing if_nametoindex the hard way")
 
623
1368
        def if_nametoindex(interface):
 
624
1369
            "Get an interface index the hard way, i.e. using fcntl()"
 
625
1370
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
 
627
 
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
 
628
 
                                struct.pack("16s16x", interface))
 
630
 
            interface_index = struct.unpack("I", ifreq[16:20])[0]
 
 
1371
            with closing(socket.socket()) as s:
 
 
1372
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
 
 
1373
                                    struct.pack(str(u"16s16x"),
 
 
1375
            interface_index = struct.unpack(str(u"I"),
 
631
1377
            return interface_index
 
632
1378
    return if_nametoindex(interface)
 
635
1381
def daemon(nochdir = False, noclose = False):
 
636
1382
    """See daemon(3).  Standard BSD Unix function.
 
637
1384
    This should really exist as os.daemon, but it doesn't (yet)."""
 
 
689
1443
    # Default values for config file for server-global settings
 
690
 
    server_defaults = { "interface": "",
 
695
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
 
696
 
                        "servicename": "Mandos",
 
 
1444
    server_defaults = { u"interface": u"",
 
 
1449
                        u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
 
 
1450
                        u"servicename": u"Mandos",
 
 
1451
                        u"use_dbus": u"True",
 
 
1452
                        u"use_ipv6": u"True",
 
699
1455
    # Parse config file for server-global settings
 
700
 
    server_config = ConfigParser.SafeConfigParser(server_defaults)
 
 
1456
    server_config = configparser.SafeConfigParser(server_defaults)
 
701
1457
    del server_defaults
 
702
 
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
 
 
1458
    server_config.read(os.path.join(options.configdir,
 
703
1460
    # Convert the SafeConfigParser object to a dict
 
704
1461
    server_settings = server_config.defaults()
 
705
 
    # Use getboolean on the boolean config option
 
706
 
    server_settings["debug"] = server_config.getboolean\
 
 
1462
    # Use the appropriate methods on the non-string config options
 
 
1463
    for option in (u"debug", u"use_dbus", u"use_ipv6"):
 
 
1464
        server_settings[option] = server_config.getboolean(u"DEFAULT",
 
 
1466
    if server_settings["port"]:
 
 
1467
        server_settings["port"] = server_config.getint(u"DEFAULT",
 
708
1469
    del server_config
 
710
1471
    # Override the settings from the config file with command line
 
711
1472
    # options, if set.
 
712
 
    for option in ("interface", "address", "port", "debug",
 
713
 
                   "priority", "servicename", "configdir"):
 
 
1473
    for option in (u"interface", u"address", u"port", u"debug",
 
 
1474
                   u"priority", u"servicename", u"configdir",
 
 
1475
                   u"use_dbus", u"use_ipv6"):
 
714
1476
        value = getattr(options, option)
 
715
1477
        if value is not None:
 
716
1478
            server_settings[option] = value
 
 
1480
    # Force all strings to be unicode
 
 
1481
    for option in server_settings.keys():
 
 
1482
        if type(server_settings[option]) is str:
 
 
1483
            server_settings[option] = unicode(server_settings[option])
 
718
1484
    # Now we have our good server settings in "server_settings"
 
720
 
    debug = server_settings["debug"]
 
 
1486
    ##################################################################
 
 
1489
    debug = server_settings[u"debug"]
 
 
1490
    use_dbus = server_settings[u"use_dbus"]
 
 
1491
    use_ipv6 = server_settings[u"use_ipv6"]
 
723
1494
        syslogger.setLevel(logging.WARNING)
 
724
1495
        console.setLevel(logging.WARNING)
 
726
 
    if server_settings["servicename"] != "Mandos":
 
727
 
        syslogger.setFormatter(logging.Formatter\
 
728
 
                               ('Mandos (%s): %%(levelname)s:'
 
730
 
                                % server_settings["servicename"]))
 
 
1497
    if server_settings[u"servicename"] != u"Mandos":
 
 
1498
        syslogger.setFormatter(logging.Formatter
 
 
1499
                               (u'Mandos (%s) [%%(process)d]:'
 
 
1500
                                u' %%(levelname)s: %%(message)s'
 
 
1501
                                % server_settings[u"servicename"]))
 
732
1503
    # Parse config file with clients
 
733
 
    client_defaults = { "timeout": "1h",
 
735
 
                        "checker": "fping -q -- %%(host)s",
 
 
1504
    client_defaults = { u"timeout": u"1h",
 
 
1506
                        u"checker": u"fping -q -- %%(host)s",
 
737
 
    client_config = ConfigParser.SafeConfigParser(client_defaults)
 
738
 
    client_config.read(os.path.join(server_settings["configdir"],
 
742
 
    service = AvahiService(name = server_settings["servicename"],
 
743
 
                           type = "_mandos._tcp", );
 
744
 
    if server_settings["interface"]:
 
745
 
        service.interface = if_nametoindex(server_settings["interface"])
 
 
1509
    client_config = configparser.SafeConfigParser(client_defaults)
 
 
1510
    client_config.read(os.path.join(server_settings[u"configdir"],
 
 
1513
    global mandos_dbus_service
 
 
1514
    mandos_dbus_service = None
 
 
1516
    tcp_server = MandosServer((server_settings[u"address"],
 
 
1517
                               server_settings[u"port"]),
 
 
1519
                              interface=server_settings[u"interface"],
 
 
1522
                              server_settings[u"priority"],
 
 
1524
    pidfilename = u"/var/run/mandos.pid"
 
 
1526
        pidfile = open(pidfilename, u"w")
 
 
1528
        logger.error(u"Could not open file %r", pidfilename)
 
 
1531
        uid = pwd.getpwnam(u"_mandos").pw_uid
 
 
1532
        gid = pwd.getpwnam(u"_mandos").pw_gid
 
 
1535
            uid = pwd.getpwnam(u"mandos").pw_uid
 
 
1536
            gid = pwd.getpwnam(u"mandos").pw_gid
 
 
1539
                uid = pwd.getpwnam(u"nobody").pw_uid
 
 
1540
                gid = pwd.getpwnam(u"nobody").pw_gid
 
 
1547
    except OSError, error:
 
 
1548
        if error[0] != errno.EPERM:
 
 
1551
    # Enable all possible GnuTLS debugging
 
 
1553
        # "Use a log level over 10 to enable all debugging options."
 
 
1555
        gnutls.library.functions.gnutls_global_set_log_level(11)
 
 
1557
        @gnutls.library.types.gnutls_log_func
 
 
1558
        def debug_gnutls(level, string):
 
 
1559
            logger.debug(u"GnuTLS: %s", string[:-1])
 
 
1561
        (gnutls.library.functions
 
 
1562
         .gnutls_global_set_log_function(debug_gnutls))
 
747
1564
    global main_loop
 
750
1565
    # From the Avahi example code
 
751
1566
    DBusGMainLoop(set_as_default=True )
 
752
1567
    main_loop = gobject.MainLoop()
 
753
1568
    bus = dbus.SystemBus()
 
754
 
    server = dbus.Interface(
 
755
 
            bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
 
756
 
            avahi.DBUS_INTERFACE_SERVER )
 
757
1569
    # End of Avahi example code
 
760
 
    def remove_from_clients(client):
 
761
 
        clients.remove(client)
 
763
 
            logger.critical(u"No clients left, exiting")
 
766
 
    clients.update(Set(Client(name = section,
 
767
 
                              stop_hook = remove_from_clients,
 
769
 
                              = dict(client_config.items(section)))
 
770
 
                       for section in client_config.sections()))
 
772
 
        logger.critical(u"No clients defined")
 
 
1572
            bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
 
 
1573
                                            bus, do_not_queue=True)
 
 
1574
        except dbus.exceptions.NameExistsException, e:
 
 
1575
            logger.error(unicode(e) + u", disabling D-Bus")
 
 
1577
            server_settings[u"use_dbus"] = False
 
 
1578
            tcp_server.use_dbus = False
 
 
1579
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
 
 
1580
    service = AvahiService(name = server_settings[u"servicename"],
 
 
1581
                           servicetype = u"_mandos._tcp",
 
 
1582
                           protocol = protocol, bus = bus)
 
 
1583
    if server_settings["interface"]:
 
 
1584
        service.interface = (if_nametoindex
 
 
1585
                             (str(server_settings[u"interface"])))
 
 
1587
    client_class = Client
 
 
1589
        client_class = functools.partial(ClientDBus, bus = bus)
 
 
1590
    tcp_server.clients.update(set(
 
 
1591
            client_class(name = section,
 
 
1592
                         config= dict(client_config.items(section)))
 
 
1593
            for section in client_config.sections()))
 
 
1594
    if not tcp_server.clients:
 
 
1595
        logger.warning(u"No clients defined")
 
 
1598
        # Redirect stdin so all checkers get /dev/null
 
 
1599
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
 
1600
        os.dup2(null, sys.stdin.fileno())
 
 
1604
        # No console logging
 
776
1605
        logger.removeHandler(console)
 
 
1606
        # Close all input and output, do double fork, etc.
 
779
 
    pidfilename = "/var/run/mandos/mandos.pid"
 
782
 
        pidfile = open(pidfilename, "w")
 
783
 
        pidfile.write(str(pid) + "\n")
 
 
1610
        with closing(pidfile):
 
 
1612
            pidfile.write(str(pid) + "\n")
 
787
 
        logger.error(u"Could not write %s file with PID %d",
 
788
 
                     pidfilename, os.getpid())
 
 
1615
        logger.error(u"Could not write to file %r with PID %d",
 
 
1618
        # "pidfile" was never created
 
 
1623
        signal.signal(signal.SIGINT, signal.SIG_IGN)
 
 
1624
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
 
 
1625
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
 
 
1628
        class MandosDBusService(dbus.service.Object):
 
 
1629
            """A D-Bus proxy object"""
 
 
1631
                dbus.service.Object.__init__(self, bus, u"/")
 
 
1632
            _interface = u"se.bsnet.fukt.Mandos"
 
 
1634
            @dbus.service.signal(_interface, signature=u"oa{sv}")
 
 
1635
            def ClientAdded(self, objpath, properties):
 
 
1639
            @dbus.service.signal(_interface, signature=u"ss")
 
 
1640
            def ClientNotFound(self, fingerprint, address):
 
 
1644
            @dbus.service.signal(_interface, signature=u"os")
 
 
1645
            def ClientRemoved(self, objpath, name):
 
 
1649
            @dbus.service.method(_interface, out_signature=u"ao")
 
 
1650
            def GetAllClients(self):
 
 
1652
                return dbus.Array(c.dbus_object_path
 
 
1653
                                  for c in tcp_server.clients)
 
 
1655
            @dbus.service.method(_interface,
 
 
1656
                                 out_signature=u"a{oa{sv}}")
 
 
1657
            def GetAllClientsWithProperties(self):
 
 
1659
                return dbus.Dictionary(
 
 
1660
                    ((c.dbus_object_path, c.GetAll(u""))
 
 
1661
                     for c in tcp_server.clients),
 
 
1662
                    signature=u"oa{sv}")
 
 
1664
            @dbus.service.method(_interface, in_signature=u"o")
 
 
1665
            def RemoveClient(self, object_path):
 
 
1667
                for c in tcp_server.clients:
 
 
1668
                    if c.dbus_object_path == object_path:
 
 
1669
                        tcp_server.clients.remove(c)
 
 
1670
                        c.remove_from_connection()
 
 
1671
                        # Don't signal anything except ClientRemoved
 
 
1672
                        c.disable(quiet=True)
 
 
1674
                        self.ClientRemoved(object_path, c.name)
 
 
1676
                raise KeyError(object_path)
 
 
1680
        mandos_dbus_service = MandosDBusService()
 
791
1683
        "Cleanup function; run on exit"
 
793
 
        # From the Avahi example code
 
794
 
        if not group is None:
 
797
 
        # End of Avahi example code
 
800
 
            client = clients.pop()
 
801
 
            client.stop_hook = None
 
 
1686
        while tcp_server.clients:
 
 
1687
            client = tcp_server.clients.pop()
 
 
1689
                client.remove_from_connection()
 
 
1690
            client.disable_hook = None
 
 
1691
            # Don't signal anything except ClientRemoved
 
 
1692
            client.disable(quiet=True)
 
 
1695
                mandos_dbus_service.ClientRemoved(client.dbus_object_path,
 
804
1698
    atexit.register(cleanup)
 
807
 
        signal.signal(signal.SIGINT, signal.SIG_IGN)
 
808
 
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
 
809
 
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
 
811
 
    for client in clients:
 
814
 
    tcp_server = IPv6_TCPServer((server_settings["address"],
 
815
 
                                 server_settings["port"]),
 
817
 
                                settings=server_settings,
 
 
1700
    for client in tcp_server.clients:
 
 
1703
            mandos_dbus_service.ClientAdded(client.dbus_object_path,
 
 
1708
    tcp_server.server_activate()
 
819
1710
    # Find out what port we got
 
820
1711
    service.port = tcp_server.socket.getsockname()[1]
 
821
 
    logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
 
822
 
                u" scope_id %d" % tcp_server.socket.getsockname())
 
 
1713
        logger.info(u"Now listening on address %r, port %d,"
 
 
1714
                    " flowinfo %d, scope_id %d"
 
 
1715
                    % tcp_server.socket.getsockname())
 
 
1717
        logger.info(u"Now listening on address %r, port %d"
 
 
1718
                    % tcp_server.socket.getsockname())
 
824
1720
    #service.interface = tcp_server.socket.getsockname()[3]
 
827
1723
        # From the Avahi example code
 
828
 
        server.connect_to_signal("StateChanged", server_state_changed)
 
830
 
            server_state_changed(server.GetState())
 
831
1726
        except dbus.exceptions.DBusException, error:
 
832
1727
            logger.critical(u"DBusException: %s", error)
 
834
1730
        # End of Avahi example code
 
836
1732
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
 
837
1733
                             lambda *args, **kwargs:
 
838
 
                             tcp_server.handle_request\
 
839
 
                             (*args[2:], **kwargs) or True)
 
 
1734
                             (tcp_server.handle_request
 
 
1735
                              (*args[2:], **kwargs) or True))
 
841
1737
        logger.debug(u"Starting main loop")
 
842
 
        main_loop_started = True
 
844
1739
    except AvahiError, error:
 
845
 
        logger.critical(u"AvahiError: %s" + unicode(error))
 
 
1740
        logger.critical(u"AvahiError: %s", error)
 
847
1743
    except KeyboardInterrupt:
 
 
1746
        logger.debug(u"Server received KeyboardInterrupt")
 
 
1747
    logger.debug(u"Server exiting")
 
 
1748
    # Must run before the D-Bus bus name gets deregistered
 
851
1751
if __name__ == '__main__':