115
98
class AvahiService(object):
116
"""An Avahi (Zeroconf) service.
119
100
interface: integer; avahi.IF_UNSPEC or an interface index.
120
101
Used to optionally bind to the specified interface.
121
name: string; Example: u'Mandos'
122
type: string; Example: u'_mandos._tcp'.
123
See <http://www.dns-sd.org/ServiceTypes.html>
124
port: integer; what port to announce
125
TXT: list of strings; TXT record for the service
126
domain: string; Domain to publish on, default to .local if empty.
127
host: string; Host to publish records for, default is localhost
128
max_renames: integer; maximum number of renames
129
rename_count: integer; counter so we only rename after collisions
130
a sensible number of times
131
group: D-Bus Entry Group
133
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
135
114
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
136
servicetype = None, port = None, TXT = None,
137
domain = u"", host = u"", max_renames = 32768,
138
protocol = avahi.PROTO_UNSPEC, bus = None):
115
type = None, port = None, TXT = None, domain = "",
116
host = "", max_renames = 12):
117
"""An Avahi (Zeroconf) service. """
139
118
self.interface = interface
141
self.type = servicetype
143
self.TXT = TXT if TXT is not None else []
144
126
self.domain = domain
146
128
self.rename_count = 0
147
self.max_renames = max_renames
148
self.protocol = protocol
149
self.group = None # our entry group
152
129
def rename(self):
153
130
"""Derived from the Avahi example code"""
154
131
if self.rename_count >= self.max_renames:
155
logger.critical(u"No suitable Zeroconf service name found"
156
u" after %i retries, exiting.",
158
raise AvahiServiceError(u"Too many renames")
159
self.name = self.server.GetAlternativeServiceName(self.name)
160
logger.info(u"Changing Zeroconf service name to %r ...",
162
syslogger.setFormatter(logging.Formatter
163
(u'Mandos (%s) [%%(process)d]:'
164
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.notice(u"Changing name to %r ...", name)
168
139
self.rename_count += 1
169
140
def remove(self):
170
141
"""Derived from the Avahi example code"""
171
if self.group is not None:
142
if group is not None:
174
145
"""Derived from the Avahi example code"""
175
if self.group is None:
176
self.group = dbus.Interface(
177
self.bus.get_object(avahi.DBUS_NAME,
178
self.server.EntryGroupNew()),
179
avahi.DBUS_INTERFACE_ENTRY_GROUP)
180
self.group.connect_to_signal('StateChanged',
182
.entry_group_state_changed)
183
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
184
self.name, self.type)
185
self.group.AddService(
188
dbus.UInt32(0), # flags
189
self.name, self.type,
190
self.domain, self.host,
191
dbus.UInt16(self.port),
192
avahi.string_array_to_txt_array(self.TXT))
194
def entry_group_state_changed(self, state, error):
195
"""Derived from the Avahi example code"""
196
logger.debug(u"Avahi entry group state change: %i", state)
198
if state == avahi.ENTRY_GROUP_ESTABLISHED:
199
logger.debug(u"Zeroconf service established.")
200
elif state == avahi.ENTRY_GROUP_COLLISION:
201
logger.warning(u"Zeroconf service name collision.")
203
elif state == avahi.ENTRY_GROUP_FAILURE:
204
logger.critical(u"Avahi: Error in group state changed %s",
206
raise AvahiGroupError(u"State changed: %s"
209
"""Derived from the Avahi example code"""
210
if self.group is not None:
213
def server_state_changed(self, state):
214
"""Derived from the Avahi example code"""
215
logger.debug(u"Avahi server state change: %i", state)
216
if state == avahi.SERVER_COLLISION:
217
logger.error(u"Zeroconf server name collision")
219
elif state == avahi.SERVER_RUNNING:
222
"""Derived from the Avahi example code"""
223
if self.server is None:
224
self.server = dbus.Interface(
225
self.bus.get_object(avahi.DBUS_NAME,
226
avahi.DBUS_PATH_SERVER),
227
avahi.DBUS_INTERFACE_SERVER)
228
self.server.connect_to_signal(u"StateChanged",
229
self.server_state_changed)
230
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
233
171
class Client(object):
234
172
"""A representation of a client host served by this server.
237
name: string; from the config file, used in log messages and
174
name: string; from the config file, used in log messages
239
175
fingerprint: string (40 or 32 hexadecimal digits); used to
240
176
uniquely identify the client
241
secret: bytestring; sent verbatim (over TLS) to client
242
host: string; available for use by the checker command
243
created: datetime.datetime(); (UTC) object creation
244
last_enabled: datetime.datetime(); (UTC)
246
last_checked_ok: datetime.datetime(); (UTC) or None
247
timeout: datetime.timedelta(); How long from last_checked_ok
248
until this client is disabled
249
interval: datetime.timedelta(); How often to start a new checker
250
disable_hook: If set, called by disable() as disable_hook(self)
251
checker: subprocess.Popen(); a running checker process used
252
to see if the client lives.
253
'None' if no process is running.
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.
254
188
checker_initiator_tag: a gobject event source tag, or None
255
disable_initiator_tag: - '' -
189
stop_initiator_tag: - '' -
256
190
checker_callback_tag: - '' -
257
191
checker_command: string; External command which is run to check if
258
192
client lives. %() expansions are done at
259
193
runtime with vars(self) as dict, so that for
260
194
instance %(name)s can be used in the command.
261
current_checker_command: string; current running checker_command
196
_timeout: Real variable for 'timeout'
197
_interval: Real variable for 'interval'
198
_timeout_milliseconds: Used when calling gobject.timeout_add()
199
_interval_milliseconds: - '' -
265
def _timedelta_to_milliseconds(td):
266
"Convert a datetime.timedelta() to milliseconds"
267
return ((td.days * 24 * 60 * 60 * 1000)
268
+ (td.seconds * 1000)
269
+ (td.microseconds // 1000))
271
def timeout_milliseconds(self):
272
"Return the 'timeout' attribute in milliseconds"
273
return self._timedelta_to_milliseconds(self.timeout)
275
def interval_milliseconds(self):
276
"Return the 'interval' attribute in milliseconds"
277
return self._timedelta_to_milliseconds(self.interval)
279
def __init__(self, name = None, disable_hook=None, config=None):
280
"""Note: the 'checker' key in 'config' sets the
281
'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, fingerprint=None,
225
secret=None, secfile=None, fqdn=None, timeout=None,
226
interval=-1, checker=None):
227
"""Note: the 'checker' argument sets the 'checker_command'
228
attribute and not the 'checker' attribute.."""
286
230
logger.debug(u"Creating client %r", self.name)
287
# Uppercase and remove spaces from fingerprint for later
288
# comparison purposes with return value from the fingerprint()
290
self.fingerprint = (config[u"fingerprint"].upper()
231
# Uppercase and remove spaces from fingerprint
232
# for later comparison purposes with return value of
233
# the fingerprint() function
234
self.fingerprint = fingerprint.upper().replace(u" ", u"")
292
235
logger.debug(u" Fingerprint: %s", self.fingerprint)
293
if u"secret" in config:
294
self.secret = config[u"secret"].decode(u"base64")
295
elif u"secfile" in config:
296
with open(os.path.expanduser(os.path.expandvars
297
(config[u"secfile"])),
299
self.secret = secfile.read()
237
self.secret = secret.decode(u"base64")
240
self.secret = sf.read()
301
243
raise TypeError(u"No secret or secfile for client %s"
303
self.host = config.get(u"host", u"")
304
self.created = datetime.datetime.utcnow()
306
self.last_enabled = None
246
self.created = datetime.datetime.now()
307
247
self.last_checked_ok = None
308
self.timeout = string_to_delta(config[u"timeout"])
309
self.interval = string_to_delta(config[u"interval"])
310
self.disable_hook = disable_hook
248
self.timeout = string_to_delta(timeout)
249
self.interval = string_to_delta(interval)
250
self.stop_hook = stop_hook
311
251
self.checker = None
312
252
self.checker_initiator_tag = None
313
self.disable_initiator_tag = None
253
self.stop_initiator_tag = None
314
254
self.checker_callback_tag = None
315
self.checker_command = config[u"checker"]
316
self.current_checker_command = None
317
self.last_connect = None
255
self.check_command = checker
320
257
"""Start this client's checker and timeout hooks"""
321
if getattr(self, u"enabled", False):
324
self.last_enabled = datetime.datetime.utcnow()
325
258
# Schedule a new checker to be started an 'interval' from now,
326
259
# and every interval from then on.
327
self.checker_initiator_tag = (gobject.timeout_add
328
(self.interval_milliseconds(),
330
# Schedule a disable() when 'timeout' has passed
331
self.disable_initiator_tag = (gobject.timeout_add
332
(self.timeout_milliseconds(),
260
self.checker_initiator_tag = gobject.timeout_add\
261
(self._interval_milliseconds,
335
263
# Also start a new checker *right now*.
336
264
self.start_checker()
338
def disable(self, quiet=True):
339
"""Disable this client."""
340
if not getattr(self, "enabled", False):
265
# Schedule a stop() when 'timeout' has passed
266
self.stop_initiator_tag = gobject.timeout_add\
267
(self._timeout_milliseconds,
271
The possibility that a client might be restarted is left open,
272
but not currently used."""
273
# If this client doesn't have a secret, it is already stopped.
275
logger.debug(u"Stopping client %s", self.name)
343
logger.info(u"Disabling client %s", self.name)
344
if getattr(self, u"disable_initiator_tag", False):
345
gobject.source_remove(self.disable_initiator_tag)
346
self.disable_initiator_tag = None
347
if getattr(self, u"checker_initiator_tag", False):
279
if getattr(self, "stop_initiator_tag", False):
280
gobject.source_remove(self.stop_initiator_tag)
281
self.stop_initiator_tag = None
282
if getattr(self, "checker_initiator_tag", False):
348
283
gobject.source_remove(self.checker_initiator_tag)
349
284
self.checker_initiator_tag = None
350
285
self.stop_checker()
351
if self.disable_hook:
352
self.disable_hook(self)
354
288
# Do not run this again if called by a gobject.timeout_add
357
290
def __del__(self):
358
self.disable_hook = None
361
def checker_callback(self, pid, condition, command):
291
self.stop_hook = None
293
def checker_callback(self, pid, condition):
362
294
"""The checker has completed, so take appropriate actions."""
295
now = datetime.datetime.now()
363
296
self.checker_callback_tag = None
364
297
self.checker = None
365
if os.WIFEXITED(condition):
366
exitstatus = os.WEXITSTATUS(condition)
368
logger.info(u"Checker for %(name)s succeeded",
372
logger.info(u"Checker for %(name)s failed",
298
if os.WIFEXITED(condition) \
299
and (os.WEXITSTATUS(condition) == 0):
300
logger.debug(u"Checker for %(name)s succeeded",
302
self.last_checked_ok = now
303
gobject.source_remove(self.stop_initiator_tag)
304
self.stop_initiator_tag = gobject.timeout_add\
305
(self._timeout_milliseconds,
307
elif not os.WIFEXITED(condition):
375
308
logger.warning(u"Checker for %(name)s crashed?",
378
def checked_ok(self):
379
"""Bump up the timeout for this client.
381
This should only be called when the client has been seen,
384
self.last_checked_ok = datetime.datetime.utcnow()
385
gobject.source_remove(self.disable_initiator_tag)
386
self.disable_initiator_tag = (gobject.timeout_add
387
(self.timeout_milliseconds(),
311
logger.debug(u"Checker for %(name)s failed",
390
313
def start_checker(self):
391
314
"""Start a new checker subprocess if one is not running.
393
315
If a checker already exists, leave it running and do
395
317
# The reason for not killing a running checker is that if we
398
320
# client would inevitably timeout, since no checker would get
399
321
# a chance to run to completion. If we instead leave running
400
322
# checkers alone, the checker would have to take more time
401
# than 'timeout' for the client to be disabled, which is as it
404
# If a checker exists, make sure it is not a zombie
406
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
407
except (AttributeError, OSError), error:
408
if (isinstance(error, OSError)
409
and error.errno != errno.ECHILD):
413
logger.warning(u"Checker was a zombie")
414
gobject.source_remove(self.checker_callback_tag)
415
self.checker_callback(pid, status,
416
self.current_checker_command)
417
# Start a new checker if needed
323
# than 'timeout' for the client to be declared invalid, which
324
# is as it should be.
418
325
if self.checker is None:
420
# In case checker_command has exactly one % operator
421
command = self.checker_command % self.host
327
# In case check_command has exactly one % operator
328
command = self.check_command % self.fqdn
422
329
except TypeError:
423
330
# Escape attributes for the shell
424
escaped_attrs = dict((key,
425
re.escape(unicode(str(val),
331
escaped_attrs = dict((key, re.escape(str(val)))
429
333
vars(self).iteritems())
431
command = self.checker_command % escaped_attrs
335
command = self.check_command % escaped_attrs
432
336
except TypeError, error:
433
337
logger.error(u'Could not format string "%s":'
434
u' %s', self.checker_command, error)
338
u' %s', self.check_command, error)
435
339
return True # Try again later
436
self.current_checker_command = command
438
logger.info(u"Starting checker %r for %s",
440
# We don't need to redirect stdout and stderr, since
441
# in normal mode, that is already done by daemon(),
442
# and in debug mode we don't want to. (Stdin is
443
# always replaced by /dev/null.)
341
logger.debug(u"Starting checker %r for %s",
444
343
self.checker = subprocess.Popen(command,
446
shell=True, cwd=u"/")
447
self.checker_callback_tag = (gobject.child_watch_add
449
self.checker_callback,
451
# The checker may have completed before the gobject
452
# watch was added. Check for this.
453
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
455
gobject.source_remove(self.checker_callback_tag)
456
self.checker_callback(pid, status, command)
457
except OSError, error:
346
self.checker_callback_tag = gobject.child_watch_add\
348
self.checker_callback)
349
except subprocess.OSError, error:
458
350
logger.error(u"Failed to start subprocess: %s",
460
352
# Re-run this periodically if run by gobject.timeout_add
463
354
def stop_checker(self):
464
355
"""Force the checker process, if any, to stop."""
465
356
if self.checker_callback_tag:
466
357
gobject.source_remove(self.checker_callback_tag)
467
358
self.checker_callback_tag = None
468
if getattr(self, u"checker", None) is None:
359
if getattr(self, "checker", None) is None:
470
logger.debug(u"Stopping checker for %(name)s", vars(self))
361
logger.debug("Stopping checker for %(name)s", vars(self))
472
363
os.kill(self.checker.pid, signal.SIGTERM)
474
365
#if self.checker.poll() is None:
475
366
# os.kill(self.checker.pid, signal.SIGKILL)
476
367
except OSError, error:
477
368
if error.errno != errno.ESRCH: # No such process
479
370
self.checker = None
482
def dbus_service_property(dbus_interface, signature=u"v",
483
access=u"readwrite", byte_arrays=False):
484
"""Decorators for marking methods of a DBusObjectWithProperties to
485
become properties on the D-Bus.
487
The decorated method will be called with no arguments by "Get"
488
and with one argument by "Set".
490
The parameters, where they are supported, are the same as
491
dbus.service.method, except there is only "signature", since the
492
type from Get() and the type sent to Set() is the same.
494
# Encoding deeply encoded byte arrays is not supported yet by the
495
# "Set" method, so we fail early here:
496
if byte_arrays and signature != u"ay":
497
raise ValueError(u"Byte arrays not supported for non-'ay'"
498
u" signature %r" % signature)
500
func._dbus_is_property = True
501
func._dbus_interface = dbus_interface
502
func._dbus_signature = signature
503
func._dbus_access = access
504
func._dbus_name = func.__name__
505
if func._dbus_name.endswith(u"_dbus_property"):
506
func._dbus_name = func._dbus_name[:-14]
507
func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
512
class DBusPropertyException(dbus.exceptions.DBusException):
513
"""A base class for D-Bus property-related exceptions
515
def __unicode__(self):
516
return unicode(str(self))
519
class DBusPropertyAccessException(DBusPropertyException):
520
"""A property's access permissions disallows an operation.
525
class DBusPropertyNotFound(DBusPropertyException):
526
"""An attempt was made to access a non-existing property.
531
class DBusObjectWithProperties(dbus.service.Object):
532
"""A D-Bus object with properties.
534
Classes inheriting from this can use the dbus_service_property
535
decorator to expose methods as D-Bus properties. It exposes the
536
standard Get(), Set(), and GetAll() methods on the D-Bus.
540
def _is_dbus_property(obj):
541
return getattr(obj, u"_dbus_is_property", False)
543
def _get_all_dbus_properties(self):
544
"""Returns a generator of (name, attribute) pairs
546
return ((prop._dbus_name, prop)
548
inspect.getmembers(self, self._is_dbus_property))
550
def _get_dbus_property(self, interface_name, property_name):
551
"""Returns a bound method if one exists which is a D-Bus
552
property with the specified name and interface.
554
for name in (property_name,
555
property_name + u"_dbus_property"):
556
prop = getattr(self, name, None)
558
or not self._is_dbus_property(prop)
559
or prop._dbus_name != property_name
560
or (interface_name and prop._dbus_interface
561
and interface_name != prop._dbus_interface)):
565
raise DBusPropertyNotFound(self.dbus_object_path + u":"
566
+ interface_name + u"."
569
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
571
def Get(self, interface_name, property_name):
572
"""Standard D-Bus property Get() method, see D-Bus standard.
574
prop = self._get_dbus_property(interface_name, property_name)
575
if prop._dbus_access == u"write":
576
raise DBusPropertyAccessException(property_name)
578
if not hasattr(value, u"variant_level"):
580
return type(value)(value, variant_level=value.variant_level+1)
582
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
583
def Set(self, interface_name, property_name, value):
584
"""Standard D-Bus property Set() method, see D-Bus standard.
586
prop = self._get_dbus_property(interface_name, property_name)
587
if prop._dbus_access == u"read":
588
raise DBusPropertyAccessException(property_name)
589
if prop._dbus_get_args_options[u"byte_arrays"]:
590
# The byte_arrays option is not supported yet on
591
# signatures other than "ay".
592
if prop._dbus_signature != u"ay":
594
value = dbus.ByteArray(''.join(unichr(byte)
598
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
599
out_signature=u"a{sv}")
600
def GetAll(self, interface_name):
601
"""Standard D-Bus property GetAll() method, see D-Bus
604
Note: Will not include properties with access="write".
607
for name, prop in self._get_all_dbus_properties():
609
and interface_name != prop._dbus_interface):
610
# Interface non-empty but did not match
612
# Ignore write-only properties
613
if prop._dbus_access == u"write":
616
if not hasattr(value, u"variant_level"):
619
all[name] = type(value)(value, variant_level=
620
value.variant_level+1)
621
return dbus.Dictionary(all, signature=u"sv")
623
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
625
path_keyword='object_path',
626
connection_keyword='connection')
627
def Introspect(self, object_path, connection):
628
"""Standard D-Bus method, overloaded to insert property tags.
630
xmlstring = dbus.service.Object.Introspect(self, object_path,
633
document = xml.dom.minidom.parseString(xmlstring)
634
def make_tag(document, name, prop):
635
e = document.createElement(u"property")
636
e.setAttribute(u"name", name)
637
e.setAttribute(u"type", prop._dbus_signature)
638
e.setAttribute(u"access", prop._dbus_access)
640
for if_tag in document.getElementsByTagName(u"interface"):
641
for tag in (make_tag(document, name, prop)
643
in self._get_all_dbus_properties()
644
if prop._dbus_interface
645
== if_tag.getAttribute(u"name")):
646
if_tag.appendChild(tag)
647
# Add the names to the return values for the
648
# "org.freedesktop.DBus.Properties" methods
649
if (if_tag.getAttribute(u"name")
650
== u"org.freedesktop.DBus.Properties"):
651
for cn in if_tag.getElementsByTagName(u"method"):
652
if cn.getAttribute(u"name") == u"Get":
653
for arg in cn.getElementsByTagName(u"arg"):
654
if (arg.getAttribute(u"direction")
656
arg.setAttribute(u"name", u"value")
657
elif cn.getAttribute(u"name") == u"GetAll":
658
for arg in cn.getElementsByTagName(u"arg"):
659
if (arg.getAttribute(u"direction")
661
arg.setAttribute(u"name", u"props")
662
xmlstring = document.toxml(u"utf-8")
664
except (AttributeError, xml.dom.DOMException,
665
xml.parsers.expat.ExpatError), error:
666
logger.error(u"Failed to override Introspection method",
671
class ClientDBus(Client, DBusObjectWithProperties):
672
"""A Client class using D-Bus
675
dbus_object_path: dbus.ObjectPath
676
bus: dbus.SystemBus()
678
# dbus.service.Object doesn't use super(), so we can't either.
680
def __init__(self, bus = None, *args, **kwargs):
682
Client.__init__(self, *args, **kwargs)
683
# Only now, when this client is initialized, can it show up on
685
self.dbus_object_path = (dbus.ObjectPath
687
+ self.name.replace(u".", u"_")))
688
DBusObjectWithProperties.__init__(self, self.bus,
689
self.dbus_object_path)
692
def _datetime_to_dbus(dt, variant_level=0):
693
"""Convert a UTC datetime.datetime() to a D-Bus type."""
694
return dbus.String(dt.isoformat(),
695
variant_level=variant_level)
698
oldstate = getattr(self, u"enabled", False)
699
r = Client.enable(self)
700
if oldstate != self.enabled:
702
self.PropertyChanged(dbus.String(u"enabled"),
703
dbus.Boolean(True, variant_level=1))
704
self.PropertyChanged(
705
dbus.String(u"last_enabled"),
706
self._datetime_to_dbus(self.last_enabled,
710
def disable(self, quiet = False):
711
oldstate = getattr(self, u"enabled", False)
712
r = Client.disable(self, quiet=quiet)
713
if not quiet and oldstate != self.enabled:
715
self.PropertyChanged(dbus.String(u"enabled"),
716
dbus.Boolean(False, variant_level=1))
719
def __del__(self, *args, **kwargs):
721
self.remove_from_connection()
724
if hasattr(DBusObjectWithProperties, u"__del__"):
725
DBusObjectWithProperties.__del__(self, *args, **kwargs)
726
Client.__del__(self, *args, **kwargs)
728
def checker_callback(self, pid, condition, command,
730
self.checker_callback_tag = None
733
self.PropertyChanged(dbus.String(u"checker_running"),
734
dbus.Boolean(False, variant_level=1))
735
if os.WIFEXITED(condition):
736
exitstatus = os.WEXITSTATUS(condition)
738
self.CheckerCompleted(dbus.Int16(exitstatus),
739
dbus.Int64(condition),
740
dbus.String(command))
743
self.CheckerCompleted(dbus.Int16(-1),
744
dbus.Int64(condition),
745
dbus.String(command))
747
return Client.checker_callback(self, pid, condition, command,
750
def checked_ok(self, *args, **kwargs):
751
r = Client.checked_ok(self, *args, **kwargs)
753
self.PropertyChanged(
754
dbus.String(u"last_checked_ok"),
755
(self._datetime_to_dbus(self.last_checked_ok,
759
def start_checker(self, *args, **kwargs):
760
old_checker = self.checker
761
if self.checker is not None:
762
old_checker_pid = self.checker.pid
764
old_checker_pid = None
765
r = Client.start_checker(self, *args, **kwargs)
766
# Only if new checker process was started
767
if (self.checker is not None
768
and old_checker_pid != self.checker.pid):
770
self.CheckerStarted(self.current_checker_command)
771
self.PropertyChanged(
772
dbus.String(u"checker_running"),
773
dbus.Boolean(True, variant_level=1))
776
def stop_checker(self, *args, **kwargs):
777
old_checker = getattr(self, u"checker", None)
778
r = Client.stop_checker(self, *args, **kwargs)
779
if (old_checker is not None
780
and getattr(self, u"checker", None) is None):
781
self.PropertyChanged(dbus.String(u"checker_running"),
782
dbus.Boolean(False, variant_level=1))
785
## D-Bus methods, signals & properties
786
_interface = u"se.bsnet.fukt.Mandos.Client"
790
# CheckerCompleted - signal
791
@dbus.service.signal(_interface, signature=u"nxs")
792
def CheckerCompleted(self, exitcode, waitstatus, command):
796
# CheckerStarted - signal
797
@dbus.service.signal(_interface, signature=u"s")
798
def CheckerStarted(self, command):
802
# PropertyChanged - signal
803
@dbus.service.signal(_interface, signature=u"sv")
804
def PropertyChanged(self, property, value):
809
@dbus.service.signal(_interface)
815
@dbus.service.signal(_interface)
823
@dbus.service.method(_interface)
825
return self.checked_ok()
828
@dbus.service.method(_interface)
833
# StartChecker - method
834
@dbus.service.method(_interface)
835
def StartChecker(self):
840
@dbus.service.method(_interface)
845
# StopChecker - method
846
@dbus.service.method(_interface)
847
def StopChecker(self):
853
@dbus_service_property(_interface, signature=u"s", access=u"read")
854
def name_dbus_property(self):
855
return dbus.String(self.name)
857
# fingerprint - property
858
@dbus_service_property(_interface, signature=u"s", access=u"read")
859
def fingerprint_dbus_property(self):
860
return dbus.String(self.fingerprint)
863
@dbus_service_property(_interface, signature=u"s",
865
def host_dbus_property(self, value=None):
866
if value is None: # get
867
return dbus.String(self.host)
870
self.PropertyChanged(dbus.String(u"host"),
871
dbus.String(value, variant_level=1))
874
@dbus_service_property(_interface, signature=u"s", access=u"read")
875
def created_dbus_property(self):
876
return dbus.String(self._datetime_to_dbus(self.created))
878
# last_enabled - property
879
@dbus_service_property(_interface, signature=u"s", access=u"read")
880
def last_enabled_dbus_property(self):
881
if self.last_enabled is None:
882
return dbus.String(u"")
883
return dbus.String(self._datetime_to_dbus(self.last_enabled))
886
@dbus_service_property(_interface, signature=u"b",
888
def enabled_dbus_property(self, value=None):
889
if value is None: # get
890
return dbus.Boolean(self.enabled)
896
# last_checked_ok - property
897
@dbus_service_property(_interface, signature=u"s",
899
def last_checked_ok_dbus_property(self, value=None):
900
if value is not None:
371
def still_valid(self):
372
"""Has the timeout not yet passed for this client?"""
373
now = datetime.datetime.now()
903
374
if self.last_checked_ok is None:
904
return dbus.String(u"")
905
return dbus.String(self._datetime_to_dbus(self
909
@dbus_service_property(_interface, signature=u"t",
911
def timeout_dbus_property(self, value=None):
912
if value is None: # get
913
return dbus.UInt64(self.timeout_milliseconds())
914
self.timeout = datetime.timedelta(0, 0, 0, value)
916
self.PropertyChanged(dbus.String(u"timeout"),
917
dbus.UInt64(value, variant_level=1))
918
if getattr(self, u"disable_initiator_tag", None) is None:
921
gobject.source_remove(self.disable_initiator_tag)
922
self.disable_initiator_tag = None
924
_timedelta_to_milliseconds((self
930
# The timeout has passed
933
self.disable_initiator_tag = (gobject.timeout_add
934
(time_to_die, self.disable))
936
# interval - property
937
@dbus_service_property(_interface, signature=u"t",
939
def interval_dbus_property(self, value=None):
940
if value is None: # get
941
return dbus.UInt64(self.interval_milliseconds())
942
self.interval = datetime.timedelta(0, 0, 0, value)
944
self.PropertyChanged(dbus.String(u"interval"),
945
dbus.UInt64(value, variant_level=1))
946
if getattr(self, u"checker_initiator_tag", None) is None:
948
# Reschedule checker run
949
gobject.source_remove(self.checker_initiator_tag)
950
self.checker_initiator_tag = (gobject.timeout_add
951
(value, self.start_checker))
952
self.start_checker() # Start one now, too
955
@dbus_service_property(_interface, signature=u"s",
957
def checker_dbus_property(self, value=None):
958
if value is None: # get
959
return dbus.String(self.checker_command)
960
self.checker_command = value
962
self.PropertyChanged(dbus.String(u"checker"),
963
dbus.String(self.checker_command,
966
# checker_running - property
967
@dbus_service_property(_interface, signature=u"b",
969
def checker_running_dbus_property(self, value=None):
970
if value is None: # get
971
return dbus.Boolean(self.checker is not None)
977
# object_path - property
978
@dbus_service_property(_interface, signature=u"o", access=u"read")
979
def object_path_dbus_property(self):
980
return self.dbus_object_path # is already a dbus.ObjectPath
983
@dbus_service_property(_interface, signature=u"ay",
984
access=u"write", byte_arrays=True)
985
def secret_dbus_property(self, value):
986
self.secret = str(value)
991
class ClientHandler(socketserver.BaseRequestHandler, object):
992
"""A class to handle client connections.
994
Instantiated once for each connection to handle it.
375
return now < (self.created + self.timeout)
377
return now < (self.last_checked_ok + self.timeout)
380
def peer_certificate(session):
381
"Return the peer's OpenPGP certificate as a bytestring"
382
# If not an OpenPGP certificate...
383
if gnutls.library.functions.gnutls_certificate_type_get\
384
(session._c_object) \
385
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
386
# ...do the normal thing
387
return session.peer_certificate
388
list_size = ctypes.c_uint()
389
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
390
(session._c_object, ctypes.byref(list_size))
391
if list_size.value == 0:
394
return ctypes.string_at(cert.data, cert.size)
397
def fingerprint(openpgp):
398
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
399
# New empty GnuTLS certificate
400
crt = gnutls.library.types.gnutls_openpgp_crt_t()
401
gnutls.library.functions.gnutls_openpgp_crt_init\
403
# New GnuTLS "datum" with the OpenPGP public key
404
datum = gnutls.library.types.gnutls_datum_t\
405
(ctypes.cast(ctypes.c_char_p(openpgp),
406
ctypes.POINTER(ctypes.c_ubyte)),
407
ctypes.c_uint(len(openpgp)))
408
# Import the OpenPGP public key into the certificate
409
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
412
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
413
# New buffer for the fingerprint
414
buffer = ctypes.create_string_buffer(20)
415
buffer_length = ctypes.c_size_t()
416
# Get the fingerprint from the certificate into the buffer
417
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
418
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
419
# Deinit the certificate
420
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
421
# Convert the buffer to a Python bytestring
422
fpr = ctypes.string_at(buffer, buffer_length.value)
423
# Convert the bytestring to hexadecimal notation
424
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
428
class tcp_handler(SocketServer.BaseRequestHandler, object):
429
"""A TCP request handler class.
430
Instantiated by IPv6_TCPServer for each request to handle it.
995
431
Note: This will run in its own forked process."""
997
433
def handle(self):
998
logger.info(u"TCP connection from: %s",
999
unicode(self.client_address))
1000
logger.debug(u"IPC Pipe FD: %d",
1001
self.server.child_pipe[1].fileno())
1002
# Open IPC pipe to parent process
1003
with contextlib.nested(self.server.child_pipe[1],
1004
self.server.parent_pipe[0]
1005
) as (ipc, ipc_return):
1006
session = (gnutls.connection
1007
.ClientSession(self.request,
1009
.X509Credentials()))
1011
# Note: gnutls.connection.X509Credentials is really a
1012
# generic GnuTLS certificate credentials object so long as
1013
# no X.509 keys are added to it. Therefore, we can use it
1014
# here despite using OpenPGP certificates.
1016
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1017
# u"+AES-256-CBC", u"+SHA1",
1018
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1020
# Use a fallback default, since this MUST be set.
1021
priority = self.server.gnutls_priority
1022
if priority is None:
1023
priority = u"NORMAL"
1024
(gnutls.library.functions
1025
.gnutls_priority_set_direct(session._c_object,
1028
# Start communication using the Mandos protocol
1029
# Get protocol number
1030
line = self.request.makefile().readline()
1031
logger.debug(u"Protocol version: %r", line)
1033
if int(line.strip().split()[0]) > 1:
1035
except (ValueError, IndexError, RuntimeError), error:
1036
logger.error(u"Unknown protocol version: %s", error)
1039
# Start GnuTLS connection
1042
except gnutls.errors.GNUTLSError, error:
1043
logger.warning(u"Handshake failed: %s", error)
1044
# Do not run session.bye() here: the session is not
1045
# established. Just abandon the request.
1047
logger.debug(u"Handshake succeeded")
1050
fpr = self.fingerprint(self.peer_certificate
1052
except (TypeError, gnutls.errors.GNUTLSError), error:
1053
logger.warning(u"Bad certificate: %s", error)
1055
logger.debug(u"Fingerprint: %s", fpr)
1057
for c in self.server.clients:
1058
if c.fingerprint == fpr:
1062
ipc.write(u"NOTFOUND %s %s\n"
1063
% (fpr, unicode(self.client_address)))
1066
class ClientProxy(object):
1067
"""Client proxy object. Not for calling methods."""
1068
def __init__(self, client):
1069
self.client = client
1070
def __getattr__(self, name):
1071
if name.startswith("ipc_"):
1073
ipc.write("%s %s\n" % (name[4:].upper(),
1076
if not hasattr(self.client, name):
1077
raise AttributeError
1078
ipc.write(u"GETATTR %s %s\n"
1079
% (name, self.client.fingerprint))
1080
return pickle.load(ipc_return)
1081
clientproxy = ClientProxy(client)
1082
# Have to check if client.enabled, since it is
1083
# possible that the client was disabled since the
1084
# GnuTLS session was established.
1085
if not clientproxy.enabled:
1086
clientproxy.ipc_disabled()
1089
clientproxy.ipc_sending()
1091
while sent_size < len(client.secret):
1092
sent = session.send(client.secret[sent_size:])
1093
logger.debug(u"Sent: %d, remaining: %d",
1094
sent, len(client.secret)
1095
- (sent_size + sent))
1101
def peer_certificate(session):
1102
"Return the peer's OpenPGP certificate as a bytestring"
1103
# If not an OpenPGP certificate...
1104
if (gnutls.library.functions
1105
.gnutls_certificate_type_get(session._c_object)
1106
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1107
# ...do the normal thing
1108
return session.peer_certificate
1109
list_size = ctypes.c_uint(1)
1110
cert_list = (gnutls.library.functions
1111
.gnutls_certificate_get_peers
1112
(session._c_object, ctypes.byref(list_size)))
1113
if not bool(cert_list) and list_size.value != 0:
1114
raise gnutls.errors.GNUTLSError(u"error getting peer"
1116
if list_size.value == 0:
1119
return ctypes.string_at(cert.data, cert.size)
1122
def fingerprint(openpgp):
1123
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1124
# New GnuTLS "datum" with the OpenPGP public key
1125
datum = (gnutls.library.types
1126
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1129
ctypes.c_uint(len(openpgp))))
1130
# New empty GnuTLS certificate
1131
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1132
(gnutls.library.functions
1133
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1134
# Import the OpenPGP public key into the certificate
1135
(gnutls.library.functions
1136
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1137
gnutls.library.constants
1138
.GNUTLS_OPENPGP_FMT_RAW))
1139
# Verify the self signature in the key
1140
crtverify = ctypes.c_uint()
1141
(gnutls.library.functions
1142
.gnutls_openpgp_crt_verify_self(crt, 0,
1143
ctypes.byref(crtverify)))
1144
if crtverify.value != 0:
1145
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1146
raise (gnutls.errors.CertificateSecurityError
1148
# New buffer for the fingerprint
1149
buf = ctypes.create_string_buffer(20)
1150
buf_len = ctypes.c_size_t()
1151
# Get the fingerprint from the certificate into the buffer
1152
(gnutls.library.functions
1153
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1154
ctypes.byref(buf_len)))
1155
# Deinit the certificate
1156
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1157
# Convert the buffer to a Python bytestring
1158
fpr = ctypes.string_at(buf, buf_len.value)
1159
# Convert the bytestring to hexadecimal notation
1160
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1164
class ForkingMixInWithPipes(socketserver.ForkingMixIn, object):
1165
"""Like socketserver.ForkingMixIn, but also pass a pipe pair."""
1166
def process_request(self, request, client_address):
1167
"""Overrides and wraps the original process_request().
1169
This function creates a new pipe in self.pipe
1171
# Child writes to child_pipe
1172
self.child_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0))
1173
# Parent writes to parent_pipe
1174
self.parent_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0))
1175
super(ForkingMixInWithPipes,
1176
self).process_request(request, client_address)
1177
# Close unused ends for parent
1178
self.parent_pipe[0].close() # close read end
1179
self.child_pipe[1].close() # close write end
1180
self.add_pipe_fds(self.child_pipe[0], self.parent_pipe[1])
1181
def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
1182
"""Dummy function; override as necessary"""
1183
child_pipe_fd.close()
1184
parent_pipe_fd.close()
1187
class IPv6_TCPServer(ForkingMixInWithPipes,
1188
socketserver.TCPServer, object):
1189
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
434
logger.debug(u"TCP connection from: %s",
435
unicode(self.client_address))
436
session = gnutls.connection.ClientSession\
437
(self.request, gnutls.connection.X509Credentials())
438
# Note: gnutls.connection.X509Credentials is really a generic
439
# GnuTLS certificate credentials object so long as no X.509
440
# keys are added to it. Therefore, we can use it here despite
441
# using OpenPGP certificates.
443
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
444
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
446
priority = "NORMAL" # Fallback default, since this
448
if self.server.settings["priority"]:
449
priority = self.server.settings["priority"]
450
gnutls.library.functions.gnutls_priority_set_direct\
451
(session._c_object, priority, None);
455
except gnutls.errors.GNUTLSError, error:
456
logger.debug(u"Handshake failed: %s", error)
457
# Do not run session.bye() here: the session is not
458
# established. Just abandon the request.
461
fpr = fingerprint(peer_certificate(session))
462
except (TypeError, gnutls.errors.GNUTLSError), error:
463
logger.debug(u"Bad certificate: %s", error)
466
logger.debug(u"Fingerprint: %s", fpr)
468
for c in self.server.clients:
469
if c.fingerprint == fpr:
473
logger.debug(u"Client not found for fingerprint: %s", fpr)
476
# Have to check if client.still_valid(), since it is possible
477
# that the client timed out while establishing the GnuTLS
479
if not client.still_valid():
480
logger.debug(u"Client %(name)s is invalid", vars(client))
484
while sent_size < len(client.secret):
485
sent = session.send(client.secret[sent_size:])
486
logger.debug(u"Sent: %d, remaining: %d",
487
sent, len(client.secret)
488
- (sent_size + sent))
493
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
494
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1192
enabled: Boolean; whether this server is activated yet
1193
interface: None or a network interface name (string)
1194
use_ipv6: Boolean; to use IPv6 or not
496
settings: Server settings
497
clients: Set() of Client objects
1196
def __init__(self, server_address, RequestHandlerClass,
1197
interface=None, use_ipv6=True):
1198
self.interface = interface
1200
self.address_family = socket.AF_INET6
1201
socketserver.TCPServer.__init__(self, server_address,
1202
RequestHandlerClass)
499
address_family = socket.AF_INET6
500
def __init__(self, *args, **kwargs):
501
if "settings" in kwargs:
502
self.settings = kwargs["settings"]
503
del kwargs["settings"]
504
if "clients" in kwargs:
505
self.clients = kwargs["clients"]
506
del kwargs["clients"]
507
return super(type(self), self).__init__(*args, **kwargs)
1203
508
def server_bind(self):
1204
509
"""This overrides the normal server_bind() function
1205
510
to bind to an interface if one was specified, and also NOT to
1206
511
bind to an address or port if they were not specified."""
1207
if self.interface is not None:
1208
if SO_BINDTODEVICE is None:
1209
logger.error(u"SO_BINDTODEVICE does not exist;"
1210
u" cannot bind to interface %s",
1214
self.socket.setsockopt(socket.SOL_SOCKET,
1218
except socket.error, error:
1219
if error[0] == errno.EPERM:
1220
logger.error(u"No permission to"
1221
u" bind to interface %s",
1223
elif error[0] == errno.ENOPROTOOPT:
1224
logger.error(u"SO_BINDTODEVICE not available;"
1225
u" cannot bind to interface %s",
512
if self.settings["interface"] != avahi.IF_UNSPEC:
513
# 25 is from /usr/include/asm-i486/socket.h
514
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
516
self.socket.setsockopt(socket.SOL_SOCKET,
518
self.settings["interface"])
519
except socket.error, error:
520
if error[0] == errno.EPERM:
521
logger.warning(u"No permission to"
522
u" bind to interface %s",
523
self.settings["interface"])
1229
526
# Only bind(2) the socket if we really need to.
1230
527
if self.server_address[0] or self.server_address[1]:
1231
528
if not self.server_address[0]:
1232
if self.address_family == socket.AF_INET6:
1233
any_address = u"::" # in6addr_any
1235
any_address = socket.INADDR_ANY
1236
self.server_address = (any_address,
530
self.server_address = (in6addr_any,
1237
531
self.server_address[1])
1238
elif not self.server_address[1]:
532
elif self.server_address[1] is None:
1239
533
self.server_address = (self.server_address[0],
1241
# if self.interface:
1242
# self.server_address = (self.server_address[0],
1247
return socketserver.TCPServer.server_bind(self)
1250
class MandosServer(IPv6_TCPServer):
1254
clients: set of Client objects
1255
gnutls_priority GnuTLS priority string
1256
use_dbus: Boolean; to emit D-Bus signals or not
1258
Assumes a gobject.MainLoop event loop.
1260
def __init__(self, server_address, RequestHandlerClass,
1261
interface=None, use_ipv6=True, clients=None,
1262
gnutls_priority=None, use_dbus=True):
1263
self.enabled = False
1264
self.clients = clients
1265
if self.clients is None:
1266
self.clients = set()
1267
self.use_dbus = use_dbus
1268
self.gnutls_priority = gnutls_priority
1269
IPv6_TCPServer.__init__(self, server_address,
1270
RequestHandlerClass,
1271
interface = interface,
1272
use_ipv6 = use_ipv6)
1273
def server_activate(self):
1275
return socketserver.TCPServer.server_activate(self)
1278
def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
1279
# Call "handle_ipc" for both data and EOF events
1280
gobject.io_add_watch(child_pipe_fd.fileno(),
1281
gobject.IO_IN | gobject.IO_HUP,
1282
functools.partial(self.handle_ipc,
1283
reply = parent_pipe_fd,
1284
sender= child_pipe_fd))
1285
def handle_ipc(self, source, condition, reply=None, sender=None):
1287
gobject.IO_IN: u"IN", # There is data to read.
1288
gobject.IO_OUT: u"OUT", # Data can be written (without
1290
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1291
gobject.IO_ERR: u"ERR", # Error condition.
1292
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1293
# broken, usually for pipes and
1296
conditions_string = ' | '.join(name
1298
condition_names.iteritems()
1299
if cond & condition)
1300
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1303
# Read a line from the file object
1304
cmdline = sender.readline()
1305
if not cmdline: # Empty line means end of file
1306
# close the IPC pipes
1310
# Stop calling this function
1313
logger.debug(u"IPC command: %r", cmdline)
1315
# Parse and act on command
1316
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1318
if cmd == u"NOTFOUND":
1319
fpr, address = args.split(None, 1)
1320
logger.warning(u"Client not found for fingerprint: %s, ad"
1321
u"dress: %s", fpr, address)
1324
mandos_dbus_service.ClientNotFound(fpr, address)
1325
elif cmd == u"DISABLED":
1326
for client in self.clients:
1327
if client.name == args:
1328
logger.warning(u"Client %s is disabled", args)
1334
logger.error(u"Unknown client %s is disabled", args)
1335
elif cmd == u"SENDING":
1336
for client in self.clients:
1337
if client.name == args:
1338
logger.info(u"Sending secret to %s", client.name)
1345
logger.error(u"Sending secret to unknown client %s",
1347
elif cmd == u"GETATTR":
1348
attr_name, fpr = args.split(None, 1)
1349
for client in self.clients:
1350
if client.fingerprint == fpr:
1351
attr_value = getattr(client, attr_name, None)
1352
logger.debug("IPC reply: %r", attr_value)
1353
pickle.dump(attr_value, reply)
1356
logger.error(u"Client %s on address %s requesting "
1357
u"attribute %s not found", fpr, address,
1359
pickle.dump(None, reply)
1361
logger.error(u"Unknown IPC command: %r", cmdline)
1363
# Keep calling this function
535
return super(type(self), self).server_bind()
1367
538
def string_to_delta(interval):
1368
539
"""Parse a string and return a datetime.timedelta
1370
>>> string_to_delta(u'7d')
541
>>> string_to_delta('7d')
1371
542
datetime.timedelta(7)
1372
>>> string_to_delta(u'60s')
543
>>> string_to_delta('60s')
1373
544
datetime.timedelta(0, 60)
1374
>>> string_to_delta(u'60m')
545
>>> string_to_delta('60m')
1375
546
datetime.timedelta(0, 3600)
1376
>>> string_to_delta(u'24h')
547
>>> string_to_delta('24h')
1377
548
datetime.timedelta(1)
1378
549
>>> string_to_delta(u'1w')
1379
550
datetime.timedelta(7)
1380
>>> string_to_delta(u'5m 30s')
1381
datetime.timedelta(0, 330)
1383
timevalue = datetime.timedelta(0)
1384
for s in interval.split():
1386
suffix = unicode(s[-1])
1389
delta = datetime.timedelta(value)
1390
elif suffix == u"s":
1391
delta = datetime.timedelta(0, value)
1392
elif suffix == u"m":
1393
delta = datetime.timedelta(0, 0, 0, 0, value)
1394
elif suffix == u"h":
1395
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1396
elif suffix == u"w":
1397
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1399
raise ValueError(u"Unknown suffix %r" % suffix)
1400
except (ValueError, IndexError), e:
1401
raise ValueError(e.message)
1406
def if_nametoindex(interface):
1407
"""Call the C function if_nametoindex(), or equivalent
553
suffix=unicode(interval[-1])
554
value=int(interval[:-1])
556
delta = datetime.timedelta(value)
558
delta = datetime.timedelta(0, value)
560
delta = datetime.timedelta(0, 0, 0, 0, value)
562
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
564
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
567
except (ValueError, IndexError):
572
def server_state_changed(state):
573
"""Derived from the Avahi example code"""
574
if state == avahi.SERVER_COLLISION:
575
logger.warning(u"Server name collision")
577
elif state == avahi.SERVER_RUNNING:
581
def entry_group_state_changed(state, error):
582
"""Derived from the Avahi example code"""
583
logger.debug(u"state change: %i", state)
1409
Note: This function cannot accept a unicode string."""
1410
global if_nametoindex
585
if state == avahi.ENTRY_GROUP_ESTABLISHED:
586
logger.debug(u"Service established.")
587
elif state == avahi.ENTRY_GROUP_COLLISION:
588
logger.warning(u"Service name collision.")
590
elif state == avahi.ENTRY_GROUP_FAILURE:
591
logger.critical(u"Error in group state changed %s",
593
raise AvahiGroupError("State changed: %s", str(error))
595
def if_nametoindex(interface, _func=[None]):
596
"""Call the C function if_nametoindex(), or equivalent"""
597
if _func[0] is not None:
598
return _func[0](interface)
1412
if_nametoindex = (ctypes.cdll.LoadLibrary
1413
(ctypes.util.find_library(u"c"))
600
if "ctypes.util" not in sys.modules:
604
libc = ctypes.cdll.LoadLibrary\
605
(ctypes.util.find_library("c"))
606
func[0] = libc.if_nametoindex
607
return _func[0](interface)
1415
611
except (OSError, AttributeError):
1416
logger.warning(u"Doing if_nametoindex the hard way")
1417
def if_nametoindex(interface):
612
if "struct" not in sys.modules:
614
if "fcntl" not in sys.modules:
616
def the_hard_way(interface):
1418
617
"Get an interface index the hard way, i.e. using fcntl()"
1419
618
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1420
with contextlib.closing(socket.socket()) as s:
1421
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1422
struct.pack(str(u"16s16x"),
1424
interface_index = struct.unpack(str(u"I"),
620
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
621
struct.pack("16s16x", interface))
623
interface_index = struct.unpack("I", ifreq[16:20])[0]
1426
624
return interface_index
1427
return if_nametoindex(interface)
1430
def daemon(nochdir = False, noclose = False):
625
_func[0] = the_hard_way
626
return _func[0](interface)
629
def daemon(nochdir, noclose):
1431
630
"""See daemon(3). Standard BSD Unix function.
1433
631
This should really exist as os.daemon, but it doesn't (yet)."""
1442
638
# Close all standard open file descriptors
1443
639
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1444
640
if not stat.S_ISCHR(os.fstat(null).st_mode):
1445
641
raise OSError(errno.ENODEV,
1446
u"%s not a character device"
642
"/dev/null not a character device")
1448
643
os.dup2(null, sys.stdin.fileno())
1449
644
os.dup2(null, sys.stdout.fileno())
1450
645
os.dup2(null, sys.stderr.fileno())
1492
681
# Default values for config file for server-global settings
1493
server_defaults = { u"interface": u"",
1498
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1499
u"servicename": u"Mandos",
1500
u"use_dbus": u"True",
1501
u"use_ipv6": u"True",
682
server_defaults = { "interface": "",
687
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
688
"servicename": "Mandos",
1504
691
# Parse config file for server-global settings
1505
server_config = configparser.SafeConfigParser(server_defaults)
692
server_config = ConfigParser.SafeConfigParser(server_defaults)
1506
693
del server_defaults
1507
server_config.read(os.path.join(options.configdir,
694
server_config.read(os.path.join(options.configdir, "server.conf"))
695
server_section = "server"
1509
696
# Convert the SafeConfigParser object to a dict
1510
server_settings = server_config.defaults()
1511
# Use the appropriate methods on the non-string config options
1512
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1513
server_settings[option] = server_config.getboolean(u"DEFAULT",
1515
if server_settings["port"]:
1516
server_settings["port"] = server_config.getint(u"DEFAULT",
697
server_settings = dict(server_config.items(server_section))
698
# Use getboolean on the boolean config option
699
server_settings["debug"] = server_config.getboolean\
700
(server_section, "debug")
1518
701
del server_config
702
if not server_settings["interface"]:
703
server_settings["interface"] = avahi.IF_UNSPEC
1520
705
# Override the settings from the config file with command line
1521
706
# options, if set.
1522
for option in (u"interface", u"address", u"port", u"debug",
1523
u"priority", u"servicename", u"configdir",
1524
u"use_dbus", u"use_ipv6"):
707
for option in ("interface", "address", "port", "debug",
708
"priority", "servicename", "configdir"):
1525
709
value = getattr(options, option)
1526
710
if value is not None:
1527
711
server_settings[option] = value
1529
# Force all strings to be unicode
1530
for option in server_settings.keys():
1531
if type(server_settings[option]) is str:
1532
server_settings[option] = unicode(server_settings[option])
1533
713
# Now we have our good server settings in "server_settings"
1535
##################################################################
1538
debug = server_settings[u"debug"]
1539
use_dbus = server_settings[u"use_dbus"]
1540
use_ipv6 = server_settings[u"use_ipv6"]
1543
syslogger.setLevel(logging.WARNING)
1544
console.setLevel(logging.WARNING)
1546
if server_settings[u"servicename"] != u"Mandos":
1547
syslogger.setFormatter(logging.Formatter
1548
(u'Mandos (%s) [%%(process)d]:'
1549
u' %%(levelname)s: %%(message)s'
1550
% server_settings[u"servicename"]))
1552
715
# Parse config file with clients
1553
client_defaults = { u"timeout": u"1h",
1555
u"checker": u"fping -q -- %%(host)s",
716
client_defaults = { "timeout": "1h",
718
"checker": "fping -q -- %%(fqdn)s",
1558
client_config = configparser.SafeConfigParser(client_defaults)
1559
client_config.read(os.path.join(server_settings[u"configdir"],
1562
global mandos_dbus_service
1563
mandos_dbus_service = None
1565
tcp_server = MandosServer((server_settings[u"address"],
1566
server_settings[u"port"]),
1568
interface=(server_settings[u"interface"]
1572
server_settings[u"priority"],
1574
pidfilename = u"/var/run/mandos.pid"
1576
pidfile = open(pidfilename, u"w")
1578
logger.error(u"Could not open file %r", pidfilename)
1581
uid = pwd.getpwnam(u"_mandos").pw_uid
1582
gid = pwd.getpwnam(u"_mandos").pw_gid
1585
uid = pwd.getpwnam(u"mandos").pw_uid
1586
gid = pwd.getpwnam(u"mandos").pw_gid
1589
uid = pwd.getpwnam(u"nobody").pw_uid
1590
gid = pwd.getpwnam(u"nobody").pw_gid
1597
except OSError, error:
1598
if error[0] != errno.EPERM:
1601
# Enable all possible GnuTLS debugging
1603
# "Use a log level over 10 to enable all debugging options."
1605
gnutls.library.functions.gnutls_global_set_log_level(11)
1607
@gnutls.library.types.gnutls_log_func
1608
def debug_gnutls(level, string):
1609
logger.debug(u"GnuTLS: %s", string[:-1])
1611
(gnutls.library.functions
1612
.gnutls_global_set_log_function(debug_gnutls))
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", );
1614
728
global main_loop
1615
731
# From the Avahi example code
1616
732
DBusGMainLoop(set_as_default=True )
1617
733
main_loop = gobject.MainLoop()
1618
734
bus = dbus.SystemBus()
735
server = dbus.Interface(
736
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
737
avahi.DBUS_INTERFACE_SERVER )
1619
738
# End of Avahi example code
1622
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1623
bus, do_not_queue=True)
1624
except dbus.exceptions.NameExistsException, e:
1625
logger.error(unicode(e) + u", disabling D-Bus")
1627
server_settings[u"use_dbus"] = False
1628
tcp_server.use_dbus = False
1629
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1630
service = AvahiService(name = server_settings[u"servicename"],
1631
servicetype = u"_mandos._tcp",
1632
protocol = protocol, bus = bus)
1633
if server_settings["interface"]:
1634
service.interface = (if_nametoindex
1635
(str(server_settings[u"interface"])))
1637
client_class = Client
1639
client_class = functools.partial(ClientDBus, bus = bus)
1640
tcp_server.clients.update(set(
1641
client_class(name = section,
1642
config= dict(client_config.items(section)))
1643
for section in client_config.sections()))
1644
if not tcp_server.clients:
1645
logger.warning(u"No clients defined")
740
debug = server_settings["debug"]
1648
# Redirect stdin so all checkers get /dev/null
1649
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1650
os.dup2(null, sys.stdin.fileno())
1654
# No console logging
1655
logger.removeHandler(console)
1656
# Close all input and output, do double fork, etc.
1662
pidfile.write(str(pid) + "\n")
1665
logger.error(u"Could not write to file %r with PID %d",
1668
# "pidfile" was never created
743
console = logging.StreamHandler()
744
# console.setLevel(logging.DEBUG)
745
console.setFormatter(logging.Formatter\
746
('%(levelname)s: %(message)s'))
747
logger.addHandler(console)
751
def remove_from_clients(client):
752
clients.remove(client)
754
logger.debug(u"No clients left, exiting")
757
clients.update(Set(Client(name=section,
758
stop_hook = remove_from_clients,
759
**(dict(client_config\
761
for section in client_config.sections()))
767
"Cleanup function; run on exit"
769
# From the Avahi example code
770
if not group is None:
773
# End of Avahi example code
776
client = clients.pop()
777
client.stop_hook = None
780
atexit.register(cleanup)
1673
783
signal.signal(signal.SIGINT, signal.SIG_IGN)
1674
784
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1675
785
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1678
class MandosDBusService(dbus.service.Object):
1679
"""A D-Bus proxy object"""
1681
dbus.service.Object.__init__(self, bus, u"/")
1682
_interface = u"se.bsnet.fukt.Mandos"
1684
@dbus.service.signal(_interface, signature=u"o")
1685
def ClientAdded(self, objpath):
1689
@dbus.service.signal(_interface, signature=u"ss")
1690
def ClientNotFound(self, fingerprint, address):
1694
@dbus.service.signal(_interface, signature=u"os")
1695
def ClientRemoved(self, objpath, name):
1699
@dbus.service.method(_interface, out_signature=u"ao")
1700
def GetAllClients(self):
1702
return dbus.Array(c.dbus_object_path
1703
for c in tcp_server.clients)
1705
@dbus.service.method(_interface,
1706
out_signature=u"a{oa{sv}}")
1707
def GetAllClientsWithProperties(self):
1709
return dbus.Dictionary(
1710
((c.dbus_object_path, c.GetAll(u""))
1711
for c in tcp_server.clients),
1712
signature=u"oa{sv}")
1714
@dbus.service.method(_interface, in_signature=u"o")
1715
def RemoveClient(self, object_path):
1717
for c in tcp_server.clients:
1718
if c.dbus_object_path == object_path:
1719
tcp_server.clients.remove(c)
1720
c.remove_from_connection()
1721
# Don't signal anything except ClientRemoved
1722
c.disable(quiet=True)
1724
self.ClientRemoved(object_path, c.name)
1726
raise KeyError(object_path)
1730
mandos_dbus_service = MandosDBusService()
1733
"Cleanup function; run on exit"
1736
while tcp_server.clients:
1737
client = tcp_server.clients.pop()
1739
client.remove_from_connection()
1740
client.disable_hook = None
1741
# Don't signal anything except ClientRemoved
1742
client.disable(quiet=True)
1745
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
1748
atexit.register(cleanup)
1750
for client in tcp_server.clients:
1753
mandos_dbus_service.ClientAdded(client.dbus_object_path)
1757
tcp_server.server_activate()
787
for client in clients:
790
tcp_server = IPv6_TCPServer((server_settings["address"],
791
server_settings["port"]),
793
settings=server_settings,
1759
795
# Find out what port we got
1760
796
service.port = tcp_server.socket.getsockname()[1]
1762
logger.info(u"Now listening on address %r, port %d,"
1763
" flowinfo %d, scope_id %d"
1764
% tcp_server.socket.getsockname())
1766
logger.info(u"Now listening on address %r, port %d"
1767
% tcp_server.socket.getsockname())
797
logger.debug(u"Now listening on port %d", service.port)
1769
#service.interface = tcp_server.socket.getsockname()[3]
799
if not server_settings["interface"]:
800
service.interface = if_nametoindex\
801
(server_settings["interface"])
1772
804
# From the Avahi example code
805
server.connect_to_signal("StateChanged", server_state_changed)
807
server_state_changed(server.GetState())
1775
808
except dbus.exceptions.DBusException, error:
1776
809
logger.critical(u"DBusException: %s", error)
1779
811
# End of Avahi example code
1781
813
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
1782
814
lambda *args, **kwargs:
1783
(tcp_server.handle_request
1784
(*args[2:], **kwargs) or True))
815
tcp_server.handle_request\
816
(*args[2:], **kwargs) or True)
1786
logger.debug(u"Starting main loop")
818
logger.debug("Starting main loop")
819
main_loop_started = True
1788
821
except AvahiError, error:
1789
logger.critical(u"AvahiError: %s", error)
822
logger.critical(u"AvahiError: %s" + unicode(error))
1792
824
except KeyboardInterrupt:
1795
logger.debug(u"Server received KeyboardInterrupt")
1796
logger.debug(u"Server exiting")
1797
# Must run before the D-Bus bus name gets deregistered
1800
828
if __name__ == '__main__':