352
191
dbus.UInt16(self.port),
353
192
avahi.string_array_to_txt_array(self.TXT))
354
193
self.group.Commit()
356
194
def entry_group_state_changed(self, state, error):
357
195
"""Derived from the Avahi example code"""
358
logger.debug("Avahi entry group state change: %i", state)
196
logger.debug(u"Avahi state change: %i", state)
360
198
if state == avahi.ENTRY_GROUP_ESTABLISHED:
361
logger.debug("Zeroconf service established.")
199
logger.debug(u"Zeroconf service established.")
362
200
elif state == avahi.ENTRY_GROUP_COLLISION:
363
logger.info("Zeroconf service name collision.")
201
logger.warning(u"Zeroconf service name collision.")
365
203
elif state == avahi.ENTRY_GROUP_FAILURE:
366
logger.critical("Avahi: Error in group state changed %s",
368
raise AvahiGroupError("State changed: {!s}".format(error))
204
logger.critical(u"Avahi: Error in group state changed %s",
206
raise AvahiGroupError(u"State changed: %s"
370
208
def cleanup(self):
371
209
"""Derived from the Avahi example code"""
372
210
if self.group is not None:
375
except (dbus.exceptions.UnknownMethodException,
376
dbus.exceptions.DBusException):
378
212
self.group = None
381
def server_state_changed(self, state, error=None):
213
def server_state_changed(self, state):
382
214
"""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)
215
if state == avahi.SERVER_COLLISION:
216
logger.error(u"Zeroconf server name collision")
397
218
elif state == avahi.SERVER_RUNNING:
400
except dbus.exceptions.DBusException as error:
401
if (error.get_dbus_name()
402
== "org.freedesktop.Avahi.CollisionError"):
403
logger.info("Local Zeroconf service name"
405
return self.rename(remove=False)
407
logger.critical("D-Bus Exception", exc_info=error)
412
logger.debug("Unknown state: %r", state)
414
logger.debug("Unknown state: %r: %r", state, error)
416
220
def activate(self):
417
221
"""Derived from the Avahi example code"""
418
222
if self.server is None:
419
223
self.server = dbus.Interface(
420
224
self.bus.get_object(avahi.DBUS_NAME,
421
avahi.DBUS_PATH_SERVER,
422
follow_name_owner_changes=True),
225
avahi.DBUS_PATH_SERVER),
423
226
avahi.DBUS_INTERFACE_SERVER)
424
self.server.connect_to_signal("StateChanged",
425
self.server_state_changed)
227
self.server.connect_to_signal(u"StateChanged",
228
self.server_state_changed)
426
229
self.server_state_changed(self.server.GetState())
429
class AvahiServiceToSyslog(AvahiService):
430
def rename(self, *args, **kwargs):
431
"""Add the new name to the syslog messages"""
432
ret = AvahiService.rename(self, *args, **kwargs)
433
syslogger.setFormatter(logging.Formatter(
434
'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
438
def call_pipe(connection, # : multiprocessing.Connection
439
func, *args, **kwargs):
440
"""This function is meant to be called by multiprocessing.Process
442
This function runs func(*args, **kwargs), and writes the resulting
443
return value on the provided multiprocessing.Connection.
445
connection.send(func(*args, **kwargs))
448
232
class Client(object):
449
233
"""A representation of a client host served by this server.
452
approved: bool(); 'None' if not yet approved/disapproved
453
approval_delay: datetime.timedelta(); Time to wait for approval
454
approval_duration: datetime.timedelta(); Duration of one approval
236
name: string; from the config file, used in log messages and
238
fingerprint: string (40 or 32 hexadecimal digits); used to
239
uniquely identify the client
240
secret: bytestring; sent verbatim (over TLS) to client
241
host: string; available for use by the checker command
242
created: datetime.datetime(); (UTC) object creation
243
last_enabled: datetime.datetime(); (UTC)
245
last_checked_ok: datetime.datetime(); (UTC) or None
246
timeout: datetime.timedelta(); How long from last_checked_ok
247
until this client is disabled
248
interval: datetime.timedelta(); How often to start a new checker
249
disable_hook: If set, called by disable() as disable_hook(self)
455
250
checker: subprocess.Popen(); a running checker process used
456
251
to see if the client lives.
457
252
'None' if no process is running.
458
checker_callback_tag: a gobject event source tag, or None
459
checker_command: string; External command which is run to check
460
if client lives. %() expansions are done at
253
checker_initiator_tag: a gobject event source tag, or None
254
disable_initiator_tag: - '' -
255
checker_callback_tag: - '' -
256
checker_command: string; External command which is run to check if
257
client lives. %() expansions are done at
461
258
runtime with vars(self) as dict, so that for
462
259
instance %(name)s can be used in the command.
463
checker_initiator_tag: a gobject event source tag, or None
464
created: datetime.datetime(); (UTC) object creation
465
client_structure: Object describing what attributes a client has
466
and is used for storing the client at exit
467
260
current_checker_command: string; current running checker_command
468
disable_initiator_tag: a gobject event source tag, or None
470
fingerprint: string (40 or 32 hexadecimal digits); used to
471
uniquely identify the client
472
host: string; available for use by the checker command
473
interval: datetime.timedelta(); How often to start a new checker
474
last_approval_request: datetime.datetime(); (UTC) or None
475
last_checked_ok: datetime.datetime(); (UTC) or None
476
last_checker_status: integer between 0 and 255 reflecting exit
477
status of last checker. -1 reflects crashed
478
checker, -2 means no checker completed yet.
479
last_checker_signal: The signal which killed the last checker, if
480
last_checker_status is -1
481
last_enabled: datetime.datetime(); (UTC) or None
482
name: string; from the config file, used in log messages and
484
secret: bytestring; sent verbatim (over TLS) to client
485
timeout: datetime.timedelta(); How long from last_checked_ok
486
until this client is disabled
487
extended_timeout: extra long timeout when secret has been sent
488
runtime_expansions: Allowed attributes for runtime expansion.
489
expires: datetime.datetime(); time (UTC) when a client will be
491
server_settings: The server_settings dict from main()
261
approved_delay: datetime.timedelta(); Time to wait for approval
262
_approved: bool(); 'None' if not yet approved/disapproved
263
approved_duration: datetime.timedelta(); Duration of one approval
494
runtime_expansions = ("approval_delay", "approval_duration",
495
"created", "enabled", "expires",
496
"fingerprint", "host", "interval",
497
"last_approval_request", "last_checked_ok",
498
"last_enabled", "name", "timeout")
501
"extended_timeout": "PT15M",
503
"checker": "fping -q -- %%(host)s",
505
"approval_delay": "PT0S",
506
"approval_duration": "PT1S",
507
"approved_by_default": "True",
512
def config_parser(config):
513
"""Construct a new dict of client settings of this form:
514
{ client_name: {setting_name: value, ...}, ...}
515
with exceptions for any special settings as defined above.
516
NOTE: Must be a pure function. Must return the same result
517
value given the same arguments.
520
for client_name in config.sections():
521
section = dict(config.items(client_name))
522
client = settings[client_name] = {}
524
client["host"] = section["host"]
525
# Reformat values from string types to Python types
526
client["approved_by_default"] = config.getboolean(
527
client_name, "approved_by_default")
528
client["enabled"] = config.getboolean(client_name,
531
# Uppercase and remove spaces from fingerprint for later
532
# comparison purposes with return value from the
533
# fingerprint() function
534
client["fingerprint"] = (section["fingerprint"].upper()
536
if "secret" in section:
537
client["secret"] = section["secret"].decode("base64")
538
elif "secfile" in section:
539
with open(os.path.expanduser(os.path.expandvars
540
(section["secfile"])),
542
client["secret"] = secfile.read()
544
raise TypeError("No secret or secfile for section {}"
546
client["timeout"] = string_to_delta(section["timeout"])
547
client["extended_timeout"] = string_to_delta(
548
section["extended_timeout"])
549
client["interval"] = string_to_delta(section["interval"])
550
client["approval_delay"] = string_to_delta(
551
section["approval_delay"])
552
client["approval_duration"] = string_to_delta(
553
section["approval_duration"])
554
client["checker_command"] = section["checker"]
555
client["last_approval_request"] = None
556
client["last_checked_ok"] = None
557
client["last_checker_status"] = -2
561
def __init__(self, settings, name = None, server_settings=None):
267
def _timedelta_to_milliseconds(td):
268
"Convert a datetime.timedelta() to milliseconds"
269
return ((td.days * 24 * 60 * 60 * 1000)
270
+ (td.seconds * 1000)
271
+ (td.microseconds // 1000))
273
def timeout_milliseconds(self):
274
"Return the 'timeout' attribute in milliseconds"
275
return self._timedelta_to_milliseconds(self.timeout)
277
def interval_milliseconds(self):
278
"Return the 'interval' attribute in milliseconds"
279
return self._timedelta_to_milliseconds(self.interval)
281
def approved_delay_milliseconds(self):
282
return self._timedelta_to_milliseconds(self.approved_delay)
284
def __init__(self, name = None, disable_hook=None, config=None):
285
"""Note: the 'checker' key in 'config' sets the
286
'checker_command' attribute and *not* the 'checker'
563
if server_settings is None:
565
self.server_settings = server_settings
566
# adding all client settings
567
for setting, value in settings.items():
568
setattr(self, setting, value)
571
if not hasattr(self, "last_enabled"):
572
self.last_enabled = datetime.datetime.utcnow()
573
if not hasattr(self, "expires"):
574
self.expires = (datetime.datetime.utcnow()
291
logger.debug(u"Creating client %r", self.name)
292
# Uppercase and remove spaces from fingerprint for later
293
# comparison purposes with return value from the fingerprint()
295
self.fingerprint = (config[u"fingerprint"].upper()
297
logger.debug(u" Fingerprint: %s", self.fingerprint)
298
if u"secret" in config:
299
self.secret = config[u"secret"].decode(u"base64")
300
elif u"secfile" in config:
301
with open(os.path.expanduser(os.path.expandvars
302
(config[u"secfile"])),
304
self.secret = secfile.read()
577
self.last_enabled = None
580
logger.debug("Creating client %r", self.name)
581
logger.debug(" Fingerprint: %s", self.fingerprint)
582
self.created = settings.get("created",
583
datetime.datetime.utcnow())
585
# attributes specific for this server instance
306
#XXX Need to allow secret on demand!
307
raise TypeError(u"No secret or secfile for client %s"
309
self.host = config.get(u"host", u"")
310
self.created = datetime.datetime.utcnow()
312
self.last_enabled = None
313
self.last_checked_ok = None
314
self.timeout = string_to_delta(config[u"timeout"])
315
self.interval = string_to_delta(config[u"interval"])
316
self.disable_hook = disable_hook
586
317
self.checker = None
587
318
self.checker_initiator_tag = None
588
319
self.disable_initiator_tag = None
589
320
self.checker_callback_tag = None
321
self.checker_command = config[u"checker"]
590
322
self.current_checker_command = None
323
self.last_connect = None
592
324
self.approvals_pending = 0
593
self.changedstate = multiprocessing_manager.Condition(
594
multiprocessing_manager.Lock())
595
self.client_structure = [attr
596
for attr in self.__dict__.iterkeys()
597
if not attr.startswith("_")]
598
self.client_structure.append("client_structure")
600
for name, t in inspect.getmembers(
601
type(self), lambda obj: isinstance(obj, property)):
602
if not name.startswith("_"):
603
self.client_structure.append(name)
605
# Send notice to process children that client state has changed
325
self._approved = None
326
self.approved_by_default = config.get(u"approved_by_default",
328
self.approved_delay = string_to_delta(
329
config[u"approved_delay"])
330
self.approved_duration = string_to_delta(
331
config[u"approved_duration"])
332
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
606
334
def send_changedstate(self):
607
with self.changedstate:
608
self.changedstate.notify_all()
335
self.changedstate.acquire()
336
self.changedstate.notify_all()
337
self.changedstate.release()
610
339
def enable(self):
611
340
"""Start this client's checker and timeout hooks"""
612
if getattr(self, "enabled", False):
341
if getattr(self, u"enabled", False):
613
342
# Already enabled
615
self.expires = datetime.datetime.utcnow() + self.timeout
344
self.send_changedstate()
617
345
self.last_enabled = datetime.datetime.utcnow()
619
self.send_changedstate()
346
# Schedule a new checker to be started an 'interval' from now,
347
# and every interval from then on.
348
self.checker_initiator_tag = (gobject.timeout_add
349
(self.interval_milliseconds(),
351
# Schedule a disable() when 'timeout' has passed
352
self.disable_initiator_tag = (gobject.timeout_add
353
(self.timeout_milliseconds(),
356
# Also start a new checker *right now*.
621
359
def disable(self, quiet=True):
622
360
"""Disable this client."""
623
361
if not getattr(self, "enabled", False):
626
logger.info("Disabling client %s", self.name)
627
if getattr(self, "disable_initiator_tag", None) is not None:
364
self.send_changedstate()
366
logger.info(u"Disabling client %s", self.name)
367
if getattr(self, u"disable_initiator_tag", False):
628
368
gobject.source_remove(self.disable_initiator_tag)
629
369
self.disable_initiator_tag = None
631
if getattr(self, "checker_initiator_tag", None) is not None:
370
if getattr(self, u"checker_initiator_tag", False):
632
371
gobject.source_remove(self.checker_initiator_tag)
633
372
self.checker_initiator_tag = None
634
373
self.stop_checker()
374
if self.disable_hook:
375
self.disable_hook(self)
635
376
self.enabled = False
637
self.send_changedstate()
638
377
# Do not run this again if called by a gobject.timeout_add
641
380
def __del__(self):
381
self.disable_hook = None
644
def init_checker(self):
645
# Schedule a new checker to be started an 'interval' from now,
646
# and every interval from then on.
647
if self.checker_initiator_tag is not None:
648
gobject.source_remove(self.checker_initiator_tag)
649
self.checker_initiator_tag = gobject.timeout_add(
650
int(self.interval.total_seconds() * 1000),
652
# Schedule a disable() when 'timeout' has passed
653
if self.disable_initiator_tag is not None:
654
gobject.source_remove(self.disable_initiator_tag)
655
self.disable_initiator_tag = gobject.timeout_add(
656
int(self.timeout.total_seconds() * 1000), self.disable)
657
# Also start a new checker *right now*.
660
def checker_callback(self, source, condition, connection,
384
def checker_callback(self, pid, condition, command):
662
385
"""The checker has completed, so take appropriate actions."""
663
386
self.checker_callback_tag = None
664
387
self.checker = None
665
# Read return code from connection (see call_pipe)
666
returncode = connection.recv()
670
self.last_checker_status = returncode
671
self.last_checker_signal = None
672
if self.last_checker_status == 0:
673
logger.info("Checker for %(name)s succeeded",
388
if os.WIFEXITED(condition):
389
exitstatus = os.WEXITSTATUS(condition)
391
logger.info(u"Checker for %(name)s succeeded",
675
393
self.checked_ok()
677
logger.info("Checker for %(name)s failed", vars(self))
395
logger.info(u"Checker for %(name)s failed",
679
self.last_checker_status = -1
680
self.last_checker_signal = -returncode
681
logger.warning("Checker for %(name)s crashed?",
398
logger.warning(u"Checker for %(name)s crashed?",
685
401
def checked_ok(self):
686
"""Assert that the client has been seen, alive and well."""
402
"""Bump up the timeout for this client.
404
This should only be called when the client has been seen,
687
407
self.last_checked_ok = datetime.datetime.utcnow()
688
self.last_checker_status = 0
689
self.last_checker_signal = None
692
def bump_timeout(self, timeout=None):
693
"""Bump up the timeout for this client."""
695
timeout = self.timeout
696
if self.disable_initiator_tag is not None:
697
gobject.source_remove(self.disable_initiator_tag)
698
self.disable_initiator_tag = None
699
if getattr(self, "enabled", False):
700
self.disable_initiator_tag = gobject.timeout_add(
701
int(timeout.total_seconds() * 1000), self.disable)
702
self.expires = datetime.datetime.utcnow() + timeout
704
def need_approval(self):
705
self.last_approval_request = datetime.datetime.utcnow()
408
gobject.source_remove(self.disable_initiator_tag)
409
self.disable_initiator_tag = (gobject.timeout_add
410
(self.timeout_milliseconds(),
707
413
def start_checker(self):
708
414
"""Start a new checker subprocess if one is not running.
874
class DBusObjectWithAnnotations(dbus.service.Object):
875
"""A D-Bus object with annotations.
877
Classes inheriting from this can use the dbus_annotations
878
decorator to add annotations to methods or signals.
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
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
902
path_keyword = 'object_path',
903
connection_keyword = 'connection')
904
def Introspect(self, object_path, connection):
905
"""Overloading of standard D-Bus method.
907
Inserts annotation tags on methods and signals.
909
xmlstring = dbus.service.Object.Introspect(self, object_path,
912
document = xml.dom.minidom.parseString(xmlstring)
914
for if_tag in document.getElementsByTagName("interface"):
915
# Add annotation tags
916
for typ in ("method", "signal"):
917
for tag in if_tag.getElementsByTagName(typ):
919
for name, prop in (self.
920
_get_all_dbus_things(typ)):
921
if (name == tag.getAttribute("name")
922
and prop._dbus_interface
923
== if_tag.getAttribute("name")):
924
annots.update(getattr(
925
prop, "_dbus_annotations", {}))
926
for name, value in annots.items():
927
ann_tag = document.createElement(
929
ann_tag.setAttribute("name", name)
930
ann_tag.setAttribute("value", value)
931
tag.appendChild(ann_tag)
932
# Add interface annotation tags
933
for annotation, value in dict(
934
itertools.chain.from_iterable(
935
annotations().items()
936
for name, annotations
937
in self._get_all_dbus_things("interface")
938
if name == if_tag.getAttribute("name")
940
ann_tag = document.createElement("annotation")
941
ann_tag.setAttribute("name", annotation)
942
ann_tag.setAttribute("value", value)
943
if_tag.appendChild(ann_tag)
944
# Fix argument name for the Introspect method itself
945
if (if_tag.getAttribute("name")
946
== dbus.INTROSPECTABLE_IFACE):
947
for cn in if_tag.getElementsByTagName("method"):
948
if cn.getAttribute("name") == "Introspect":
949
for arg in cn.getElementsByTagName("arg"):
950
if (arg.getAttribute("direction")
952
arg.setAttribute("name",
954
xmlstring = document.toxml("utf-8")
956
except (AttributeError, xml.dom.DOMException,
957
xml.parsers.expat.ExpatError) as error:
958
logger.error("Failed to override Introspection method",
963
class DBusObjectWithProperties(DBusObjectWithAnnotations):
553
class DBusObjectWithProperties(dbus.service.Object):
964
554
"""A D-Bus object with properties.
966
556
Classes inheriting from this can use the dbus_service_property
967
557
decorator to expose methods as D-Bus properties. It exposes the
968
558
standard Get(), Set(), and GetAll() methods on the D-Bus.
562
def _is_dbus_property(obj):
563
return getattr(obj, u"_dbus_is_property", False)
565
def _get_all_dbus_properties(self):
566
"""Returns a generator of (name, attribute) pairs
568
return ((prop._dbus_name, prop)
570
inspect.getmembers(self, self._is_dbus_property))
971
572
def _get_dbus_property(self, interface_name, property_name):
972
573
"""Returns a bound method if one exists which is a D-Bus
973
574
property with the specified name and interface.
975
for cls in self.__class__.__mro__:
976
for name, value in inspect.getmembers(
977
cls, self._is_dbus_thing("property")):
978
if (value._dbus_name == property_name
979
and value._dbus_interface == interface_name):
980
return value.__get__(self)
576
for name in (property_name,
577
property_name + u"_dbus_property"):
578
prop = getattr(self, name, None)
580
or not self._is_dbus_property(prop)
581
or prop._dbus_name != property_name
582
or (interface_name and prop._dbus_interface
583
and interface_name != prop._dbus_interface)):
982
586
# No such property
983
raise DBusPropertyNotFound("{}:{}.{}".format(
984
self.dbus_object_path, interface_name, property_name))
987
def _get_all_interface_names(cls):
988
"""Get a sequence of all interfaces supported by an object"""
989
return (name for name in set(getattr(getattr(x, attr),
990
"_dbus_interface", None)
991
for x in (inspect.getmro(cls))
995
@dbus.service.method(dbus.PROPERTIES_IFACE,
587
raise DBusPropertyNotFound(self.dbus_object_path + u":"
588
+ interface_name + u"."
591
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
998
593
def Get(self, interface_name, property_name):
999
594
"""Standard D-Bus property Get() method, see D-Bus standard.
1001
596
prop = self._get_dbus_property(interface_name, property_name)
1002
if prop._dbus_access == "write":
597
if prop._dbus_access == u"write":
1003
598
raise DBusPropertyAccessException(property_name)
1005
if not hasattr(value, "variant_level"):
600
if not hasattr(value, u"variant_level"):
1007
602
return type(value)(value, variant_level=value.variant_level+1)
1009
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
604
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
1010
605
def Set(self, interface_name, property_name, value):
1011
606
"""Standard D-Bus property Set() method, see D-Bus standard.
1013
608
prop = self._get_dbus_property(interface_name, property_name)
1014
if prop._dbus_access == "read":
609
if prop._dbus_access == u"read":
1015
610
raise DBusPropertyAccessException(property_name)
1016
if prop._dbus_get_args_options["byte_arrays"]:
611
if prop._dbus_get_args_options[u"byte_arrays"]:
1017
612
# The byte_arrays option is not supported yet on
1018
613
# signatures other than "ay".
1019
if prop._dbus_signature != "ay":
1020
raise ValueError("Byte arrays not supported for non-"
1021
"'ay' signature {!r}"
1022
.format(prop._dbus_signature))
1023
value = dbus.ByteArray(b''.join(chr(byte)
614
if prop._dbus_signature != u"ay":
616
value = dbus.ByteArray(''.join(unichr(byte)
1027
@dbus.service.method(dbus.PROPERTIES_IFACE,
1029
out_signature="a{sv}")
620
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
621
out_signature=u"a{sv}")
1030
622
def GetAll(self, interface_name):
1031
623
"""Standard D-Bus property GetAll() method, see D-Bus
1034
626
Note: Will not include properties with access="write".
1037
for name, prop in self._get_all_dbus_things("property"):
629
for name, prop in self._get_all_dbus_properties():
1038
630
if (interface_name
1039
631
and interface_name != prop._dbus_interface):
1040
632
# Interface non-empty but did not match
1042
634
# Ignore write-only properties
1043
if prop._dbus_access == "write":
635
if prop._dbus_access == u"write":
1046
if not hasattr(value, "variant_level"):
1047
properties[name] = value
638
if not hasattr(value, u"variant_level"):
1049
properties[name] = type(value)(
1050
value, variant_level = value.variant_level + 1)
1051
return dbus.Dictionary(properties, signature="sv")
1053
@dbus.service.signal(dbus.PROPERTIES_IFACE, signature="sa{sv}as")
1054
def PropertiesChanged(self, interface_name, changed_properties,
1055
invalidated_properties):
1056
"""Standard D-Bus PropertiesChanged() signal, see D-Bus
641
all[name] = type(value)(value, variant_level=
642
value.variant_level+1)
643
return dbus.Dictionary(all, signature=u"sv")
1061
645
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1063
647
path_keyword='object_path',
1064
648
connection_keyword='connection')
1065
649
def Introspect(self, object_path, connection):
1066
"""Overloading of standard D-Bus method.
1068
Inserts property tags and interface annotation tags.
650
"""Standard D-Bus method, overloaded to insert property tags.
1070
xmlstring = DBusObjectWithAnnotations.Introspect(self,
652
xmlstring = dbus.service.Object.Introspect(self, object_path,
1074
655
document = xml.dom.minidom.parseString(xmlstring)
1076
656
def make_tag(document, name, prop):
1077
e = document.createElement("property")
1078
e.setAttribute("name", name)
1079
e.setAttribute("type", prop._dbus_signature)
1080
e.setAttribute("access", prop._dbus_access)
657
e = document.createElement(u"property")
658
e.setAttribute(u"name", name)
659
e.setAttribute(u"type", prop._dbus_signature)
660
e.setAttribute(u"access", prop._dbus_access)
1083
for if_tag in document.getElementsByTagName("interface"):
662
for if_tag in document.getElementsByTagName(u"interface"):
1085
663
for tag in (make_tag(document, name, prop)
1087
in self._get_all_dbus_things("property")
665
in self._get_all_dbus_properties()
1088
666
if prop._dbus_interface
1089
== if_tag.getAttribute("name")):
667
== if_tag.getAttribute(u"name")):
1090
668
if_tag.appendChild(tag)
1091
# Add annotation tags for properties
1092
for tag in if_tag.getElementsByTagName("property"):
1094
for name, prop in self._get_all_dbus_things(
1096
if (name == tag.getAttribute("name")
1097
and prop._dbus_interface
1098
== if_tag.getAttribute("name")):
1099
annots.update(getattr(
1100
prop, "_dbus_annotations", {}))
1101
for name, value in annots.items():
1102
ann_tag = document.createElement(
1104
ann_tag.setAttribute("name", name)
1105
ann_tag.setAttribute("value", value)
1106
tag.appendChild(ann_tag)
1107
669
# Add the names to the return values for the
1108
670
# "org.freedesktop.DBus.Properties" methods
1109
if (if_tag.getAttribute("name")
1110
== "org.freedesktop.DBus.Properties"):
1111
for cn in if_tag.getElementsByTagName("method"):
1112
if cn.getAttribute("name") == "Get":
1113
for arg in cn.getElementsByTagName("arg"):
1114
if (arg.getAttribute("direction")
1116
arg.setAttribute("name", "value")
1117
elif cn.getAttribute("name") == "GetAll":
1118
for arg in cn.getElementsByTagName("arg"):
1119
if (arg.getAttribute("direction")
1121
arg.setAttribute("name", "props")
1122
xmlstring = document.toxml("utf-8")
1124
except (AttributeError, xml.dom.DOMException,
1125
xml.parsers.expat.ExpatError) as error:
1126
logger.error("Failed to override Introspection method",
1131
dbus.OBJECT_MANAGER_IFACE
1132
except AttributeError:
1133
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1135
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1136
"""A D-Bus object with an ObjectManager.
1138
Classes inheriting from this exposes the standard
1139
GetManagedObjects call and the InterfacesAdded and
1140
InterfacesRemoved signals on the standard
1141
"org.freedesktop.DBus.ObjectManager" interface.
1143
Note: No signals are sent automatically; they must be sent
1146
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1147
out_signature = "a{oa{sa{sv}}}")
1148
def GetManagedObjects(self):
1149
"""This function must be overridden"""
1150
raise NotImplementedError()
1152
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1153
signature = "oa{sa{sv}}")
1154
def InterfacesAdded(self, object_path, interfaces_and_properties):
1157
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
1158
def InterfacesRemoved(self, object_path, interfaces):
1161
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1162
out_signature = "s",
1163
path_keyword = 'object_path',
1164
connection_keyword = 'connection')
1165
def Introspect(self, object_path, connection):
1166
"""Overloading of standard D-Bus method.
1168
Override return argument name of GetManagedObjects to be
1169
"objpath_interfaces_and_properties"
1171
xmlstring = DBusObjectWithAnnotations.Introspect(self,
1175
document = xml.dom.minidom.parseString(xmlstring)
1177
for if_tag in document.getElementsByTagName("interface"):
1178
# Fix argument name for the GetManagedObjects method
1179
if (if_tag.getAttribute("name")
1180
== dbus.OBJECT_MANAGER_IFACE):
1181
for cn in if_tag.getElementsByTagName("method"):
1182
if (cn.getAttribute("name")
1183
== "GetManagedObjects"):
1184
for arg in cn.getElementsByTagName("arg"):
1185
if (arg.getAttribute("direction")
1189
"objpath_interfaces"
1191
xmlstring = document.toxml("utf-8")
1193
except (AttributeError, xml.dom.DOMException,
1194
xml.parsers.expat.ExpatError) as error:
1195
logger.error("Failed to override Introspection method",
1199
def datetime_to_dbus(dt, variant_level=0):
1200
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1202
return dbus.String("", variant_level = variant_level)
1203
return dbus.String(dt.isoformat(), variant_level=variant_level)
1206
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1207
"""A class decorator; applied to a subclass of
1208
dbus.service.Object, it will add alternate D-Bus attributes with
1209
interface names according to the "alt_interface_names" mapping.
1212
@alternate_dbus_interfaces({"org.example.Interface":
1213
"net.example.AlternateInterface"})
1214
class SampleDBusObject(dbus.service.Object):
1215
@dbus.service.method("org.example.Interface")
1216
def SampleDBusMethod():
1219
The above "SampleDBusMethod" on "SampleDBusObject" will be
1220
reachable via two interfaces: "org.example.Interface" and
1221
"net.example.AlternateInterface", the latter of which will have
1222
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1223
"true", unless "deprecate" is passed with a False value.
1225
This works for methods and signals, and also for D-Bus properties
1226
(from DBusObjectWithProperties) and interfaces (from the
1227
dbus_interface_annotations decorator).
1231
for orig_interface_name, alt_interface_name in (
1232
alt_interface_names.items()):
1234
interface_names = set()
1235
# Go though all attributes of the class
1236
for attrname, attribute in inspect.getmembers(cls):
1237
# Ignore non-D-Bus attributes, and D-Bus attributes
1238
# with the wrong interface name
1239
if (not hasattr(attribute, "_dbus_interface")
1240
or not attribute._dbus_interface.startswith(
1241
orig_interface_name)):
1243
# Create an alternate D-Bus interface name based on
1245
alt_interface = attribute._dbus_interface.replace(
1246
orig_interface_name, alt_interface_name)
1247
interface_names.add(alt_interface)
1248
# Is this a D-Bus signal?
1249
if getattr(attribute, "_dbus_is_signal", False):
1250
if sys.version_info.major == 2:
1251
# Extract the original non-method undecorated
1252
# function by black magic
1253
nonmethod_func = (dict(
1254
zip(attribute.func_code.co_freevars,
1255
attribute.__closure__))
1256
["func"].cell_contents)
1258
nonmethod_func = attribute
1259
# Create a new, but exactly alike, function
1260
# object, and decorate it to be a new D-Bus signal
1261
# with the alternate D-Bus interface name
1262
if sys.version_info.major == 2:
1263
new_function = types.FunctionType(
1264
nonmethod_func.func_code,
1265
nonmethod_func.func_globals,
1266
nonmethod_func.func_name,
1267
nonmethod_func.func_defaults,
1268
nonmethod_func.func_closure)
1270
new_function = types.FunctionType(
1271
nonmethod_func.__code__,
1272
nonmethod_func.__globals__,
1273
nonmethod_func.__name__,
1274
nonmethod_func.__defaults__,
1275
nonmethod_func.__closure__)
1276
new_function = (dbus.service.signal(
1278
attribute._dbus_signature)(new_function))
1279
# Copy annotations, if any
1281
new_function._dbus_annotations = dict(
1282
attribute._dbus_annotations)
1283
except AttributeError:
1285
# Define a creator of a function to call both the
1286
# original and alternate functions, so both the
1287
# original and alternate signals gets sent when
1288
# the function is called
1289
def fixscope(func1, func2):
1290
"""This function is a scope container to pass
1291
func1 and func2 to the "call_both" function
1292
outside of its arguments"""
1294
@functools.wraps(func2)
1295
def call_both(*args, **kwargs):
1296
"""This function will emit two D-Bus
1297
signals by calling func1 and func2"""
1298
func1(*args, **kwargs)
1299
func2(*args, **kwargs)
1300
# Make wrapper function look like a D-Bus signal
1301
for name, attr in inspect.getmembers(func2):
1302
if name.startswith("_dbus_"):
1303
setattr(call_both, name, attr)
1306
# Create the "call_both" function and add it to
1308
attr[attrname] = fixscope(attribute, new_function)
1309
# Is this a D-Bus method?
1310
elif getattr(attribute, "_dbus_is_method", False):
1311
# Create a new, but exactly alike, function
1312
# object. Decorate it to be a new D-Bus method
1313
# with the alternate D-Bus interface name. Add it
1316
dbus.service.method(
1318
attribute._dbus_in_signature,
1319
attribute._dbus_out_signature)
1320
(types.FunctionType(attribute.func_code,
1321
attribute.func_globals,
1322
attribute.func_name,
1323
attribute.func_defaults,
1324
attribute.func_closure)))
1325
# Copy annotations, if any
1327
attr[attrname]._dbus_annotations = dict(
1328
attribute._dbus_annotations)
1329
except AttributeError:
1331
# Is this a D-Bus property?
1332
elif getattr(attribute, "_dbus_is_property", False):
1333
# Create a new, but exactly alike, function
1334
# object, and decorate it to be a new D-Bus
1335
# property with the alternate D-Bus interface
1336
# name. Add it to the class.
1337
attr[attrname] = (dbus_service_property(
1338
alt_interface, attribute._dbus_signature,
1339
attribute._dbus_access,
1340
attribute._dbus_get_args_options
1342
(types.FunctionType(
1343
attribute.func_code,
1344
attribute.func_globals,
1345
attribute.func_name,
1346
attribute.func_defaults,
1347
attribute.func_closure)))
1348
# Copy annotations, if any
1350
attr[attrname]._dbus_annotations = dict(
1351
attribute._dbus_annotations)
1352
except AttributeError:
1354
# Is this a D-Bus interface?
1355
elif getattr(attribute, "_dbus_is_interface", False):
1356
# Create a new, but exactly alike, function
1357
# object. Decorate it to be a new D-Bus interface
1358
# with the alternate D-Bus interface name. Add it
1361
dbus_interface_annotations(alt_interface)
1362
(types.FunctionType(attribute.func_code,
1363
attribute.func_globals,
1364
attribute.func_name,
1365
attribute.func_defaults,
1366
attribute.func_closure)))
1368
# Deprecate all alternate interfaces
1369
iname="_AlternateDBusNames_interface_annotation{}"
1370
for interface_name in interface_names:
1372
@dbus_interface_annotations(interface_name)
1374
return { "org.freedesktop.DBus.Deprecated":
1376
# Find an unused name
1377
for aname in (iname.format(i)
1378
for i in itertools.count()):
1379
if aname not in attr:
1383
# Replace the class with a new subclass of it with
1384
# methods, signals, etc. as created above.
1385
cls = type(b"{}Alternate".format(cls.__name__),
1392
@alternate_dbus_interfaces({"se.recompile.Mandos":
1393
"se.bsnet.fukt.Mandos"})
671
if (if_tag.getAttribute(u"name")
672
== u"org.freedesktop.DBus.Properties"):
673
for cn in if_tag.getElementsByTagName(u"method"):
674
if cn.getAttribute(u"name") == u"Get":
675
for arg in cn.getElementsByTagName(u"arg"):
676
if (arg.getAttribute(u"direction")
678
arg.setAttribute(u"name", u"value")
679
elif cn.getAttribute(u"name") == u"GetAll":
680
for arg in cn.getElementsByTagName(u"arg"):
681
if (arg.getAttribute(u"direction")
683
arg.setAttribute(u"name", u"props")
684
xmlstring = document.toxml(u"utf-8")
686
except (AttributeError, xml.dom.DOMException,
687
xml.parsers.expat.ExpatError), error:
688
logger.error(u"Failed to override Introspection method",
1394
693
class ClientDBus(Client, DBusObjectWithProperties):
1395
694
"""A Client class using D-Bus
1411
704
Client.__init__(self, *args, **kwargs)
1412
705
# Only now, when this client is initialized, can it show up on
1414
client_object_name = str(self.name).translate(
1415
{ord("."): ord("_"),
1416
ord("-"): ord("_")})
1417
self.dbus_object_path = dbus.ObjectPath(
1418
"/clients/" + client_object_name)
707
self.dbus_object_path = (dbus.ObjectPath
709
+ self.name.replace(u".", u"_")))
1419
710
DBusObjectWithProperties.__init__(self, self.bus,
1420
711
self.dbus_object_path)
1422
def notifychangeproperty(transform_func, dbus_name,
1423
type_func=lambda x: x,
1425
invalidate_only=False,
1426
_interface=_interface):
1427
""" Modify a variable so that it's a property which announces
1428
its changes to DBus.
1430
transform_fun: Function that takes a value and a variant_level
1431
and transforms it to a D-Bus type.
1432
dbus_name: D-Bus name of the variable
1433
type_func: Function that transform the value before sending it
1434
to the D-Bus. Default: no transform
1435
variant_level: D-Bus variant level. Default: 1
1437
attrname = "_{}".format(dbus_name)
1439
def setter(self, value):
1440
if hasattr(self, "dbus_object_path"):
1441
if (not hasattr(self, attrname) or
1442
type_func(getattr(self, attrname, None))
1443
!= type_func(value)):
1445
self.PropertiesChanged(
1446
_interface, dbus.Dictionary(),
1447
dbus.Array((dbus_name, )))
1449
dbus_value = transform_func(
1451
variant_level = variant_level)
1452
self.PropertyChanged(dbus.String(dbus_name),
1454
self.PropertiesChanged(
1456
dbus.Dictionary({ dbus.String(dbus_name):
1459
setattr(self, attrname, value)
1461
return property(lambda self: getattr(self, attrname), setter)
1463
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1464
approvals_pending = notifychangeproperty(dbus.Boolean,
1467
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1468
last_enabled = notifychangeproperty(datetime_to_dbus,
1470
checker = notifychangeproperty(
1471
dbus.Boolean, "CheckerRunning",
1472
type_func = lambda checker: checker is not None)
1473
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1475
last_checker_status = notifychangeproperty(dbus.Int16,
1476
"LastCheckerStatus")
1477
last_approval_request = notifychangeproperty(
1478
datetime_to_dbus, "LastApprovalRequest")
1479
approved_by_default = notifychangeproperty(dbus.Boolean,
1480
"ApprovedByDefault")
1481
approval_delay = notifychangeproperty(
1482
dbus.UInt64, "ApprovalDelay",
1483
type_func = lambda td: td.total_seconds() * 1000)
1484
approval_duration = notifychangeproperty(
1485
dbus.UInt64, "ApprovalDuration",
1486
type_func = lambda td: td.total_seconds() * 1000)
1487
host = notifychangeproperty(dbus.String, "Host")
1488
timeout = notifychangeproperty(
1489
dbus.UInt64, "Timeout",
1490
type_func = lambda td: td.total_seconds() * 1000)
1491
extended_timeout = notifychangeproperty(
1492
dbus.UInt64, "ExtendedTimeout",
1493
type_func = lambda td: td.total_seconds() * 1000)
1494
interval = notifychangeproperty(
1495
dbus.UInt64, "Interval",
1496
type_func = lambda td: td.total_seconds() * 1000)
1497
checker_command = notifychangeproperty(dbus.String, "Checker")
1498
secret = notifychangeproperty(dbus.ByteArray, "Secret",
1499
invalidate_only=True)
1501
del notifychangeproperty
714
def _datetime_to_dbus(dt, variant_level=0):
715
"""Convert a UTC datetime.datetime() to a D-Bus type."""
716
return dbus.String(dt.isoformat(),
717
variant_level=variant_level)
720
oldstate = getattr(self, u"enabled", False)
721
r = Client.enable(self)
722
if oldstate != self.enabled:
724
self.PropertyChanged(dbus.String(u"enabled"),
725
dbus.Boolean(True, variant_level=1))
726
self.PropertyChanged(
727
dbus.String(u"last_enabled"),
728
self._datetime_to_dbus(self.last_enabled,
732
def disable(self, quiet = False):
733
oldstate = getattr(self, u"enabled", False)
734
r = Client.disable(self, quiet=quiet)
735
if not quiet and oldstate != self.enabled:
737
self.PropertyChanged(dbus.String(u"enabled"),
738
dbus.Boolean(False, variant_level=1))
1503
741
def __del__(self, *args, **kwargs):
1505
743
self.remove_from_connection()
1506
744
except LookupError:
1508
if hasattr(DBusObjectWithProperties, "__del__"):
746
if hasattr(DBusObjectWithProperties, u"__del__"):
1509
747
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1510
748
Client.__del__(self, *args, **kwargs)
1512
def checker_callback(self, source, condition,
1513
connection, command, *args, **kwargs):
1514
ret = Client.checker_callback(self, source, condition,
1515
connection, command, *args,
1517
exitstatus = self.last_checker_status
750
def checker_callback(self, pid, condition, command,
752
self.checker_callback_tag = None
755
self.PropertyChanged(dbus.String(u"checker_running"),
756
dbus.Boolean(False, variant_level=1))
757
if os.WIFEXITED(condition):
758
exitstatus = os.WEXITSTATUS(condition)
1519
759
# Emit D-Bus signal
1520
760
self.CheckerCompleted(dbus.Int16(exitstatus),
1521
# This is specific to GNU libC
1522
dbus.Int64(exitstatus << 8),
761
dbus.Int64(condition),
1523
762
dbus.String(command))
1525
764
# Emit D-Bus signal
1526
765
self.CheckerCompleted(dbus.Int16(-1),
1528
# This is specific to GNU libC
1530
| self.last_checker_signal),
766
dbus.Int64(condition),
1531
767
dbus.String(command))
769
return Client.checker_callback(self, pid, condition, command,
772
def checked_ok(self, *args, **kwargs):
773
r = Client.checked_ok(self, *args, **kwargs)
775
self.PropertyChanged(
776
dbus.String(u"last_checked_ok"),
777
(self._datetime_to_dbus(self.last_checked_ok,
1534
781
def start_checker(self, *args, **kwargs):
1535
old_checker_pid = getattr(self.checker, "pid", None)
782
old_checker = self.checker
783
if self.checker is not None:
784
old_checker_pid = self.checker.pid
786
old_checker_pid = None
1536
787
r = Client.start_checker(self, *args, **kwargs)
1537
788
# Only if new checker process was started
1538
789
if (self.checker is not None
1539
790
and old_checker_pid != self.checker.pid):
1540
791
# Emit D-Bus signal
1541
792
self.CheckerStarted(self.current_checker_command)
793
self.PropertyChanged(
794
dbus.String(u"checker_running"),
795
dbus.Boolean(True, variant_level=1))
798
def stop_checker(self, *args, **kwargs):
799
old_checker = getattr(self, u"checker", None)
800
r = Client.stop_checker(self, *args, **kwargs)
801
if (old_checker is not None
802
and getattr(self, u"checker", None) is None):
803
self.PropertyChanged(dbus.String(u"checker_running"),
804
dbus.Boolean(False, variant_level=1))
1544
807
def _reset_approved(self):
1545
self.approved = None
808
self._approved = None
1548
811
def approve(self, value=True):
1549
self.approved = value
1550
gobject.timeout_add(int(self.approval_duration.total_seconds()
1551
* 1000), self._reset_approved)
1552
self.send_changedstate()
812
self._approved = value
813
gobject.timeout_add(self._timedelta_to_milliseconds(self.approved_duration, self._reset_approved))
815
def approved_pending(self):
816
return self.approvals_pending > 0
1554
819
## D-Bus methods, signals & properties
820
_interface = u"se.bsnet.fukt.Mandos.Client"
1560
824
# CheckerCompleted - signal
1561
@dbus.service.signal(_interface, signature="nxs")
825
@dbus.service.signal(_interface, signature=u"nxs")
1562
826
def CheckerCompleted(self, exitcode, waitstatus, command):
1566
830
# CheckerStarted - signal
1567
@dbus.service.signal(_interface, signature="s")
831
@dbus.service.signal(_interface, signature=u"s")
1568
832
def CheckerStarted(self, command):
1572
836
# PropertyChanged - signal
1573
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1574
@dbus.service.signal(_interface, signature="sv")
837
@dbus.service.signal(_interface, signature=u"sv")
1575
838
def PropertyChanged(self, property, value):
1579
842
# GotSecret - signal
1580
843
@dbus.service.signal(_interface)
1581
844
def GotSecret(self):
1583
Is sent after a successful transfer of secret from the Mandos
1584
server to mandos-client
1588
848
# Rejected - signal
1589
@dbus.service.signal(_interface, signature="s")
849
@dbus.service.signal(_interface, signature=u"s")
1590
850
def Rejected(self, reason):
1594
854
# NeedApproval - signal
1595
@dbus.service.signal(_interface, signature="tb")
855
@dbus.service.signal(_interface, signature=u"db")
1596
856
def NeedApproval(self, timeout, default):
1598
return self.need_approval()
1602
862
# Approve - method
1603
@dbus.service.method(_interface, in_signature="b")
863
@dbus.service.method(_interface, in_signature=u"b")
1604
864
def Approve(self, value):
1605
865
self.approve(value)
1607
867
# CheckedOK - method
1608
868
@dbus.service.method(_interface)
1609
869
def CheckedOK(self):
870
return self.checked_ok()
1612
872
# Enable - method
1613
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1614
873
@dbus.service.method(_interface)
1615
874
def Enable(self):
1619
878
# StartChecker - method
1620
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1621
879
@dbus.service.method(_interface)
1622
880
def StartChecker(self):
1624
882
self.start_checker()
1626
884
# Disable - method
1627
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1628
885
@dbus.service.method(_interface)
1629
886
def Disable(self):
1633
890
# StopChecker - method
1634
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1635
891
@dbus.service.method(_interface)
1636
892
def StopChecker(self):
1637
893
self.stop_checker()
1641
# ApprovalPending - property
1642
@dbus_service_property(_interface, signature="b", access="read")
1643
def ApprovalPending_dbus_property(self):
1644
return dbus.Boolean(bool(self.approvals_pending))
1646
# ApprovedByDefault - property
1647
@dbus_service_property(_interface,
1650
def ApprovedByDefault_dbus_property(self, value=None):
1651
if value is None: # get
1652
return dbus.Boolean(self.approved_by_default)
1653
self.approved_by_default = bool(value)
1655
# ApprovalDelay - property
1656
@dbus_service_property(_interface,
1659
def ApprovalDelay_dbus_property(self, value=None):
1660
if value is None: # get
1661
return dbus.UInt64(self.approval_delay.total_seconds()
1663
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1665
# ApprovalDuration - property
1666
@dbus_service_property(_interface,
1669
def ApprovalDuration_dbus_property(self, value=None):
1670
if value is None: # get
1671
return dbus.UInt64(self.approval_duration.total_seconds()
1673
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1677
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1678
@dbus_service_property(_interface, signature="s", access="read")
1679
def Name_dbus_property(self):
897
# approved_pending - property
898
@dbus_service_property(_interface, signature=u"b", access=u"read")
899
def approved_pending_dbus_property(self):
900
return dbus.Boolean(self.approved_pending())
902
# approved_by_default - property
903
@dbus_service_property(_interface, signature=u"b",
905
def approved_by_default_dbus_property(self):
906
return dbus.Boolean(self.approved_by_default)
908
# approved_delay - property
909
@dbus_service_property(_interface, signature=u"t",
911
def approved_delay_dbus_property(self):
912
return dbus.UInt64(self.approved_delay_milliseconds())
914
# approved_duration - property
915
@dbus_service_property(_interface, signature=u"t",
917
def approved_duration_dbus_property(self):
918
return dbus.UInt64(self._timedelta_to_milliseconds(
919
self.approved_duration))
922
@dbus_service_property(_interface, signature=u"s", access=u"read")
923
def name_dbus_property(self):
1680
924
return dbus.String(self.name)
1682
# Fingerprint - property
1684
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1685
@dbus_service_property(_interface, signature="s", access="read")
1686
def Fingerprint_dbus_property(self):
926
# fingerprint - property
927
@dbus_service_property(_interface, signature=u"s", access=u"read")
928
def fingerprint_dbus_property(self):
1687
929
return dbus.String(self.fingerprint)
1690
@dbus_service_property(_interface,
1693
def Host_dbus_property(self, value=None):
932
@dbus_service_property(_interface, signature=u"s",
934
def host_dbus_property(self, value=None):
1694
935
if value is None: # get
1695
936
return dbus.String(self.host)
1696
self.host = str(value)
1698
# Created - property
1700
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1701
@dbus_service_property(_interface, signature="s", access="read")
1702
def Created_dbus_property(self):
1703
return datetime_to_dbus(self.created)
1705
# LastEnabled - property
1706
@dbus_service_property(_interface, signature="s", access="read")
1707
def LastEnabled_dbus_property(self):
1708
return datetime_to_dbus(self.last_enabled)
1710
# Enabled - property
1711
@dbus_service_property(_interface,
1714
def Enabled_dbus_property(self, value=None):
939
self.PropertyChanged(dbus.String(u"host"),
940
dbus.String(value, variant_level=1))
943
@dbus_service_property(_interface, signature=u"s", access=u"read")
944
def created_dbus_property(self):
945
return dbus.String(self._datetime_to_dbus(self.created))
947
# last_enabled - property
948
@dbus_service_property(_interface, signature=u"s", access=u"read")
949
def last_enabled_dbus_property(self):
950
if self.last_enabled is None:
951
return dbus.String(u"")
952
return dbus.String(self._datetime_to_dbus(self.last_enabled))
955
@dbus_service_property(_interface, signature=u"b",
957
def enabled_dbus_property(self, value=None):
1715
958
if value is None: # get
1716
959
return dbus.Boolean(self.enabled)
2224
1400
Assumes a gobject.MainLoop event loop.
2227
1402
def __init__(self, server_address, RequestHandlerClass,
2231
gnutls_priority=None,
1403
interface=None, use_ipv6=True, clients=None,
1404
gnutls_priority=None, use_dbus=True):
2234
1405
self.enabled = False
2235
1406
self.clients = clients
2236
1407
if self.clients is None:
1408
self.clients = set()
2238
1409
self.use_dbus = use_dbus
2239
1410
self.gnutls_priority = gnutls_priority
2240
1411
IPv6_TCPServer.__init__(self, server_address,
2241
1412
RequestHandlerClass,
2242
1413
interface = interface,
2243
use_ipv6 = use_ipv6,
2244
socketfd = socketfd)
1414
use_ipv6 = use_ipv6)
2246
1415
def server_activate(self):
2247
1416
if self.enabled:
2248
1417
return socketserver.TCPServer.server_activate(self)
2250
1418
def enable(self):
2251
1419
self.enabled = True
2253
def add_pipe(self, parent_pipe, proc):
1420
def add_pipe(self, parent_pipe):
2254
1421
# Call "handle_ipc" for both data and EOF events
2255
gobject.io_add_watch(
2256
parent_pipe.fileno(),
2257
gobject.IO_IN | gobject.IO_HUP,
2258
functools.partial(self.handle_ipc,
2259
parent_pipe = parent_pipe,
2262
def handle_ipc(self, source, condition,
1422
gobject.io_add_watch(parent_pipe.fileno(),
1423
gobject.IO_IN | gobject.IO_HUP,
1424
functools.partial(self.handle_ipc,
1425
parent_pipe = parent_pipe))
1427
def handle_ipc(self, source, condition, parent_pipe=None,
2265
1428
client_object=None):
2266
# error, or the other end of multiprocessing.Pipe has closed
2267
if condition & (gobject.IO_ERR | gobject.IO_HUP):
2268
# Wait for other process to exit
1430
gobject.IO_IN: u"IN", # There is data to read.
1431
gobject.IO_OUT: u"OUT", # Data can be written (without
1433
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1434
gobject.IO_ERR: u"ERR", # Error condition.
1435
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1436
# broken, usually for pipes and
1439
conditions_string = ' | '.join(name
1441
condition_names.iteritems()
1442
if cond & condition)
1443
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1446
# error or the other end of multiprocessing.Pipe has closed
1447
if condition & gobject.IO_HUP or condition & gobject.IO_ERR:
2272
1450
# Read a request from the child
2273
1451
request = parent_pipe.recv()
1452
logger.debug(u"IPC request: %s", repr(request))
2274
1453
command = request[0]
2276
1455
if command == 'init':
2277
1456
fpr = request[1]
2278
1457
address = request[2]
2280
for c in self.clients.itervalues():
1459
for c in self.clients:
2281
1460
if c.fingerprint == fpr:
2285
logger.info("Client not found for fingerprint: %s, ad"
2286
"dress: %s", fpr, address)
1464
logger.warning(u"Client not found for fingerprint: %s, ad"
1465
u"dress: %s", fpr, address)
2287
1466
if self.use_dbus:
2288
1467
# Emit D-Bus signal
2289
mandos_dbus_service.ClientNotFound(fpr,
1468
mandos_dbus_service.ClientNotFound(fpr, address)
2291
1469
parent_pipe.send(False)
2294
gobject.io_add_watch(
2295
parent_pipe.fileno(),
2296
gobject.IO_IN | gobject.IO_HUP,
2297
functools.partial(self.handle_ipc,
2298
parent_pipe = parent_pipe,
2300
client_object = client))
1472
gobject.io_add_watch(parent_pipe.fileno(),
1473
gobject.IO_IN | gobject.IO_HUP,
1474
functools.partial(self.handle_ipc,
1475
parent_pipe = parent_pipe,
1476
client_object = client))
2301
1477
parent_pipe.send(True)
2302
# remove the old hook in favor of the new above hook on
1478
# remove the old hook in favor of the new above hook on same fileno
2305
1480
if command == 'funcall':
2306
1481
funcname = request[1]
2307
1482
args = request[2]
2308
1483
kwargs = request[3]
2310
parent_pipe.send(('data', getattr(client_object,
1485
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
2314
1487
if command == 'getattr':
2315
1488
attrname = request[1]
2316
if isinstance(client_object.__getattribute__(attrname),
2317
collections.Callable):
2318
parent_pipe.send(('function', ))
1489
if callable(client_object.__getattribute__(attrname)):
1490
parent_pipe.send(('function',))
2321
'data', client_object.__getattribute__(attrname)))
1492
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
2323
1494
if command == 'setattr':
2324
1495
attrname = request[1]
2325
1496
value = request[2]
2326
1497
setattr(client_object, attrname, value)
2331
def rfc3339_duration_to_delta(duration):
2332
"""Parse an RFC 3339 "duration" and return a datetime.timedelta
2334
>>> rfc3339_duration_to_delta("P7D")
2335
datetime.timedelta(7)
2336
>>> rfc3339_duration_to_delta("PT60S")
2337
datetime.timedelta(0, 60)
2338
>>> rfc3339_duration_to_delta("PT60M")
2339
datetime.timedelta(0, 3600)
2340
>>> rfc3339_duration_to_delta("PT24H")
2341
datetime.timedelta(1)
2342
>>> rfc3339_duration_to_delta("P1W")
2343
datetime.timedelta(7)
2344
>>> rfc3339_duration_to_delta("PT5M30S")
2345
datetime.timedelta(0, 330)
2346
>>> rfc3339_duration_to_delta("P1DT3M20S")
2347
datetime.timedelta(1, 200)
2350
# Parsing an RFC 3339 duration with regular expressions is not
2351
# possible - there would have to be multiple places for the same
2352
# values, like seconds. The current code, while more esoteric, is
2353
# cleaner without depending on a parsing library. If Python had a
2354
# built-in library for parsing we would use it, but we'd like to
2355
# avoid excessive use of external libraries.
2357
# New type for defining tokens, syntax, and semantics all-in-one
2358
Token = collections.namedtuple("Token", (
2359
"regexp", # To match token; if "value" is not None, must have
2360
# a "group" containing digits
2361
"value", # datetime.timedelta or None
2362
"followers")) # Tokens valid after this token
2363
# RFC 3339 "duration" tokens, syntax, and semantics; taken from
2364
# the "duration" ABNF definition in RFC 3339, Appendix A.
2365
token_end = Token(re.compile(r"$"), None, frozenset())
2366
token_second = Token(re.compile(r"(\d+)S"),
2367
datetime.timedelta(seconds=1),
2368
frozenset((token_end, )))
2369
token_minute = Token(re.compile(r"(\d+)M"),
2370
datetime.timedelta(minutes=1),
2371
frozenset((token_second, token_end)))
2372
token_hour = Token(re.compile(r"(\d+)H"),
2373
datetime.timedelta(hours=1),
2374
frozenset((token_minute, token_end)))
2375
token_time = Token(re.compile(r"T"),
2377
frozenset((token_hour, token_minute,
2379
token_day = Token(re.compile(r"(\d+)D"),
2380
datetime.timedelta(days=1),
2381
frozenset((token_time, token_end)))
2382
token_month = Token(re.compile(r"(\d+)M"),
2383
datetime.timedelta(weeks=4),
2384
frozenset((token_day, token_end)))
2385
token_year = Token(re.compile(r"(\d+)Y"),
2386
datetime.timedelta(weeks=52),
2387
frozenset((token_month, token_end)))
2388
token_week = Token(re.compile(r"(\d+)W"),
2389
datetime.timedelta(weeks=1),
2390
frozenset((token_end, )))
2391
token_duration = Token(re.compile(r"P"), None,
2392
frozenset((token_year, token_month,
2393
token_day, token_time,
2395
# Define starting values
2396
value = datetime.timedelta() # Value so far
2398
followers = frozenset((token_duration, )) # Following valid tokens
2399
s = duration # String left to parse
2400
# Loop until end token is found
2401
while found_token is not token_end:
2402
# Search for any currently valid tokens
2403
for token in followers:
2404
match = token.regexp.match(s)
2405
if match is not None:
2407
if token.value is not None:
2408
# Value found, parse digits
2409
factor = int(match.group(1), 10)
2410
# Add to value so far
2411
value += factor * token.value
2412
# Strip token from string
2413
s = token.regexp.sub("", s, 1)
2416
# Set valid next tokens
2417
followers = found_token.followers
2420
# No currently valid tokens were found
2421
raise ValueError("Invalid RFC 3339 duration: {!r}"
2427
1502
def string_to_delta(interval):
2428
1503
"""Parse a string and return a datetime.timedelta
2430
>>> string_to_delta('7d')
1505
>>> string_to_delta(u'7d')
2431
1506
datetime.timedelta(7)
2432
>>> string_to_delta('60s')
1507
>>> string_to_delta(u'60s')
2433
1508
datetime.timedelta(0, 60)
2434
>>> string_to_delta('60m')
1509
>>> string_to_delta(u'60m')
2435
1510
datetime.timedelta(0, 3600)
2436
>>> string_to_delta('24h')
1511
>>> string_to_delta(u'24h')
2437
1512
datetime.timedelta(1)
2438
>>> string_to_delta('1w')
1513
>>> string_to_delta(u'1w')
2439
1514
datetime.timedelta(7)
2440
>>> string_to_delta('5m 30s')
1515
>>> string_to_delta(u'5m 30s')
2441
1516
datetime.timedelta(0, 330)
2445
return rfc3339_duration_to_delta(interval)
2449
1518
timevalue = datetime.timedelta(0)
2450
1519
for s in interval.split():
1521
suffix = unicode(s[-1])
2453
1522
value = int(s[:-1])
2455
1524
delta = datetime.timedelta(value)
1525
elif suffix == u"s":
2457
1526
delta = datetime.timedelta(0, value)
1527
elif suffix == u"m":
2459
1528
delta = datetime.timedelta(0, 0, 0, 0, value)
1529
elif suffix == u"h":
2461
1530
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1531
elif suffix == u"w":
2463
1532
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
2465
raise ValueError("Unknown suffix {!r}".format(suffix))
2466
except IndexError as e:
2467
raise ValueError(*(e.args))
1534
raise ValueError(u"Unknown suffix %r" % suffix)
1535
except (ValueError, IndexError), e:
1536
raise ValueError(e.message)
2468
1537
timevalue += delta
2469
1538
return timevalue
1541
def if_nametoindex(interface):
1542
"""Call the C function if_nametoindex(), or equivalent
1544
Note: This function cannot accept a unicode string."""
1545
global if_nametoindex
1547
if_nametoindex = (ctypes.cdll.LoadLibrary
1548
(ctypes.util.find_library(u"c"))
1550
except (OSError, AttributeError):
1551
logger.warning(u"Doing if_nametoindex the hard way")
1552
def if_nametoindex(interface):
1553
"Get an interface index the hard way, i.e. using fcntl()"
1554
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1555
with contextlib.closing(socket.socket()) as s:
1556
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1557
struct.pack(str(u"16s16x"),
1559
interface_index = struct.unpack(str(u"I"),
1561
return interface_index
1562
return if_nametoindex(interface)
2472
1565
def daemon(nochdir = False, noclose = False):
2473
1566
"""See daemon(3). Standard BSD Unix function.
2499
1592
##################################################################
2500
1593
# Parsing of options, both command line and config file
2502
parser = argparse.ArgumentParser()
2503
parser.add_argument("-v", "--version", action="version",
2504
version = "%(prog)s {}".format(version),
2505
help="show version number and exit")
2506
parser.add_argument("-i", "--interface", metavar="IF",
2507
help="Bind to interface IF")
2508
parser.add_argument("-a", "--address",
2509
help="Address to listen for requests on")
2510
parser.add_argument("-p", "--port", type=int,
2511
help="Port number to receive requests on")
2512
parser.add_argument("--check", action="store_true",
2513
help="Run self-test")
2514
parser.add_argument("--debug", action="store_true",
2515
help="Debug mode; run in foreground and log"
2516
" to terminal", default=None)
2517
parser.add_argument("--debuglevel", metavar="LEVEL",
2518
help="Debug level for stdout output")
2519
parser.add_argument("--priority", help="GnuTLS"
2520
" priority string (see GnuTLS documentation)")
2521
parser.add_argument("--servicename",
2522
metavar="NAME", help="Zeroconf service name")
2523
parser.add_argument("--configdir",
2524
default="/etc/mandos", metavar="DIR",
2525
help="Directory to search for configuration"
2527
parser.add_argument("--no-dbus", action="store_false",
2528
dest="use_dbus", help="Do not provide D-Bus"
2529
" system bus interface", default=None)
2530
parser.add_argument("--no-ipv6", action="store_false",
2531
dest="use_ipv6", help="Do not use IPv6",
2533
parser.add_argument("--no-restore", action="store_false",
2534
dest="restore", help="Do not restore stored"
2535
" state", default=None)
2536
parser.add_argument("--socket", type=int,
2537
help="Specify a file descriptor to a network"
2538
" socket to use instead of creating one")
2539
parser.add_argument("--statedir", metavar="DIR",
2540
help="Directory to save/restore state in")
2541
parser.add_argument("--foreground", action="store_true",
2542
help="Run in foreground", default=None)
2543
parser.add_argument("--no-zeroconf", action="store_false",
2544
dest="zeroconf", help="Do not use Zeroconf",
2547
options = parser.parse_args()
1595
parser = optparse.OptionParser(version = "%%prog %s" % version)
1596
parser.add_option("-i", u"--interface", type=u"string",
1597
metavar="IF", help=u"Bind to interface IF")
1598
parser.add_option("-a", u"--address", type=u"string",
1599
help=u"Address to listen for requests on")
1600
parser.add_option("-p", u"--port", type=u"int",
1601
help=u"Port number to receive requests on")
1602
parser.add_option("--check", action=u"store_true",
1603
help=u"Run self-test")
1604
parser.add_option("--debug", action=u"store_true",
1605
help=u"Debug mode; run in foreground and log to"
1607
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1608
u" priority string (see GnuTLS documentation)")
1609
parser.add_option("--servicename", type=u"string",
1610
metavar=u"NAME", help=u"Zeroconf service name")
1611
parser.add_option("--configdir", type=u"string",
1612
default=u"/etc/mandos", metavar=u"DIR",
1613
help=u"Directory to search for configuration"
1615
parser.add_option("--no-dbus", action=u"store_false",
1616
dest=u"use_dbus", help=u"Do not provide D-Bus"
1617
u" system bus interface")
1618
parser.add_option("--no-ipv6", action=u"store_false",
1619
dest=u"use_ipv6", help=u"Do not use IPv6")
1620
options = parser.parse_args()[0]
2549
1622
if options.check:
2551
fail_count, test_count = doctest.testmod()
2552
sys.exit(os.EX_OK if fail_count == 0 else 1)
2554
1627
# Default values for config file for server-global settings
2555
server_defaults = { "interface": "",
2560
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2561
":+SIGN-DSA-SHA256",
2562
"servicename": "Mandos",
2568
"statedir": "/var/lib/mandos",
2569
"foreground": "False",
1628
server_defaults = { u"interface": u"",
1633
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1634
u"servicename": u"Mandos",
1635
u"use_dbus": u"True",
1636
u"use_ipv6": u"True",
2573
1639
# Parse config file for server-global settings
2574
1640
server_config = configparser.SafeConfigParser(server_defaults)
2575
1641
del server_defaults
2576
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1642
server_config.read(os.path.join(options.configdir,
2577
1644
# Convert the SafeConfigParser object to a dict
2578
1645
server_settings = server_config.defaults()
2579
1646
# Use the appropriate methods on the non-string config options
2580
for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
2581
server_settings[option] = server_config.getboolean("DEFAULT",
1647
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1648
server_settings[option] = server_config.getboolean(u"DEFAULT",
2583
1650
if server_settings["port"]:
2584
server_settings["port"] = server_config.getint("DEFAULT",
2586
if server_settings["socket"]:
2587
server_settings["socket"] = server_config.getint("DEFAULT",
2589
# Later, stdin will, and stdout and stderr might, be dup'ed
2590
# over with an opened os.devnull. But we don't want this to
2591
# happen with a supplied network socket.
2592
if 0 <= server_settings["socket"] <= 2:
2593
server_settings["socket"] = os.dup(server_settings
1651
server_settings["port"] = server_config.getint(u"DEFAULT",
2595
1653
del server_config
2597
1655
# Override the settings from the config file with command line
2598
1656
# options, if set.
2599
for option in ("interface", "address", "port", "debug",
2600
"priority", "servicename", "configdir", "use_dbus",
2601
"use_ipv6", "debuglevel", "restore", "statedir",
2602
"socket", "foreground", "zeroconf"):
1657
for option in (u"interface", u"address", u"port", u"debug",
1658
u"priority", u"servicename", u"configdir",
1659
u"use_dbus", u"use_ipv6"):
2603
1660
value = getattr(options, option)
2604
1661
if value is not None:
2605
1662
server_settings[option] = value
2607
1664
# Force all strings to be unicode
2608
1665
for option in server_settings.keys():
2609
if isinstance(server_settings[option], bytes):
2610
server_settings[option] = (server_settings[option]
2612
# Force all boolean options to be boolean
2613
for option in ("debug", "use_dbus", "use_ipv6", "restore",
2614
"foreground", "zeroconf"):
2615
server_settings[option] = bool(server_settings[option])
2616
# Debug implies foreground
2617
if server_settings["debug"]:
2618
server_settings["foreground"] = True
1666
if type(server_settings[option]) is str:
1667
server_settings[option] = unicode(server_settings[option])
2619
1668
# Now we have our good server settings in "server_settings"
2621
1670
##################################################################
2623
if (not server_settings["zeroconf"]
2624
and not (server_settings["port"]
2625
or server_settings["socket"] != "")):
2626
parser.error("Needs port or socket to work without Zeroconf")
2628
1672
# For convenience
2629
debug = server_settings["debug"]
2630
debuglevel = server_settings["debuglevel"]
2631
use_dbus = server_settings["use_dbus"]
2632
use_ipv6 = server_settings["use_ipv6"]
2633
stored_state_path = os.path.join(server_settings["statedir"],
2635
foreground = server_settings["foreground"]
2636
zeroconf = server_settings["zeroconf"]
2639
initlogger(debug, logging.DEBUG)
2644
level = getattr(logging, debuglevel.upper())
2645
initlogger(debug, level)
2647
if server_settings["servicename"] != "Mandos":
2648
syslogger.setFormatter(
2649
logging.Formatter('Mandos ({}) [%(process)d]:'
2650
' %(levelname)s: %(message)s'.format(
2651
server_settings["servicename"])))
1673
debug = server_settings[u"debug"]
1674
use_dbus = server_settings[u"use_dbus"]
1675
use_ipv6 = server_settings[u"use_ipv6"]
1678
syslogger.setLevel(logging.WARNING)
1679
console.setLevel(logging.WARNING)
1681
if server_settings[u"servicename"] != u"Mandos":
1682
syslogger.setFormatter(logging.Formatter
1683
(u'Mandos (%s) [%%(process)d]:'
1684
u' %%(levelname)s: %%(message)s'
1685
% server_settings[u"servicename"]))
2653
1687
# Parse config file with clients
2654
client_config = configparser.SafeConfigParser(Client
2656
client_config.read(os.path.join(server_settings["configdir"],
1688
client_defaults = { u"timeout": u"1h",
1690
u"checker": u"fping -q -- %%(host)s",
1692
u"approved_delay": u"5m",
1693
u"approved_duration": u"1s",
1695
client_config = configparser.SafeConfigParser(client_defaults)
1696
client_config.read(os.path.join(server_settings[u"configdir"],
2659
1699
global mandos_dbus_service
2660
1700
mandos_dbus_service = None
2663
if server_settings["socket"] != "":
2664
socketfd = server_settings["socket"]
2665
tcp_server = MandosServer(
2666
(server_settings["address"], server_settings["port"]),
2668
interface=(server_settings["interface"] or None),
2670
gnutls_priority=server_settings["priority"],
2674
pidfilename = "/run/mandos.pid"
2675
if not os.path.isdir("/run/."):
2676
pidfilename = "/var/run/mandos.pid"
2679
pidfile = codecs.open(pidfilename, "w", encoding="utf-8")
2680
except IOError as e:
2681
logger.error("Could not open file %r", pidfilename,
1702
tcp_server = MandosServer((server_settings[u"address"],
1703
server_settings[u"port"]),
1705
interface=server_settings[u"interface"],
1708
server_settings[u"priority"],
1710
pidfilename = u"/var/run/mandos.pid"
1712
pidfile = open(pidfilename, u"w")
1714
logger.error(u"Could not open file %r", pidfilename)
2684
for name in ("_mandos", "mandos", "nobody"):
1717
uid = pwd.getpwnam(u"_mandos").pw_uid
1718
gid = pwd.getpwnam(u"_mandos").pw_gid
2686
uid = pwd.getpwnam(name).pw_uid
2687
gid = pwd.getpwnam(name).pw_gid
1721
uid = pwd.getpwnam(u"mandos").pw_uid
1722
gid = pwd.getpwnam(u"mandos").pw_gid
2689
1723
except KeyError:
1725
uid = pwd.getpwnam(u"nobody").pw_uid
1726
gid = pwd.getpwnam(u"nobody").pw_gid
2697
except OSError as error:
2698
if error.errno != errno.EPERM:
1733
except OSError, error:
1734
if error[0] != errno.EPERM:
1737
# Enable all possible GnuTLS debugging
2702
# Enable all possible GnuTLS debugging
2704
1739
# "Use a log level over 10 to enable all debugging options."
2705
1740
# - GnuTLS manual
2706
1741
gnutls.library.functions.gnutls_global_set_log_level(11)
2708
1743
@gnutls.library.types.gnutls_log_func
2709
1744
def debug_gnutls(level, string):
2710
logger.debug("GnuTLS: %s", string[:-1])
2712
gnutls.library.functions.gnutls_global_set_log_function(
2715
# Redirect stdin so all checkers get /dev/null
2716
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2717
os.dup2(null, sys.stdin.fileno())
2721
# Need to fork before connecting to D-Bus
2723
# Close all input and output, do double fork, etc.
2726
# multiprocessing will use threads, so before we use gobject we
2727
# need to inform gobject that threads will be used.
2728
gobject.threads_init()
1745
logger.debug(u"GnuTLS: %s", string[:-1])
1747
(gnutls.library.functions
1748
.gnutls_global_set_log_function(debug_gnutls))
2730
1750
global main_loop
2731
1751
# From the Avahi example code
2732
DBusGMainLoop(set_as_default=True)
1752
DBusGMainLoop(set_as_default=True )
2733
1753
main_loop = gobject.MainLoop()
2734
1754
bus = dbus.SystemBus()
2735
1755
# End of Avahi example code
2738
bus_name = dbus.service.BusName("se.recompile.Mandos",
2741
old_bus_name = dbus.service.BusName(
2742
"se.bsnet.fukt.Mandos", bus,
2744
except dbus.exceptions.DBusException as e:
2745
logger.error("Disabling D-Bus:", exc_info=e)
1758
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1759
bus, do_not_queue=True)
1760
except dbus.exceptions.NameExistsException, e:
1761
logger.error(unicode(e) + u", disabling D-Bus")
2746
1762
use_dbus = False
2747
server_settings["use_dbus"] = False
1763
server_settings[u"use_dbus"] = False
2748
1764
tcp_server.use_dbus = False
2750
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2751
service = AvahiServiceToSyslog(
2752
name = server_settings["servicename"],
2753
servicetype = "_mandos._tcp",
2754
protocol = protocol,
2756
if server_settings["interface"]:
2757
service.interface = if_nametoindex(
2758
server_settings["interface"].encode("utf-8"))
1765
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1766
service = AvahiService(name = server_settings[u"servicename"],
1767
servicetype = u"_mandos._tcp",
1768
protocol = protocol, bus = bus)
1769
if server_settings["interface"]:
1770
service.interface = (if_nametoindex
1771
(str(server_settings[u"interface"])))
2760
1773
global multiprocessing_manager
2761
1774
multiprocessing_manager = multiprocessing.Manager()
2763
1776
client_class = Client
2765
1778
client_class = functools.partial(ClientDBus, bus = bus)
2767
client_settings = Client.config_parser(client_config)
2768
old_client_settings = {}
2771
# This is used to redirect stdout and stderr for checker processes
2773
wnull = open(os.devnull, "w") # A writable /dev/null
2774
# Only used if server is running in foreground but not in debug
2776
if debug or not foreground:
2779
# Get client data and settings from last running state.
2780
if server_settings["restore"]:
2782
with open(stored_state_path, "rb") as stored_state:
2783
clients_data, old_client_settings = pickle.load(
2785
os.remove(stored_state_path)
2786
except IOError as e:
2787
if e.errno == errno.ENOENT:
2788
logger.warning("Could not load persistent state:"
2789
" {}".format(os.strerror(e.errno)))
2791
logger.critical("Could not load persistent state:",
2794
except EOFError as e:
2795
logger.warning("Could not load persistent state: "
2799
with PGPEngine() as pgp:
2800
for client_name, client in clients_data.items():
2801
# Skip removed clients
2802
if client_name not in client_settings:
2805
# Decide which value to use after restoring saved state.
2806
# We have three different values: Old config file,
2807
# new config file, and saved state.
2808
# New config value takes precedence if it differs from old
2809
# config value, otherwise use saved state.
2810
for name, value in client_settings[client_name].items():
2812
# For each value in new config, check if it
2813
# differs from the old config value (Except for
2814
# the "secret" attribute)
2815
if (name != "secret"
2817
old_client_settings[client_name][name])):
2818
client[name] = value
2822
# Clients who has passed its expire date can still be
2823
# enabled if its last checker was successful. A Client
2824
# whose checker succeeded before we stored its state is
2825
# assumed to have successfully run all checkers during
2827
if client["enabled"]:
2828
if datetime.datetime.utcnow() >= client["expires"]:
2829
if not client["last_checked_ok"]:
2831
"disabling client {} - Client never "
2832
"performed a successful checker".format(
2834
client["enabled"] = False
2835
elif client["last_checker_status"] != 0:
2837
"disabling client {} - Client last"
2838
" checker failed with error code"
2841
client["last_checker_status"]))
2842
client["enabled"] = False
2844
client["expires"] = (
2845
datetime.datetime.utcnow()
2846
+ client["timeout"])
2847
logger.debug("Last checker succeeded,"
2848
" keeping {} enabled".format(
1779
def client_config_items(config, section):
1780
special_settings = {
1781
"approved_by_default":
1782
lambda: config.getboolean(section,
1783
"approved_by_default"),
1785
for name, value in config.items(section):
2851
client["secret"] = pgp.decrypt(
2852
client["encrypted_secret"],
2853
client_settings[client_name]["secret"])
2855
# If decryption fails, we use secret from new settings
2856
logger.debug("Failed to decrypt {} old secret".format(
2858
client["secret"] = (client_settings[client_name]
2861
# Add/remove clients based on new changes made to config
2862
for client_name in (set(old_client_settings)
2863
- set(client_settings)):
2864
del clients_data[client_name]
2865
for client_name in (set(client_settings)
2866
- set(old_client_settings)):
2867
clients_data[client_name] = client_settings[client_name]
2869
# Create all client objects
2870
for client_name, client in clients_data.items():
2871
tcp_server.clients[client_name] = client_class(
2874
server_settings = server_settings)
1787
yield (name, special_settings[name]())
1791
tcp_server.clients.update(set(
1792
client_class(name = section,
1793
config= dict(client_config_items(
1794
client_config, section)))
1795
for section in client_config.sections()))
2876
1796
if not tcp_server.clients:
2877
logger.warning("No clients defined")
2880
if pidfile is not None:
1797
logger.warning(u"No clients defined")
1800
# Redirect stdin so all checkers get /dev/null
1801
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1802
os.dup2(null, sys.stdin.fileno())
1806
# No console logging
1807
logger.removeHandler(console)
1808
# Close all input and output, do double fork, etc.
2881
1813
pid = os.getpid()
2884
print(pid, file=pidfile)
2886
logger.error("Could not write to file %r with PID %d",
1814
pidfile.write(str(pid) + "\n")
1817
logger.error(u"Could not write to file %r with PID %d",
1820
# "pidfile" was never created
1825
signal.signal(signal.SIGINT, signal.SIG_IGN)
2891
1826
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2892
1827
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2896
@alternate_dbus_interfaces(
2897
{ "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
2898
class MandosDBusService(DBusObjectWithObjectManager):
1830
class MandosDBusService(dbus.service.Object):
2899
1831
"""A D-Bus proxy object"""
2901
1832
def __init__(self):
2902
dbus.service.Object.__init__(self, bus, "/")
2904
_interface = "se.recompile.Mandos"
2906
@dbus.service.signal(_interface, signature="o")
1833
dbus.service.Object.__init__(self, bus, u"/")
1834
_interface = u"se.bsnet.fukt.Mandos"
1836
@dbus.service.signal(_interface, signature=u"o")
2907
1837
def ClientAdded(self, objpath):
2911
@dbus.service.signal(_interface, signature="ss")
1841
@dbus.service.signal(_interface, signature=u"ss")
2912
1842
def ClientNotFound(self, fingerprint, address):
2916
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2918
@dbus.service.signal(_interface, signature="os")
1846
@dbus.service.signal(_interface, signature=u"os")
2919
1847
def ClientRemoved(self, objpath, name):
2923
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2925
@dbus.service.method(_interface, out_signature="ao")
1851
@dbus.service.method(_interface, out_signature=u"ao")
2926
1852
def GetAllClients(self):
2928
return dbus.Array(c.dbus_object_path for c in
2929
tcp_server.clients.itervalues())
1854
return dbus.Array(c.dbus_object_path
1855
for c in tcp_server.clients)
2931
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2933
1857
@dbus.service.method(_interface,
2934
out_signature="a{oa{sv}}")
1858
out_signature=u"a{oa{sv}}")
2935
1859
def GetAllClientsWithProperties(self):
2937
1861
return dbus.Dictionary(
2938
{ c.dbus_object_path: c.GetAll(
2939
"se.recompile.Mandos.Client")
2940
for c in tcp_server.clients.itervalues() },
1862
((c.dbus_object_path, c.GetAll(u""))
1863
for c in tcp_server.clients),
1864
signature=u"oa{sv}")
2943
@dbus.service.method(_interface, in_signature="o")
1866
@dbus.service.method(_interface, in_signature=u"o")
2944
1867
def RemoveClient(self, object_path):
2946
for c in tcp_server.clients.itervalues():
1869
for c in tcp_server.clients:
2947
1870
if c.dbus_object_path == object_path:
2948
del tcp_server.clients[c.name]
1871
tcp_server.clients.remove(c)
2949
1872
c.remove_from_connection()
2950
# Don't signal the disabling
1873
# Don't signal anything except ClientRemoved
2951
1874
c.disable(quiet=True)
2952
# Emit D-Bus signal for removal
2953
self.client_removed_signal(c)
1876
self.ClientRemoved(object_path, c.name)
2955
1878
raise KeyError(object_path)
2959
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
2960
out_signature = "a{oa{sa{sv}}}")
2961
def GetManagedObjects(self):
2963
return dbus.Dictionary(
2964
{ client.dbus_object_path:
2966
{ interface: client.GetAll(interface)
2968
client._get_all_interface_names()})
2969
for client in tcp_server.clients.values()})
2971
def client_added_signal(self, client):
2972
"""Send the new standard signal and the old signal"""
2974
# New standard signal
2975
self.InterfacesAdded(
2976
client.dbus_object_path,
2978
{ interface: client.GetAll(interface)
2980
client._get_all_interface_names()}))
2982
self.ClientAdded(client.dbus_object_path)
2984
def client_removed_signal(self, client):
2985
"""Send the new standard signal and the old signal"""
2987
# New standard signal
2988
self.InterfacesRemoved(
2989
client.dbus_object_path,
2990
client._get_all_interface_names())
2992
self.ClientRemoved(client.dbus_object_path,
2995
1882
mandos_dbus_service = MandosDBusService()
2998
1885
"Cleanup function; run on exit"
3002
multiprocessing.active_children()
3004
if not (tcp_server.clients or client_settings):
3007
# Store client before exiting. Secrets are encrypted with key
3008
# based on what config file has. If config file is
3009
# removed/edited, old secret will thus be unrecovable.
3011
with PGPEngine() as pgp:
3012
for client in tcp_server.clients.itervalues():
3013
key = client_settings[client.name]["secret"]
3014
client.encrypted_secret = pgp.encrypt(client.secret,
3018
# A list of attributes that can not be pickled
3020
exclude = { "bus", "changedstate", "secret",
3021
"checker", "server_settings" }
3022
for name, typ in inspect.getmembers(dbus.service
3026
client_dict["encrypted_secret"] = (client
3028
for attr in client.client_structure:
3029
if attr not in exclude:
3030
client_dict[attr] = getattr(client, attr)
3032
clients[client.name] = client_dict
3033
del client_settings[client.name]["secret"]
3036
with tempfile.NamedTemporaryFile(
3040
dir=os.path.dirname(stored_state_path),
3041
delete=False) as stored_state:
3042
pickle.dump((clients, client_settings), stored_state)
3043
tempname = stored_state.name
3044
os.rename(tempname, stored_state_path)
3045
except (IOError, OSError) as e:
3051
if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
3052
logger.warning("Could not save persistent state: {}"
3053
.format(os.strerror(e.errno)))
3055
logger.warning("Could not save persistent state:",
3059
# Delete all clients, and settings from config
3060
1888
while tcp_server.clients:
3061
name, client = tcp_server.clients.popitem()
1889
client = tcp_server.clients.pop()
3063
1891
client.remove_from_connection()
3064
# Don't signal the disabling
1892
client.disable_hook = None
1893
# Don't signal anything except ClientRemoved
3065
1894
client.disable(quiet=True)
3066
# Emit D-Bus signal for removal
3068
mandos_dbus_service.client_removed_signal(client)
3069
client_settings.clear()
1897
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
3071
1900
atexit.register(cleanup)
3073
for client in tcp_server.clients.itervalues():
1902
for client in tcp_server.clients:
3075
# Emit D-Bus signal for adding
3076
mandos_dbus_service.client_added_signal(client)
3077
# Need to initiate checking of clients
3079
client.init_checker()
1905
mandos_dbus_service.ClientAdded(client.dbus_object_path)
3081
1908
tcp_server.enable()
3082
1909
tcp_server.server_activate()
3084
1911
# Find out what port we got
3086
service.port = tcp_server.socket.getsockname()[1]
1912
service.port = tcp_server.socket.getsockname()[1]
3088
logger.info("Now listening on address %r, port %d,"
3089
" flowinfo %d, scope_id %d",
3090
*tcp_server.socket.getsockname())
1914
logger.info(u"Now listening on address %r, port %d,"
1915
" flowinfo %d, scope_id %d"
1916
% tcp_server.socket.getsockname())
3092
logger.info("Now listening on address %r, port %d",
3093
*tcp_server.socket.getsockname())
1918
logger.info(u"Now listening on address %r, port %d"
1919
% tcp_server.socket.getsockname())
3095
1921
#service.interface = tcp_server.socket.getsockname()[3]
3099
# From the Avahi example code
3102
except dbus.exceptions.DBusException as error:
3103
logger.critical("D-Bus Exception", exc_info=error)
3106
# End of Avahi example code
1924
# From the Avahi example code
1927
except dbus.exceptions.DBusException, error:
1928
logger.critical(u"DBusException: %s", error)
1931
# End of Avahi example code
3108
1933
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
3109
1934
lambda *args, **kwargs:
3110
1935
(tcp_server.handle_request
3111
1936
(*args[2:], **kwargs) or True))
3113
logger.debug("Starting main loop")
1938
logger.debug(u"Starting main loop")
3114
1939
main_loop.run()
3115
except AvahiError as error:
3116
logger.critical("Avahi Error", exc_info=error)
1940
except AvahiError, error:
1941
logger.critical(u"AvahiError: %s", error)
3119
1944
except KeyboardInterrupt:
3121
print("", file=sys.stderr)
3122
logger.debug("Server received KeyboardInterrupt")
3123
logger.debug("Server exiting")
1947
logger.debug(u"Server received KeyboardInterrupt")
1948
logger.debug(u"Server exiting")
3124
1949
# Must run before the D-Bus bus name gets deregistered
3128
1952
if __name__ == '__main__':