104
127
max_renames: integer; maximum number of renames
105
128
rename_count: integer; counter so we only rename after collisions
106
129
a sensible number of times
130
group: D-Bus Entry Group
132
bus: dbus.SystemBus()
108
134
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
109
type = None, port = None, TXT = None, domain = "",
110
host = "", max_renames = 32768):
135
servicetype = None, port = None, TXT = None,
136
domain = u"", host = u"", max_renames = 32768,
137
protocol = avahi.PROTO_UNSPEC, bus = None):
111
138
self.interface = interface
140
self.type = servicetype
142
self.TXT = TXT if TXT is not None else []
119
143
self.domain = domain
121
145
self.rename_count = 0
122
146
self.max_renames = max_renames
147
self.protocol = protocol
148
self.group = None # our entry group
123
151
def rename(self):
124
152
"""Derived from the Avahi example code"""
125
153
if self.rename_count >= self.max_renames:
126
logger.critical(u"No suitable service name found after %i"
127
u" retries, exiting.", rename_count)
128
raise AvahiServiceError("Too many renames")
129
self.name = server.GetAlternativeServiceName(self.name)
130
logger.info(u"Changing name to %r ...", str(self.name))
131
syslogger.setFormatter(logging.Formatter\
132
('Mandos (%s): %%(levelname)s:'
133
' %%(message)s' % self.name))
154
logger.critical(u"No suitable Zeroconf service name found"
155
u" after %i retries, exiting.",
157
raise AvahiServiceError(u"Too many renames")
158
self.name = self.server.GetAlternativeServiceName(self.name)
159
logger.info(u"Changing Zeroconf service name to %r ...",
161
syslogger.setFormatter(logging.Formatter
162
(u'Mandos (%s) [%%(process)d]:'
163
u' %%(levelname)s: %%(message)s'
136
167
self.rename_count += 1
137
168
def remove(self):
138
169
"""Derived from the Avahi example code"""
139
if group is not None:
170
if self.group is not None:
142
173
"""Derived from the Avahi example code"""
145
group = dbus.Interface\
146
(bus.get_object(avahi.DBUS_NAME,
147
server.EntryGroupNew()),
148
avahi.DBUS_INTERFACE_ENTRY_GROUP)
149
group.connect_to_signal('StateChanged',
150
entry_group_state_changed)
151
logger.debug(u"Adding service '%s' of type '%s' ...",
152
service.name, service.type)
154
self.interface, # interface
155
avahi.PROTO_INET6, # protocol
156
dbus.UInt32(0), # flags
157
self.name, self.type,
158
self.domain, self.host,
159
dbus.UInt16(self.port),
160
avahi.string_array_to_txt_array(self.TXT))
163
# From the Avahi example code:
164
group = None # our entry group
165
# End of Avahi example code
174
if self.group is None:
175
self.group = dbus.Interface(
176
self.bus.get_object(avahi.DBUS_NAME,
177
self.server.EntryGroupNew()),
178
avahi.DBUS_INTERFACE_ENTRY_GROUP)
179
self.group.connect_to_signal('StateChanged',
181
.entry_group_state_changed)
182
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
183
self.name, self.type)
184
self.group.AddService(
187
dbus.UInt32(0), # flags
188
self.name, self.type,
189
self.domain, self.host,
190
dbus.UInt16(self.port),
191
avahi.string_array_to_txt_array(self.TXT))
193
def entry_group_state_changed(self, state, error):
194
"""Derived from the Avahi example code"""
195
logger.debug(u"Avahi state change: %i", state)
197
if state == avahi.ENTRY_GROUP_ESTABLISHED:
198
logger.debug(u"Zeroconf service established.")
199
elif state == avahi.ENTRY_GROUP_COLLISION:
200
logger.warning(u"Zeroconf service name collision.")
202
elif state == avahi.ENTRY_GROUP_FAILURE:
203
logger.critical(u"Avahi: Error in group state changed %s",
205
raise AvahiGroupError(u"State changed: %s"
208
"""Derived from the Avahi example code"""
209
if self.group is not None:
212
def server_state_changed(self, state):
213
"""Derived from the Avahi example code"""
214
if state == avahi.SERVER_COLLISION:
215
logger.error(u"Zeroconf server name collision")
217
elif state == avahi.SERVER_RUNNING:
220
"""Derived from the Avahi example code"""
221
if self.server is None:
222
self.server = dbus.Interface(
223
self.bus.get_object(avahi.DBUS_NAME,
224
avahi.DBUS_PATH_SERVER),
225
avahi.DBUS_INTERFACE_SERVER)
226
self.server.connect_to_signal(u"StateChanged",
227
self.server_state_changed)
228
self.server_state_changed(self.server.GetState())
168
231
class Client(object):
169
232
"""A representation of a client host served by this server.
171
name: string; from the config file, used in log messages
235
name: string; from the config file, used in log messages and
172
237
fingerprint: string (40 or 32 hexadecimal digits); used to
173
238
uniquely identify the client
174
secret: bytestring; sent verbatim (over TLS) to client
175
host: string; available for use by the checker command
176
created: datetime.datetime(); object creation, not client host
177
last_checked_ok: datetime.datetime() or None if not yet checked OK
178
timeout: datetime.timedelta(); How long from last_checked_ok
179
until this client is invalid
180
interval: datetime.timedelta(); How often to start a new checker
181
stop_hook: If set, called by stop() as stop_hook(self)
182
checker: subprocess.Popen(); a running checker process used
183
to see if the client lives.
184
'None' if no process is running.
239
secret: bytestring; sent verbatim (over TLS) to client
240
host: string; available for use by the checker command
241
created: datetime.datetime(); (UTC) object creation
242
last_enabled: datetime.datetime(); (UTC)
244
last_checked_ok: datetime.datetime(); (UTC) or None
245
timeout: datetime.timedelta(); How long from last_checked_ok
246
until this client is disabled
247
interval: datetime.timedelta(); How often to start a new checker
248
disable_hook: If set, called by disable() as disable_hook(self)
249
checker: subprocess.Popen(); a running checker process used
250
to see if the client lives.
251
'None' if no process is running.
185
252
checker_initiator_tag: a gobject event source tag, or None
186
stop_initiator_tag: - '' -
253
disable_initiator_tag: - '' -
187
254
checker_callback_tag: - '' -
188
255
checker_command: string; External command which is run to check if
189
256
client lives. %() expansions are done at
190
257
runtime with vars(self) as dict, so that for
191
258
instance %(name)s can be used in the command.
193
_timeout: Real variable for 'timeout'
194
_interval: Real variable for 'interval'
195
_timeout_milliseconds: Used when calling gobject.timeout_add()
196
_interval_milliseconds: - '' -
259
current_checker_command: string; current running checker_command
198
def _set_timeout(self, timeout):
199
"Setter function for 'timeout' attribute"
200
self._timeout = timeout
201
self._timeout_milliseconds = ((self.timeout.days
202
* 24 * 60 * 60 * 1000)
203
+ (self.timeout.seconds * 1000)
204
+ (self.timeout.microseconds
206
timeout = property(lambda self: self._timeout,
209
def _set_interval(self, interval):
210
"Setter function for 'interval' attribute"
211
self._interval = interval
212
self._interval_milliseconds = ((self.interval.days
213
* 24 * 60 * 60 * 1000)
214
+ (self.interval.seconds
216
+ (self.interval.microseconds
218
interval = property(lambda self: self._interval,
221
def __init__(self, name = None, stop_hook=None, config={}):
263
def _timedelta_to_milliseconds(td):
264
"Convert a datetime.timedelta() to milliseconds"
265
return ((td.days * 24 * 60 * 60 * 1000)
266
+ (td.seconds * 1000)
267
+ (td.microseconds // 1000))
269
def timeout_milliseconds(self):
270
"Return the 'timeout' attribute in milliseconds"
271
return self._timedelta_to_milliseconds(self.timeout)
273
def interval_milliseconds(self):
274
"Return the 'interval' attribute in milliseconds"
275
return self._timedelta_to_milliseconds(self.interval)
277
def __init__(self, name = None, disable_hook=None, config=None):
222
278
"""Note: the 'checker' key in 'config' sets the
223
279
'checker_command' attribute and *not* the 'checker'
226
284
logger.debug(u"Creating client %r", self.name)
227
285
# Uppercase and remove spaces from fingerprint for later
228
286
# comparison purposes with return value from the fingerprint()
230
self.fingerprint = config["fingerprint"].upper()\
288
self.fingerprint = (config[u"fingerprint"].upper()
232
290
logger.debug(u" Fingerprint: %s", self.fingerprint)
233
if "secret" in config:
234
self.secret = config["secret"].decode(u"base64")
235
elif "secfile" in config:
236
sf = open(config["secfile"])
237
self.secret = sf.read()
291
if u"secret" in config:
292
self.secret = config[u"secret"].decode(u"base64")
293
elif u"secfile" in config:
294
with open(os.path.expanduser(os.path.expandvars
295
(config[u"secfile"])),
297
self.secret = secfile.read()
240
299
raise TypeError(u"No secret or secfile for client %s"
242
self.host = config.get("host", "")
243
self.created = datetime.datetime.now()
301
self.host = config.get(u"host", u"")
302
self.created = datetime.datetime.utcnow()
304
self.last_enabled = None
244
305
self.last_checked_ok = None
245
self.timeout = string_to_delta(config["timeout"])
246
self.interval = string_to_delta(config["interval"])
247
self.stop_hook = stop_hook
306
self.timeout = string_to_delta(config[u"timeout"])
307
self.interval = string_to_delta(config[u"interval"])
308
self.disable_hook = disable_hook
248
309
self.checker = None
249
310
self.checker_initiator_tag = None
250
self.stop_initiator_tag = None
311
self.disable_initiator_tag = None
251
312
self.checker_callback_tag = None
252
self.check_command = config["checker"]
313
self.checker_command = config[u"checker"]
314
self.current_checker_command = None
315
self.last_connect = None
254
318
"""Start this client's checker and timeout hooks"""
319
if getattr(self, u"enabled", False):
322
self.last_enabled = datetime.datetime.utcnow()
255
323
# Schedule a new checker to be started an 'interval' from now,
256
324
# and every interval from then on.
257
self.checker_initiator_tag = gobject.timeout_add\
258
(self._interval_milliseconds,
325
self.checker_initiator_tag = (gobject.timeout_add
326
(self.interval_milliseconds(),
328
# Schedule a disable() when 'timeout' has passed
329
self.disable_initiator_tag = (gobject.timeout_add
330
(self.timeout_milliseconds(),
260
333
# Also start a new checker *right now*.
261
334
self.start_checker()
262
# Schedule a stop() when 'timeout' has passed
263
self.stop_initiator_tag = gobject.timeout_add\
264
(self._timeout_milliseconds,
268
The possibility that a client might be restarted is left open,
269
but not currently used."""
270
# If this client doesn't have a secret, it is already stopped.
271
if hasattr(self, "secret") and self.secret:
272
logger.info(u"Stopping client %s", self.name)
336
def disable(self, quiet=True):
337
"""Disable this client."""
338
if not getattr(self, "enabled", False):
276
if getattr(self, "stop_initiator_tag", False):
277
gobject.source_remove(self.stop_initiator_tag)
278
self.stop_initiator_tag = None
279
if getattr(self, "checker_initiator_tag", False):
341
logger.info(u"Disabling client %s", self.name)
342
if getattr(self, u"disable_initiator_tag", False):
343
gobject.source_remove(self.disable_initiator_tag)
344
self.disable_initiator_tag = None
345
if getattr(self, u"checker_initiator_tag", False):
280
346
gobject.source_remove(self.checker_initiator_tag)
281
347
self.checker_initiator_tag = None
282
348
self.stop_checker()
349
if self.disable_hook:
350
self.disable_hook(self)
285
352
# Do not run this again if called by a gobject.timeout_add
287
355
def __del__(self):
288
self.stop_hook = None
290
def checker_callback(self, pid, condition):
356
self.disable_hook = None
359
def checker_callback(self, pid, condition, command):
291
360
"""The checker has completed, so take appropriate actions."""
292
now = datetime.datetime.now()
293
361
self.checker_callback_tag = None
294
362
self.checker = None
295
if os.WIFEXITED(condition) \
296
and (os.WEXITSTATUS(condition) == 0):
297
logger.info(u"Checker for %(name)s succeeded",
299
self.last_checked_ok = now
300
gobject.source_remove(self.stop_initiator_tag)
301
self.stop_initiator_tag = gobject.timeout_add\
302
(self._timeout_milliseconds,
304
elif not os.WIFEXITED(condition):
363
if os.WIFEXITED(condition):
364
exitstatus = os.WEXITSTATUS(condition)
366
logger.info(u"Checker for %(name)s succeeded",
370
logger.info(u"Checker for %(name)s failed",
305
373
logger.warning(u"Checker for %(name)s crashed?",
308
logger.info(u"Checker for %(name)s failed",
376
def checked_ok(self):
377
"""Bump up the timeout for this client.
379
This should only be called when the client has been seen,
382
self.last_checked_ok = datetime.datetime.utcnow()
383
gobject.source_remove(self.disable_initiator_tag)
384
self.disable_initiator_tag = (gobject.timeout_add
385
(self.timeout_milliseconds(),
310
388
def start_checker(self):
311
389
"""Start a new checker subprocess if one is not running.
312
391
If a checker already exists, leave it running and do
314
393
# The reason for not killing a running checker is that if we
317
396
# client would inevitably timeout, since no checker would get
318
397
# a chance to run to completion. If we instead leave running
319
398
# checkers alone, the checker would have to take more time
320
# than 'timeout' for the client to be declared invalid, which
321
# is as it should be.
399
# than 'timeout' for the client to be disabled, which is as it
402
# If a checker exists, make sure it is not a zombie
404
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
405
except (AttributeError, OSError), error:
406
if (isinstance(error, OSError)
407
and error.errno != errno.ECHILD):
411
logger.warning(u"Checker was a zombie")
412
gobject.source_remove(self.checker_callback_tag)
413
self.checker_callback(pid, status,
414
self.current_checker_command)
415
# Start a new checker if needed
322
416
if self.checker is None:
324
# In case check_command has exactly one % operator
325
command = self.check_command % self.host
418
# In case checker_command has exactly one % operator
419
command = self.checker_command % self.host
326
420
except TypeError:
327
421
# Escape attributes for the shell
328
escaped_attrs = dict((key, re.escape(str(val)))
422
escaped_attrs = dict((key,
423
re.escape(unicode(str(val),
330
427
vars(self).iteritems())
332
command = self.check_command % escaped_attrs
429
command = self.checker_command % escaped_attrs
333
430
except TypeError, error:
334
431
logger.error(u'Could not format string "%s":'
335
u' %s', self.check_command, error)
432
u' %s', self.checker_command, error)
336
433
return True # Try again later
434
self.current_checker_command = command
338
436
logger.info(u"Starting checker %r for %s",
339
437
command, self.name)
438
# We don't need to redirect stdout and stderr, since
439
# in normal mode, that is already done by daemon(),
440
# and in debug mode we don't want to. (Stdin is
441
# always replaced by /dev/null.)
340
442
self.checker = subprocess.Popen(command,
343
self.checker_callback_tag = gobject.child_watch_add\
345
self.checker_callback)
346
except subprocess.OSError, error:
444
shell=True, cwd=u"/")
445
self.checker_callback_tag = (gobject.child_watch_add
447
self.checker_callback,
449
# The checker may have completed before the gobject
450
# watch was added. Check for this.
451
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
453
gobject.source_remove(self.checker_callback_tag)
454
self.checker_callback(pid, status, command)
455
except OSError, error:
347
456
logger.error(u"Failed to start subprocess: %s",
349
458
# Re-run this periodically if run by gobject.timeout_add
351
461
def stop_checker(self):
352
462
"""Force the checker process, if any, to stop."""
353
463
if self.checker_callback_tag:
354
464
gobject.source_remove(self.checker_callback_tag)
355
465
self.checker_callback_tag = None
356
if getattr(self, "checker", None) is None:
466
if getattr(self, u"checker", None) is None:
358
468
logger.debug(u"Stopping checker for %(name)s", vars(self))
360
470
os.kill(self.checker.pid, signal.SIGTERM)
362
472
#if self.checker.poll() is None:
363
473
# os.kill(self.checker.pid, signal.SIGKILL)
364
474
except OSError, error:
365
475
if error.errno != errno.ESRCH: # No such process
367
477
self.checker = None
368
def still_valid(self):
369
"""Has the timeout not yet passed for this client?"""
370
now = datetime.datetime.now()
480
def dbus_service_property(dbus_interface, signature=u"v",
481
access=u"readwrite", byte_arrays=False):
482
"""Decorators for marking methods of a DBusObjectWithProperties to
483
become properties on the D-Bus.
485
The decorated method will be called with no arguments by "Get"
486
and with one argument by "Set".
488
The parameters, where they are supported, are the same as
489
dbus.service.method, except there is only "signature", since the
490
type from Get() and the type sent to Set() is the same.
492
# Encoding deeply encoded byte arrays is not supported yet by the
493
# "Set" method, so we fail early here:
494
if byte_arrays and signature != u"ay":
495
raise ValueError(u"Byte arrays not supported for non-'ay'"
496
u" signature %r" % signature)
498
func._dbus_is_property = True
499
func._dbus_interface = dbus_interface
500
func._dbus_signature = signature
501
func._dbus_access = access
502
func._dbus_name = func.__name__
503
if func._dbus_name.endswith(u"_dbus_property"):
504
func._dbus_name = func._dbus_name[:-14]
505
func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
510
class DBusPropertyException(dbus.exceptions.DBusException):
511
"""A base class for D-Bus property-related exceptions
513
def __unicode__(self):
514
return unicode(str(self))
517
class DBusPropertyAccessException(DBusPropertyException):
518
"""A property's access permissions disallows an operation.
523
class DBusPropertyNotFound(DBusPropertyException):
524
"""An attempt was made to access a non-existing property.
529
class DBusObjectWithProperties(dbus.service.Object):
530
"""A D-Bus object with properties.
532
Classes inheriting from this can use the dbus_service_property
533
decorator to expose methods as D-Bus properties. It exposes the
534
standard Get(), Set(), and GetAll() methods on the D-Bus.
538
def _is_dbus_property(obj):
539
return getattr(obj, u"_dbus_is_property", False)
541
def _get_all_dbus_properties(self):
542
"""Returns a generator of (name, attribute) pairs
544
return ((prop._dbus_name, prop)
546
inspect.getmembers(self, self._is_dbus_property))
548
def _get_dbus_property(self, interface_name, property_name):
549
"""Returns a bound method if one exists which is a D-Bus
550
property with the specified name and interface.
552
for name in (property_name,
553
property_name + u"_dbus_property"):
554
prop = getattr(self, name, None)
556
or not self._is_dbus_property(prop)
557
or prop._dbus_name != property_name
558
or (interface_name and prop._dbus_interface
559
and interface_name != prop._dbus_interface)):
563
raise DBusPropertyNotFound(self.dbus_object_path + u":"
564
+ interface_name + u"."
567
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
569
def Get(self, interface_name, property_name):
570
"""Standard D-Bus property Get() method, see D-Bus standard.
572
prop = self._get_dbus_property(interface_name, property_name)
573
if prop._dbus_access == u"write":
574
raise DBusPropertyAccessException(property_name)
576
if not hasattr(value, u"variant_level"):
578
return type(value)(value, variant_level=value.variant_level+1)
580
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
581
def Set(self, interface_name, property_name, value):
582
"""Standard D-Bus property Set() method, see D-Bus standard.
584
prop = self._get_dbus_property(interface_name, property_name)
585
if prop._dbus_access == u"read":
586
raise DBusPropertyAccessException(property_name)
587
if prop._dbus_get_args_options[u"byte_arrays"]:
588
# The byte_arrays option is not supported yet on
589
# signatures other than "ay".
590
if prop._dbus_signature != u"ay":
592
value = dbus.ByteArray(''.join(unichr(byte)
596
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
597
out_signature=u"a{sv}")
598
def GetAll(self, interface_name):
599
"""Standard D-Bus property GetAll() method, see D-Bus
602
Note: Will not include properties with access="write".
605
for name, prop in self._get_all_dbus_properties():
607
and interface_name != prop._dbus_interface):
608
# Interface non-empty but did not match
610
# Ignore write-only properties
611
if prop._dbus_access == u"write":
614
if not hasattr(value, u"variant_level"):
617
all[name] = type(value)(value, variant_level=
618
value.variant_level+1)
619
return dbus.Dictionary(all, signature=u"sv")
621
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
623
path_keyword='object_path',
624
connection_keyword='connection')
625
def Introspect(self, object_path, connection):
626
"""Standard D-Bus method, overloaded to insert property tags.
628
xmlstring = dbus.service.Object.Introspect(self, object_path,
631
document = xml.dom.minidom.parseString(xmlstring)
632
def make_tag(document, name, prop):
633
e = document.createElement(u"property")
634
e.setAttribute(u"name", name)
635
e.setAttribute(u"type", prop._dbus_signature)
636
e.setAttribute(u"access", prop._dbus_access)
638
for if_tag in document.getElementsByTagName(u"interface"):
639
for tag in (make_tag(document, name, prop)
641
in self._get_all_dbus_properties()
642
if prop._dbus_interface
643
== if_tag.getAttribute(u"name")):
644
if_tag.appendChild(tag)
645
# Add the names to the return values for the
646
# "org.freedesktop.DBus.Properties" methods
647
if (if_tag.getAttribute(u"name")
648
== u"org.freedesktop.DBus.Properties"):
649
for cn in if_tag.getElementsByTagName(u"method"):
650
if cn.getAttribute(u"name") == u"Get":
651
for arg in cn.getElementsByTagName(u"arg"):
652
if (arg.getAttribute(u"direction")
654
arg.setAttribute(u"name", u"value")
655
elif cn.getAttribute(u"name") == u"GetAll":
656
for arg in cn.getElementsByTagName(u"arg"):
657
if (arg.getAttribute(u"direction")
659
arg.setAttribute(u"name", u"props")
660
xmlstring = document.toxml(u"utf-8")
662
except (AttributeError, xml.dom.DOMException,
663
xml.parsers.expat.ExpatError), error:
664
logger.error(u"Failed to override Introspection method",
669
class ClientDBus(Client, DBusObjectWithProperties):
670
"""A Client class using D-Bus
673
dbus_object_path: dbus.ObjectPath
674
bus: dbus.SystemBus()
676
# dbus.service.Object doesn't use super(), so we can't either.
678
def __init__(self, bus = None, *args, **kwargs):
680
Client.__init__(self, *args, **kwargs)
681
# Only now, when this client is initialized, can it show up on
683
self.dbus_object_path = (dbus.ObjectPath
685
+ self.name.replace(u".", u"_")))
686
DBusObjectWithProperties.__init__(self, self.bus,
687
self.dbus_object_path)
690
def _datetime_to_dbus(dt, variant_level=0):
691
"""Convert a UTC datetime.datetime() to a D-Bus type."""
692
return dbus.String(dt.isoformat(),
693
variant_level=variant_level)
696
oldstate = getattr(self, u"enabled", False)
697
r = Client.enable(self)
698
if oldstate != self.enabled:
700
self.PropertyChanged(dbus.String(u"enabled"),
701
dbus.Boolean(True, variant_level=1))
702
self.PropertyChanged(
703
dbus.String(u"last_enabled"),
704
self._datetime_to_dbus(self.last_enabled,
708
def disable(self, quiet = False):
709
oldstate = getattr(self, u"enabled", False)
710
r = Client.disable(self, quiet=quiet)
711
if not quiet and oldstate != self.enabled:
713
self.PropertyChanged(dbus.String(u"enabled"),
714
dbus.Boolean(False, variant_level=1))
717
def __del__(self, *args, **kwargs):
719
self.remove_from_connection()
722
if hasattr(DBusObjectWithProperties, u"__del__"):
723
DBusObjectWithProperties.__del__(self, *args, **kwargs)
724
Client.__del__(self, *args, **kwargs)
726
def checker_callback(self, pid, condition, command,
728
self.checker_callback_tag = None
731
self.PropertyChanged(dbus.String(u"checker_running"),
732
dbus.Boolean(False, variant_level=1))
733
if os.WIFEXITED(condition):
734
exitstatus = os.WEXITSTATUS(condition)
736
self.CheckerCompleted(dbus.Int16(exitstatus),
737
dbus.Int64(condition),
738
dbus.String(command))
741
self.CheckerCompleted(dbus.Int16(-1),
742
dbus.Int64(condition),
743
dbus.String(command))
745
return Client.checker_callback(self, pid, condition, command,
748
def checked_ok(self, *args, **kwargs):
749
r = Client.checked_ok(self, *args, **kwargs)
751
self.PropertyChanged(
752
dbus.String(u"last_checked_ok"),
753
(self._datetime_to_dbus(self.last_checked_ok,
757
def start_checker(self, *args, **kwargs):
758
old_checker = self.checker
759
if self.checker is not None:
760
old_checker_pid = self.checker.pid
762
old_checker_pid = None
763
r = Client.start_checker(self, *args, **kwargs)
764
# Only if new checker process was started
765
if (self.checker is not None
766
and old_checker_pid != self.checker.pid):
768
self.CheckerStarted(self.current_checker_command)
769
self.PropertyChanged(
770
dbus.String(u"checker_running"),
771
dbus.Boolean(True, variant_level=1))
774
def stop_checker(self, *args, **kwargs):
775
old_checker = getattr(self, u"checker", None)
776
r = Client.stop_checker(self, *args, **kwargs)
777
if (old_checker is not None
778
and getattr(self, u"checker", None) is None):
779
self.PropertyChanged(dbus.String(u"checker_running"),
780
dbus.Boolean(False, variant_level=1))
783
## D-Bus methods & signals
784
_interface = u"se.bsnet.fukt.Mandos.Client"
787
@dbus.service.method(_interface)
789
return self.checked_ok()
791
# CheckerCompleted - signal
792
@dbus.service.signal(_interface, signature=u"nxs")
793
def CheckerCompleted(self, exitcode, waitstatus, command):
797
# CheckerStarted - signal
798
@dbus.service.signal(_interface, signature=u"s")
799
def CheckerStarted(self, command):
803
# PropertyChanged - signal
804
@dbus.service.signal(_interface, signature=u"sv")
805
def PropertyChanged(self, property, value):
810
@dbus.service.signal(_interface)
816
@dbus.service.signal(_interface)
822
@dbus.service.method(_interface)
827
# StartChecker - method
828
@dbus.service.method(_interface)
829
def StartChecker(self):
834
@dbus.service.method(_interface)
839
# StopChecker - method
840
@dbus.service.method(_interface)
841
def StopChecker(self):
845
@dbus_service_property(_interface, signature=u"s", access=u"read")
846
def name_dbus_property(self):
847
return dbus.String(self.name)
849
# fingerprint - property
850
@dbus_service_property(_interface, signature=u"s", access=u"read")
851
def fingerprint_dbus_property(self):
852
return dbus.String(self.fingerprint)
855
@dbus_service_property(_interface, signature=u"s",
857
def host_dbus_property(self, value=None):
858
if value is None: # get
859
return dbus.String(self.host)
862
self.PropertyChanged(dbus.String(u"host"),
863
dbus.String(value, variant_level=1))
866
@dbus_service_property(_interface, signature=u"s", access=u"read")
867
def created_dbus_property(self):
868
return dbus.String(self._datetime_to_dbus(self.created))
870
# last_enabled - property
871
@dbus_service_property(_interface, signature=u"s", access=u"read")
872
def last_enabled_dbus_property(self):
873
if self.last_enabled is None:
874
return dbus.String(u"")
875
return dbus.String(self._datetime_to_dbus(self.last_enabled))
878
@dbus_service_property(_interface, signature=u"b",
880
def enabled_dbus_property(self, value=None):
881
if value is None: # get
882
return dbus.Boolean(self.enabled)
888
# last_checked_ok - property
889
@dbus_service_property(_interface, signature=u"s",
891
def last_checked_ok_dbus_property(self, value=None):
892
if value is not None:
371
895
if self.last_checked_ok is None:
372
return now < (self.created + self.timeout)
374
return now < (self.last_checked_ok + self.timeout)
377
def peer_certificate(session):
378
"Return the peer's OpenPGP certificate as a bytestring"
379
# If not an OpenPGP certificate...
380
if gnutls.library.functions.gnutls_certificate_type_get\
381
(session._c_object) \
382
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
383
# ...do the normal thing
384
return session.peer_certificate
385
list_size = ctypes.c_uint()
386
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
387
(session._c_object, ctypes.byref(list_size))
388
if list_size.value == 0:
391
return ctypes.string_at(cert.data, cert.size)
394
def fingerprint(openpgp):
395
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
396
# New GnuTLS "datum" with the OpenPGP public key
397
datum = gnutls.library.types.gnutls_datum_t\
398
(ctypes.cast(ctypes.c_char_p(openpgp),
399
ctypes.POINTER(ctypes.c_ubyte)),
400
ctypes.c_uint(len(openpgp)))
401
# New empty GnuTLS certificate
402
crt = gnutls.library.types.gnutls_openpgp_crt_t()
403
gnutls.library.functions.gnutls_openpgp_crt_init\
405
# Import the OpenPGP public key into the certificate
406
gnutls.library.functions.gnutls_openpgp_crt_import\
407
(crt, ctypes.byref(datum),
408
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
409
# New buffer for the fingerprint
410
buffer = ctypes.create_string_buffer(20)
411
buffer_length = ctypes.c_size_t()
412
# Get the fingerprint from the certificate into the buffer
413
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
414
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
415
# Deinit the certificate
416
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
417
# Convert the buffer to a Python bytestring
418
fpr = ctypes.string_at(buffer, buffer_length.value)
419
# Convert the bytestring to hexadecimal notation
420
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
424
class tcp_handler(SocketServer.BaseRequestHandler, object):
425
"""A TCP request handler class.
426
Instantiated by IPv6_TCPServer for each request to handle it.
896
return dbus.String(u"")
897
return dbus.String(self._datetime_to_dbus(self
901
@dbus_service_property(_interface, signature=u"t",
903
def timeout_dbus_property(self, value=None):
904
if value is None: # get
905
return dbus.UInt64(self.timeout_milliseconds())
906
self.timeout = datetime.timedelta(0, 0, 0, value)
908
self.PropertyChanged(dbus.String(u"timeout"),
909
dbus.UInt64(value, variant_level=1))
910
if getattr(self, u"disable_initiator_tag", None) is None:
913
gobject.source_remove(self.disable_initiator_tag)
914
self.disable_initiator_tag = None
916
_timedelta_to_milliseconds((self
922
# The timeout has passed
925
self.disable_initiator_tag = (gobject.timeout_add
926
(time_to_die, self.disable))
928
# interval - property
929
@dbus_service_property(_interface, signature=u"t",
931
def interval_dbus_property(self, value=None):
932
if value is None: # get
933
return dbus.UInt64(self.interval_milliseconds())
934
self.interval = datetime.timedelta(0, 0, 0, value)
936
self.PropertyChanged(dbus.String(u"interval"),
937
dbus.UInt64(value, variant_level=1))
938
if getattr(self, u"checker_initiator_tag", None) is None:
940
# Reschedule checker run
941
gobject.source_remove(self.checker_initiator_tag)
942
self.checker_initiator_tag = (gobject.timeout_add
943
(value, self.start_checker))
944
self.start_checker() # Start one now, too
947
@dbus_service_property(_interface, signature=u"s",
949
def checker_dbus_property(self, value=None):
950
if value is None: # get
951
return dbus.String(self.checker_command)
952
self.checker_command = value
954
self.PropertyChanged(dbus.String(u"checker"),
955
dbus.String(self.checker_command,
958
# checker_running - property
959
@dbus_service_property(_interface, signature=u"b",
961
def checker_running_dbus_property(self, value=None):
962
if value is None: # get
963
return dbus.Boolean(self.checker is not None)
969
# object_path - property
970
@dbus_service_property(_interface, signature=u"o", access=u"read")
971
def object_path_dbus_property(self):
972
return self.dbus_object_path # is already a dbus.ObjectPath
975
@dbus_service_property(_interface, signature=u"ay",
976
access=u"write", byte_arrays=True)
977
def secret_dbus_property(self, value):
978
self.secret = str(value)
983
class ClientHandler(socketserver.BaseRequestHandler, object):
984
"""A class to handle client connections.
986
Instantiated once for each connection to handle it.
427
987
Note: This will run in its own forked process."""
429
989
def handle(self):
430
990
logger.info(u"TCP connection from: %s",
431
unicode(self.client_address))
432
session = gnutls.connection.ClientSession\
433
(self.request, gnutls.connection.X509Credentials())
435
line = self.request.makefile().readline()
436
logger.debug(u"Protocol version: %r", line)
438
if int(line.strip().split()[0]) > 1:
440
except (ValueError, IndexError, RuntimeError), error:
441
logger.error(u"Unknown protocol version: %s", error)
444
# Note: gnutls.connection.X509Credentials is really a generic
445
# GnuTLS certificate credentials object so long as no X.509
446
# keys are added to it. Therefore, we can use it here despite
447
# using OpenPGP certificates.
449
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
450
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
452
priority = "NORMAL" # Fallback default, since this
454
if self.server.settings["priority"]:
455
priority = self.server.settings["priority"]
456
gnutls.library.functions.gnutls_priority_set_direct\
457
(session._c_object, priority, None);
461
except gnutls.errors.GNUTLSError, error:
462
logger.warning(u"Handshake failed: %s", error)
463
# Do not run session.bye() here: the session is not
464
# established. Just abandon the request.
467
fpr = fingerprint(peer_certificate(session))
468
except (TypeError, gnutls.errors.GNUTLSError), error:
469
logger.warning(u"Bad certificate: %s", error)
472
logger.debug(u"Fingerprint: %s", fpr)
474
for c in self.server.clients:
475
if c.fingerprint == fpr:
479
logger.warning(u"Client not found for fingerprint: %s",
483
# Have to check if client.still_valid(), since it is possible
484
# that the client timed out while establishing the GnuTLS
486
if not client.still_valid():
487
logger.warning(u"Client %(name)s is invalid",
492
while sent_size < len(client.secret):
493
sent = session.send(client.secret[sent_size:])
494
logger.debug(u"Sent: %d, remaining: %d",
495
sent, len(client.secret)
496
- (sent_size + sent))
501
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
502
"""IPv6 TCP server. Accepts 'None' as address and/or port.
991
unicode(self.client_address))
992
logger.debug(u"IPC Pipe FD: %d", self.server.child_pipe[1])
993
# Open IPC pipe to parent process
994
with contextlib.nested(os.fdopen(self.server.child_pipe[1],
996
os.fdopen(self.server.parent_pipe[0],
999
session = (gnutls.connection
1000
.ClientSession(self.request,
1002
.X509Credentials()))
1004
line = self.request.makefile().readline()
1005
logger.debug(u"Protocol version: %r", line)
1007
if int(line.strip().split()[0]) > 1:
1009
except (ValueError, IndexError, RuntimeError), error:
1010
logger.error(u"Unknown protocol version: %s", error)
1013
# Note: gnutls.connection.X509Credentials is really a
1014
# generic GnuTLS certificate credentials object so long as
1015
# no X.509 keys are added to it. Therefore, we can use it
1016
# here despite using OpenPGP certificates.
1018
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1019
# u"+AES-256-CBC", u"+SHA1",
1020
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1022
# Use a fallback default, since this MUST be set.
1023
priority = self.server.gnutls_priority
1024
if priority is None:
1025
priority = u"NORMAL"
1026
(gnutls.library.functions
1027
.gnutls_priority_set_direct(session._c_object,
1032
except gnutls.errors.GNUTLSError, error:
1033
logger.warning(u"Handshake failed: %s", error)
1034
# Do not run session.bye() here: the session is not
1035
# established. Just abandon the request.
1037
logger.debug(u"Handshake succeeded")
1040
fpr = self.fingerprint(self.peer_certificate
1042
except (TypeError, gnutls.errors.GNUTLSError), error:
1043
logger.warning(u"Bad certificate: %s", error)
1045
logger.debug(u"Fingerprint: %s", fpr)
1047
for c in self.server.clients:
1048
if c.fingerprint == fpr:
1052
ipc.write(u"NOTFOUND %s %s\n"
1053
% (fpr, unicode(self.client_address)))
1055
# Have to check if client.enabled, since it is
1056
# possible that the client was disabled since the
1057
# GnuTLS session was established.
1058
ipc.write(u"GETATTR enabled %s\n" % fpr)
1059
enabled = pickle.load(ipc_return)
1061
ipc.write(u"DISABLED %s\n" % client.name)
1063
ipc.write(u"SENDING %s\n" % client.name)
1065
while sent_size < len(client.secret):
1066
sent = session.send(client.secret[sent_size:])
1067
logger.debug(u"Sent: %d, remaining: %d",
1068
sent, len(client.secret)
1069
- (sent_size + sent))
1075
def peer_certificate(session):
1076
"Return the peer's OpenPGP certificate as a bytestring"
1077
# If not an OpenPGP certificate...
1078
if (gnutls.library.functions
1079
.gnutls_certificate_type_get(session._c_object)
1080
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1081
# ...do the normal thing
1082
return session.peer_certificate
1083
list_size = ctypes.c_uint(1)
1084
cert_list = (gnutls.library.functions
1085
.gnutls_certificate_get_peers
1086
(session._c_object, ctypes.byref(list_size)))
1087
if not bool(cert_list) and list_size.value != 0:
1088
raise gnutls.errors.GNUTLSError(u"error getting peer"
1090
if list_size.value == 0:
1093
return ctypes.string_at(cert.data, cert.size)
1096
def fingerprint(openpgp):
1097
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1098
# New GnuTLS "datum" with the OpenPGP public key
1099
datum = (gnutls.library.types
1100
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1103
ctypes.c_uint(len(openpgp))))
1104
# New empty GnuTLS certificate
1105
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1106
(gnutls.library.functions
1107
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1108
# Import the OpenPGP public key into the certificate
1109
(gnutls.library.functions
1110
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1111
gnutls.library.constants
1112
.GNUTLS_OPENPGP_FMT_RAW))
1113
# Verify the self signature in the key
1114
crtverify = ctypes.c_uint()
1115
(gnutls.library.functions
1116
.gnutls_openpgp_crt_verify_self(crt, 0,
1117
ctypes.byref(crtverify)))
1118
if crtverify.value != 0:
1119
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1120
raise (gnutls.errors.CertificateSecurityError
1122
# New buffer for the fingerprint
1123
buf = ctypes.create_string_buffer(20)
1124
buf_len = ctypes.c_size_t()
1125
# Get the fingerprint from the certificate into the buffer
1126
(gnutls.library.functions
1127
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1128
ctypes.byref(buf_len)))
1129
# Deinit the certificate
1130
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1131
# Convert the buffer to a Python bytestring
1132
fpr = ctypes.string_at(buf, buf_len.value)
1133
# Convert the bytestring to hexadecimal notation
1134
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1138
class ForkingMixInWithPipes(socketserver.ForkingMixIn, object):
1139
"""Like socketserver.ForkingMixIn, but also pass a pipe pair."""
1140
def process_request(self, request, client_address):
1141
"""Overrides and wraps the original process_request().
1143
This function creates a new pipe in self.pipe
1145
self.child_pipe = os.pipe() # Child writes here
1146
self.parent_pipe = os.pipe() # Parent writes here
1147
super(ForkingMixInWithPipes,
1148
self).process_request(request, client_address)
1149
# Close unused ends for parent
1150
os.close(self.parent_pipe[0]) # close read end
1151
os.close(self.child_pipe[1]) # close write end
1152
self.add_pipe_fds(self.child_pipe[0], self.parent_pipe[1])
1153
def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
1154
"""Dummy function; override as necessary"""
1155
os.close(child_pipe_fd)
1156
os.close(parent_pipe_fd)
1159
class IPv6_TCPServer(ForkingMixInWithPipes,
1160
socketserver.TCPServer, object):
1161
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
504
settings: Server settings
505
clients: Set() of Client objects
1164
enabled: Boolean; whether this server is activated yet
1165
interface: None or a network interface name (string)
1166
use_ipv6: Boolean; to use IPv6 or not
507
address_family = socket.AF_INET6
508
def __init__(self, *args, **kwargs):
509
if "settings" in kwargs:
510
self.settings = kwargs["settings"]
511
del kwargs["settings"]
512
if "clients" in kwargs:
513
self.clients = kwargs["clients"]
514
del kwargs["clients"]
515
return super(type(self), self).__init__(*args, **kwargs)
1168
def __init__(self, server_address, RequestHandlerClass,
1169
interface=None, use_ipv6=True):
1170
self.interface = interface
1172
self.address_family = socket.AF_INET6
1173
socketserver.TCPServer.__init__(self, server_address,
1174
RequestHandlerClass)
516
1175
def server_bind(self):
517
1176
"""This overrides the normal server_bind() function
518
1177
to bind to an interface if one was specified, and also NOT to
519
1178
bind to an address or port if they were not specified."""
520
if self.settings["interface"]:
521
# 25 is from /usr/include/asm-i486/socket.h
522
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
524
self.socket.setsockopt(socket.SOL_SOCKET,
526
self.settings["interface"])
527
except socket.error, error:
528
if error[0] == errno.EPERM:
529
logger.error(u"No permission to"
530
u" bind to interface %s",
531
self.settings["interface"])
1179
if self.interface is not None:
1180
if SO_BINDTODEVICE is None:
1181
logger.error(u"SO_BINDTODEVICE does not exist;"
1182
u" cannot bind to interface %s",
1186
self.socket.setsockopt(socket.SOL_SOCKET,
1190
except socket.error, error:
1191
if error[0] == errno.EPERM:
1192
logger.error(u"No permission to"
1193
u" bind to interface %s",
1195
elif error[0] == errno.ENOPROTOOPT:
1196
logger.error(u"SO_BINDTODEVICE not available;"
1197
u" cannot bind to interface %s",
534
1201
# Only bind(2) the socket if we really need to.
535
1202
if self.server_address[0] or self.server_address[1]:
536
1203
if not self.server_address[0]:
538
self.server_address = (in6addr_any,
1204
if self.address_family == socket.AF_INET6:
1205
any_address = u"::" # in6addr_any
1207
any_address = socket.INADDR_ANY
1208
self.server_address = (any_address,
539
1209
self.server_address[1])
540
1210
elif not self.server_address[1]:
541
1211
self.server_address = (self.server_address[0],
543
# if self.settings["interface"]:
1213
# if self.interface:
544
1214
# self.server_address = (self.server_address[0],
547
1217
# if_nametoindex
550
return super(type(self), self).server_bind()
1219
return socketserver.TCPServer.server_bind(self)
1222
class MandosServer(IPv6_TCPServer):
1226
clients: set of Client objects
1227
gnutls_priority GnuTLS priority string
1228
use_dbus: Boolean; to emit D-Bus signals or not
1230
Assumes a gobject.MainLoop event loop.
1232
def __init__(self, server_address, RequestHandlerClass,
1233
interface=None, use_ipv6=True, clients=None,
1234
gnutls_priority=None, use_dbus=True):
1235
self.enabled = False
1236
self.clients = clients
1237
if self.clients is None:
1238
self.clients = set()
1239
self.use_dbus = use_dbus
1240
self.gnutls_priority = gnutls_priority
1241
IPv6_TCPServer.__init__(self, server_address,
1242
RequestHandlerClass,
1243
interface = interface,
1244
use_ipv6 = use_ipv6)
1245
def server_activate(self):
1247
return socketserver.TCPServer.server_activate(self)
1250
def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
1251
# Call "handle_ipc" for both data and EOF events
1252
gobject.io_add_watch(child_pipe_fd,
1253
gobject.IO_IN | gobject.IO_HUP,
1254
functools.partial(self.handle_ipc,
1257
def handle_ipc(self, source, condition, reply_fd=None,
1260
gobject.IO_IN: u"IN", # There is data to read.
1261
gobject.IO_OUT: u"OUT", # Data can be written (without
1263
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1264
gobject.IO_ERR: u"ERR", # Error condition.
1265
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1266
# broken, usually for pipes and
1269
conditions_string = ' | '.join(name
1271
condition_names.iteritems()
1272
if cond & condition)
1273
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1276
# Turn the pipe file descriptors into Python file objects
1277
if source not in file_objects:
1278
file_objects[source] = os.fdopen(source, u"r", 1)
1279
if reply_fd not in file_objects:
1280
file_objects[reply_fd] = os.fdopen(reply_fd, u"w", 0)
1282
# Read a line from the file object
1283
cmdline = file_objects[source].readline()
1284
if not cmdline: # Empty line means end of file
1285
# close the IPC pipes
1286
file_objects[source].close()
1287
del file_objects[source]
1288
file_objects[reply_fd].close()
1289
del file_objects[reply_fd]
1291
# Stop calling this function
1294
logger.debug(u"IPC command: %r", cmdline)
1296
# Parse and act on command
1297
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1299
if cmd == u"NOTFOUND":
1300
fpr, address = args.split(None, 1)
1301
logger.warning(u"Client not found for fingerprint: %s, ad"
1302
u"dress: %s", fpr, address)
1305
mandos_dbus_service.ClientNotFound(fpr, address)
1306
elif cmd == u"DISABLED":
1307
for client in self.clients:
1308
if client.name == args:
1309
logger.warning(u"Client %s is disabled", args)
1315
logger.error(u"Unknown client %s is disabled", args)
1316
elif cmd == u"SENDING":
1317
for client in self.clients:
1318
if client.name == args:
1319
logger.info(u"Sending secret to %s", client.name)
1326
logger.error(u"Sending secret to unknown client %s",
1328
elif cmd == u"GETATTR":
1329
attr_name, fpr = args.split(None, 1)
1330
for client in self.clients:
1331
if client.fingerprint == fpr:
1332
attr_value = getattr(client, attr_name, None)
1333
logger.debug("IPC reply: %r", attr_value)
1334
pickle.dump(attr_value, file_objects[reply_fd])
1337
logger.error(u"Client %s on address %s requesting "
1338
u"attribute %s not found", fpr, address,
1340
pickle.dump(None, file_objects[reply_fd])
1342
logger.error(u"Unknown IPC command: %r", cmdline)
1344
# Keep calling this function
553
1348
def string_to_delta(interval):
554
1349
"""Parse a string and return a datetime.timedelta
556
>>> string_to_delta('7d')
1351
>>> string_to_delta(u'7d')
557
1352
datetime.timedelta(7)
558
>>> string_to_delta('60s')
1353
>>> string_to_delta(u'60s')
559
1354
datetime.timedelta(0, 60)
560
>>> string_to_delta('60m')
1355
>>> string_to_delta(u'60m')
561
1356
datetime.timedelta(0, 3600)
562
>>> string_to_delta('24h')
1357
>>> string_to_delta(u'24h')
563
1358
datetime.timedelta(1)
564
1359
>>> string_to_delta(u'1w')
565
1360
datetime.timedelta(7)
1361
>>> string_to_delta(u'5m 30s')
1362
datetime.timedelta(0, 330)
568
suffix=unicode(interval[-1])
569
value=int(interval[:-1])
571
delta = datetime.timedelta(value)
573
delta = datetime.timedelta(0, value)
575
delta = datetime.timedelta(0, 0, 0, 0, value)
577
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
579
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
582
except (ValueError, IndexError):
587
def server_state_changed(state):
588
"""Derived from the Avahi example code"""
589
if state == avahi.SERVER_COLLISION:
590
logger.error(u"Server name collision")
592
elif state == avahi.SERVER_RUNNING:
596
def entry_group_state_changed(state, error):
597
"""Derived from the Avahi example code"""
598
logger.debug(u"state change: %i", state)
600
if state == avahi.ENTRY_GROUP_ESTABLISHED:
601
logger.debug(u"Service established.")
602
elif state == avahi.ENTRY_GROUP_COLLISION:
603
logger.warning(u"Service name collision.")
605
elif state == avahi.ENTRY_GROUP_FAILURE:
606
logger.critical(u"Error in group state changed %s",
608
raise AvahiGroupError("State changed: %s", str(error))
1364
timevalue = datetime.timedelta(0)
1365
for s in interval.split():
1367
suffix = unicode(s[-1])
1370
delta = datetime.timedelta(value)
1371
elif suffix == u"s":
1372
delta = datetime.timedelta(0, value)
1373
elif suffix == u"m":
1374
delta = datetime.timedelta(0, 0, 0, 0, value)
1375
elif suffix == u"h":
1376
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1377
elif suffix == u"w":
1378
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1380
raise ValueError(u"Unknown suffix %r" % suffix)
1381
except (ValueError, IndexError), e:
1382
raise ValueError(e.message)
610
1387
def if_nametoindex(interface):
611
"""Call the C function if_nametoindex(), or equivalent"""
1388
"""Call the C function if_nametoindex(), or equivalent
1390
Note: This function cannot accept a unicode string."""
612
1391
global if_nametoindex
614
if "ctypes.util" not in sys.modules:
616
if_nametoindex = ctypes.cdll.LoadLibrary\
617
(ctypes.util.find_library("c")).if_nametoindex
1393
if_nametoindex = (ctypes.cdll.LoadLibrary
1394
(ctypes.util.find_library(u"c"))
618
1396
except (OSError, AttributeError):
619
if "struct" not in sys.modules:
621
if "fcntl" not in sys.modules:
1397
logger.warning(u"Doing if_nametoindex the hard way")
623
1398
def if_nametoindex(interface):
624
1399
"Get an interface index the hard way, i.e. using fcntl()"
625
1400
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
627
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
628
struct.pack("16s16x", interface))
630
interface_index = struct.unpack("I", ifreq[16:20])[0]
1401
with contextlib.closing(socket.socket()) as s:
1402
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1403
struct.pack(str(u"16s16x"),
1405
interface_index = struct.unpack(str(u"I"),
631
1407
return interface_index
632
1408
return if_nametoindex(interface)
635
1411
def daemon(nochdir = False, noclose = False):
636
1412
"""See daemon(3). Standard BSD Unix function.
637
1414
This should really exist as os.daemon, but it doesn't (yet)."""
689
1473
# Default values for config file for server-global settings
690
server_defaults = { "interface": "",
695
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
696
"servicename": "Mandos",
1474
server_defaults = { u"interface": u"",
1479
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1480
u"servicename": u"Mandos",
1481
u"use_dbus": u"True",
1482
u"use_ipv6": u"True",
699
1485
# Parse config file for server-global settings
700
server_config = ConfigParser.SafeConfigParser(server_defaults)
1486
server_config = configparser.SafeConfigParser(server_defaults)
701
1487
del server_defaults
702
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1488
server_config.read(os.path.join(options.configdir,
703
1490
# Convert the SafeConfigParser object to a dict
704
1491
server_settings = server_config.defaults()
705
# Use getboolean on the boolean config option
706
server_settings["debug"] = server_config.getboolean\
1492
# Use the appropriate methods on the non-string config options
1493
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1494
server_settings[option] = server_config.getboolean(u"DEFAULT",
1496
if server_settings["port"]:
1497
server_settings["port"] = server_config.getint(u"DEFAULT",
708
1499
del server_config
710
1501
# Override the settings from the config file with command line
711
1502
# options, if set.
712
for option in ("interface", "address", "port", "debug",
713
"priority", "servicename", "configdir"):
1503
for option in (u"interface", u"address", u"port", u"debug",
1504
u"priority", u"servicename", u"configdir",
1505
u"use_dbus", u"use_ipv6"):
714
1506
value = getattr(options, option)
715
1507
if value is not None:
716
1508
server_settings[option] = value
1510
# Force all strings to be unicode
1511
for option in server_settings.keys():
1512
if type(server_settings[option]) is str:
1513
server_settings[option] = unicode(server_settings[option])
718
1514
# Now we have our good server settings in "server_settings"
720
debug = server_settings["debug"]
1516
##################################################################
1519
debug = server_settings[u"debug"]
1520
use_dbus = server_settings[u"use_dbus"]
1521
use_ipv6 = server_settings[u"use_ipv6"]
723
1524
syslogger.setLevel(logging.WARNING)
724
1525
console.setLevel(logging.WARNING)
726
if server_settings["servicename"] != "Mandos":
727
syslogger.setFormatter(logging.Formatter\
728
('Mandos (%s): %%(levelname)s:'
730
% server_settings["servicename"]))
1527
if server_settings[u"servicename"] != u"Mandos":
1528
syslogger.setFormatter(logging.Formatter
1529
(u'Mandos (%s) [%%(process)d]:'
1530
u' %%(levelname)s: %%(message)s'
1531
% server_settings[u"servicename"]))
732
1533
# Parse config file with clients
733
client_defaults = { "timeout": "1h",
735
"checker": "fping -q -- %%(host)s",
1534
client_defaults = { u"timeout": u"1h",
1536
u"checker": u"fping -q -- %%(host)s",
737
client_config = ConfigParser.SafeConfigParser(client_defaults)
738
client_config.read(os.path.join(server_settings["configdir"],
742
service = AvahiService(name = server_settings["servicename"],
743
type = "_mandos._tcp", );
744
if server_settings["interface"]:
745
service.interface = if_nametoindex(server_settings["interface"])
1539
client_config = configparser.SafeConfigParser(client_defaults)
1540
client_config.read(os.path.join(server_settings[u"configdir"],
1543
global mandos_dbus_service
1544
mandos_dbus_service = None
1546
tcp_server = MandosServer((server_settings[u"address"],
1547
server_settings[u"port"]),
1549
interface=server_settings[u"interface"],
1552
server_settings[u"priority"],
1554
pidfilename = u"/var/run/mandos.pid"
1556
pidfile = open(pidfilename, u"w")
1558
logger.error(u"Could not open file %r", pidfilename)
1561
uid = pwd.getpwnam(u"_mandos").pw_uid
1562
gid = pwd.getpwnam(u"_mandos").pw_gid
1565
uid = pwd.getpwnam(u"mandos").pw_uid
1566
gid = pwd.getpwnam(u"mandos").pw_gid
1569
uid = pwd.getpwnam(u"nobody").pw_uid
1570
gid = pwd.getpwnam(u"nobody").pw_gid
1577
except OSError, error:
1578
if error[0] != errno.EPERM:
1581
# Enable all possible GnuTLS debugging
1583
# "Use a log level over 10 to enable all debugging options."
1585
gnutls.library.functions.gnutls_global_set_log_level(11)
1587
@gnutls.library.types.gnutls_log_func
1588
def debug_gnutls(level, string):
1589
logger.debug(u"GnuTLS: %s", string[:-1])
1591
(gnutls.library.functions
1592
.gnutls_global_set_log_function(debug_gnutls))
747
1594
global main_loop
750
1595
# From the Avahi example code
751
1596
DBusGMainLoop(set_as_default=True )
752
1597
main_loop = gobject.MainLoop()
753
1598
bus = dbus.SystemBus()
754
server = dbus.Interface(
755
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
756
avahi.DBUS_INTERFACE_SERVER )
757
1599
# End of Avahi example code
760
def remove_from_clients(client):
761
clients.remove(client)
763
logger.critical(u"No clients left, exiting")
766
clients.update(Set(Client(name = section,
767
stop_hook = remove_from_clients,
769
= dict(client_config.items(section)))
770
for section in client_config.sections()))
772
logger.critical(u"No clients defined")
1602
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1603
bus, do_not_queue=True)
1604
except dbus.exceptions.NameExistsException, e:
1605
logger.error(unicode(e) + u", disabling D-Bus")
1607
server_settings[u"use_dbus"] = False
1608
tcp_server.use_dbus = False
1609
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1610
service = AvahiService(name = server_settings[u"servicename"],
1611
servicetype = u"_mandos._tcp",
1612
protocol = protocol, bus = bus)
1613
if server_settings["interface"]:
1614
service.interface = (if_nametoindex
1615
(str(server_settings[u"interface"])))
1617
client_class = Client
1619
client_class = functools.partial(ClientDBus, bus = bus)
1620
tcp_server.clients.update(set(
1621
client_class(name = section,
1622
config= dict(client_config.items(section)))
1623
for section in client_config.sections()))
1624
if not tcp_server.clients:
1625
logger.warning(u"No clients defined")
1628
# Redirect stdin so all checkers get /dev/null
1629
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1630
os.dup2(null, sys.stdin.fileno())
1634
# No console logging
776
1635
logger.removeHandler(console)
1636
# Close all input and output, do double fork, etc.
779
pidfilename = "/var/run/mandos/mandos.pid"
782
pidfile = open(pidfilename, "w")
783
pidfile.write(str(pid) + "\n")
1642
pidfile.write(str(pid) + "\n")
787
logger.error(u"Could not write %s file with PID %d",
788
pidfilename, os.getpid())
1645
logger.error(u"Could not write to file %r with PID %d",
1648
# "pidfile" was never created
1653
signal.signal(signal.SIGINT, signal.SIG_IGN)
1654
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1655
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1658
class MandosDBusService(dbus.service.Object):
1659
"""A D-Bus proxy object"""
1661
dbus.service.Object.__init__(self, bus, u"/")
1662
_interface = u"se.bsnet.fukt.Mandos"
1664
@dbus.service.signal(_interface, signature=u"o")
1665
def ClientAdded(self, objpath):
1669
@dbus.service.signal(_interface, signature=u"ss")
1670
def ClientNotFound(self, fingerprint, address):
1674
@dbus.service.signal(_interface, signature=u"os")
1675
def ClientRemoved(self, objpath, name):
1679
@dbus.service.method(_interface, out_signature=u"ao")
1680
def GetAllClients(self):
1682
return dbus.Array(c.dbus_object_path
1683
for c in tcp_server.clients)
1685
@dbus.service.method(_interface,
1686
out_signature=u"a{oa{sv}}")
1687
def GetAllClientsWithProperties(self):
1689
return dbus.Dictionary(
1690
((c.dbus_object_path, c.GetAll(u""))
1691
for c in tcp_server.clients),
1692
signature=u"oa{sv}")
1694
@dbus.service.method(_interface, in_signature=u"o")
1695
def RemoveClient(self, object_path):
1697
for c in tcp_server.clients:
1698
if c.dbus_object_path == object_path:
1699
tcp_server.clients.remove(c)
1700
c.remove_from_connection()
1701
# Don't signal anything except ClientRemoved
1702
c.disable(quiet=True)
1704
self.ClientRemoved(object_path, c.name)
1706
raise KeyError(object_path)
1710
mandos_dbus_service = MandosDBusService()
791
1713
"Cleanup function; run on exit"
793
# From the Avahi example code
794
if not group is None:
797
# End of Avahi example code
800
client = clients.pop()
801
client.stop_hook = None
1716
while tcp_server.clients:
1717
client = tcp_server.clients.pop()
1719
client.remove_from_connection()
1720
client.disable_hook = None
1721
# Don't signal anything except ClientRemoved
1722
client.disable(quiet=True)
1725
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
804
1728
atexit.register(cleanup)
807
signal.signal(signal.SIGINT, signal.SIG_IGN)
808
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
809
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
811
for client in clients:
814
tcp_server = IPv6_TCPServer((server_settings["address"],
815
server_settings["port"]),
817
settings=server_settings,
1730
for client in tcp_server.clients:
1733
mandos_dbus_service.ClientAdded(client.dbus_object_path)
1737
tcp_server.server_activate()
819
1739
# Find out what port we got
820
1740
service.port = tcp_server.socket.getsockname()[1]
821
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
822
u" scope_id %d" % tcp_server.socket.getsockname())
1742
logger.info(u"Now listening on address %r, port %d,"
1743
" flowinfo %d, scope_id %d"
1744
% tcp_server.socket.getsockname())
1746
logger.info(u"Now listening on address %r, port %d"
1747
% tcp_server.socket.getsockname())
824
1749
#service.interface = tcp_server.socket.getsockname()[3]
827
1752
# From the Avahi example code
828
server.connect_to_signal("StateChanged", server_state_changed)
830
server_state_changed(server.GetState())
831
1755
except dbus.exceptions.DBusException, error:
832
1756
logger.critical(u"DBusException: %s", error)
834
1759
# End of Avahi example code
836
1761
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
837
1762
lambda *args, **kwargs:
838
tcp_server.handle_request\
839
(*args[2:], **kwargs) or True)
1763
(tcp_server.handle_request
1764
(*args[2:], **kwargs) or True))
841
1766
logger.debug(u"Starting main loop")
842
main_loop_started = True
844
1768
except AvahiError, error:
845
logger.critical(u"AvahiError: %s" + unicode(error))
1769
logger.critical(u"AvahiError: %s", error)
847
1772
except KeyboardInterrupt:
1775
logger.debug(u"Server received KeyboardInterrupt")
1776
logger.debug(u"Server exiting")
1777
# Must run before the D-Bus bus name gets deregistered
851
1780
if __name__ == '__main__':