116
98
class AvahiService(object):
117
"""An Avahi (Zeroconf) service.
120
100
interface: integer; avahi.IF_UNSPEC or an interface index.
121
101
Used to optionally bind to the specified interface.
122
name: string; Example: u'Mandos'
123
type: string; Example: u'_mandos._tcp'.
124
See <http://www.dns-sd.org/ServiceTypes.html>
125
port: integer; what port to announce
126
TXT: list of strings; TXT record for the service
127
domain: string; Domain to publish on, default to .local if empty.
128
host: string; Host to publish records for, default is localhost
129
max_renames: integer; maximum number of renames
130
rename_count: integer; counter so we only rename after collisions
131
a sensible number of times
132
group: D-Bus Entry Group
134
bus: dbus.SystemBus()
102
name = string; Example: "Mandos"
103
type = string; Example: "_mandos._tcp".
104
See <http://www.dns-sd.org/ServiceTypes.html>
105
port = integer; what port to announce
106
TXT = list of strings; TXT record for the service
107
domain = string; Domain to publish on, default to .local if empty.
108
host = string; Host to publish records for, default to localhost
110
max_renames = integer; maximum number of renames
111
rename_count = integer; counter so we only rename after collisions
112
a sensible number of times
136
114
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
137
servicetype = None, port = None, TXT = None,
138
domain = u"", host = u"", max_renames = 32768,
139
protocol = avahi.PROTO_UNSPEC, bus = None):
115
type = None, port = None, TXT = None, domain = "",
116
host = "", max_renames = 12):
117
"""An Avahi (Zeroconf) service. """
140
118
self.interface = interface
142
self.type = servicetype
144
self.TXT = TXT if TXT is not None else []
145
126
self.domain = domain
147
128
self.rename_count = 0
148
self.max_renames = max_renames
149
self.protocol = protocol
150
self.group = None # our entry group
153
129
def rename(self):
154
130
"""Derived from the Avahi example code"""
155
131
if self.rename_count >= self.max_renames:
156
logger.critical(u"No suitable Zeroconf service name found"
157
u" after %i retries, exiting.",
159
raise AvahiServiceError(u"Too many renames")
160
self.name = unicode(self.server.GetAlternativeServiceName(self.name))
161
logger.info(u"Changing Zeroconf service name to %r ...",
163
syslogger.setFormatter(logging.Formatter
164
(u'Mandos (%s) [%%(process)d]:'
165
u' %%(levelname)s: %%(message)s'
132
logger.critical(u"No suitable service name found after %i"
133
u" retries, exiting.", rename_count)
134
raise AvahiServiceError("Too many renames")
135
name = server.GetAlternativeServiceName(name)
136
logger.error(u"Changing name to %r ...", name)
170
except dbus.exceptions.DBusException, error:
171
logger.critical(u"DBusException: %s", error)
174
139
self.rename_count += 1
175
140
def remove(self):
176
141
"""Derived from the Avahi example code"""
177
if self.group is not None:
142
if group is not None:
180
145
"""Derived from the Avahi example code"""
181
if self.group is None:
182
self.group = dbus.Interface(
183
self.bus.get_object(avahi.DBUS_NAME,
184
self.server.EntryGroupNew()),
185
avahi.DBUS_INTERFACE_ENTRY_GROUP)
186
self.group.connect_to_signal('StateChanged',
188
.entry_group_state_changed)
189
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
190
self.name, self.type)
191
self.group.AddService(
194
dbus.UInt32(0), # flags
195
self.name, self.type,
196
self.domain, self.host,
197
dbus.UInt16(self.port),
198
avahi.string_array_to_txt_array(self.TXT))
200
def entry_group_state_changed(self, state, error):
201
"""Derived from the Avahi example code"""
202
logger.debug(u"Avahi entry group state change: %i", state)
204
if state == avahi.ENTRY_GROUP_ESTABLISHED:
205
logger.debug(u"Zeroconf service established.")
206
elif state == avahi.ENTRY_GROUP_COLLISION:
207
logger.warning(u"Zeroconf service name collision.")
209
elif state == avahi.ENTRY_GROUP_FAILURE:
210
logger.critical(u"Avahi: Error in group state changed %s",
212
raise AvahiGroupError(u"State changed: %s"
215
"""Derived from the Avahi example code"""
216
if self.group is not None:
219
def server_state_changed(self, state):
220
"""Derived from the Avahi example code"""
221
logger.debug(u"Avahi server state change: %i", state)
222
if state == avahi.SERVER_COLLISION:
223
logger.error(u"Zeroconf server name collision")
225
elif state == avahi.SERVER_RUNNING:
228
"""Derived from the Avahi example code"""
229
if self.server is None:
230
self.server = dbus.Interface(
231
self.bus.get_object(avahi.DBUS_NAME,
232
avahi.DBUS_PATH_SERVER),
233
avahi.DBUS_INTERFACE_SERVER)
234
self.server.connect_to_signal(u"StateChanged",
235
self.server_state_changed)
236
self.server_state_changed(self.server.GetState())
148
group = dbus.Interface\
149
(bus.get_object(avahi.DBUS_NAME,
150
server.EntryGroupNew()),
151
avahi.DBUS_INTERFACE_ENTRY_GROUP)
152
group.connect_to_signal('StateChanged',
153
entry_group_state_changed)
154
logger.debug(u"Adding 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
239
171
class Client(object):
240
172
"""A representation of a client host served by this server.
243
_approved: bool(); 'None' if not yet approved/disapproved
244
approval_delay: datetime.timedelta(); Time to wait for approval
245
approval_duration: datetime.timedelta(); Duration of one approval
246
checker: subprocess.Popen(); a running checker process used
247
to see if the client lives.
248
'None' if no process is running.
249
checker_callback_tag: a gobject event source tag, or None
250
checker_command: string; External command which is run to check
251
if client lives. %() expansions are done at
174
name: string; from the config file, used in log messages
175
fingerprint: string (40 or 32 hexadecimal digits); used to
176
uniquely identify the client
177
secret: bytestring; sent verbatim (over TLS) to client
178
fqdn: string (FQDN); available for use by the checker command
179
created: datetime.datetime(); object creation, not client host
180
last_checked_ok: datetime.datetime() or None if not yet checked OK
181
timeout: datetime.timedelta(); How long from last_checked_ok
182
until this client is invalid
183
interval: datetime.timedelta(); How often to start a new checker
184
stop_hook: If set, called by stop() as stop_hook(self)
185
checker: subprocess.Popen(); a running checker process used
186
to see if the client lives.
187
'None' if no process is running.
188
checker_initiator_tag: a gobject event source tag, or None
189
stop_initiator_tag: - '' -
190
checker_callback_tag: - '' -
191
checker_command: string; External command which is run to check if
192
client lives. %() expansions are done at
252
193
runtime with vars(self) as dict, so that for
253
194
instance %(name)s can be used in the command.
254
checker_initiator_tag: a gobject event source tag, or None
255
created: datetime.datetime(); (UTC) object creation
256
current_checker_command: string; current running checker_command
257
disable_hook: If set, called by disable() as disable_hook(self)
258
disable_initiator_tag: a gobject event source tag, or None
260
fingerprint: string (40 or 32 hexadecimal digits); used to
261
uniquely identify the client
262
host: string; available for use by the checker command
263
interval: datetime.timedelta(); How often to start a new checker
264
last_approval_request: datetime.datetime(); (UTC) or None
265
last_checked_ok: datetime.datetime(); (UTC) or None
266
last_enabled: datetime.datetime(); (UTC)
267
name: string; from the config file, used in log messages and
269
secret: bytestring; sent verbatim (over TLS) to client
270
timeout: datetime.timedelta(); How long from last_checked_ok
271
until this client is disabled
272
runtime_expansions: Allowed attributes for runtime expansion.
196
_timeout: Real variable for 'timeout'
197
_interval: Real variable for 'interval'
198
_timeout_milliseconds: Used when calling gobject.timeout_add()
199
_interval_milliseconds: - '' -
275
runtime_expansions = (u"approval_delay", u"approval_duration",
276
u"created", u"enabled", u"fingerprint",
277
u"host", u"interval", u"last_checked_ok",
278
u"last_enabled", u"name", u"timeout")
281
def _timedelta_to_milliseconds(td):
282
"Convert a datetime.timedelta() to milliseconds"
283
return ((td.days * 24 * 60 * 60 * 1000)
284
+ (td.seconds * 1000)
285
+ (td.microseconds // 1000))
287
def timeout_milliseconds(self):
288
"Return the 'timeout' attribute in milliseconds"
289
return self._timedelta_to_milliseconds(self.timeout)
291
def interval_milliseconds(self):
292
"Return the 'interval' attribute in milliseconds"
293
return self._timedelta_to_milliseconds(self.interval)
295
def approval_delay_milliseconds(self):
296
return self._timedelta_to_milliseconds(self.approval_delay)
298
def __init__(self, name = None, disable_hook=None, config=None):
299
"""Note: the 'checker' key in 'config' sets the
300
'checker_command' attribute and *not* the 'checker'
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={}):
225
"""Note: the 'checker' argument sets the 'checker_command'
226
attribute and not the 'checker' attribute.."""
305
228
logger.debug(u"Creating client %r", self.name)
306
# Uppercase and remove spaces from fingerprint for later
307
# comparison purposes with return value from the fingerprint()
309
self.fingerprint = (config[u"fingerprint"].upper()
229
# Uppercase and remove spaces from fingerprint
230
# for later comparison purposes with return value of
231
# the fingerprint() function
232
self.fingerprint = config["fingerprint"].upper()\
311
234
logger.debug(u" Fingerprint: %s", self.fingerprint)
312
if u"secret" in config:
313
self.secret = config[u"secret"].decode(u"base64")
314
elif u"secfile" in config:
315
with open(os.path.expanduser(os.path.expandvars
316
(config[u"secfile"])),
318
self.secret = secfile.read()
235
if "secret" in config:
236
self.secret = config["secret"].decode(u"base64")
237
elif "secfile" in config:
238
sf = open(config["secfile"])
239
self.secret = sf.read()
320
242
raise TypeError(u"No secret or secfile for client %s"
322
self.host = config.get(u"host", u"")
323
self.created = datetime.datetime.utcnow()
325
self.last_approval_request = None
326
self.last_enabled = None
244
self.fqdn = config.get("fqdn", "")
245
self.created = datetime.datetime.now()
327
246
self.last_checked_ok = None
328
self.timeout = string_to_delta(config[u"timeout"])
329
self.interval = string_to_delta(config[u"interval"])
330
self.disable_hook = disable_hook
247
self.timeout = string_to_delta(config["timeout"])
248
self.interval = string_to_delta(config["interval"])
249
self.stop_hook = stop_hook
331
250
self.checker = None
332
251
self.checker_initiator_tag = None
333
self.disable_initiator_tag = None
252
self.stop_initiator_tag = None
334
253
self.checker_callback_tag = None
335
self.checker_command = config[u"checker"]
336
self.current_checker_command = None
337
self.last_connect = None
338
self._approved = None
339
self.approved_by_default = config.get(u"approved_by_default",
341
self.approvals_pending = 0
342
self.approval_delay = string_to_delta(
343
config[u"approval_delay"])
344
self.approval_duration = string_to_delta(
345
config[u"approval_duration"])
346
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
348
def send_changedstate(self):
349
self.changedstate.acquire()
350
self.changedstate.notify_all()
351
self.changedstate.release()
254
self.check_command = config["checker"]
354
256
"""Start this client's checker and timeout hooks"""
355
if getattr(self, u"enabled", False):
358
self.send_changedstate()
359
self.last_enabled = datetime.datetime.utcnow()
360
257
# Schedule a new checker to be started an 'interval' from now,
361
258
# and every interval from then on.
362
self.checker_initiator_tag = (gobject.timeout_add
363
(self.interval_milliseconds(),
365
# Schedule a disable() when 'timeout' has passed
366
self.disable_initiator_tag = (gobject.timeout_add
367
(self.timeout_milliseconds(),
259
self.checker_initiator_tag = gobject.timeout_add\
260
(self._interval_milliseconds,
370
262
# Also start a new checker *right now*.
371
263
self.start_checker()
373
def disable(self, quiet=True):
374
"""Disable this client."""
375
if not getattr(self, "enabled", False):
264
# Schedule a stop() when 'timeout' has passed
265
self.stop_initiator_tag = gobject.timeout_add\
266
(self._timeout_milliseconds,
270
The possibility that a client might be restarted is left open,
271
but not currently used."""
272
# If this client doesn't have a secret, it is already stopped.
274
logger.info(u"Stopping client %s", self.name)
378
self.send_changedstate()
380
logger.info(u"Disabling client %s", self.name)
381
if getattr(self, u"disable_initiator_tag", False):
382
gobject.source_remove(self.disable_initiator_tag)
383
self.disable_initiator_tag = None
384
if getattr(self, u"checker_initiator_tag", False):
278
if getattr(self, "stop_initiator_tag", False):
279
gobject.source_remove(self.stop_initiator_tag)
280
self.stop_initiator_tag = None
281
if getattr(self, "checker_initiator_tag", False):
385
282
gobject.source_remove(self.checker_initiator_tag)
386
283
self.checker_initiator_tag = None
387
284
self.stop_checker()
388
if self.disable_hook:
389
self.disable_hook(self)
391
287
# Do not run this again if called by a gobject.timeout_add
394
289
def __del__(self):
395
self.disable_hook = None
398
def checker_callback(self, pid, condition, command):
290
self.stop_hook = None
292
def checker_callback(self, pid, condition):
399
293
"""The checker has completed, so take appropriate actions."""
294
now = datetime.datetime.now()
400
295
self.checker_callback_tag = None
401
296
self.checker = None
402
if os.WIFEXITED(condition):
403
exitstatus = os.WEXITSTATUS(condition)
405
logger.info(u"Checker for %(name)s succeeded",
409
logger.info(u"Checker for %(name)s failed",
297
if os.WIFEXITED(condition) \
298
and (os.WEXITSTATUS(condition) == 0):
299
logger.info(u"Checker for %(name)s succeeded",
301
self.last_checked_ok = now
302
gobject.source_remove(self.stop_initiator_tag)
303
self.stop_initiator_tag = gobject.timeout_add\
304
(self._timeout_milliseconds,
306
elif not os.WIFEXITED(condition):
412
307
logger.warning(u"Checker for %(name)s crashed?",
415
def checked_ok(self):
416
"""Bump up the timeout for this client.
418
This should only be called when the client has been seen,
421
self.last_checked_ok = datetime.datetime.utcnow()
422
gobject.source_remove(self.disable_initiator_tag)
423
self.disable_initiator_tag = (gobject.timeout_add
424
(self.timeout_milliseconds(),
427
def need_approval(self):
428
self.last_approval_request = datetime.datetime.utcnow()
310
logger.info(u"Checker for %(name)s failed",
430
312
def start_checker(self):
431
313
"""Start a new checker subprocess if one is not running.
433
314
If a checker already exists, leave it running and do
435
316
# The reason for not killing a running checker is that if we
438
319
# client would inevitably timeout, since no checker would get
439
320
# a chance to run to completion. If we instead leave running
440
321
# checkers alone, the checker would have to take more time
441
# than 'timeout' for the client to be disabled, which is as it
444
# If a checker exists, make sure it is not a zombie
446
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
447
except (AttributeError, OSError), error:
448
if (isinstance(error, OSError)
449
and error.errno != errno.ECHILD):
453
logger.warning(u"Checker was a zombie")
454
gobject.source_remove(self.checker_callback_tag)
455
self.checker_callback(pid, status,
456
self.current_checker_command)
457
# Start a new checker if needed
322
# than 'timeout' for the client to be declared invalid, which
323
# is as it should be.
458
324
if self.checker is None:
460
# In case checker_command has exactly one % operator
461
command = self.checker_command % self.host
326
# In case check_command has exactly one % operator
327
command = self.check_command % self.fqdn
462
328
except TypeError:
463
329
# Escape attributes for the shell
464
escaped_attrs = dict(
466
re.escape(unicode(str(getattr(self, attr, u"")),
470
self.runtime_expansions)
330
escaped_attrs = dict((key, re.escape(str(val)))
332
vars(self).iteritems())
473
command = self.checker_command % escaped_attrs
334
command = self.check_command % escaped_attrs
474
335
except TypeError, error:
475
336
logger.error(u'Could not format string "%s":'
476
u' %s', self.checker_command, error)
337
u' %s', self.check_command, error)
477
338
return True # Try again later
478
self.current_checker_command = command
480
340
logger.info(u"Starting checker %r for %s",
481
341
command, self.name)
482
# We don't need to redirect stdout and stderr, since
483
# in normal mode, that is already done by daemon(),
484
# and in debug mode we don't want to. (Stdin is
485
# always replaced by /dev/null.)
486
342
self.checker = subprocess.Popen(command,
488
shell=True, cwd=u"/")
489
self.checker_callback_tag = (gobject.child_watch_add
491
self.checker_callback,
493
# The checker may have completed before the gobject
494
# watch was added. Check for this.
495
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
497
gobject.source_remove(self.checker_callback_tag)
498
self.checker_callback(pid, status, command)
499
except OSError, error:
345
self.checker_callback_tag = gobject.child_watch_add\
347
self.checker_callback)
348
except subprocess.OSError, error:
500
349
logger.error(u"Failed to start subprocess: %s",
502
351
# Re-run this periodically if run by gobject.timeout_add
505
353
def stop_checker(self):
506
354
"""Force the checker process, if any, to stop."""
507
355
if self.checker_callback_tag:
508
356
gobject.source_remove(self.checker_callback_tag)
509
357
self.checker_callback_tag = None
510
if getattr(self, u"checker", None) is None:
358
if getattr(self, "checker", None) is None:
512
logger.debug(u"Stopping checker for %(name)s", vars(self))
360
logger.debug("Stopping checker for %(name)s", vars(self))
514
362
os.kill(self.checker.pid, signal.SIGTERM)
516
364
#if self.checker.poll() is None:
517
365
# os.kill(self.checker.pid, signal.SIGKILL)
518
366
except OSError, error:
519
367
if error.errno != errno.ESRCH: # No such process
521
369
self.checker = None
523
def dbus_service_property(dbus_interface, signature=u"v",
524
access=u"readwrite", byte_arrays=False):
525
"""Decorators for marking methods of a DBusObjectWithProperties to
526
become properties on the D-Bus.
528
The decorated method will be called with no arguments by "Get"
529
and with one argument by "Set".
531
The parameters, where they are supported, are the same as
532
dbus.service.method, except there is only "signature", since the
533
type from Get() and the type sent to Set() is the same.
535
# Encoding deeply encoded byte arrays is not supported yet by the
536
# "Set" method, so we fail early here:
537
if byte_arrays and signature != u"ay":
538
raise ValueError(u"Byte arrays not supported for non-'ay'"
539
u" signature %r" % signature)
541
func._dbus_is_property = True
542
func._dbus_interface = dbus_interface
543
func._dbus_signature = signature
544
func._dbus_access = access
545
func._dbus_name = func.__name__
546
if func._dbus_name.endswith(u"_dbus_property"):
547
func._dbus_name = func._dbus_name[:-14]
548
func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
553
class DBusPropertyException(dbus.exceptions.DBusException):
554
"""A base class for D-Bus property-related exceptions
556
def __unicode__(self):
557
return unicode(str(self))
560
class DBusPropertyAccessException(DBusPropertyException):
561
"""A property's access permissions disallows an operation.
566
class DBusPropertyNotFound(DBusPropertyException):
567
"""An attempt was made to access a non-existing property.
572
class DBusObjectWithProperties(dbus.service.Object):
573
"""A D-Bus object with properties.
575
Classes inheriting from this can use the dbus_service_property
576
decorator to expose methods as D-Bus properties. It exposes the
577
standard Get(), Set(), and GetAll() methods on the D-Bus.
581
def _is_dbus_property(obj):
582
return getattr(obj, u"_dbus_is_property", False)
584
def _get_all_dbus_properties(self):
585
"""Returns a generator of (name, attribute) pairs
587
return ((prop._dbus_name, prop)
589
inspect.getmembers(self, self._is_dbus_property))
591
def _get_dbus_property(self, interface_name, property_name):
592
"""Returns a bound method if one exists which is a D-Bus
593
property with the specified name and interface.
595
for name in (property_name,
596
property_name + u"_dbus_property"):
597
prop = getattr(self, name, None)
599
or not self._is_dbus_property(prop)
600
or prop._dbus_name != property_name
601
or (interface_name and prop._dbus_interface
602
and interface_name != prop._dbus_interface)):
606
raise DBusPropertyNotFound(self.dbus_object_path + u":"
607
+ interface_name + u"."
610
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
612
def Get(self, interface_name, property_name):
613
"""Standard D-Bus property Get() method, see D-Bus standard.
615
prop = self._get_dbus_property(interface_name, property_name)
616
if prop._dbus_access == u"write":
617
raise DBusPropertyAccessException(property_name)
619
if not hasattr(value, u"variant_level"):
621
return type(value)(value, variant_level=value.variant_level+1)
623
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
624
def Set(self, interface_name, property_name, value):
625
"""Standard D-Bus property Set() method, see D-Bus standard.
627
prop = self._get_dbus_property(interface_name, property_name)
628
if prop._dbus_access == u"read":
629
raise DBusPropertyAccessException(property_name)
630
if prop._dbus_get_args_options[u"byte_arrays"]:
631
# The byte_arrays option is not supported yet on
632
# signatures other than "ay".
633
if prop._dbus_signature != u"ay":
635
value = dbus.ByteArray(''.join(unichr(byte)
639
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
640
out_signature=u"a{sv}")
641
def GetAll(self, interface_name):
642
"""Standard D-Bus property GetAll() method, see D-Bus
645
Note: Will not include properties with access="write".
648
for name, prop in self._get_all_dbus_properties():
650
and interface_name != prop._dbus_interface):
651
# Interface non-empty but did not match
653
# Ignore write-only properties
654
if prop._dbus_access == u"write":
657
if not hasattr(value, u"variant_level"):
660
all[name] = type(value)(value, variant_level=
661
value.variant_level+1)
662
return dbus.Dictionary(all, signature=u"sv")
664
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
666
path_keyword='object_path',
667
connection_keyword='connection')
668
def Introspect(self, object_path, connection):
669
"""Standard D-Bus method, overloaded to insert property tags.
671
xmlstring = dbus.service.Object.Introspect(self, object_path,
674
document = xml.dom.minidom.parseString(xmlstring)
675
def make_tag(document, name, prop):
676
e = document.createElement(u"property")
677
e.setAttribute(u"name", name)
678
e.setAttribute(u"type", prop._dbus_signature)
679
e.setAttribute(u"access", prop._dbus_access)
681
for if_tag in document.getElementsByTagName(u"interface"):
682
for tag in (make_tag(document, name, prop)
684
in self._get_all_dbus_properties()
685
if prop._dbus_interface
686
== if_tag.getAttribute(u"name")):
687
if_tag.appendChild(tag)
688
# Add the names to the return values for the
689
# "org.freedesktop.DBus.Properties" methods
690
if (if_tag.getAttribute(u"name")
691
== u"org.freedesktop.DBus.Properties"):
692
for cn in if_tag.getElementsByTagName(u"method"):
693
if cn.getAttribute(u"name") == u"Get":
694
for arg in cn.getElementsByTagName(u"arg"):
695
if (arg.getAttribute(u"direction")
697
arg.setAttribute(u"name", u"value")
698
elif cn.getAttribute(u"name") == u"GetAll":
699
for arg in cn.getElementsByTagName(u"arg"):
700
if (arg.getAttribute(u"direction")
702
arg.setAttribute(u"name", u"props")
703
xmlstring = document.toxml(u"utf-8")
705
except (AttributeError, xml.dom.DOMException,
706
xml.parsers.expat.ExpatError), error:
707
logger.error(u"Failed to override Introspection method",
712
class ClientDBus(Client, DBusObjectWithProperties):
713
"""A Client class using D-Bus
716
dbus_object_path: dbus.ObjectPath
717
bus: dbus.SystemBus()
720
runtime_expansions = (Client.runtime_expansions
721
+ (u"dbus_object_path",))
723
# dbus.service.Object doesn't use super(), so we can't either.
725
def __init__(self, bus = None, *args, **kwargs):
726
self._approvals_pending = 0
728
Client.__init__(self, *args, **kwargs)
729
# Only now, when this client is initialized, can it show up on
731
client_object_name = unicode(self.name).translate(
732
{ord(u"."): ord(u"_"),
733
ord(u"-"): ord(u"_")})
734
self.dbus_object_path = (dbus.ObjectPath
735
(u"/clients/" + client_object_name))
736
DBusObjectWithProperties.__init__(self, self.bus,
737
self.dbus_object_path)
739
def _get_approvals_pending(self):
740
return self._approvals_pending
741
def _set_approvals_pending(self, value):
742
old_value = self._approvals_pending
743
self._approvals_pending = value
745
if (hasattr(self, "dbus_object_path")
746
and bval is not bool(old_value)):
747
dbus_bool = dbus.Boolean(bval, variant_level=1)
748
self.PropertyChanged(dbus.String(u"ApprovalPending"),
751
approvals_pending = property(_get_approvals_pending,
752
_set_approvals_pending)
753
del _get_approvals_pending, _set_approvals_pending
756
def _datetime_to_dbus(dt, variant_level=0):
757
"""Convert a UTC datetime.datetime() to a D-Bus type."""
758
return dbus.String(dt.isoformat(),
759
variant_level=variant_level)
762
oldstate = getattr(self, u"enabled", False)
763
r = Client.enable(self)
764
if oldstate != self.enabled:
766
self.PropertyChanged(dbus.String(u"Enabled"),
767
dbus.Boolean(True, variant_level=1))
768
self.PropertyChanged(
769
dbus.String(u"LastEnabled"),
770
self._datetime_to_dbus(self.last_enabled,
774
def disable(self, quiet = False):
775
oldstate = getattr(self, u"enabled", False)
776
r = Client.disable(self, quiet=quiet)
777
if not quiet and oldstate != self.enabled:
779
self.PropertyChanged(dbus.String(u"Enabled"),
780
dbus.Boolean(False, variant_level=1))
783
def __del__(self, *args, **kwargs):
785
self.remove_from_connection()
788
if hasattr(DBusObjectWithProperties, u"__del__"):
789
DBusObjectWithProperties.__del__(self, *args, **kwargs)
790
Client.__del__(self, *args, **kwargs)
792
def checker_callback(self, pid, condition, command,
794
self.checker_callback_tag = None
797
self.PropertyChanged(dbus.String(u"CheckerRunning"),
798
dbus.Boolean(False, variant_level=1))
799
if os.WIFEXITED(condition):
800
exitstatus = os.WEXITSTATUS(condition)
802
self.CheckerCompleted(dbus.Int16(exitstatus),
803
dbus.Int64(condition),
804
dbus.String(command))
807
self.CheckerCompleted(dbus.Int16(-1),
808
dbus.Int64(condition),
809
dbus.String(command))
811
return Client.checker_callback(self, pid, condition, command,
814
def checked_ok(self, *args, **kwargs):
815
r = Client.checked_ok(self, *args, **kwargs)
817
self.PropertyChanged(
818
dbus.String(u"LastCheckedOK"),
819
(self._datetime_to_dbus(self.last_checked_ok,
823
def need_approval(self, *args, **kwargs):
824
r = Client.need_approval(self, *args, **kwargs)
826
self.PropertyChanged(
827
dbus.String(u"LastApprovalRequest"),
828
(self._datetime_to_dbus(self.last_approval_request,
832
def start_checker(self, *args, **kwargs):
833
old_checker = self.checker
834
if self.checker is not None:
835
old_checker_pid = self.checker.pid
837
old_checker_pid = None
838
r = Client.start_checker(self, *args, **kwargs)
839
# Only if new checker process was started
840
if (self.checker is not None
841
and old_checker_pid != self.checker.pid):
843
self.CheckerStarted(self.current_checker_command)
844
self.PropertyChanged(
845
dbus.String(u"CheckerRunning"),
846
dbus.Boolean(True, variant_level=1))
849
def stop_checker(self, *args, **kwargs):
850
old_checker = getattr(self, u"checker", None)
851
r = Client.stop_checker(self, *args, **kwargs)
852
if (old_checker is not None
853
and getattr(self, u"checker", None) is None):
854
self.PropertyChanged(dbus.String(u"CheckerRunning"),
855
dbus.Boolean(False, variant_level=1))
858
def _reset_approved(self):
859
self._approved = None
862
def approve(self, value=True):
863
self.send_changedstate()
864
self._approved = value
865
gobject.timeout_add(self._timedelta_to_milliseconds
866
(self.approval_duration),
867
self._reset_approved)
870
## D-Bus methods, signals & properties
871
_interface = u"se.bsnet.fukt.Mandos.Client"
875
# CheckerCompleted - signal
876
@dbus.service.signal(_interface, signature=u"nxs")
877
def CheckerCompleted(self, exitcode, waitstatus, command):
881
# CheckerStarted - signal
882
@dbus.service.signal(_interface, signature=u"s")
883
def CheckerStarted(self, command):
887
# PropertyChanged - signal
888
@dbus.service.signal(_interface, signature=u"sv")
889
def PropertyChanged(self, property, value):
894
@dbus.service.signal(_interface)
897
Is sent after a successful transfer of secret from the Mandos
898
server to mandos-client
903
@dbus.service.signal(_interface, signature=u"s")
904
def Rejected(self, reason):
908
# NeedApproval - signal
909
@dbus.service.signal(_interface, signature=u"tb")
910
def NeedApproval(self, timeout, default):
912
return self.need_approval()
917
@dbus.service.method(_interface, in_signature=u"b")
918
def Approve(self, value):
922
@dbus.service.method(_interface)
924
return self.checked_ok()
927
@dbus.service.method(_interface)
932
# StartChecker - method
933
@dbus.service.method(_interface)
934
def StartChecker(self):
939
@dbus.service.method(_interface)
944
# StopChecker - method
945
@dbus.service.method(_interface)
946
def StopChecker(self):
951
# ApprovalPending - property
952
@dbus_service_property(_interface, signature=u"b", access=u"read")
953
def ApprovalPending_dbus_property(self):
954
return dbus.Boolean(bool(self.approvals_pending))
956
# ApprovedByDefault - property
957
@dbus_service_property(_interface, signature=u"b",
959
def ApprovedByDefault_dbus_property(self, value=None):
960
if value is None: # get
961
return dbus.Boolean(self.approved_by_default)
962
self.approved_by_default = bool(value)
964
self.PropertyChanged(dbus.String(u"ApprovedByDefault"),
965
dbus.Boolean(value, variant_level=1))
967
# ApprovalDelay - property
968
@dbus_service_property(_interface, signature=u"t",
970
def ApprovalDelay_dbus_property(self, value=None):
971
if value is None: # get
972
return dbus.UInt64(self.approval_delay_milliseconds())
973
self.approval_delay = datetime.timedelta(0, 0, 0, value)
975
self.PropertyChanged(dbus.String(u"ApprovalDelay"),
976
dbus.UInt64(value, variant_level=1))
978
# ApprovalDuration - property
979
@dbus_service_property(_interface, signature=u"t",
981
def ApprovalDuration_dbus_property(self, value=None):
982
if value is None: # get
983
return dbus.UInt64(self._timedelta_to_milliseconds(
984
self.approval_duration))
985
self.approval_duration = datetime.timedelta(0, 0, 0, value)
987
self.PropertyChanged(dbus.String(u"ApprovalDuration"),
988
dbus.UInt64(value, variant_level=1))
991
@dbus_service_property(_interface, signature=u"s", access=u"read")
992
def Name_dbus_property(self):
993
return dbus.String(self.name)
995
# Fingerprint - property
996
@dbus_service_property(_interface, signature=u"s", access=u"read")
997
def Fingerprint_dbus_property(self):
998
return dbus.String(self.fingerprint)
1001
@dbus_service_property(_interface, signature=u"s",
1002
access=u"readwrite")
1003
def Host_dbus_property(self, value=None):
1004
if value is None: # get
1005
return dbus.String(self.host)
1008
self.PropertyChanged(dbus.String(u"Host"),
1009
dbus.String(value, variant_level=1))
1011
# Created - property
1012
@dbus_service_property(_interface, signature=u"s", access=u"read")
1013
def Created_dbus_property(self):
1014
return dbus.String(self._datetime_to_dbus(self.created))
1016
# LastEnabled - property
1017
@dbus_service_property(_interface, signature=u"s", access=u"read")
1018
def LastEnabled_dbus_property(self):
1019
if self.last_enabled is None:
1020
return dbus.String(u"")
1021
return dbus.String(self._datetime_to_dbus(self.last_enabled))
1023
# Enabled - property
1024
@dbus_service_property(_interface, signature=u"b",
1025
access=u"readwrite")
1026
def Enabled_dbus_property(self, value=None):
1027
if value is None: # get
1028
return dbus.Boolean(self.enabled)
1034
# LastCheckedOK - property
1035
@dbus_service_property(_interface, signature=u"s",
1036
access=u"readwrite")
1037
def LastCheckedOK_dbus_property(self, value=None):
1038
if value is not None:
370
def still_valid(self):
371
"""Has the timeout not yet passed for this client?"""
372
now = datetime.datetime.now()
1041
373
if self.last_checked_ok is None:
1042
return dbus.String(u"")
1043
return dbus.String(self._datetime_to_dbus(self
1046
# LastApprovalRequest - property
1047
@dbus_service_property(_interface, signature=u"s", access=u"read")
1048
def LastApprovalRequest_dbus_property(self):
1049
if self.last_approval_request is None:
1050
return dbus.String(u"")
1051
return dbus.String(self.
1052
_datetime_to_dbus(self
1053
.last_approval_request))
1055
# Timeout - property
1056
@dbus_service_property(_interface, signature=u"t",
1057
access=u"readwrite")
1058
def Timeout_dbus_property(self, value=None):
1059
if value is None: # get
1060
return dbus.UInt64(self.timeout_milliseconds())
1061
self.timeout = datetime.timedelta(0, 0, 0, value)
1063
self.PropertyChanged(dbus.String(u"Timeout"),
1064
dbus.UInt64(value, variant_level=1))
1065
if getattr(self, u"disable_initiator_tag", None) is None:
1067
# Reschedule timeout
1068
gobject.source_remove(self.disable_initiator_tag)
1069
self.disable_initiator_tag = None
1070
time_to_die = (self.
1071
_timedelta_to_milliseconds((self
1076
if time_to_die <= 0:
1077
# The timeout has passed
1080
self.disable_initiator_tag = (gobject.timeout_add
1081
(time_to_die, self.disable))
1083
# Interval - property
1084
@dbus_service_property(_interface, signature=u"t",
1085
access=u"readwrite")
1086
def Interval_dbus_property(self, value=None):
1087
if value is None: # get
1088
return dbus.UInt64(self.interval_milliseconds())
1089
self.interval = datetime.timedelta(0, 0, 0, value)
1091
self.PropertyChanged(dbus.String(u"Interval"),
1092
dbus.UInt64(value, variant_level=1))
1093
if getattr(self, u"checker_initiator_tag", None) is None:
1095
# Reschedule checker run
1096
gobject.source_remove(self.checker_initiator_tag)
1097
self.checker_initiator_tag = (gobject.timeout_add
1098
(value, self.start_checker))
1099
self.start_checker() # Start one now, too
1101
# Checker - property
1102
@dbus_service_property(_interface, signature=u"s",
1103
access=u"readwrite")
1104
def Checker_dbus_property(self, value=None):
1105
if value is None: # get
1106
return dbus.String(self.checker_command)
1107
self.checker_command = value
1109
self.PropertyChanged(dbus.String(u"Checker"),
1110
dbus.String(self.checker_command,
1113
# CheckerRunning - property
1114
@dbus_service_property(_interface, signature=u"b",
1115
access=u"readwrite")
1116
def CheckerRunning_dbus_property(self, value=None):
1117
if value is None: # get
1118
return dbus.Boolean(self.checker is not None)
1120
self.start_checker()
1124
# ObjectPath - property
1125
@dbus_service_property(_interface, signature=u"o", access=u"read")
1126
def ObjectPath_dbus_property(self):
1127
return self.dbus_object_path # is already a dbus.ObjectPath
1130
@dbus_service_property(_interface, signature=u"ay",
1131
access=u"write", byte_arrays=True)
1132
def Secret_dbus_property(self, value):
1133
self.secret = str(value)
1138
class ProxyClient(object):
1139
def __init__(self, child_pipe, fpr, address):
1140
self._pipe = child_pipe
1141
self._pipe.send(('init', fpr, address))
1142
if not self._pipe.recv():
1145
def __getattribute__(self, name):
1146
if(name == '_pipe'):
1147
return super(ProxyClient, self).__getattribute__(name)
1148
self._pipe.send(('getattr', name))
1149
data = self._pipe.recv()
1150
if data[0] == 'data':
1152
if data[0] == 'function':
1153
def func(*args, **kwargs):
1154
self._pipe.send(('funcall', name, args, kwargs))
1155
return self._pipe.recv()[1]
1158
def __setattr__(self, name, value):
1159
if(name == '_pipe'):
1160
return super(ProxyClient, self).__setattr__(name, value)
1161
self._pipe.send(('setattr', name, value))
1164
class ClientHandler(socketserver.BaseRequestHandler, object):
1165
"""A class to handle client connections.
1167
Instantiated once for each connection to handle it.
374
return now < (self.created + self.timeout)
376
return now < (self.last_checked_ok + self.timeout)
379
def peer_certificate(session):
380
"Return the peer's OpenPGP certificate as a bytestring"
381
# If not an OpenPGP certificate...
382
if gnutls.library.functions.gnutls_certificate_type_get\
383
(session._c_object) \
384
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
385
# ...do the normal thing
386
return session.peer_certificate
387
list_size = ctypes.c_uint()
388
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
389
(session._c_object, ctypes.byref(list_size))
390
if list_size.value == 0:
393
return ctypes.string_at(cert.data, cert.size)
396
def fingerprint(openpgp):
397
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
398
# New empty GnuTLS certificate
399
crt = gnutls.library.types.gnutls_openpgp_crt_t()
400
gnutls.library.functions.gnutls_openpgp_crt_init\
402
# New GnuTLS "datum" with the OpenPGP public key
403
datum = gnutls.library.types.gnutls_datum_t\
404
(ctypes.cast(ctypes.c_char_p(openpgp),
405
ctypes.POINTER(ctypes.c_ubyte)),
406
ctypes.c_uint(len(openpgp)))
407
# Import the OpenPGP public key into the certificate
408
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
411
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
412
# New buffer for the fingerprint
413
buffer = ctypes.create_string_buffer(20)
414
buffer_length = ctypes.c_size_t()
415
# Get the fingerprint from the certificate into the buffer
416
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
417
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
418
# Deinit the certificate
419
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
420
# Convert the buffer to a Python bytestring
421
fpr = ctypes.string_at(buffer, buffer_length.value)
422
# Convert the bytestring to hexadecimal notation
423
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
427
class tcp_handler(SocketServer.BaseRequestHandler, object):
428
"""A TCP request handler class.
429
Instantiated by IPv6_TCPServer for each request to handle it.
1168
430
Note: This will run in its own forked process."""
1170
432
def handle(self):
1171
with contextlib.closing(self.server.child_pipe) as child_pipe:
1172
logger.info(u"TCP connection from: %s",
1173
unicode(self.client_address))
1174
logger.debug(u"Pipe FD: %d",
1175
self.server.child_pipe.fileno())
1177
session = (gnutls.connection
1178
.ClientSession(self.request,
1180
.X509Credentials()))
1182
# Note: gnutls.connection.X509Credentials is really a
1183
# generic GnuTLS certificate credentials object so long as
1184
# no X.509 keys are added to it. Therefore, we can use it
1185
# here despite using OpenPGP certificates.
1187
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1188
# u"+AES-256-CBC", u"+SHA1",
1189
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1191
# Use a fallback default, since this MUST be set.
1192
priority = self.server.gnutls_priority
1193
if priority is None:
1194
priority = u"NORMAL"
1195
(gnutls.library.functions
1196
.gnutls_priority_set_direct(session._c_object,
1199
# Start communication using the Mandos protocol
1200
# Get protocol number
1201
line = self.request.makefile().readline()
1202
logger.debug(u"Protocol version: %r", line)
1204
if int(line.strip().split()[0]) > 1:
1206
except (ValueError, IndexError, RuntimeError), error:
1207
logger.error(u"Unknown protocol version: %s", error)
1210
# Start GnuTLS connection
1213
except gnutls.errors.GNUTLSError, error:
1214
logger.warning(u"Handshake failed: %s", error)
1215
# Do not run session.bye() here: the session is not
1216
# established. Just abandon the request.
1218
logger.debug(u"Handshake succeeded")
1220
approval_required = False
1223
fpr = self.fingerprint(self.peer_certificate
1225
except (TypeError, gnutls.errors.GNUTLSError), error:
1226
logger.warning(u"Bad certificate: %s", error)
1228
logger.debug(u"Fingerprint: %s", fpr)
1231
client = ProxyClient(child_pipe, fpr,
1232
self.client_address)
1236
if client.approval_delay:
1237
delay = client.approval_delay
1238
client.approvals_pending += 1
1239
approval_required = True
1242
if not client.enabled:
1243
logger.warning(u"Client %s is disabled",
1245
if self.server.use_dbus:
1247
client.Rejected("Disabled")
1250
if client._approved or not client.approval_delay:
1251
#We are approved or approval is disabled
1253
elif client._approved is None:
1254
logger.info(u"Client %s needs approval",
1256
if self.server.use_dbus:
1258
client.NeedApproval(
1259
client.approval_delay_milliseconds(),
1260
client.approved_by_default)
1262
logger.warning(u"Client %s was not approved",
1264
if self.server.use_dbus:
1266
client.Rejected("Denied")
1269
#wait until timeout or approved
1270
#x = float(client._timedelta_to_milliseconds(delay))
1271
time = datetime.datetime.now()
1272
client.changedstate.acquire()
1273
client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1274
client.changedstate.release()
1275
time2 = datetime.datetime.now()
1276
if (time2 - time) >= delay:
1277
if not client.approved_by_default:
1278
logger.warning("Client %s timed out while"
1279
" waiting for approval",
1281
if self.server.use_dbus:
1283
client.Rejected("Approval timed out")
1288
delay -= time2 - time
1291
while sent_size < len(client.secret):
1293
sent = session.send(client.secret[sent_size:])
1294
except (gnutls.errors.GNUTLSError), error:
1295
logger.warning("gnutls send failed")
1297
logger.debug(u"Sent: %d, remaining: %d",
1298
sent, len(client.secret)
1299
- (sent_size + sent))
1302
logger.info(u"Sending secret to %s", client.name)
1303
# bump the timeout as if seen
1305
if self.server.use_dbus:
1310
if approval_required:
1311
client.approvals_pending -= 1
1314
except (gnutls.errors.GNUTLSError), error:
1315
logger.warning("GnuTLS bye failed")
1318
def peer_certificate(session):
1319
"Return the peer's OpenPGP certificate as a bytestring"
1320
# If not an OpenPGP certificate...
1321
if (gnutls.library.functions
1322
.gnutls_certificate_type_get(session._c_object)
1323
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1324
# ...do the normal thing
1325
return session.peer_certificate
1326
list_size = ctypes.c_uint(1)
1327
cert_list = (gnutls.library.functions
1328
.gnutls_certificate_get_peers
1329
(session._c_object, ctypes.byref(list_size)))
1330
if not bool(cert_list) and list_size.value != 0:
1331
raise gnutls.errors.GNUTLSError(u"error getting peer"
1333
if list_size.value == 0:
1336
return ctypes.string_at(cert.data, cert.size)
1339
def fingerprint(openpgp):
1340
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1341
# New GnuTLS "datum" with the OpenPGP public key
1342
datum = (gnutls.library.types
1343
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1346
ctypes.c_uint(len(openpgp))))
1347
# New empty GnuTLS certificate
1348
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1349
(gnutls.library.functions
1350
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1351
# Import the OpenPGP public key into the certificate
1352
(gnutls.library.functions
1353
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1354
gnutls.library.constants
1355
.GNUTLS_OPENPGP_FMT_RAW))
1356
# Verify the self signature in the key
1357
crtverify = ctypes.c_uint()
1358
(gnutls.library.functions
1359
.gnutls_openpgp_crt_verify_self(crt, 0,
1360
ctypes.byref(crtverify)))
1361
if crtverify.value != 0:
1362
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1363
raise (gnutls.errors.CertificateSecurityError
1365
# New buffer for the fingerprint
1366
buf = ctypes.create_string_buffer(20)
1367
buf_len = ctypes.c_size_t()
1368
# Get the fingerprint from the certificate into the buffer
1369
(gnutls.library.functions
1370
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1371
ctypes.byref(buf_len)))
1372
# Deinit the certificate
1373
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1374
# Convert the buffer to a Python bytestring
1375
fpr = ctypes.string_at(buf, buf_len.value)
1376
# Convert the bytestring to hexadecimal notation
1377
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1381
class MultiprocessingMixIn(object):
1382
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1383
def sub_process_main(self, request, address):
1385
self.finish_request(request, address)
1387
self.handle_error(request, address)
1388
self.close_request(request)
1390
def process_request(self, request, address):
1391
"""Start a new process to process the request."""
1392
multiprocessing.Process(target = self.sub_process_main,
1393
args = (request, address)).start()
1395
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1396
""" adds a pipe to the MixIn """
1397
def process_request(self, request, client_address):
1398
"""Overrides and wraps the original process_request().
1400
This function creates a new pipe in self.pipe
1402
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1404
super(MultiprocessingMixInWithPipe,
1405
self).process_request(request, client_address)
1406
self.child_pipe.close()
1407
self.add_pipe(parent_pipe)
1409
def add_pipe(self, parent_pipe):
1410
"""Dummy function; override as necessary"""
1413
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1414
socketserver.TCPServer, object):
1415
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
433
logger.info(u"TCP connection from: %s",
434
unicode(self.client_address))
435
session = gnutls.connection.ClientSession\
436
(self.request, gnutls.connection.X509Credentials())
438
line = self.request.makefile().readline()
439
logger.debug(u"Protocol version: %r", line)
441
if int(line.strip().split()[0]) > 1:
443
except (ValueError, IndexError, RuntimeError), error:
444
logger.error(u"Unknown protocol version: %s", error)
447
# Note: gnutls.connection.X509Credentials is really a generic
448
# GnuTLS certificate credentials object so long as no X.509
449
# keys are added to it. Therefore, we can use it here despite
450
# using OpenPGP certificates.
452
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
453
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
455
priority = "NORMAL" # Fallback default, since this
457
if self.server.settings["priority"]:
458
priority = self.server.settings["priority"]
459
gnutls.library.functions.gnutls_priority_set_direct\
460
(session._c_object, priority, None);
464
except gnutls.errors.GNUTLSError, error:
465
logger.warning(u"Handshake failed: %s", error)
466
# Do not run session.bye() here: the session is not
467
# established. Just abandon the request.
470
fpr = fingerprint(peer_certificate(session))
471
except (TypeError, gnutls.errors.GNUTLSError), error:
472
logger.warning(u"Bad certificate: %s", error)
475
logger.debug(u"Fingerprint: %s", fpr)
477
for c in self.server.clients:
478
if c.fingerprint == fpr:
482
logger.warning(u"Client not found for fingerprint: %s",
486
# Have to check if client.still_valid(), since it is possible
487
# that the client timed out while establishing the GnuTLS
489
if not client.still_valid():
490
logger.warning(u"Client %(name)s is invalid",
495
while sent_size < len(client.secret):
496
sent = session.send(client.secret[sent_size:])
497
logger.debug(u"Sent: %d, remaining: %d",
498
sent, len(client.secret)
499
- (sent_size + sent))
504
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
505
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1418
enabled: Boolean; whether this server is activated yet
1419
interface: None or a network interface name (string)
1420
use_ipv6: Boolean; to use IPv6 or not
507
settings: Server settings
508
clients: Set() of Client objects
1422
def __init__(self, server_address, RequestHandlerClass,
1423
interface=None, use_ipv6=True):
1424
self.interface = interface
1426
self.address_family = socket.AF_INET6
1427
socketserver.TCPServer.__init__(self, server_address,
1428
RequestHandlerClass)
510
address_family = socket.AF_INET6
511
def __init__(self, *args, **kwargs):
512
if "settings" in kwargs:
513
self.settings = kwargs["settings"]
514
del kwargs["settings"]
515
if "clients" in kwargs:
516
self.clients = kwargs["clients"]
517
del kwargs["clients"]
518
return super(type(self), self).__init__(*args, **kwargs)
1429
519
def server_bind(self):
1430
520
"""This overrides the normal server_bind() function
1431
521
to bind to an interface if one was specified, and also NOT to
1432
522
bind to an address or port if they were not specified."""
1433
if self.interface is not None:
1434
if SO_BINDTODEVICE is None:
1435
logger.error(u"SO_BINDTODEVICE does not exist;"
1436
u" cannot bind to interface %s",
1440
self.socket.setsockopt(socket.SOL_SOCKET,
1444
except socket.error, error:
1445
if error[0] == errno.EPERM:
1446
logger.error(u"No permission to"
1447
u" bind to interface %s",
1449
elif error[0] == errno.ENOPROTOOPT:
1450
logger.error(u"SO_BINDTODEVICE not available;"
1451
u" cannot bind to interface %s",
523
if self.settings["interface"]:
524
# 25 is from /usr/include/asm-i486/socket.h
525
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
527
self.socket.setsockopt(socket.SOL_SOCKET,
529
self.settings["interface"])
530
except socket.error, error:
531
if error[0] == errno.EPERM:
532
logger.error(u"No permission to"
533
u" bind to interface %s",
534
self.settings["interface"])
1455
537
# Only bind(2) the socket if we really need to.
1456
538
if self.server_address[0] or self.server_address[1]:
1457
539
if not self.server_address[0]:
1458
if self.address_family == socket.AF_INET6:
1459
any_address = u"::" # in6addr_any
1461
any_address = socket.INADDR_ANY
1462
self.server_address = (any_address,
541
self.server_address = (in6addr_any,
1463
542
self.server_address[1])
1464
elif not self.server_address[1]:
543
elif self.server_address[1] is None:
1465
544
self.server_address = (self.server_address[0],
1467
# if self.interface:
1468
# self.server_address = (self.server_address[0],
1473
return socketserver.TCPServer.server_bind(self)
1476
class MandosServer(IPv6_TCPServer):
1480
clients: set of Client objects
1481
gnutls_priority GnuTLS priority string
1482
use_dbus: Boolean; to emit D-Bus signals or not
1484
Assumes a gobject.MainLoop event loop.
1486
def __init__(self, server_address, RequestHandlerClass,
1487
interface=None, use_ipv6=True, clients=None,
1488
gnutls_priority=None, use_dbus=True):
1489
self.enabled = False
1490
self.clients = clients
1491
if self.clients is None:
1492
self.clients = set()
1493
self.use_dbus = use_dbus
1494
self.gnutls_priority = gnutls_priority
1495
IPv6_TCPServer.__init__(self, server_address,
1496
RequestHandlerClass,
1497
interface = interface,
1498
use_ipv6 = use_ipv6)
1499
def server_activate(self):
1501
return socketserver.TCPServer.server_activate(self)
1504
def add_pipe(self, parent_pipe):
1505
# Call "handle_ipc" for both data and EOF events
1506
gobject.io_add_watch(parent_pipe.fileno(),
1507
gobject.IO_IN | gobject.IO_HUP,
1508
functools.partial(self.handle_ipc,
1509
parent_pipe = parent_pipe))
1511
def handle_ipc(self, source, condition, parent_pipe=None,
1512
client_object=None):
1514
gobject.IO_IN: u"IN", # There is data to read.
1515
gobject.IO_OUT: u"OUT", # Data can be written (without
1517
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1518
gobject.IO_ERR: u"ERR", # Error condition.
1519
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1520
# broken, usually for pipes and
1523
conditions_string = ' | '.join(name
1525
condition_names.iteritems()
1526
if cond & condition)
1527
# error or the other end of multiprocessing.Pipe has closed
1528
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1531
# Read a request from the child
1532
request = parent_pipe.recv()
1533
command = request[0]
1535
if command == 'init':
1537
address = request[2]
1539
for c in self.clients:
1540
if c.fingerprint == fpr:
1544
logger.warning(u"Client not found for fingerprint: %s, ad"
1545
u"dress: %s", fpr, address)
1548
mandos_dbus_service.ClientNotFound(fpr, address[0])
1549
parent_pipe.send(False)
1552
gobject.io_add_watch(parent_pipe.fileno(),
1553
gobject.IO_IN | gobject.IO_HUP,
1554
functools.partial(self.handle_ipc,
1555
parent_pipe = parent_pipe,
1556
client_object = client))
1557
parent_pipe.send(True)
1558
# remove the old hook in favor of the new above hook on same fileno
1560
if command == 'funcall':
1561
funcname = request[1]
1565
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1567
if command == 'getattr':
1568
attrname = request[1]
1569
if callable(client_object.__getattribute__(attrname)):
1570
parent_pipe.send(('function',))
1572
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1574
if command == 'setattr':
1575
attrname = request[1]
1577
setattr(client_object, attrname, value)
546
return super(type(self), self).server_bind()
1582
549
def string_to_delta(interval):
1583
550
"""Parse a string and return a datetime.timedelta
1585
>>> string_to_delta(u'7d')
552
>>> string_to_delta('7d')
1586
553
datetime.timedelta(7)
1587
>>> string_to_delta(u'60s')
554
>>> string_to_delta('60s')
1588
555
datetime.timedelta(0, 60)
1589
>>> string_to_delta(u'60m')
556
>>> string_to_delta('60m')
1590
557
datetime.timedelta(0, 3600)
1591
>>> string_to_delta(u'24h')
558
>>> string_to_delta('24h')
1592
559
datetime.timedelta(1)
1593
560
>>> string_to_delta(u'1w')
1594
561
datetime.timedelta(7)
1595
>>> string_to_delta(u'5m 30s')
1596
datetime.timedelta(0, 330)
1598
timevalue = datetime.timedelta(0)
1599
for s in interval.split():
1601
suffix = unicode(s[-1])
1604
delta = datetime.timedelta(value)
1605
elif suffix == u"s":
1606
delta = datetime.timedelta(0, value)
1607
elif suffix == u"m":
1608
delta = datetime.timedelta(0, 0, 0, 0, value)
1609
elif suffix == u"h":
1610
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1611
elif suffix == u"w":
1612
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1614
raise ValueError(u"Unknown suffix %r" % suffix)
1615
except (ValueError, IndexError), e:
1616
raise ValueError(e.message)
564
suffix=unicode(interval[-1])
565
value=int(interval[:-1])
567
delta = datetime.timedelta(value)
569
delta = datetime.timedelta(0, value)
571
delta = datetime.timedelta(0, 0, 0, 0, value)
573
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
575
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
578
except (ValueError, IndexError):
583
def server_state_changed(state):
584
"""Derived from the Avahi example code"""
585
if state == avahi.SERVER_COLLISION:
586
logger.error(u"Server name collision")
588
elif state == avahi.SERVER_RUNNING:
592
def entry_group_state_changed(state, error):
593
"""Derived from the Avahi example code"""
594
logger.debug(u"state change: %i", state)
596
if state == avahi.ENTRY_GROUP_ESTABLISHED:
597
logger.debug(u"Service established.")
598
elif state == avahi.ENTRY_GROUP_COLLISION:
599
logger.warning(u"Service name collision.")
601
elif state == avahi.ENTRY_GROUP_FAILURE:
602
logger.critical(u"Error in group state changed %s",
604
raise AvahiGroupError("State changed: %s", str(error))
1621
606
def if_nametoindex(interface):
1622
"""Call the C function if_nametoindex(), or equivalent
1624
Note: This function cannot accept a unicode string."""
607
"""Call the C function if_nametoindex(), or equivalent"""
1625
608
global if_nametoindex
1627
if_nametoindex = (ctypes.cdll.LoadLibrary
1628
(ctypes.util.find_library(u"c"))
610
if "ctypes.util" not in sys.modules:
612
if_nametoindex = ctypes.cdll.LoadLibrary\
613
(ctypes.util.find_library("c")).if_nametoindex
1630
614
except (OSError, AttributeError):
1631
logger.warning(u"Doing if_nametoindex the hard way")
615
if "struct" not in sys.modules:
617
if "fcntl" not in sys.modules:
1632
619
def if_nametoindex(interface):
1633
620
"Get an interface index the hard way, i.e. using fcntl()"
1634
621
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1635
with contextlib.closing(socket.socket()) as s:
1636
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1637
struct.pack(str(u"16s16x"),
1639
interface_index = struct.unpack(str(u"I"),
623
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
624
struct.pack("16s16x", interface))
626
interface_index = struct.unpack("I", ifreq[16:20])[0]
1641
627
return interface_index
1642
628
return if_nametoindex(interface)
1645
def daemon(nochdir = False, noclose = False):
631
def daemon(nochdir, noclose):
1646
632
"""See daemon(3). Standard BSD Unix function.
1648
633
This should really exist as os.daemon, but it doesn't (yet)."""
1657
640
# Close all standard open file descriptors
1658
641
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1659
642
if not stat.S_ISCHR(os.fstat(null).st_mode):
1660
643
raise OSError(errno.ENODEV,
1661
u"%s not a character device"
644
"/dev/null not a character device")
1663
645
os.dup2(null, sys.stdin.fileno())
1664
646
os.dup2(null, sys.stdout.fileno())
1665
647
os.dup2(null, sys.stderr.fileno())
1709
683
# Default values for config file for server-global settings
1710
server_defaults = { u"interface": u"",
1715
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1716
u"servicename": u"Mandos",
1717
u"use_dbus": u"True",
1718
u"use_ipv6": u"True",
684
server_defaults = { "interface": "",
689
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
690
"servicename": "Mandos",
1722
693
# Parse config file for server-global settings
1723
server_config = configparser.SafeConfigParser(server_defaults)
694
server_config = ConfigParser.SafeConfigParser(server_defaults)
1724
695
del server_defaults
1725
server_config.read(os.path.join(options.configdir,
696
server_config.read(os.path.join(options.configdir, "server.conf"))
697
server_section = "server"
1727
698
# Convert the SafeConfigParser object to a dict
1728
server_settings = server_config.defaults()
1729
# Use the appropriate methods on the non-string config options
1730
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1731
server_settings[option] = server_config.getboolean(u"DEFAULT",
1733
if server_settings["port"]:
1734
server_settings["port"] = server_config.getint(u"DEFAULT",
699
server_settings = dict(server_config.items(server_section))
700
# Use getboolean on the boolean config option
701
server_settings["debug"] = server_config.getboolean\
702
(server_section, "debug")
1736
703
del server_config
1738
705
# Override the settings from the config file with command line
1739
706
# options, if set.
1740
for option in (u"interface", u"address", u"port", u"debug",
1741
u"priority", u"servicename", u"configdir",
1742
u"use_dbus", u"use_ipv6", u"debuglevel"):
707
for option in ("interface", "address", "port", "debug",
708
"priority", "servicename", "configdir"):
1743
709
value = getattr(options, option)
1744
710
if value is not None:
1745
711
server_settings[option] = value
1747
# Force all strings to be unicode
1748
for option in server_settings.keys():
1749
if type(server_settings[option]) is str:
1750
server_settings[option] = unicode(server_settings[option])
1751
713
# Now we have our good server settings in "server_settings"
1753
##################################################################
1756
debug = server_settings[u"debug"]
1757
debuglevel = server_settings[u"debuglevel"]
1758
use_dbus = server_settings[u"use_dbus"]
1759
use_ipv6 = server_settings[u"use_ipv6"]
1761
if server_settings[u"servicename"] != u"Mandos":
1762
syslogger.setFormatter(logging.Formatter
1763
(u'Mandos (%s) [%%(process)d]:'
1764
u' %%(levelname)s: %%(message)s'
1765
% server_settings[u"servicename"]))
1767
715
# Parse config file with clients
1768
client_defaults = { u"timeout": u"1h",
1770
u"checker": u"fping -q -- %%(host)s",
1772
u"approval_delay": u"0s",
1773
u"approval_duration": u"1s",
716
client_defaults = { "timeout": "1h",
718
"checker": "fping -q -- %%(fqdn)s",
1775
client_config = configparser.SafeConfigParser(client_defaults)
1776
client_config.read(os.path.join(server_settings[u"configdir"],
1779
global mandos_dbus_service
1780
mandos_dbus_service = None
1782
tcp_server = MandosServer((server_settings[u"address"],
1783
server_settings[u"port"]),
1785
interface=(server_settings[u"interface"]
1789
server_settings[u"priority"],
1792
pidfilename = u"/var/run/mandos.pid"
1794
pidfile = open(pidfilename, u"w")
1796
logger.error(u"Could not open file %r", pidfilename)
1799
uid = pwd.getpwnam(u"_mandos").pw_uid
1800
gid = pwd.getpwnam(u"_mandos").pw_gid
1803
uid = pwd.getpwnam(u"mandos").pw_uid
1804
gid = pwd.getpwnam(u"mandos").pw_gid
1807
uid = pwd.getpwnam(u"nobody").pw_uid
1808
gid = pwd.getpwnam(u"nobody").pw_gid
1815
except OSError, error:
1816
if error[0] != errno.EPERM:
1819
if not debug and not debuglevel:
1820
syslogger.setLevel(logging.WARNING)
1821
console.setLevel(logging.WARNING)
1823
level = getattr(logging, debuglevel.upper())
1824
syslogger.setLevel(level)
1825
console.setLevel(level)
1828
# Enable all possible GnuTLS debugging
1830
# "Use a log level over 10 to enable all debugging options."
1832
gnutls.library.functions.gnutls_global_set_log_level(11)
1834
@gnutls.library.types.gnutls_log_func
1835
def debug_gnutls(level, string):
1836
logger.debug(u"GnuTLS: %s", string[:-1])
1838
(gnutls.library.functions
1839
.gnutls_global_set_log_function(debug_gnutls))
1841
# Redirect stdin so all checkers get /dev/null
1842
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1843
os.dup2(null, sys.stdin.fileno())
1847
# No console logging
1848
logger.removeHandler(console)
720
client_config = ConfigParser.SafeConfigParser(client_defaults)
721
client_config.read(os.path.join(server_settings["configdir"],
725
service = AvahiService(name = server_settings["servicename"],
726
type = "_mandos._tcp", );
727
if server_settings["interface"]:
728
service.interface = if_nametoindex(server_settings["interface"])
1851
730
global main_loop
1852
733
# From the Avahi example code
1853
734
DBusGMainLoop(set_as_default=True )
1854
735
main_loop = gobject.MainLoop()
1855
736
bus = dbus.SystemBus()
737
server = dbus.Interface(
738
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
739
avahi.DBUS_INTERFACE_SERVER )
1856
740
# End of Avahi example code
1859
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1860
bus, do_not_queue=True)
1861
except dbus.exceptions.NameExistsException, e:
1862
logger.error(unicode(e) + u", disabling D-Bus")
1864
server_settings[u"use_dbus"] = False
1865
tcp_server.use_dbus = False
1866
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1867
service = AvahiService(name = server_settings[u"servicename"],
1868
servicetype = u"_mandos._tcp",
1869
protocol = protocol, bus = bus)
1870
if server_settings["interface"]:
1871
service.interface = (if_nametoindex
1872
(str(server_settings[u"interface"])))
1875
# Close all input and output, do double fork, etc.
1878
global multiprocessing_manager
1879
multiprocessing_manager = multiprocessing.Manager()
1881
client_class = Client
1883
client_class = functools.partial(ClientDBus, bus = bus)
1884
def client_config_items(config, section):
1885
special_settings = {
1886
"approved_by_default":
1887
lambda: config.getboolean(section,
1888
"approved_by_default"),
1890
for name, value in config.items(section):
1892
yield (name, special_settings[name]())
1896
tcp_server.clients.update(set(
1897
client_class(name = section,
1898
config= dict(client_config_items(
1899
client_config, section)))
1900
for section in client_config.sections()))
1901
if not tcp_server.clients:
1902
logger.warning(u"No clients defined")
1908
pidfile.write(str(pid) + "\n")
1911
logger.error(u"Could not write to file %r with PID %d",
1914
# "pidfile" was never created
742
debug = server_settings["debug"]
745
console = logging.StreamHandler()
746
# console.setLevel(logging.DEBUG)
747
console.setFormatter(logging.Formatter\
748
('%(levelname)s: %(message)s'))
749
logger.addHandler(console)
753
def remove_from_clients(client):
754
clients.remove(client)
756
logger.critical(u"No clients left, exiting")
759
clients.update(Set(Client(name = section,
760
stop_hook = remove_from_clients,
762
= dict(client_config.items(section)))
763
for section in client_config.sections()))
769
"Cleanup function; run on exit"
771
# From the Avahi example code
772
if not group is None:
775
# End of Avahi example code
778
client = clients.pop()
779
client.stop_hook = None
782
atexit.register(cleanup)
1918
785
signal.signal(signal.SIGINT, signal.SIG_IGN)
1920
786
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1921
787
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1924
class MandosDBusService(dbus.service.Object):
1925
"""A D-Bus proxy object"""
1927
dbus.service.Object.__init__(self, bus, u"/")
1928
_interface = u"se.bsnet.fukt.Mandos"
1930
@dbus.service.signal(_interface, signature=u"o")
1931
def ClientAdded(self, objpath):
1935
@dbus.service.signal(_interface, signature=u"ss")
1936
def ClientNotFound(self, fingerprint, address):
1940
@dbus.service.signal(_interface, signature=u"os")
1941
def ClientRemoved(self, objpath, name):
1945
@dbus.service.method(_interface, out_signature=u"ao")
1946
def GetAllClients(self):
1948
return dbus.Array(c.dbus_object_path
1949
for c in tcp_server.clients)
1951
@dbus.service.method(_interface,
1952
out_signature=u"a{oa{sv}}")
1953
def GetAllClientsWithProperties(self):
1955
return dbus.Dictionary(
1956
((c.dbus_object_path, c.GetAll(u""))
1957
for c in tcp_server.clients),
1958
signature=u"oa{sv}")
1960
@dbus.service.method(_interface, in_signature=u"o")
1961
def RemoveClient(self, object_path):
1963
for c in tcp_server.clients:
1964
if c.dbus_object_path == object_path:
1965
tcp_server.clients.remove(c)
1966
c.remove_from_connection()
1967
# Don't signal anything except ClientRemoved
1968
c.disable(quiet=True)
1970
self.ClientRemoved(object_path, c.name)
1972
raise KeyError(object_path)
1976
mandos_dbus_service = MandosDBusService()
1979
"Cleanup function; run on exit"
1982
while tcp_server.clients:
1983
client = tcp_server.clients.pop()
1985
client.remove_from_connection()
1986
client.disable_hook = None
1987
# Don't signal anything except ClientRemoved
1988
client.disable(quiet=True)
1991
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
1994
atexit.register(cleanup)
1996
for client in tcp_server.clients:
1999
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2003
tcp_server.server_activate()
789
for client in clients:
792
tcp_server = IPv6_TCPServer((server_settings["address"],
793
server_settings["port"]),
795
settings=server_settings,
2005
797
# Find out what port we got
2006
798
service.port = tcp_server.socket.getsockname()[1]
2008
logger.info(u"Now listening on address %r, port %d,"
2009
" flowinfo %d, scope_id %d"
2010
% tcp_server.socket.getsockname())
2012
logger.info(u"Now listening on address %r, port %d"
2013
% tcp_server.socket.getsockname())
799
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
800
u" scope_id %d" % tcp_server.socket.getsockname())
2015
802
#service.interface = tcp_server.socket.getsockname()[3]
2018
805
# From the Avahi example code
806
server.connect_to_signal("StateChanged", server_state_changed)
808
server_state_changed(server.GetState())
2021
809
except dbus.exceptions.DBusException, error:
2022
810
logger.critical(u"DBusException: %s", error)
2025
812
# End of Avahi example code
2027
814
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2028
815
lambda *args, **kwargs:
2029
(tcp_server.handle_request
2030
(*args[2:], **kwargs) or True))
816
tcp_server.handle_request\
817
(*args[2:], **kwargs) or True)
2032
logger.debug(u"Starting main loop")
819
logger.debug("Starting main loop")
820
main_loop_started = True
2034
822
except AvahiError, error:
2035
logger.critical(u"AvahiError: %s", error)
823
logger.critical(u"AvahiError: %s" + unicode(error))
2038
825
except KeyboardInterrupt:
2041
logger.debug(u"Server received KeyboardInterrupt")
2042
logger.debug(u"Server exiting")
2043
# Must run before the D-Bus bus name gets deregistered
2046
829
if __name__ == '__main__':