291
139
self.rename_count = 0
292
140
self.max_renames = max_renames
293
141
self.protocol = protocol
294
self.group = None # our entry group
297
self.entry_group_state_changed_match = None
299
def rename(self, remove=True):
300
143
"""Derived from the Avahi example code"""
301
144
if self.rename_count >= self.max_renames:
302
logger.critical("No suitable Zeroconf service name found"
303
" after %i retries, exiting.",
145
logger.critical(u"No suitable Zeroconf service name found"
146
u" after %i retries, exiting.",
304
147
self.rename_count)
305
raise AvahiServiceError("Too many renames")
307
self.server.GetAlternativeServiceName(self.name))
148
raise AvahiServiceError(u"Too many renames")
149
self.name = server.GetAlternativeServiceName(self.name)
150
logger.info(u"Changing Zeroconf service name to %r ...",
152
syslogger.setFormatter(logging.Formatter
153
(u'Mandos (%s) [%%(process)d]:'
154
u' %%(levelname)s: %%(message)s'
308
158
self.rename_count += 1
309
logger.info("Changing Zeroconf service name to %r ...",
315
except dbus.exceptions.DBusException as error:
316
if (error.get_dbus_name()
317
== "org.freedesktop.Avahi.CollisionError"):
318
logger.info("Local Zeroconf service name collision.")
319
return self.rename(remove=False)
321
logger.critical("D-Bus Exception", exc_info=error)
325
159
def remove(self):
326
160
"""Derived from the Avahi example code"""
327
if self.entry_group_state_changed_match is not None:
328
self.entry_group_state_changed_match.remove()
329
self.entry_group_state_changed_match = None
330
if self.group is not None:
161
if group is not None:
334
164
"""Derived from the Avahi example code"""
336
if self.group is None:
337
self.group = dbus.Interface(
338
self.bus.get_object(avahi.DBUS_NAME,
339
self.server.EntryGroupNew()),
340
avahi.DBUS_INTERFACE_ENTRY_GROUP)
341
self.entry_group_state_changed_match = (
342
self.group.connect_to_signal(
343
'StateChanged', self.entry_group_state_changed))
344
logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
345
self.name, self.type)
346
self.group.AddService(
349
dbus.UInt32(0), # flags
350
self.name, self.type,
351
self.domain, self.host,
352
dbus.UInt16(self.port),
353
avahi.string_array_to_txt_array(self.TXT))
356
def entry_group_state_changed(self, state, error):
357
"""Derived from the Avahi example code"""
358
logger.debug("Avahi entry group state change: %i", state)
360
if state == avahi.ENTRY_GROUP_ESTABLISHED:
361
logger.debug("Zeroconf service established.")
362
elif state == avahi.ENTRY_GROUP_COLLISION:
363
logger.info("Zeroconf service name collision.")
365
elif state == avahi.ENTRY_GROUP_FAILURE:
366
logger.critical("Avahi: Error in group state changed %s",
368
raise AvahiGroupError("State changed: {!s}".format(error))
371
"""Derived from the Avahi example code"""
372
if self.group is not None:
375
except (dbus.exceptions.UnknownMethodException,
376
dbus.exceptions.DBusException):
381
def server_state_changed(self, state, error=None):
382
"""Derived from the Avahi example code"""
383
logger.debug("Avahi server state change: %i", state)
385
avahi.SERVER_INVALID: "Zeroconf server invalid",
386
avahi.SERVER_REGISTERING: None,
387
avahi.SERVER_COLLISION: "Zeroconf server name collision",
388
avahi.SERVER_FAILURE: "Zeroconf server failure",
390
if state in bad_states:
391
if bad_states[state] is not None:
393
logger.error(bad_states[state])
395
logger.error(bad_states[state] + ": %r", error)
397
elif state == avahi.SERVER_RUNNING:
401
logger.debug("Unknown state: %r", state)
403
logger.debug("Unknown state: %r: %r", state, error)
406
"""Derived from the Avahi example code"""
407
if self.server is None:
408
self.server = dbus.Interface(
409
self.bus.get_object(avahi.DBUS_NAME,
410
avahi.DBUS_PATH_SERVER,
411
follow_name_owner_changes=True),
412
avahi.DBUS_INTERFACE_SERVER)
413
self.server.connect_to_signal("StateChanged",
414
self.server_state_changed)
415
self.server_state_changed(self.server.GetState())
418
class AvahiServiceToSyslog(AvahiService):
419
def rename(self, *args, **kwargs):
420
"""Add the new name to the syslog messages"""
421
ret = AvahiService.rename(self, *args, **kwargs)
422
syslogger.setFormatter(logging.Formatter(
423
'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
167
group = dbus.Interface(bus.get_object
169
server.EntryGroupNew()),
170
avahi.DBUS_INTERFACE_ENTRY_GROUP)
171
group.connect_to_signal('StateChanged',
172
entry_group_state_changed)
173
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
174
service.name, service.type)
176
self.interface, # interface
177
self.protocol, # protocol
178
dbus.UInt32(0), # flags
179
self.name, self.type,
180
self.domain, self.host,
181
dbus.UInt16(self.port),
182
avahi.string_array_to_txt_array(self.TXT))
185
# From the Avahi example code:
186
group = None # our entry group
187
# End of Avahi example code
190
def _datetime_to_dbus(dt, variant_level=0):
191
"""Convert a UTC datetime.datetime() to a D-Bus type."""
192
return dbus.String(dt.isoformat(), variant_level=variant_level)
428
195
class Client(object):
429
196
"""A representation of a client host served by this server.
432
approved: bool(); 'None' if not yet approved/disapproved
433
approval_delay: datetime.timedelta(); Time to wait for approval
434
approval_duration: datetime.timedelta(); Duration of one approval
199
name: string; from the config file, used in log messages and
201
fingerprint: string (40 or 32 hexadecimal digits); used to
202
uniquely identify the client
203
secret: bytestring; sent verbatim (over TLS) to client
204
host: string; available for use by the checker command
205
created: datetime.datetime(); (UTC) object creation
206
last_enabled: datetime.datetime(); (UTC)
208
last_checked_ok: datetime.datetime(); (UTC) or None
209
timeout: datetime.timedelta(); How long from last_checked_ok
210
until this client is invalid
211
interval: datetime.timedelta(); How often to start a new checker
212
disable_hook: If set, called by disable() as disable_hook(self)
435
213
checker: subprocess.Popen(); a running checker process used
436
214
to see if the client lives.
437
215
'None' if no process is running.
438
checker_callback_tag: a gobject event source tag, or None
439
checker_command: string; External command which is run to check
440
if client lives. %() expansions are done at
216
checker_initiator_tag: a gobject event source tag, or None
217
disable_initiator_tag: - '' -
218
checker_callback_tag: - '' -
219
checker_command: string; External command which is run to check if
220
client lives. %() expansions are done at
441
221
runtime with vars(self) as dict, so that for
442
222
instance %(name)s can be used in the command.
443
checker_initiator_tag: a gobject event source tag, or None
444
created: datetime.datetime(); (UTC) object creation
445
client_structure: Object describing what attributes a client has
446
and is used for storing the client at exit
447
223
current_checker_command: string; current running checker_command
448
disable_initiator_tag: a gobject event source tag, or None
450
fingerprint: string (40 or 32 hexadecimal digits); used to
451
uniquely identify the client
452
host: string; available for use by the checker command
453
interval: datetime.timedelta(); How often to start a new checker
454
last_approval_request: datetime.datetime(); (UTC) or None
455
last_checked_ok: datetime.datetime(); (UTC) or None
456
last_checker_status: integer between 0 and 255 reflecting exit
457
status of last checker. -1 reflects crashed
458
checker, -2 means no checker completed yet.
459
last_enabled: datetime.datetime(); (UTC) or None
460
name: string; from the config file, used in log messages and
462
secret: bytestring; sent verbatim (over TLS) to client
463
timeout: datetime.timedelta(); How long from last_checked_ok
464
until this client is disabled
465
extended_timeout: extra long timeout when secret has been sent
466
runtime_expansions: Allowed attributes for runtime expansion.
467
expires: datetime.datetime(); time (UTC) when a client will be
469
server_settings: The server_settings dict from main()
472
runtime_expansions = ("approval_delay", "approval_duration",
473
"created", "enabled", "expires",
474
"fingerprint", "host", "interval",
475
"last_approval_request", "last_checked_ok",
476
"last_enabled", "name", "timeout")
479
"extended_timeout": "PT15M",
481
"checker": "fping -q -- %%(host)s",
483
"approval_delay": "PT0S",
484
"approval_duration": "PT1S",
485
"approved_by_default": "True",
490
def config_parser(config):
491
"""Construct a new dict of client settings of this form:
492
{ client_name: {setting_name: value, ...}, ...}
493
with exceptions for any special settings as defined above.
494
NOTE: Must be a pure function. Must return the same result
495
value given the same arguments.
498
for client_name in config.sections():
499
section = dict(config.items(client_name))
500
client = settings[client_name] = {}
502
client["host"] = section["host"]
503
# Reformat values from string types to Python types
504
client["approved_by_default"] = config.getboolean(
505
client_name, "approved_by_default")
506
client["enabled"] = config.getboolean(client_name,
509
# Uppercase and remove spaces from fingerprint for later
510
# comparison purposes with return value from the
511
# fingerprint() function
512
client["fingerprint"] = (section["fingerprint"].upper()
514
if "secret" in section:
515
client["secret"] = section["secret"].decode("base64")
516
elif "secfile" in section:
517
with open(os.path.expanduser(os.path.expandvars
518
(section["secfile"])),
520
client["secret"] = secfile.read()
522
raise TypeError("No secret or secfile for section {}"
524
client["timeout"] = string_to_delta(section["timeout"])
525
client["extended_timeout"] = string_to_delta(
526
section["extended_timeout"])
527
client["interval"] = string_to_delta(section["interval"])
528
client["approval_delay"] = string_to_delta(
529
section["approval_delay"])
530
client["approval_duration"] = string_to_delta(
531
section["approval_duration"])
532
client["checker_command"] = section["checker"]
533
client["last_approval_request"] = None
534
client["last_checked_ok"] = None
535
client["last_checker_status"] = -2
539
def __init__(self, settings, name = None, server_settings=None):
227
def _datetime_to_milliseconds(dt):
228
"Convert a datetime.datetime() to milliseconds"
229
return ((dt.days * 24 * 60 * 60 * 1000)
230
+ (dt.seconds * 1000)
231
+ (dt.microseconds // 1000))
233
def timeout_milliseconds(self):
234
"Return the 'timeout' attribute in milliseconds"
235
return self._datetime_to_milliseconds(self.timeout)
237
def interval_milliseconds(self):
238
"Return the 'interval' attribute in milliseconds"
239
return self._datetime_to_milliseconds(self.interval)
241
def __init__(self, name = None, disable_hook=None, config=None):
242
"""Note: the 'checker' key in 'config' sets the
243
'checker_command' attribute and *not* the 'checker'
541
if server_settings is None:
543
self.server_settings = server_settings
544
# adding all client settings
545
for setting, value in settings.items():
546
setattr(self, setting, value)
549
if not hasattr(self, "last_enabled"):
550
self.last_enabled = datetime.datetime.utcnow()
551
if not hasattr(self, "expires"):
552
self.expires = (datetime.datetime.utcnow()
248
logger.debug(u"Creating client %r", self.name)
249
# Uppercase and remove spaces from fingerprint for later
250
# comparison purposes with return value from the fingerprint()
252
self.fingerprint = (config[u"fingerprint"].upper()
254
logger.debug(u" Fingerprint: %s", self.fingerprint)
255
if u"secret" in config:
256
self.secret = config[u"secret"].decode(u"base64")
257
elif u"secfile" in config:
258
with closing(open(os.path.expanduser
260
(config[u"secfile"])))) as secfile:
261
self.secret = secfile.read()
555
self.last_enabled = None
558
logger.debug("Creating client %r", self.name)
559
logger.debug(" Fingerprint: %s", self.fingerprint)
560
self.created = settings.get("created",
561
datetime.datetime.utcnow())
563
# attributes specific for this server instance
263
raise TypeError(u"No secret or secfile for client %s"
265
self.host = config.get(u"host", u"")
266
self.created = datetime.datetime.utcnow()
268
self.last_enabled = None
269
self.last_checked_ok = None
270
self.timeout = string_to_delta(config[u"timeout"])
271
self.interval = string_to_delta(config[u"interval"])
272
self.disable_hook = disable_hook
564
273
self.checker = None
565
274
self.checker_initiator_tag = None
566
275
self.disable_initiator_tag = None
567
276
self.checker_callback_tag = None
277
self.checker_command = config[u"checker"]
568
278
self.current_checker_command = None
570
self.approvals_pending = 0
571
self.changedstate = multiprocessing_manager.Condition(
572
multiprocessing_manager.Lock())
573
self.client_structure = [attr
574
for attr in self.__dict__.iterkeys()
575
if not attr.startswith("_")]
576
self.client_structure.append("client_structure")
578
for name, t in inspect.getmembers(
579
type(self), lambda obj: isinstance(obj, property)):
580
if not name.startswith("_"):
581
self.client_structure.append(name)
583
# Send notice to process children that client state has changed
584
def send_changedstate(self):
585
with self.changedstate:
586
self.changedstate.notify_all()
279
self.last_connect = None
588
281
def enable(self):
589
282
"""Start this client's checker and timeout hooks"""
590
if getattr(self, "enabled", False):
593
self.expires = datetime.datetime.utcnow() + self.timeout
595
283
self.last_enabled = datetime.datetime.utcnow()
597
self.send_changedstate()
284
# Schedule a new checker to be started an 'interval' from now,
285
# and every interval from then on.
286
self.checker_initiator_tag = (gobject.timeout_add
287
(self.interval_milliseconds(),
289
# Also start a new checker *right now*.
291
# Schedule a disable() when 'timeout' has passed
292
self.disable_initiator_tag = (gobject.timeout_add
293
(self.timeout_milliseconds(),
599
def disable(self, quiet=True):
600
298
"""Disable this client."""
601
299
if not getattr(self, "enabled", False):
604
logger.info("Disabling client %s", self.name)
605
if getattr(self, "disable_initiator_tag", None) is not None:
301
logger.info(u"Disabling client %s", self.name)
302
if getattr(self, u"disable_initiator_tag", False):
606
303
gobject.source_remove(self.disable_initiator_tag)
607
304
self.disable_initiator_tag = None
609
if getattr(self, "checker_initiator_tag", None) is not None:
305
if getattr(self, u"checker_initiator_tag", False):
610
306
gobject.source_remove(self.checker_initiator_tag)
611
307
self.checker_initiator_tag = None
612
308
self.stop_checker()
309
if self.disable_hook:
310
self.disable_hook(self)
613
311
self.enabled = False
615
self.send_changedstate()
616
312
# Do not run this again if called by a gobject.timeout_add
619
315
def __del__(self):
316
self.disable_hook = None
622
def init_checker(self):
623
# Schedule a new checker to be started an 'interval' from now,
624
# and every interval from then on.
625
if self.checker_initiator_tag is not None:
626
gobject.source_remove(self.checker_initiator_tag)
627
self.checker_initiator_tag = gobject.timeout_add(
628
int(self.interval.total_seconds() * 1000),
630
# Schedule a disable() when 'timeout' has passed
631
if self.disable_initiator_tag is not None:
632
gobject.source_remove(self.disable_initiator_tag)
633
self.disable_initiator_tag = gobject.timeout_add(
634
int(self.timeout.total_seconds() * 1000), self.disable)
635
# Also start a new checker *right now*.
638
319
def checker_callback(self, pid, condition, command):
639
320
"""The checker has completed, so take appropriate actions."""
640
321
self.checker_callback_tag = None
641
322
self.checker = None
642
323
if os.WIFEXITED(condition):
643
self.last_checker_status = os.WEXITSTATUS(condition)
644
if self.last_checker_status == 0:
645
logger.info("Checker for %(name)s succeeded",
324
exitstatus = os.WEXITSTATUS(condition)
326
logger.info(u"Checker for %(name)s succeeded",
647
328
self.checked_ok()
649
logger.info("Checker for %(name)s failed", vars(self))
330
logger.info(u"Checker for %(name)s failed",
651
self.last_checker_status = -1
652
logger.warning("Checker for %(name)s crashed?",
333
logger.warning(u"Checker for %(name)s crashed?",
655
336
def checked_ok(self):
656
"""Assert that the client has been seen, alive and well."""
337
"""Bump up the timeout for this client.
339
This should only be called when the client has been seen,
657
342
self.last_checked_ok = datetime.datetime.utcnow()
658
self.last_checker_status = 0
661
def bump_timeout(self, timeout=None):
662
"""Bump up the timeout for this client."""
664
timeout = self.timeout
665
if self.disable_initiator_tag is not None:
666
gobject.source_remove(self.disable_initiator_tag)
667
self.disable_initiator_tag = None
668
if getattr(self, "enabled", False):
669
self.disable_initiator_tag = gobject.timeout_add(
670
int(timeout.total_seconds() * 1000), self.disable)
671
self.expires = datetime.datetime.utcnow() + timeout
673
def need_approval(self):
674
self.last_approval_request = datetime.datetime.utcnow()
343
gobject.source_remove(self.disable_initiator_tag)
344
self.disable_initiator_tag = (gobject.timeout_add
345
(self.timeout_milliseconds(),
676
348
def start_checker(self):
677
349
"""Start a new checker subprocess if one is not running.
763
418
if self.checker_callback_tag:
764
419
gobject.source_remove(self.checker_callback_tag)
765
420
self.checker_callback_tag = None
766
if getattr(self, "checker", None) is None:
421
if getattr(self, u"checker", None) is None:
768
logger.debug("Stopping checker for %(name)s", vars(self))
423
logger.debug(u"Stopping checker for %(name)s", vars(self))
770
self.checker.terminate()
425
os.kill(self.checker.pid, signal.SIGTERM)
772
427
#if self.checker.poll() is None:
773
# self.checker.kill()
774
except OSError as error:
428
# os.kill(self.checker.pid, signal.SIGKILL)
429
except OSError, error:
775
430
if error.errno != errno.ESRCH: # No such process
777
432
self.checker = None
780
def dbus_service_property(dbus_interface,
784
"""Decorators for marking methods of a DBusObjectWithProperties to
785
become properties on the D-Bus.
787
The decorated method will be called with no arguments by "Get"
788
and with one argument by "Set".
790
The parameters, where they are supported, are the same as
791
dbus.service.method, except there is only "signature", since the
792
type from Get() and the type sent to Set() is the same.
794
# Encoding deeply encoded byte arrays is not supported yet by the
795
# "Set" method, so we fail early here:
796
if byte_arrays and signature != "ay":
797
raise ValueError("Byte arrays not supported for non-'ay'"
798
" signature {!r}".format(signature))
801
func._dbus_is_property = True
802
func._dbus_interface = dbus_interface
803
func._dbus_signature = signature
804
func._dbus_access = access
805
func._dbus_name = func.__name__
806
if func._dbus_name.endswith("_dbus_property"):
807
func._dbus_name = func._dbus_name[:-14]
808
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
814
def dbus_interface_annotations(dbus_interface):
815
"""Decorator for marking functions returning interface annotations
819
@dbus_interface_annotations("org.example.Interface")
820
def _foo(self): # Function name does not matter
821
return {"org.freedesktop.DBus.Deprecated": "true",
822
"org.freedesktop.DBus.Property.EmitsChangedSignal":
827
func._dbus_is_interface = True
828
func._dbus_interface = dbus_interface
829
func._dbus_name = dbus_interface
835
def dbus_annotations(annotations):
836
"""Decorator to annotate D-Bus methods, signals or properties
839
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
840
"org.freedesktop.DBus.Property."
841
"EmitsChangedSignal": "false"})
842
@dbus_service_property("org.example.Interface", signature="b",
844
def Property_dbus_property(self):
845
return dbus.Boolean(False)
849
func._dbus_annotations = annotations
855
class DBusPropertyException(dbus.exceptions.DBusException):
856
"""A base class for D-Bus property-related exceptions
861
class DBusPropertyAccessException(DBusPropertyException):
862
"""A property's access permissions disallows an operation.
867
class DBusPropertyNotFound(DBusPropertyException):
868
"""An attempt was made to access a non-existing property.
873
class DBusObjectWithProperties(dbus.service.Object):
874
"""A D-Bus object with properties.
876
Classes inheriting from this can use the dbus_service_property
877
decorator to expose methods as D-Bus properties. It exposes the
878
standard Get(), Set(), and GetAll() methods on the D-Bus.
882
def _is_dbus_thing(thing):
883
"""Returns a function testing if an attribute is a D-Bus thing
885
If called like _is_dbus_thing("method") it returns a function
886
suitable for use as predicate to inspect.getmembers().
888
return lambda obj: getattr(obj, "_dbus_is_{}".format(thing),
891
def _get_all_dbus_things(self, thing):
892
"""Returns a generator of (name, attribute) pairs
894
return ((getattr(athing.__get__(self), "_dbus_name", name),
895
athing.__get__(self))
896
for cls in self.__class__.__mro__
898
inspect.getmembers(cls, self._is_dbus_thing(thing)))
900
def _get_dbus_property(self, interface_name, property_name):
901
"""Returns a bound method if one exists which is a D-Bus
902
property with the specified name and interface.
904
for cls in self.__class__.__mro__:
905
for name, value in inspect.getmembers(
906
cls, self._is_dbus_thing("property")):
907
if (value._dbus_name == property_name
908
and value._dbus_interface == interface_name):
909
return value.__get__(self)
912
raise DBusPropertyNotFound("{}:{}.{}".format(
913
self.dbus_object_path, interface_name, property_name))
915
@dbus.service.method(dbus.PROPERTIES_IFACE,
918
def Get(self, interface_name, property_name):
919
"""Standard D-Bus property Get() method, see D-Bus standard.
921
prop = self._get_dbus_property(interface_name, property_name)
922
if prop._dbus_access == "write":
923
raise DBusPropertyAccessException(property_name)
925
if not hasattr(value, "variant_level"):
927
return type(value)(value, variant_level=value.variant_level+1)
929
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
930
def Set(self, interface_name, property_name, value):
931
"""Standard D-Bus property Set() method, see D-Bus standard.
933
prop = self._get_dbus_property(interface_name, property_name)
934
if prop._dbus_access == "read":
935
raise DBusPropertyAccessException(property_name)
936
if prop._dbus_get_args_options["byte_arrays"]:
937
# The byte_arrays option is not supported yet on
938
# signatures other than "ay".
939
if prop._dbus_signature != "ay":
940
raise ValueError("Byte arrays not supported for non-"
941
"'ay' signature {!r}"
942
.format(prop._dbus_signature))
943
value = dbus.ByteArray(b''.join(chr(byte)
947
@dbus.service.method(dbus.PROPERTIES_IFACE,
949
out_signature="a{sv}")
950
def GetAll(self, interface_name):
951
"""Standard D-Bus property GetAll() method, see D-Bus
954
Note: Will not include properties with access="write".
957
for name, prop in self._get_all_dbus_things("property"):
959
and interface_name != prop._dbus_interface):
960
# Interface non-empty but did not match
962
# Ignore write-only properties
963
if prop._dbus_access == "write":
966
if not hasattr(value, "variant_level"):
967
properties[name] = value
969
properties[name] = type(value)(
970
value, variant_level = value.variant_level + 1)
971
return dbus.Dictionary(properties, signature="sv")
973
@dbus.service.signal(dbus.PROPERTIES_IFACE, signature="sa{sv}as")
974
def PropertiesChanged(self, interface_name, changed_properties,
975
invalidated_properties):
976
"""Standard D-Bus PropertiesChanged() signal, see D-Bus
981
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
983
path_keyword='object_path',
984
connection_keyword='connection')
985
def Introspect(self, object_path, connection):
986
"""Overloading of standard D-Bus method.
988
Inserts property tags and interface annotation tags.
990
xmlstring = dbus.service.Object.Introspect(self, object_path,
993
document = xml.dom.minidom.parseString(xmlstring)
995
def make_tag(document, name, prop):
996
e = document.createElement("property")
997
e.setAttribute("name", name)
998
e.setAttribute("type", prop._dbus_signature)
999
e.setAttribute("access", prop._dbus_access)
1002
for if_tag in document.getElementsByTagName("interface"):
1004
for tag in (make_tag(document, name, prop)
1006
in self._get_all_dbus_things("property")
1007
if prop._dbus_interface
1008
== if_tag.getAttribute("name")):
1009
if_tag.appendChild(tag)
1010
# Add annotation tags
1011
for typ in ("method", "signal", "property"):
1012
for tag in if_tag.getElementsByTagName(typ):
1014
for name, prop in (self.
1015
_get_all_dbus_things(typ)):
1016
if (name == tag.getAttribute("name")
1017
and prop._dbus_interface
1018
== if_tag.getAttribute("name")):
1019
annots.update(getattr(
1020
prop, "_dbus_annotations", {}))
1021
for name, value in annots.items():
1022
ann_tag = document.createElement(
1024
ann_tag.setAttribute("name", name)
1025
ann_tag.setAttribute("value", value)
1026
tag.appendChild(ann_tag)
1027
# Add interface annotation tags
1028
for annotation, value in dict(
1029
itertools.chain.from_iterable(
1030
annotations().items()
1031
for name, annotations
1032
in self._get_all_dbus_things("interface")
1033
if name == if_tag.getAttribute("name")
1035
ann_tag = document.createElement("annotation")
1036
ann_tag.setAttribute("name", annotation)
1037
ann_tag.setAttribute("value", value)
1038
if_tag.appendChild(ann_tag)
1039
# Add the names to the return values for the
1040
# "org.freedesktop.DBus.Properties" methods
1041
if (if_tag.getAttribute("name")
1042
== "org.freedesktop.DBus.Properties"):
1043
for cn in if_tag.getElementsByTagName("method"):
1044
if cn.getAttribute("name") == "Get":
1045
for arg in cn.getElementsByTagName("arg"):
1046
if (arg.getAttribute("direction")
1048
arg.setAttribute("name", "value")
1049
elif cn.getAttribute("name") == "GetAll":
1050
for arg in cn.getElementsByTagName("arg"):
1051
if (arg.getAttribute("direction")
1053
arg.setAttribute("name", "props")
1054
xmlstring = document.toxml("utf-8")
1056
except (AttributeError, xml.dom.DOMException,
1057
xml.parsers.expat.ExpatError) as error:
1058
logger.error("Failed to override Introspection method",
1063
def datetime_to_dbus(dt, variant_level=0):
1064
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1066
return dbus.String("", variant_level = variant_level)
1067
return dbus.String(dt.isoformat(), variant_level=variant_level)
1070
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1071
"""A class decorator; applied to a subclass of
1072
dbus.service.Object, it will add alternate D-Bus attributes with
1073
interface names according to the "alt_interface_names" mapping.
1076
@alternate_dbus_interfaces({"org.example.Interface":
1077
"net.example.AlternateInterface"})
1078
class SampleDBusObject(dbus.service.Object):
1079
@dbus.service.method("org.example.Interface")
1080
def SampleDBusMethod():
1083
The above "SampleDBusMethod" on "SampleDBusObject" will be
1084
reachable via two interfaces: "org.example.Interface" and
1085
"net.example.AlternateInterface", the latter of which will have
1086
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1087
"true", unless "deprecate" is passed with a False value.
1089
This works for methods and signals, and also for D-Bus properties
1090
(from DBusObjectWithProperties) and interfaces (from the
1091
dbus_interface_annotations decorator).
1095
for orig_interface_name, alt_interface_name in (
1096
alt_interface_names.items()):
1098
interface_names = set()
1099
# Go though all attributes of the class
1100
for attrname, attribute in inspect.getmembers(cls):
1101
# Ignore non-D-Bus attributes, and D-Bus attributes
1102
# with the wrong interface name
1103
if (not hasattr(attribute, "_dbus_interface")
1104
or not attribute._dbus_interface.startswith(
1105
orig_interface_name)):
1107
# Create an alternate D-Bus interface name based on
1109
alt_interface = attribute._dbus_interface.replace(
1110
orig_interface_name, alt_interface_name)
1111
interface_names.add(alt_interface)
1112
# Is this a D-Bus signal?
1113
if getattr(attribute, "_dbus_is_signal", False):
1114
if sys.version == 2:
1115
# Extract the original non-method undecorated
1116
# function by black magic
1117
nonmethod_func = (dict(
1118
zip(attribute.func_code.co_freevars,
1119
attribute.__closure__))
1120
["func"].cell_contents)
1122
nonmethod_func = attribute
1123
# Create a new, but exactly alike, function
1124
# object, and decorate it to be a new D-Bus signal
1125
# with the alternate D-Bus interface name
1126
if sys.version == 2:
1127
new_function = types.FunctionType(
1128
nonmethod_func.func_code,
1129
nonmethod_func.func_globals,
1130
nonmethod_func.func_name,
1131
nonmethod_func.func_defaults,
1132
nonmethod_func.func_closure)
1134
new_function = types.FunctionType(
1135
nonmethod_func.__code__,
1136
nonmethod_func.__globals__,
1137
nonmethod_func.__name__,
1138
nonmethod_func.__defaults__,
1139
nonmethod_func.__closure__)
1140
new_function = (dbus.service.signal(
1142
attribute._dbus_signature)(new_function))
1143
# Copy annotations, if any
1145
new_function._dbus_annotations = dict(
1146
attribute._dbus_annotations)
1147
except AttributeError:
1149
# Define a creator of a function to call both the
1150
# original and alternate functions, so both the
1151
# original and alternate signals gets sent when
1152
# the function is called
1153
def fixscope(func1, func2):
1154
"""This function is a scope container to pass
1155
func1 and func2 to the "call_both" function
1156
outside of its arguments"""
1158
def call_both(*args, **kwargs):
1159
"""This function will emit two D-Bus
1160
signals by calling func1 and func2"""
1161
func1(*args, **kwargs)
1162
func2(*args, **kwargs)
1165
# Create the "call_both" function and add it to
1167
attr[attrname] = fixscope(attribute, new_function)
1168
# Is this a D-Bus method?
1169
elif getattr(attribute, "_dbus_is_method", False):
1170
# Create a new, but exactly alike, function
1171
# object. Decorate it to be a new D-Bus method
1172
# with the alternate D-Bus interface name. Add it
1175
dbus.service.method(
1177
attribute._dbus_in_signature,
1178
attribute._dbus_out_signature)
1179
(types.FunctionType(attribute.func_code,
1180
attribute.func_globals,
1181
attribute.func_name,
1182
attribute.func_defaults,
1183
attribute.func_closure)))
1184
# Copy annotations, if any
1186
attr[attrname]._dbus_annotations = dict(
1187
attribute._dbus_annotations)
1188
except AttributeError:
1190
# Is this a D-Bus property?
1191
elif getattr(attribute, "_dbus_is_property", False):
1192
# Create a new, but exactly alike, function
1193
# object, and decorate it to be a new D-Bus
1194
# property with the alternate D-Bus interface
1195
# name. Add it to the class.
1196
attr[attrname] = (dbus_service_property(
1197
alt_interface, attribute._dbus_signature,
1198
attribute._dbus_access,
1199
attribute._dbus_get_args_options
1201
(types.FunctionType(
1202
attribute.func_code,
1203
attribute.func_globals,
1204
attribute.func_name,
1205
attribute.func_defaults,
1206
attribute.func_closure)))
1207
# Copy annotations, if any
1209
attr[attrname]._dbus_annotations = dict(
1210
attribute._dbus_annotations)
1211
except AttributeError:
1213
# Is this a D-Bus interface?
1214
elif getattr(attribute, "_dbus_is_interface", False):
1215
# Create a new, but exactly alike, function
1216
# object. Decorate it to be a new D-Bus interface
1217
# with the alternate D-Bus interface name. Add it
1220
dbus_interface_annotations(alt_interface)
1221
(types.FunctionType(attribute.func_code,
1222
attribute.func_globals,
1223
attribute.func_name,
1224
attribute.func_defaults,
1225
attribute.func_closure)))
1227
# Deprecate all alternate interfaces
1228
iname="_AlternateDBusNames_interface_annotation{}"
1229
for interface_name in interface_names:
1231
@dbus_interface_annotations(interface_name)
1233
return { "org.freedesktop.DBus.Deprecated":
1235
# Find an unused name
1236
for aname in (iname.format(i)
1237
for i in itertools.count()):
1238
if aname not in attr:
1242
# Replace the class with a new subclass of it with
1243
# methods, signals, etc. as created above.
1244
cls = type(b"{}Alternate".format(cls.__name__),
1251
@alternate_dbus_interfaces({"se.recompile.Mandos":
1252
"se.bsnet.fukt.Mandos"})
1253
class ClientDBus(Client, DBusObjectWithProperties):
434
def still_valid(self):
435
"""Has the timeout not yet passed for this client?"""
436
if not getattr(self, u"enabled", False):
438
now = datetime.datetime.utcnow()
439
if self.last_checked_ok is None:
440
return now < (self.created + self.timeout)
442
return now < (self.last_checked_ok + self.timeout)
445
class ClientDBus(Client, dbus.service.Object):
1254
446
"""A Client class using D-Bus
1257
dbus_object_path: dbus.ObjectPath
1258
bus: dbus.SystemBus()
449
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
1261
runtime_expansions = (Client.runtime_expansions
1262
+ ("dbus_object_path", ))
1264
_interface = "se.recompile.Mandos.Client"
1266
451
# dbus.service.Object doesn't use super(), so we can't either.
1268
def __init__(self, bus = None, *args, **kwargs):
453
def __init__(self, *args, **kwargs):
1270
454
Client.__init__(self, *args, **kwargs)
1271
455
# Only now, when this client is initialized, can it show up on
1273
client_object_name = str(self.name).translate(
1274
{ord("."): ord("_"),
1275
ord("-"): ord("_")})
1276
self.dbus_object_path = dbus.ObjectPath(
1277
"/clients/" + client_object_name)
1278
DBusObjectWithProperties.__init__(self, self.bus,
1279
self.dbus_object_path)
1281
def notifychangeproperty(transform_func, dbus_name,
1282
type_func=lambda x: x,
1284
invalidate_only=False,
1285
_interface=_interface):
1286
""" Modify a variable so that it's a property which announces
1287
its changes to DBus.
1289
transform_fun: Function that takes a value and a variant_level
1290
and transforms it to a D-Bus type.
1291
dbus_name: D-Bus name of the variable
1292
type_func: Function that transform the value before sending it
1293
to the D-Bus. Default: no transform
1294
variant_level: D-Bus variant level. Default: 1
1296
attrname = "_{}".format(dbus_name)
1298
def setter(self, value):
1299
if hasattr(self, "dbus_object_path"):
1300
if (not hasattr(self, attrname) or
1301
type_func(getattr(self, attrname, None))
1302
!= type_func(value)):
1304
self.PropertiesChanged(
1305
_interface, dbus.Dictionary(),
1306
dbus.Array((dbus_name, )))
1308
dbus_value = transform_func(
1310
variant_level = variant_level)
1311
self.PropertyChanged(dbus.String(dbus_name),
1313
self.PropertiesChanged(
1315
dbus.Dictionary({ dbus.String(dbus_name):
1318
setattr(self, attrname, value)
1320
return property(lambda self: getattr(self, attrname), setter)
1322
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1323
approvals_pending = notifychangeproperty(dbus.Boolean,
1326
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1327
last_enabled = notifychangeproperty(datetime_to_dbus,
1329
checker = notifychangeproperty(
1330
dbus.Boolean, "CheckerRunning",
1331
type_func = lambda checker: checker is not None)
1332
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1334
last_checker_status = notifychangeproperty(dbus.Int16,
1335
"LastCheckerStatus")
1336
last_approval_request = notifychangeproperty(
1337
datetime_to_dbus, "LastApprovalRequest")
1338
approved_by_default = notifychangeproperty(dbus.Boolean,
1339
"ApprovedByDefault")
1340
approval_delay = notifychangeproperty(
1341
dbus.UInt64, "ApprovalDelay",
1342
type_func = lambda td: td.total_seconds() * 1000)
1343
approval_duration = notifychangeproperty(
1344
dbus.UInt64, "ApprovalDuration",
1345
type_func = lambda td: td.total_seconds() * 1000)
1346
host = notifychangeproperty(dbus.String, "Host")
1347
timeout = notifychangeproperty(
1348
dbus.UInt64, "Timeout",
1349
type_func = lambda td: td.total_seconds() * 1000)
1350
extended_timeout = notifychangeproperty(
1351
dbus.UInt64, "ExtendedTimeout",
1352
type_func = lambda td: td.total_seconds() * 1000)
1353
interval = notifychangeproperty(
1354
dbus.UInt64, "Interval",
1355
type_func = lambda td: td.total_seconds() * 1000)
1356
checker_command = notifychangeproperty(dbus.String, "Checker")
1357
secret = notifychangeproperty(dbus.ByteArray, "Secret",
1358
invalidate_only=True)
1360
del notifychangeproperty
457
self.dbus_object_path = (dbus.ObjectPath
459
+ self.name.replace(u".", u"_")))
460
dbus.service.Object.__init__(self, bus,
461
self.dbus_object_path)
463
oldstate = getattr(self, u"enabled", False)
464
r = Client.enable(self)
465
if oldstate != self.enabled:
467
self.PropertyChanged(dbus.String(u"enabled"),
468
dbus.Boolean(True, variant_level=1))
469
self.PropertyChanged(dbus.String(u"last_enabled"),
470
(_datetime_to_dbus(self.last_enabled,
474
def disable(self, signal = True):
475
oldstate = getattr(self, u"enabled", False)
476
r = Client.disable(self)
477
if signal and oldstate != self.enabled:
479
self.PropertyChanged(dbus.String(u"enabled"),
480
dbus.Boolean(False, variant_level=1))
1362
483
def __del__(self, *args, **kwargs):
1364
485
self.remove_from_connection()
1365
486
except LookupError:
1367
if hasattr(DBusObjectWithProperties, "__del__"):
1368
DBusObjectWithProperties.__del__(self, *args, **kwargs)
488
if hasattr(dbus.service.Object, u"__del__"):
489
dbus.service.Object.__del__(self, *args, **kwargs)
1369
490
Client.__del__(self, *args, **kwargs)
1371
492
def checker_callback(self, pid, condition, command,
1372
493
*args, **kwargs):
1373
494
self.checker_callback_tag = None
1374
495
self.checker = None
497
self.PropertyChanged(dbus.String(u"checker_running"),
498
dbus.Boolean(False, variant_level=1))
1375
499
if os.WIFEXITED(condition):
1376
500
exitstatus = os.WEXITSTATUS(condition)
1377
501
# Emit D-Bus signal
1387
511
return Client.checker_callback(self, pid, condition, command,
1388
512
*args, **kwargs)
514
def checked_ok(self, *args, **kwargs):
515
r = Client.checked_ok(self, *args, **kwargs)
517
self.PropertyChanged(
518
dbus.String(u"last_checked_ok"),
519
(_datetime_to_dbus(self.last_checked_ok,
1390
523
def start_checker(self, *args, **kwargs):
1391
old_checker_pid = getattr(self.checker, "pid", None)
524
old_checker = self.checker
525
if self.checker is not None:
526
old_checker_pid = self.checker.pid
528
old_checker_pid = None
1392
529
r = Client.start_checker(self, *args, **kwargs)
1393
530
# Only if new checker process was started
1394
531
if (self.checker is not None
1395
532
and old_checker_pid != self.checker.pid):
1396
533
# Emit D-Bus signal
1397
534
self.CheckerStarted(self.current_checker_command)
1400
def _reset_approved(self):
1401
self.approved = None
1404
def approve(self, value=True):
1405
self.approved = value
1406
gobject.timeout_add(int(self.approval_duration.total_seconds()
1407
* 1000), self._reset_approved)
1408
self.send_changedstate()
1410
## D-Bus methods, signals & properties
535
self.PropertyChanged(
536
dbus.String(u"checker_running"),
537
dbus.Boolean(True, variant_level=1))
540
def stop_checker(self, *args, **kwargs):
541
old_checker = getattr(self, u"checker", None)
542
r = Client.stop_checker(self, *args, **kwargs)
543
if (old_checker is not None
544
and getattr(self, u"checker", None) is None):
545
self.PropertyChanged(dbus.String(u"checker_running"),
546
dbus.Boolean(False, variant_level=1))
549
## D-Bus methods & signals
550
_interface = u"se.bsnet.fukt.Mandos.Client"
553
@dbus.service.method(_interface)
555
return self.checked_ok()
1416
557
# CheckerCompleted - signal
1417
@dbus.service.signal(_interface, signature="nxs")
558
@dbus.service.signal(_interface, signature=u"nxs")
1418
559
def CheckerCompleted(self, exitcode, waitstatus, command):
1422
563
# CheckerStarted - signal
1423
@dbus.service.signal(_interface, signature="s")
564
@dbus.service.signal(_interface, signature=u"s")
1424
565
def CheckerStarted(self, command):
569
# GetAllProperties - method
570
@dbus.service.method(_interface, out_signature=u"a{sv}")
571
def GetAllProperties(self):
573
return dbus.Dictionary({
574
dbus.String(u"name"):
575
dbus.String(self.name, variant_level=1),
576
dbus.String(u"fingerprint"):
577
dbus.String(self.fingerprint, variant_level=1),
578
dbus.String(u"host"):
579
dbus.String(self.host, variant_level=1),
580
dbus.String(u"created"):
581
_datetime_to_dbus(self.created, variant_level=1),
582
dbus.String(u"last_enabled"):
583
(_datetime_to_dbus(self.last_enabled,
585
if self.last_enabled is not None
586
else dbus.Boolean(False, variant_level=1)),
587
dbus.String(u"enabled"):
588
dbus.Boolean(self.enabled, variant_level=1),
589
dbus.String(u"last_checked_ok"):
590
(_datetime_to_dbus(self.last_checked_ok,
592
if self.last_checked_ok is not None
593
else dbus.Boolean (False, variant_level=1)),
594
dbus.String(u"timeout"):
595
dbus.UInt64(self.timeout_milliseconds(),
597
dbus.String(u"interval"):
598
dbus.UInt64(self.interval_milliseconds(),
600
dbus.String(u"checker"):
601
dbus.String(self.checker_command,
603
dbus.String(u"checker_running"):
604
dbus.Boolean(self.checker is not None,
606
dbus.String(u"object_path"):
607
dbus.ObjectPath(self.dbus_object_path,
611
# IsStillValid - method
612
@dbus.service.method(_interface, out_signature=u"b")
613
def IsStillValid(self):
614
return self.still_valid()
1428
616
# PropertyChanged - signal
1429
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1430
@dbus.service.signal(_interface, signature="sv")
617
@dbus.service.signal(_interface, signature=u"sv")
1431
618
def PropertyChanged(self, property, value):
1435
# GotSecret - signal
622
# ReceivedSecret - signal
1436
623
@dbus.service.signal(_interface)
1437
def GotSecret(self):
1439
Is sent after a successful transfer of secret from the Mandos
1440
server to mandos-client
624
def ReceivedSecret(self):
1444
628
# Rejected - signal
1445
@dbus.service.signal(_interface, signature="s")
1446
def Rejected(self, reason):
629
@dbus.service.signal(_interface)
1450
# NeedApproval - signal
1451
@dbus.service.signal(_interface, signature="tb")
1452
def NeedApproval(self, timeout, default):
1454
return self.need_approval()
1459
@dbus.service.method(_interface, in_signature="b")
1460
def Approve(self, value):
1463
# CheckedOK - method
1464
@dbus.service.method(_interface)
1465
def CheckedOK(self):
634
# SetChecker - method
635
@dbus.service.method(_interface, in_signature=u"s")
636
def SetChecker(self, checker):
637
"D-Bus setter method"
638
self.checker_command = checker
640
self.PropertyChanged(dbus.String(u"checker"),
641
dbus.String(self.checker_command,
645
@dbus.service.method(_interface, in_signature=u"s")
646
def SetHost(self, host):
647
"D-Bus setter method"
650
self.PropertyChanged(dbus.String(u"host"),
651
dbus.String(self.host, variant_level=1))
653
# SetInterval - method
654
@dbus.service.method(_interface, in_signature=u"t")
655
def SetInterval(self, milliseconds):
656
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
658
self.PropertyChanged(dbus.String(u"interval"),
659
(dbus.UInt64(self.interval_milliseconds(),
663
@dbus.service.method(_interface, in_signature=u"ay",
665
def SetSecret(self, secret):
666
"D-Bus setter method"
667
self.secret = str(secret)
669
# SetTimeout - method
670
@dbus.service.method(_interface, in_signature=u"t")
671
def SetTimeout(self, milliseconds):
672
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
674
self.PropertyChanged(dbus.String(u"timeout"),
675
(dbus.UInt64(self.timeout_milliseconds(),
1468
678
# Enable - method
1469
679
@dbus.service.method(_interface)
1488
698
def StopChecker(self):
1489
699
self.stop_checker()
1493
# ApprovalPending - property
1494
@dbus_service_property(_interface, signature="b", access="read")
1495
def ApprovalPending_dbus_property(self):
1496
return dbus.Boolean(bool(self.approvals_pending))
1498
# ApprovedByDefault - property
1499
@dbus_service_property(_interface,
1502
def ApprovedByDefault_dbus_property(self, value=None):
1503
if value is None: # get
1504
return dbus.Boolean(self.approved_by_default)
1505
self.approved_by_default = bool(value)
1507
# ApprovalDelay - property
1508
@dbus_service_property(_interface,
1511
def ApprovalDelay_dbus_property(self, value=None):
1512
if value is None: # get
1513
return dbus.UInt64(self.approval_delay.total_seconds()
1515
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1517
# ApprovalDuration - property
1518
@dbus_service_property(_interface,
1521
def ApprovalDuration_dbus_property(self, value=None):
1522
if value is None: # get
1523
return dbus.UInt64(self.approval_duration.total_seconds()
1525
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1528
@dbus_service_property(_interface, signature="s", access="read")
1529
def Name_dbus_property(self):
1530
return dbus.String(self.name)
1532
# Fingerprint - property
1533
@dbus_service_property(_interface, signature="s", access="read")
1534
def Fingerprint_dbus_property(self):
1535
return dbus.String(self.fingerprint)
1538
@dbus_service_property(_interface,
1541
def Host_dbus_property(self, value=None):
1542
if value is None: # get
1543
return dbus.String(self.host)
1544
self.host = str(value)
1546
# Created - property
1547
@dbus_service_property(_interface, signature="s", access="read")
1548
def Created_dbus_property(self):
1549
return datetime_to_dbus(self.created)
1551
# LastEnabled - property
1552
@dbus_service_property(_interface, signature="s", access="read")
1553
def LastEnabled_dbus_property(self):
1554
return datetime_to_dbus(self.last_enabled)
1556
# Enabled - property
1557
@dbus_service_property(_interface,
1560
def Enabled_dbus_property(self, value=None):
1561
if value is None: # get
1562
return dbus.Boolean(self.enabled)
1568
# LastCheckedOK - property
1569
@dbus_service_property(_interface,
1572
def LastCheckedOK_dbus_property(self, value=None):
1573
if value is not None:
1576
return datetime_to_dbus(self.last_checked_ok)
1578
# LastCheckerStatus - property
1579
@dbus_service_property(_interface, signature="n", access="read")
1580
def LastCheckerStatus_dbus_property(self):
1581
return dbus.Int16(self.last_checker_status)
1583
# Expires - property
1584
@dbus_service_property(_interface, signature="s", access="read")
1585
def Expires_dbus_property(self):
1586
return datetime_to_dbus(self.expires)
1588
# LastApprovalRequest - property
1589
@dbus_service_property(_interface, signature="s", access="read")
1590
def LastApprovalRequest_dbus_property(self):
1591
return datetime_to_dbus(self.last_approval_request)
1593
# Timeout - property
1594
@dbus_service_property(_interface,
1597
def Timeout_dbus_property(self, value=None):
1598
if value is None: # get
1599
return dbus.UInt64(self.timeout.total_seconds() * 1000)
1600
old_timeout = self.timeout
1601
self.timeout = datetime.timedelta(0, 0, 0, value)
1602
# Reschedule disabling
1604
now = datetime.datetime.utcnow()
1605
self.expires += self.timeout - old_timeout
1606
if self.expires <= now:
1607
# The timeout has passed
1610
if (getattr(self, "disable_initiator_tag", None)
1613
gobject.source_remove(self.disable_initiator_tag)
1614
self.disable_initiator_tag = gobject.timeout_add(
1615
int((self.expires - now).total_seconds() * 1000),
1618
# ExtendedTimeout - property
1619
@dbus_service_property(_interface,
1622
def ExtendedTimeout_dbus_property(self, value=None):
1623
if value is None: # get
1624
return dbus.UInt64(self.extended_timeout.total_seconds()
1626
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1628
# Interval - property
1629
@dbus_service_property(_interface,
1632
def Interval_dbus_property(self, value=None):
1633
if value is None: # get
1634
return dbus.UInt64(self.interval.total_seconds() * 1000)
1635
self.interval = datetime.timedelta(0, 0, 0, value)
1636
if getattr(self, "checker_initiator_tag", None) is None:
1639
# Reschedule checker run
1640
gobject.source_remove(self.checker_initiator_tag)
1641
self.checker_initiator_tag = gobject.timeout_add(
1642
value, self.start_checker)
1643
self.start_checker() # Start one now, too
1645
# Checker - property
1646
@dbus_service_property(_interface,
1649
def Checker_dbus_property(self, value=None):
1650
if value is None: # get
1651
return dbus.String(self.checker_command)
1652
self.checker_command = str(value)
1654
# CheckerRunning - property
1655
@dbus_service_property(_interface,
1658
def CheckerRunning_dbus_property(self, value=None):
1659
if value is None: # get
1660
return dbus.Boolean(self.checker is not None)
1662
self.start_checker()
1666
# ObjectPath - property
1667
@dbus_service_property(_interface, signature="o", access="read")
1668
def ObjectPath_dbus_property(self):
1669
return self.dbus_object_path # is already a dbus.ObjectPath
1672
@dbus_service_property(_interface,
1676
def Secret_dbus_property(self, value):
1677
self.secret = bytes(value)
1682
class ProxyClient(object):
1683
def __init__(self, child_pipe, fpr, address):
1684
self._pipe = child_pipe
1685
self._pipe.send(('init', fpr, address))
1686
if not self._pipe.recv():
1689
def __getattribute__(self, name):
1691
return super(ProxyClient, self).__getattribute__(name)
1692
self._pipe.send(('getattr', name))
1693
data = self._pipe.recv()
1694
if data[0] == 'data':
1696
if data[0] == 'function':
1698
def func(*args, **kwargs):
1699
self._pipe.send(('funcall', name, args, kwargs))
1700
return self._pipe.recv()[1]
1704
def __setattr__(self, name, value):
1706
return super(ProxyClient, self).__setattr__(name, value)
1707
self._pipe.send(('setattr', name, value))
1710
704
class ClientHandler(socketserver.BaseRequestHandler, object):
1711
705
"""A class to handle client connections.
1714
708
Note: This will run in its own forked process."""
1716
710
def handle(self):
1717
with contextlib.closing(self.server.child_pipe) as child_pipe:
1718
logger.info("TCP connection from: %s",
1719
str(self.client_address))
1720
logger.debug("Pipe FD: %d",
1721
self.server.child_pipe.fileno())
711
logger.info(u"TCP connection from: %s",
712
unicode(self.client_address))
713
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
714
# Open IPC pipe to parent process
715
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
716
session = (gnutls.connection
717
.ClientSession(self.request,
1723
session = gnutls.connection.ClientSession(
1724
self.request, gnutls.connection .X509Credentials())
721
line = self.request.makefile().readline()
722
logger.debug(u"Protocol version: %r", line)
724
if int(line.strip().split()[0]) > 1:
726
except (ValueError, IndexError, RuntimeError), error:
727
logger.error(u"Unknown protocol version: %s", error)
1726
730
# Note: gnutls.connection.X509Credentials is really a
1727
731
# generic GnuTLS certificate credentials object so long as
1728
732
# no X.509 keys are added to it. Therefore, we can use it
1729
733
# here despite using OpenPGP certificates.
1731
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1732
# "+AES-256-CBC", "+SHA1",
1733
# "+COMP-NULL", "+CTYPE-OPENPGP",
735
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
736
# u"+AES-256-CBC", u"+SHA1",
737
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1735
739
# Use a fallback default, since this MUST be set.
1736
740
priority = self.server.gnutls_priority
1737
741
if priority is None:
1739
gnutls.library.functions.gnutls_priority_set_direct(
1740
session._c_object, priority, None)
1742
# Start communication using the Mandos protocol
1743
# Get protocol number
1744
line = self.request.makefile().readline()
1745
logger.debug("Protocol version: %r", line)
1747
if int(line.strip().split()[0]) > 1:
1748
raise RuntimeError(line)
1749
except (ValueError, IndexError, RuntimeError) as error:
1750
logger.error("Unknown protocol version: %s", error)
1753
# Start GnuTLS connection
743
(gnutls.library.functions
744
.gnutls_priority_set_direct(session._c_object,
1755
748
session.handshake()
1756
except gnutls.errors.GNUTLSError as error:
1757
logger.warning("Handshake failed: %s", error)
749
except gnutls.errors.GNUTLSError, error:
750
logger.warning(u"Handshake failed: %s", error)
1758
751
# Do not run session.bye() here: the session is not
1759
752
# established. Just abandon the request.
1761
logger.debug("Handshake succeeded")
1763
approval_required = False
754
logger.debug(u"Handshake succeeded")
1766
fpr = self.fingerprint(
1767
self.peer_certificate(session))
1769
gnutls.errors.GNUTLSError) as error:
1770
logger.warning("Bad certificate: %s", error)
1772
logger.debug("Fingerprint: %s", fpr)
1775
client = ProxyClient(child_pipe, fpr,
1776
self.client_address)
1780
if client.approval_delay:
1781
delay = client.approval_delay
1782
client.approvals_pending += 1
1783
approval_required = True
1786
if not client.enabled:
1787
logger.info("Client %s is disabled",
1789
if self.server.use_dbus:
1791
client.Rejected("Disabled")
1794
if client.approved or not client.approval_delay:
1795
#We are approved or approval is disabled
1797
elif client.approved is None:
1798
logger.info("Client %s needs approval",
1800
if self.server.use_dbus:
1802
client.NeedApproval(
1803
client.approval_delay.total_seconds()
1804
* 1000, client.approved_by_default)
1806
logger.warning("Client %s was not approved",
1808
if self.server.use_dbus:
1810
client.Rejected("Denied")
1813
#wait until timeout or approved
1814
time = datetime.datetime.now()
1815
client.changedstate.acquire()
1816
client.changedstate.wait(delay.total_seconds())
1817
client.changedstate.release()
1818
time2 = datetime.datetime.now()
1819
if (time2 - time) >= delay:
1820
if not client.approved_by_default:
1821
logger.warning("Client %s timed out while"
1822
" waiting for approval",
1824
if self.server.use_dbus:
1826
client.Rejected("Approval timed out")
1831
delay -= time2 - time
1834
while sent_size < len(client.secret):
1836
sent = session.send(client.secret[sent_size:])
1837
except gnutls.errors.GNUTLSError as error:
1838
logger.warning("gnutls send failed",
1841
logger.debug("Sent: %d, remaining: %d", sent,
1842
len(client.secret) - (sent_size
1846
logger.info("Sending secret to %s", client.name)
1847
# bump the timeout using extended_timeout
1848
client.bump_timeout(client.extended_timeout)
1849
if self.server.use_dbus:
756
fpr = self.fingerprint(self.peer_certificate(session))
757
except (TypeError, gnutls.errors.GNUTLSError), error:
758
logger.warning(u"Bad certificate: %s", error)
761
logger.debug(u"Fingerprint: %s", fpr)
1854
if approval_required:
1855
client.approvals_pending -= 1
1858
except gnutls.errors.GNUTLSError as error:
1859
logger.warning("GnuTLS bye failed",
763
for c in self.server.clients:
764
if c.fingerprint == fpr:
768
ipc.write(u"NOTFOUND %s\n" % fpr)
771
# Have to check if client.still_valid(), since it is
772
# possible that the client timed out while establishing
773
# the GnuTLS session.
774
if not client.still_valid():
775
ipc.write(u"INVALID %s\n" % client.name)
778
ipc.write(u"SENDING %s\n" % client.name)
780
while sent_size < len(client.secret):
781
sent = session.send(client.secret[sent_size:])
782
logger.debug(u"Sent: %d, remaining: %d",
783
sent, len(client.secret)
784
- (sent_size + sent))
1863
789
def peer_certificate(session):
1864
790
"Return the peer's OpenPGP certificate as a bytestring"
1865
791
# If not an OpenPGP certificate...
1866
if (gnutls.library.functions.gnutls_certificate_type_get(
792
if (gnutls.library.functions
793
.gnutls_certificate_type_get(session._c_object)
1868
794
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1869
795
# ...do the normal thing
1870
796
return session.peer_certificate
2051
933
# if_nametoindex
2052
934
# (self.interface))
2053
935
return socketserver.TCPServer.server_bind(self)
2056
class MandosServer(IPv6_TCPServer):
2060
clients: set of Client objects
2061
gnutls_priority GnuTLS priority string
2062
use_dbus: Boolean; to emit D-Bus signals or not
2064
Assumes a gobject.MainLoop event loop.
2067
def __init__(self, server_address, RequestHandlerClass,
2071
gnutls_priority=None,
2074
self.enabled = False
2075
self.clients = clients
2076
if self.clients is None:
2078
self.use_dbus = use_dbus
2079
self.gnutls_priority = gnutls_priority
2080
IPv6_TCPServer.__init__(self, server_address,
2081
RequestHandlerClass,
2082
interface = interface,
2083
use_ipv6 = use_ipv6,
2084
socketfd = socketfd)
2086
936
def server_activate(self):
2087
937
if self.enabled:
2088
938
return socketserver.TCPServer.server_activate(self)
2090
939
def enable(self):
2091
940
self.enabled = True
2093
def add_pipe(self, parent_pipe, proc):
2094
# Call "handle_ipc" for both data and EOF events
2095
gobject.io_add_watch(
2096
parent_pipe.fileno(),
2097
gobject.IO_IN | gobject.IO_HUP,
2098
functools.partial(self.handle_ipc,
2099
parent_pipe = parent_pipe,
2102
def handle_ipc(self, source, condition,
2105
client_object=None):
2106
# error, or the other end of multiprocessing.Pipe has closed
2107
if condition & (gobject.IO_ERR | gobject.IO_HUP):
2108
# Wait for other process to exit
2112
# Read a request from the child
2113
request = parent_pipe.recv()
2114
command = request[0]
2116
if command == 'init':
2118
address = request[2]
2120
for c in self.clients.itervalues():
2121
if c.fingerprint == fpr:
2125
logger.info("Client not found for fingerprint: %s, ad"
2126
"dress: %s", fpr, address)
2129
mandos_dbus_service.ClientNotFound(fpr,
2131
parent_pipe.send(False)
2134
gobject.io_add_watch(
2135
parent_pipe.fileno(),
2136
gobject.IO_IN | gobject.IO_HUP,
2137
functools.partial(self.handle_ipc,
2138
parent_pipe = parent_pipe,
2140
client_object = client))
2141
parent_pipe.send(True)
2142
# remove the old hook in favor of the new above hook on
2145
if command == 'funcall':
2146
funcname = request[1]
2150
parent_pipe.send(('data', getattr(client_object,
2154
if command == 'getattr':
2155
attrname = request[1]
2156
if isinstance(client_object.__getattribute__(attrname),
2157
collections.Callable):
2158
parent_pipe.send(('function', ))
2161
'data', client_object.__getattribute__(attrname)))
2163
if command == 'setattr':
2164
attrname = request[1]
2166
setattr(client_object, attrname, value)
941
def handle_ipc(self, source, condition, file_objects={}):
943
gobject.IO_IN: u"IN", # There is data to read.
944
gobject.IO_OUT: u"OUT", # Data can be written (without
946
gobject.IO_PRI: u"PRI", # There is urgent data to read.
947
gobject.IO_ERR: u"ERR", # Error condition.
948
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
949
# broken, usually for pipes and
952
conditions_string = ' | '.join(name
954
condition_names.iteritems()
956
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
959
# Turn the pipe file descriptor into a Python file object
960
if source not in file_objects:
961
file_objects[source] = os.fdopen(source, u"r", 1)
963
# Read a line from the file object
964
cmdline = file_objects[source].readline()
965
if not cmdline: # Empty line means end of file
967
file_objects[source].close()
968
del file_objects[source]
970
# Stop calling this function
973
logger.debug(u"IPC command: %r", cmdline)
975
# Parse and act on command
976
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
978
if cmd == u"NOTFOUND":
979
logger.warning(u"Client not found for fingerprint: %s",
983
mandos_dbus_service.ClientNotFound(args)
984
elif cmd == u"INVALID":
985
for client in self.clients:
986
if client.name == args:
987
logger.warning(u"Client %s is invalid", args)
993
logger.error(u"Unknown client %s is invalid", args)
994
elif cmd == u"SENDING":
995
for client in self.clients:
996
if client.name == args:
997
logger.info(u"Sending secret to %s", client.name)
1001
client.ReceivedSecret()
1004
logger.error(u"Sending secret to unknown client %s",
1007
logger.error(u"Unknown IPC command: %r", cmdline)
1009
# Keep calling this function
2171
def rfc3339_duration_to_delta(duration):
2172
"""Parse an RFC 3339 "duration" and return a datetime.timedelta
2174
>>> rfc3339_duration_to_delta("P7D")
2175
datetime.timedelta(7)
2176
>>> rfc3339_duration_to_delta("PT60S")
2177
datetime.timedelta(0, 60)
2178
>>> rfc3339_duration_to_delta("PT60M")
2179
datetime.timedelta(0, 3600)
2180
>>> rfc3339_duration_to_delta("PT24H")
2181
datetime.timedelta(1)
2182
>>> rfc3339_duration_to_delta("P1W")
2183
datetime.timedelta(7)
2184
>>> rfc3339_duration_to_delta("PT5M30S")
2185
datetime.timedelta(0, 330)
2186
>>> rfc3339_duration_to_delta("P1DT3M20S")
2187
datetime.timedelta(1, 200)
2190
# Parsing an RFC 3339 duration with regular expressions is not
2191
# possible - there would have to be multiple places for the same
2192
# values, like seconds. The current code, while more esoteric, is
2193
# cleaner without depending on a parsing library. If Python had a
2194
# built-in library for parsing we would use it, but we'd like to
2195
# avoid excessive use of external libraries.
2197
# New type for defining tokens, syntax, and semantics all-in-one
2198
Token = collections.namedtuple("Token", (
2199
"regexp", # To match token; if "value" is not None, must have
2200
# a "group" containing digits
2201
"value", # datetime.timedelta or None
2202
"followers")) # Tokens valid after this token
2203
# RFC 3339 "duration" tokens, syntax, and semantics; taken from
2204
# the "duration" ABNF definition in RFC 3339, Appendix A.
2205
token_end = Token(re.compile(r"$"), None, frozenset())
2206
token_second = Token(re.compile(r"(\d+)S"),
2207
datetime.timedelta(seconds=1),
2208
frozenset((token_end, )))
2209
token_minute = Token(re.compile(r"(\d+)M"),
2210
datetime.timedelta(minutes=1),
2211
frozenset((token_second, token_end)))
2212
token_hour = Token(re.compile(r"(\d+)H"),
2213
datetime.timedelta(hours=1),
2214
frozenset((token_minute, token_end)))
2215
token_time = Token(re.compile(r"T"),
2217
frozenset((token_hour, token_minute,
2219
token_day = Token(re.compile(r"(\d+)D"),
2220
datetime.timedelta(days=1),
2221
frozenset((token_time, token_end)))
2222
token_month = Token(re.compile(r"(\d+)M"),
2223
datetime.timedelta(weeks=4),
2224
frozenset((token_day, token_end)))
2225
token_year = Token(re.compile(r"(\d+)Y"),
2226
datetime.timedelta(weeks=52),
2227
frozenset((token_month, token_end)))
2228
token_week = Token(re.compile(r"(\d+)W"),
2229
datetime.timedelta(weeks=1),
2230
frozenset((token_end, )))
2231
token_duration = Token(re.compile(r"P"), None,
2232
frozenset((token_year, token_month,
2233
token_day, token_time,
2235
# Define starting values
2236
value = datetime.timedelta() # Value so far
2238
followers = frozenset((token_duration, )) # Following valid tokens
2239
s = duration # String left to parse
2240
# Loop until end token is found
2241
while found_token is not token_end:
2242
# Search for any currently valid tokens
2243
for token in followers:
2244
match = token.regexp.match(s)
2245
if match is not None:
2247
if token.value is not None:
2248
# Value found, parse digits
2249
factor = int(match.group(1), 10)
2250
# Add to value so far
2251
value += factor * token.value
2252
# Strip token from string
2253
s = token.regexp.sub("", s, 1)
2256
# Set valid next tokens
2257
followers = found_token.followers
2260
# No currently valid tokens were found
2261
raise ValueError("Invalid RFC 3339 duration: {!r}"
2267
1013
def string_to_delta(interval):
2268
1014
"""Parse a string and return a datetime.timedelta
2270
>>> string_to_delta('7d')
1016
>>> string_to_delta(u'7d')
2271
1017
datetime.timedelta(7)
2272
>>> string_to_delta('60s')
1018
>>> string_to_delta(u'60s')
2273
1019
datetime.timedelta(0, 60)
2274
>>> string_to_delta('60m')
1020
>>> string_to_delta(u'60m')
2275
1021
datetime.timedelta(0, 3600)
2276
>>> string_to_delta('24h')
1022
>>> string_to_delta(u'24h')
2277
1023
datetime.timedelta(1)
2278
>>> string_to_delta('1w')
1024
>>> string_to_delta(u'1w')
2279
1025
datetime.timedelta(7)
2280
>>> string_to_delta('5m 30s')
1026
>>> string_to_delta(u'5m 30s')
2281
1027
datetime.timedelta(0, 330)
2285
return rfc3339_duration_to_delta(interval)
2289
1029
timevalue = datetime.timedelta(0)
2290
1030
for s in interval.split():
1032
suffix = unicode(s[-1])
2293
1033
value = int(s[:-1])
2295
1035
delta = datetime.timedelta(value)
1036
elif suffix == u"s":
2297
1037
delta = datetime.timedelta(0, value)
1038
elif suffix == u"m":
2299
1039
delta = datetime.timedelta(0, 0, 0, 0, value)
1040
elif suffix == u"h":
2301
1041
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1042
elif suffix == u"w":
2303
1043
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
2305
raise ValueError("Unknown suffix {!r}".format(suffix))
2306
except IndexError as e:
2307
raise ValueError(*(e.args))
1046
except (ValueError, IndexError):
2308
1048
timevalue += delta
2309
1049
return timevalue
1052
def server_state_changed(state):
1053
"""Derived from the Avahi example code"""
1054
if state == avahi.SERVER_COLLISION:
1055
logger.error(u"Zeroconf server name collision")
1057
elif state == avahi.SERVER_RUNNING:
1061
def entry_group_state_changed(state, error):
1062
"""Derived from the Avahi example code"""
1063
logger.debug(u"Avahi state change: %i", state)
1065
if state == avahi.ENTRY_GROUP_ESTABLISHED:
1066
logger.debug(u"Zeroconf service established.")
1067
elif state == avahi.ENTRY_GROUP_COLLISION:
1068
logger.warning(u"Zeroconf service name collision.")
1070
elif state == avahi.ENTRY_GROUP_FAILURE:
1071
logger.critical(u"Avahi: Error in group state changed %s",
1073
raise AvahiGroupError(u"State changed: %s" % unicode(error))
1075
def if_nametoindex(interface):
1076
"""Call the C function if_nametoindex(), or equivalent
1078
Note: This function cannot accept a unicode string."""
1079
global if_nametoindex
1081
if_nametoindex = (ctypes.cdll.LoadLibrary
1082
(ctypes.util.find_library(u"c"))
1084
except (OSError, AttributeError):
1085
logger.warning(u"Doing if_nametoindex the hard way")
1086
def if_nametoindex(interface):
1087
"Get an interface index the hard way, i.e. using fcntl()"
1088
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1089
with closing(socket.socket()) as s:
1090
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1091
struct.pack(str(u"16s16x"),
1093
interface_index = struct.unpack(str(u"I"),
1095
return interface_index
1096
return if_nametoindex(interface)
2312
1099
def daemon(nochdir = False, noclose = False):
2313
1100
"""See daemon(3). Standard BSD Unix function.
2339
##################################################################
1125
######################################################################
2340
1126
# Parsing of options, both command line and config file
2342
parser = argparse.ArgumentParser()
2343
parser.add_argument("-v", "--version", action="version",
2344
version = "%(prog)s {}".format(version),
2345
help="show version number and exit")
2346
parser.add_argument("-i", "--interface", metavar="IF",
2347
help="Bind to interface IF")
2348
parser.add_argument("-a", "--address",
2349
help="Address to listen for requests on")
2350
parser.add_argument("-p", "--port", type=int,
2351
help="Port number to receive requests on")
2352
parser.add_argument("--check", action="store_true",
2353
help="Run self-test")
2354
parser.add_argument("--debug", action="store_true",
2355
help="Debug mode; run in foreground and log"
2356
" to terminal", default=None)
2357
parser.add_argument("--debuglevel", metavar="LEVEL",
2358
help="Debug level for stdout output")
2359
parser.add_argument("--priority", help="GnuTLS"
2360
" priority string (see GnuTLS documentation)")
2361
parser.add_argument("--servicename",
2362
metavar="NAME", help="Zeroconf service name")
2363
parser.add_argument("--configdir",
2364
default="/etc/mandos", metavar="DIR",
2365
help="Directory to search for configuration"
2367
parser.add_argument("--no-dbus", action="store_false",
2368
dest="use_dbus", help="Do not provide D-Bus"
2369
" system bus interface", default=None)
2370
parser.add_argument("--no-ipv6", action="store_false",
2371
dest="use_ipv6", help="Do not use IPv6",
2373
parser.add_argument("--no-restore", action="store_false",
2374
dest="restore", help="Do not restore stored"
2375
" state", default=None)
2376
parser.add_argument("--socket", type=int,
2377
help="Specify a file descriptor to a network"
2378
" socket to use instead of creating one")
2379
parser.add_argument("--statedir", metavar="DIR",
2380
help="Directory to save/restore state in")
2381
parser.add_argument("--foreground", action="store_true",
2382
help="Run in foreground", default=None)
2383
parser.add_argument("--no-zeroconf", action="store_false",
2384
dest="zeroconf", help="Do not use Zeroconf",
2387
options = parser.parse_args()
1128
parser = optparse.OptionParser(version = "%%prog %s" % version)
1129
parser.add_option("-i", u"--interface", type=u"string",
1130
metavar="IF", help=u"Bind to interface IF")
1131
parser.add_option("-a", u"--address", type=u"string",
1132
help=u"Address to listen for requests on")
1133
parser.add_option("-p", u"--port", type=u"int",
1134
help=u"Port number to receive requests on")
1135
parser.add_option("--check", action=u"store_true",
1136
help=u"Run self-test")
1137
parser.add_option("--debug", action=u"store_true",
1138
help=u"Debug mode; run in foreground and log to"
1140
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1141
u" priority string (see GnuTLS documentation)")
1142
parser.add_option("--servicename", type=u"string",
1143
metavar=u"NAME", help=u"Zeroconf service name")
1144
parser.add_option("--configdir", type=u"string",
1145
default=u"/etc/mandos", metavar=u"DIR",
1146
help=u"Directory to search for configuration"
1148
parser.add_option("--no-dbus", action=u"store_false",
1149
dest=u"use_dbus", help=u"Do not provide D-Bus"
1150
u" system bus interface")
1151
parser.add_option("--no-ipv6", action=u"store_false",
1152
dest=u"use_ipv6", help=u"Do not use IPv6")
1153
options = parser.parse_args()[0]
2389
1155
if options.check:
2391
fail_count, test_count = doctest.testmod()
2392
sys.exit(os.EX_OK if fail_count == 0 else 1)
2394
1160
# Default values for config file for server-global settings
2395
server_defaults = { "interface": "",
2400
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2401
":+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
2402
"servicename": "Mandos",
2408
"statedir": "/var/lib/mandos",
2409
"foreground": "False",
1161
server_defaults = { u"interface": u"",
1166
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1167
u"servicename": u"Mandos",
1168
u"use_dbus": u"True",
1169
u"use_ipv6": u"True",
2413
1172
# Parse config file for server-global settings
2414
1173
server_config = configparser.SafeConfigParser(server_defaults)
2415
1174
del server_defaults
2416
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1175
server_config.read(os.path.join(options.configdir,
2417
1177
# Convert the SafeConfigParser object to a dict
2418
1178
server_settings = server_config.defaults()
2419
1179
# Use the appropriate methods on the non-string config options
2420
for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
2421
server_settings[option] = server_config.getboolean("DEFAULT",
1180
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1181
server_settings[option] = server_config.getboolean(u"DEFAULT",
2423
1183
if server_settings["port"]:
2424
server_settings["port"] = server_config.getint("DEFAULT",
2426
if server_settings["socket"]:
2427
server_settings["socket"] = server_config.getint("DEFAULT",
2429
# Later, stdin will, and stdout and stderr might, be dup'ed
2430
# over with an opened os.devnull. But we don't want this to
2431
# happen with a supplied network socket.
2432
if 0 <= server_settings["socket"] <= 2:
2433
server_settings["socket"] = os.dup(server_settings
1184
server_settings["port"] = server_config.getint(u"DEFAULT",
2435
1186
del server_config
2437
1188
# Override the settings from the config file with command line
2438
1189
# options, if set.
2439
for option in ("interface", "address", "port", "debug",
2440
"priority", "servicename", "configdir", "use_dbus",
2441
"use_ipv6", "debuglevel", "restore", "statedir",
2442
"socket", "foreground", "zeroconf"):
1190
for option in (u"interface", u"address", u"port", u"debug",
1191
u"priority", u"servicename", u"configdir",
1192
u"use_dbus", u"use_ipv6"):
2443
1193
value = getattr(options, option)
2444
1194
if value is not None:
2445
1195
server_settings[option] = value
2447
1197
# Force all strings to be unicode
2448
1198
for option in server_settings.keys():
2449
if isinstance(server_settings[option], bytes):
2450
server_settings[option] = (server_settings[option]
2452
# Force all boolean options to be boolean
2453
for option in ("debug", "use_dbus", "use_ipv6", "restore",
2454
"foreground", "zeroconf"):
2455
server_settings[option] = bool(server_settings[option])
2456
# Debug implies foreground
2457
if server_settings["debug"]:
2458
server_settings["foreground"] = True
1199
if type(server_settings[option]) is str:
1200
server_settings[option] = unicode(server_settings[option])
2459
1201
# Now we have our good server settings in "server_settings"
2461
1203
##################################################################
2463
if (not server_settings["zeroconf"]
2464
and not (server_settings["port"]
2465
or server_settings["socket"] != "")):
2466
parser.error("Needs port or socket to work without Zeroconf")
2468
1205
# For convenience
2469
debug = server_settings["debug"]
2470
debuglevel = server_settings["debuglevel"]
2471
use_dbus = server_settings["use_dbus"]
2472
use_ipv6 = server_settings["use_ipv6"]
2473
stored_state_path = os.path.join(server_settings["statedir"],
2475
foreground = server_settings["foreground"]
2476
zeroconf = server_settings["zeroconf"]
2479
initlogger(debug, logging.DEBUG)
2484
level = getattr(logging, debuglevel.upper())
2485
initlogger(debug, level)
2487
if server_settings["servicename"] != "Mandos":
2488
syslogger.setFormatter(
2489
logging.Formatter('Mandos ({}) [%(process)d]:'
2490
' %(levelname)s: %(message)s'.format(
2491
server_settings["servicename"])))
1206
debug = server_settings[u"debug"]
1207
use_dbus = server_settings[u"use_dbus"]
1208
use_ipv6 = server_settings[u"use_ipv6"]
1211
syslogger.setLevel(logging.WARNING)
1212
console.setLevel(logging.WARNING)
1214
if server_settings[u"servicename"] != u"Mandos":
1215
syslogger.setFormatter(logging.Formatter
1216
(u'Mandos (%s) [%%(process)d]:'
1217
u' %%(levelname)s: %%(message)s'
1218
% server_settings[u"servicename"]))
2493
1220
# Parse config file with clients
2494
client_config = configparser.SafeConfigParser(Client
2496
client_config.read(os.path.join(server_settings["configdir"],
1221
client_defaults = { u"timeout": u"1h",
1223
u"checker": u"fping -q -- %%(host)s",
1226
client_config = configparser.SafeConfigParser(client_defaults)
1227
client_config.read(os.path.join(server_settings[u"configdir"],
2499
1230
global mandos_dbus_service
2500
1231
mandos_dbus_service = None
2503
if server_settings["socket"] != "":
2504
socketfd = server_settings["socket"]
2505
tcp_server = MandosServer(
2506
(server_settings["address"], server_settings["port"]),
2508
interface=(server_settings["interface"] or None),
2510
gnutls_priority=server_settings["priority"],
2514
pidfilename = "/run/mandos.pid"
2515
if not os.path.isdir("/run/."):
2516
pidfilename = "/var/run/mandos.pid"
2519
pidfile = codecs.open(pidfilename, "w", encoding="utf-8")
2520
except IOError as e:
2521
logger.error("Could not open file %r", pidfilename,
1234
tcp_server = IPv6_TCPServer((server_settings[u"address"],
1235
server_settings[u"port"]),
1238
server_settings[u"interface"],
1242
server_settings[u"priority"],
1244
pidfilename = u"/var/run/mandos.pid"
1246
pidfile = open(pidfilename, u"w")
1248
logger.error(u"Could not open file %r", pidfilename)
2524
for name in ("_mandos", "mandos", "nobody"):
1251
uid = pwd.getpwnam(u"_mandos").pw_uid
1252
gid = pwd.getpwnam(u"_mandos").pw_gid
2526
uid = pwd.getpwnam(name).pw_uid
2527
gid = pwd.getpwnam(name).pw_gid
1255
uid = pwd.getpwnam(u"mandos").pw_uid
1256
gid = pwd.getpwnam(u"mandos").pw_gid
2529
1257
except KeyError:
1259
uid = pwd.getpwnam(u"nobody").pw_uid
1260
gid = pwd.getpwnam(u"nobody").pw_gid
2537
except OSError as error:
2538
if error.errno != errno.EPERM:
1267
except OSError, error:
1268
if error[0] != errno.EPERM:
1271
# Enable all possible GnuTLS debugging
2542
# Enable all possible GnuTLS debugging
2544
1273
# "Use a log level over 10 to enable all debugging options."
2545
1274
# - GnuTLS manual
2546
1275
gnutls.library.functions.gnutls_global_set_log_level(11)
2548
1277
@gnutls.library.types.gnutls_log_func
2549
1278
def debug_gnutls(level, string):
2550
logger.debug("GnuTLS: %s", string[:-1])
2552
gnutls.library.functions.gnutls_global_set_log_function(
2555
# Redirect stdin so all checkers get /dev/null
2556
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2557
os.dup2(null, sys.stdin.fileno())
2561
# Need to fork before connecting to D-Bus
2563
# Close all input and output, do double fork, etc.
2566
# multiprocessing will use threads, so before we use gobject we
2567
# need to inform gobject that threads will be used.
2568
gobject.threads_init()
1279
logger.debug(u"GnuTLS: %s", string[:-1])
1281
(gnutls.library.functions
1282
.gnutls_global_set_log_function(debug_gnutls))
1285
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1286
service = AvahiService(name = server_settings[u"servicename"],
1287
servicetype = u"_mandos._tcp",
1288
protocol = protocol)
1289
if server_settings["interface"]:
1290
service.interface = (if_nametoindex
1291
(str(server_settings[u"interface"])))
2570
1293
global main_loop
2571
1296
# From the Avahi example code
2572
DBusGMainLoop(set_as_default=True)
1297
DBusGMainLoop(set_as_default=True )
2573
1298
main_loop = gobject.MainLoop()
2574
1299
bus = dbus.SystemBus()
1300
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1301
avahi.DBUS_PATH_SERVER),
1302
avahi.DBUS_INTERFACE_SERVER)
2575
1303
# End of Avahi example code
2578
bus_name = dbus.service.BusName("se.recompile.Mandos",
2581
old_bus_name = dbus.service.BusName(
2582
"se.bsnet.fukt.Mandos", bus,
2584
except dbus.exceptions.DBusException as e:
2585
logger.error("Disabling D-Bus:", exc_info=e)
2587
server_settings["use_dbus"] = False
2588
tcp_server.use_dbus = False
2590
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2591
service = AvahiServiceToSyslog(
2592
name = server_settings["servicename"],
2593
servicetype = "_mandos._tcp",
2594
protocol = protocol,
2596
if server_settings["interface"]:
2597
service.interface = if_nametoindex(
2598
server_settings["interface"].encode("utf-8"))
2600
global multiprocessing_manager
2601
multiprocessing_manager = multiprocessing.Manager()
1305
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
2603
1307
client_class = Client
2605
client_class = functools.partial(ClientDBus, bus = bus)
2607
client_settings = Client.config_parser(client_config)
2608
old_client_settings = {}
2611
# This is used to redirect stdout and stderr for checker processes
2613
wnull = open(os.devnull, "w") # A writable /dev/null
2614
# Only used if server is running in foreground but not in debug
2616
if debug or not foreground:
2619
# Get client data and settings from last running state.
2620
if server_settings["restore"]:
2622
with open(stored_state_path, "rb") as stored_state:
2623
clients_data, old_client_settings = pickle.load(
2625
os.remove(stored_state_path)
2626
except IOError as e:
2627
if e.errno == errno.ENOENT:
2628
logger.warning("Could not load persistent state:"
2629
" {}".format(os.strerror(e.errno)))
2631
logger.critical("Could not load persistent state:",
2634
except EOFError as e:
2635
logger.warning("Could not load persistent state: "
2639
with PGPEngine() as pgp:
2640
for client_name, client in clients_data.items():
2641
# Skip removed clients
2642
if client_name not in client_settings:
2645
# Decide which value to use after restoring saved state.
2646
# We have three different values: Old config file,
2647
# new config file, and saved state.
2648
# New config value takes precedence if it differs from old
2649
# config value, otherwise use saved state.
2650
for name, value in client_settings[client_name].items():
2652
# For each value in new config, check if it
2653
# differs from the old config value (Except for
2654
# the "secret" attribute)
2655
if (name != "secret"
2657
old_client_settings[client_name][name])):
2658
client[name] = value
2662
# Clients who has passed its expire date can still be
2663
# enabled if its last checker was successful. Clients
2664
# whose checker succeeded before we stored its state is
2665
# assumed to have successfully run all checkers during
2667
if client["enabled"]:
2668
if datetime.datetime.utcnow() >= client["expires"]:
2669
if not client["last_checked_ok"]:
2671
"disabling client {} - Client never "
2672
"performed a successful checker".format(
2674
client["enabled"] = False
2675
elif client["last_checker_status"] != 0:
2677
"disabling client {} - Client last"
2678
" checker failed with error code"
2681
client["last_checker_status"]))
2682
client["enabled"] = False
2684
client["expires"] = (
2685
datetime.datetime.utcnow()
2686
+ client["timeout"])
2687
logger.debug("Last checker succeeded,"
2688
" keeping {} enabled".format(
2691
client["secret"] = pgp.decrypt(
2692
client["encrypted_secret"],
2693
client_settings[client_name]["secret"])
2695
# If decryption fails, we use secret from new settings
2696
logger.debug("Failed to decrypt {} old secret".format(
2698
client["secret"] = (client_settings[client_name]
2701
# Add/remove clients based on new changes made to config
2702
for client_name in (set(old_client_settings)
2703
- set(client_settings)):
2704
del clients_data[client_name]
2705
for client_name in (set(client_settings)
2706
- set(old_client_settings)):
2707
clients_data[client_name] = client_settings[client_name]
2709
# Create all client objects
2710
for client_name, client in clients_data.items():
2711
tcp_server.clients[client_name] = client_class(
2714
server_settings = server_settings)
2716
if not tcp_server.clients:
2717
logger.warning("No clients defined")
2720
if pidfile is not None:
1309
client_class = ClientDBus
1311
client_class(name = section,
1312
config= dict(client_config.items(section)))
1313
for section in client_config.sections()))
1315
logger.warning(u"No clients defined")
1318
# Redirect stdin so all checkers get /dev/null
1319
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1320
os.dup2(null, sys.stdin.fileno())
1324
# No console logging
1325
logger.removeHandler(console)
1326
# Close all input and output, do double fork, etc.
1330
with closing(pidfile):
2721
1331
pid = os.getpid()
2724
print(pid, file=pidfile)
2726
logger.error("Could not write to file %r with PID %d",
1332
pidfile.write(str(pid) + "\n")
1335
logger.error(u"Could not write to file %r with PID %d",
1338
# "pidfile" was never created
1343
"Cleanup function; run on exit"
1345
# From the Avahi example code
1346
if not group is None:
1349
# End of Avahi example code
1352
client = clients.pop()
1353
client.disable_hook = None
1356
atexit.register(cleanup)
1359
signal.signal(signal.SIGINT, signal.SIG_IGN)
2731
1360
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2732
1361
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2736
@alternate_dbus_interfaces(
2737
{ "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
2738
class MandosDBusService(DBusObjectWithProperties):
1364
class MandosDBusService(dbus.service.Object):
2739
1365
"""A D-Bus proxy object"""
2741
1366
def __init__(self):
2742
dbus.service.Object.__init__(self, bus, "/")
2744
_interface = "se.recompile.Mandos"
2746
@dbus_interface_annotations(_interface)
2749
"org.freedesktop.DBus.Property.EmitsChangedSignal":
2752
@dbus.service.signal(_interface, signature="o")
2753
def ClientAdded(self, objpath):
2757
@dbus.service.signal(_interface, signature="ss")
2758
def ClientNotFound(self, fingerprint, address):
2762
@dbus.service.signal(_interface, signature="os")
1367
dbus.service.Object.__init__(self, bus, u"/")
1368
_interface = u"se.bsnet.fukt.Mandos"
1370
@dbus.service.signal(_interface, signature=u"oa{sv}")
1371
def ClientAdded(self, objpath, properties):
1375
@dbus.service.signal(_interface, signature=u"s")
1376
def ClientNotFound(self, fingerprint):
1380
@dbus.service.signal(_interface, signature=u"os")
2763
1381
def ClientRemoved(self, objpath, name):
2767
@dbus.service.method(_interface, out_signature="ao")
1385
@dbus.service.method(_interface, out_signature=u"ao")
2768
1386
def GetAllClients(self):
2770
return dbus.Array(c.dbus_object_path for c in
2771
tcp_server.clients.itervalues())
1388
return dbus.Array(c.dbus_object_path for c in clients)
2773
1390
@dbus.service.method(_interface,
2774
out_signature="a{oa{sv}}")
1391
out_signature=u"a{oa{sv}}")
2775
1392
def GetAllClientsWithProperties(self):
2777
1394
return dbus.Dictionary(
2778
{ c.dbus_object_path: c.GetAll("")
2779
for c in tcp_server.clients.itervalues() },
1395
((c.dbus_object_path, c.GetAllProperties())
1397
signature=u"oa{sv}")
2782
@dbus.service.method(_interface, in_signature="o")
1399
@dbus.service.method(_interface, in_signature=u"o")
2783
1400
def RemoveClient(self, object_path):
2785
for c in tcp_server.clients.itervalues():
2786
1403
if c.dbus_object_path == object_path:
2787
del tcp_server.clients[c.name]
2788
1405
c.remove_from_connection()
2789
1406
# Don't signal anything except ClientRemoved
2790
c.disable(quiet=True)
1407
c.disable(signal=False)
2791
1408
# Emit D-Bus signal
2792
1409
self.ClientRemoved(object_path, c.name)
2794
raise KeyError(object_path)
2798
1415
mandos_dbus_service = MandosDBusService()
2801
"Cleanup function; run on exit"
2805
multiprocessing.active_children()
2807
if not (tcp_server.clients or client_settings):
2810
# Store client before exiting. Secrets are encrypted with key
2811
# based on what config file has. If config file is
2812
# removed/edited, old secret will thus be unrecovable.
2814
with PGPEngine() as pgp:
2815
for client in tcp_server.clients.itervalues():
2816
key = client_settings[client.name]["secret"]
2817
client.encrypted_secret = pgp.encrypt(client.secret,
2821
# A list of attributes that can not be pickled
2823
exclude = { "bus", "changedstate", "secret",
2824
"checker", "server_settings" }
2825
for name, typ in inspect.getmembers(dbus.service
2829
client_dict["encrypted_secret"] = (client
2831
for attr in client.client_structure:
2832
if attr not in exclude:
2833
client_dict[attr] = getattr(client, attr)
2835
clients[client.name] = client_dict
2836
del client_settings[client.name]["secret"]
2839
with tempfile.NamedTemporaryFile(
2843
dir=os.path.dirname(stored_state_path),
2844
delete=False) as stored_state:
2845
pickle.dump((clients, client_settings), stored_state)
2846
tempname = stored_state.name
2847
os.rename(tempname, stored_state_path)
2848
except (IOError, OSError) as e:
2854
if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
2855
logger.warning("Could not save persistent state: {}"
2856
.format(os.strerror(e.errno)))
2858
logger.warning("Could not save persistent state:",
2862
# Delete all clients, and settings from config
2863
while tcp_server.clients:
2864
name, client = tcp_server.clients.popitem()
2866
client.remove_from_connection()
2867
# Don't signal anything except ClientRemoved
2868
client.disable(quiet=True)
2871
mandos_dbus_service.ClientRemoved(
2872
client.dbus_object_path, client.name)
2873
client_settings.clear()
2875
atexit.register(cleanup)
2877
for client in tcp_server.clients.itervalues():
1417
for client in clients:
2879
1419
# Emit D-Bus signal
2880
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2881
# Need to initiate checking of clients
2883
client.init_checker()
1420
mandos_dbus_service.ClientAdded(client.dbus_object_path,
1421
client.GetAllProperties())
2885
1424
tcp_server.enable()
2886
1425
tcp_server.server_activate()
2888
1427
# Find out what port we got
2890
service.port = tcp_server.socket.getsockname()[1]
1428
service.port = tcp_server.socket.getsockname()[1]
2892
logger.info("Now listening on address %r, port %d,"
2893
" flowinfo %d, scope_id %d",
2894
*tcp_server.socket.getsockname())
1430
logger.info(u"Now listening on address %r, port %d,"
1431
" flowinfo %d, scope_id %d"
1432
% tcp_server.socket.getsockname())
2896
logger.info("Now listening on address %r, port %d",
2897
*tcp_server.socket.getsockname())
1434
logger.info(u"Now listening on address %r, port %d"
1435
% tcp_server.socket.getsockname())
2899
1437
#service.interface = tcp_server.socket.getsockname()[3]
2903
# From the Avahi example code
2906
except dbus.exceptions.DBusException as error:
2907
logger.critical("D-Bus Exception", exc_info=error)
2910
# End of Avahi example code
1440
# From the Avahi example code
1441
server.connect_to_signal(u"StateChanged", server_state_changed)
1443
server_state_changed(server.GetState())
1444
except dbus.exceptions.DBusException, error:
1445
logger.critical(u"DBusException: %s", error)
1447
# End of Avahi example code
2912
1449
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2913
1450
lambda *args, **kwargs:
2914
1451
(tcp_server.handle_request
2915
1452
(*args[2:], **kwargs) or True))
2917
logger.debug("Starting main loop")
1454
logger.debug(u"Starting main loop")
2918
1455
main_loop.run()
2919
except AvahiError as error:
2920
logger.critical("Avahi Error", exc_info=error)
1456
except AvahiError, error:
1457
logger.critical(u"AvahiError: %s", error)
2923
1459
except KeyboardInterrupt:
2925
print("", file=sys.stderr)
2926
logger.debug("Server received KeyboardInterrupt")
2927
logger.debug("Server exiting")
2928
# Must run before the D-Bus bus name gets deregistered
1462
logger.debug(u"Server received KeyboardInterrupt")
1463
logger.debug(u"Server exiting")
2932
1465
if __name__ == '__main__':