395
395
logger.error(bad_states[state] + ": %r", error)
397
397
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)
411
400
if error is None:
412
401
logger.debug("Unknown state: %r", state)
476
456
last_checker_status: integer between 0 and 255 reflecting exit
477
457
status of last checker. -1 reflects crashed
478
458
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
459
last_enabled: datetime.datetime(); (UTC) or None
482
460
name: string; from the config file, used in log messages and
483
461
D-Bus identifiers
657
635
# Also start a new checker *right now*.
658
636
self.start_checker()
660
def checker_callback(self, source, condition, connection,
638
def checker_callback(self, pid, condition, command):
662
639
"""The checker has completed, so take appropriate actions."""
663
640
self.checker_callback_tag = None
664
641
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
642
if os.WIFEXITED(condition):
643
self.last_checker_status = os.WEXITSTATUS(condition)
672
644
if self.last_checker_status == 0:
673
645
logger.info("Checker for %(name)s succeeded",
677
649
logger.info("Checker for %(name)s failed", vars(self))
679
651
self.last_checker_status = -1
680
self.last_checker_signal = -returncode
681
652
logger.warning("Checker for %(name)s crashed?",
685
655
def checked_ok(self):
686
656
"""Assert that the client has been seen, alive and well."""
687
657
self.last_checked_ok = datetime.datetime.utcnow()
688
658
self.last_checker_status = 0
689
self.last_checker_signal = None
690
659
self.bump_timeout()
692
661
def bump_timeout(self, timeout=None):
718
687
# than 'timeout' for the client to be disabled, which is as it
721
if self.checker is not None and not self.checker.is_alive():
722
logger.warning("Checker was not alive; joining")
690
# If a checker exists, make sure it is not a zombie
692
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
693
except AttributeError:
695
except OSError as error:
696
if error.errno != errno.ECHILD:
700
logger.warning("Checker was a zombie")
701
gobject.source_remove(self.checker_callback_tag)
702
self.checker_callback(pid, status,
703
self.current_checker_command)
725
704
# Start a new checker if needed
726
705
if self.checker is None:
727
706
# Escape attributes for the shell
737
716
return True # Try again later
738
717
self.current_checker_command = command
739
logger.info("Starting checker %r for %s", command,
741
# We don't need to redirect stdout and stderr, since
742
# in normal mode, that is already done by daemon(),
743
# and in debug mode we don't want to. (Stdin is
744
# always replaced by /dev/null.)
745
# The exception is when not debugging but nevertheless
746
# running in the foreground; use the previously
748
popen_args = { "close_fds": True,
751
if (not self.server_settings["debug"]
752
and self.server_settings["foreground"]):
753
popen_args.update({"stdout": wnull,
755
pipe = multiprocessing.Pipe(duplex = False)
756
self.checker = multiprocessing.Process(
758
args = (pipe[1], subprocess.call, command),
761
self.checker_callback_tag = gobject.io_add_watch(
762
pipe[0].fileno(), gobject.IO_IN,
763
self.checker_callback, pipe[0], command)
719
logger.info("Starting checker %r for %s", command,
721
# We don't need to redirect stdout and stderr, since
722
# in normal mode, that is already done by daemon(),
723
# and in debug mode we don't want to. (Stdin is
724
# always replaced by /dev/null.)
725
# The exception is when not debugging but nevertheless
726
# running in the foreground; use the previously
729
if (not self.server_settings["debug"]
730
and self.server_settings["foreground"]):
731
popen_args.update({"stdout": wnull,
733
self.checker = subprocess.Popen(command,
738
except OSError as error:
739
logger.error("Failed to start subprocess",
742
self.checker_callback_tag = gobject.child_watch_add(
743
self.checker.pid, self.checker_callback, data=command)
744
# The checker may have completed before the gobject
745
# watch was added. Check for this.
747
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
748
except OSError as error:
749
if error.errno == errno.ECHILD:
750
# This should never happen
751
logger.error("Child process vanished",
756
gobject.source_remove(self.checker_callback_tag)
757
self.checker_callback(pid, status, command)
764
758
# Re-run this periodically if run by gobject.timeout_add
874
class DBusObjectWithAnnotations(dbus.service.Object):
875
"""A D-Bus object with annotations.
873
class DBusObjectWithProperties(dbus.service.Object):
874
"""A D-Bus object with properties.
877
Classes inheriting from this can use the dbus_annotations
878
decorator to add annotations to methods or signals.
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.
897
897
for name, athing in
898
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):
964
"""A D-Bus object with properties.
966
Classes inheriting from this can use the dbus_service_property
967
decorator to expose methods as D-Bus properties. It exposes the
968
standard Get(), Set(), and GetAll() methods on the D-Bus.
971
900
def _get_dbus_property(self, interface_name, property_name):
972
901
"""Returns a bound method if one exists which is a D-Bus
973
902
property with the specified name and interface.
983
912
raise DBusPropertyNotFound("{}:{}.{}".format(
984
913
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
915
@dbus.service.method(dbus.PROPERTIES_IFACE,
996
916
in_signature="ss",
997
917
out_signature="v")
1088
1007
if prop._dbus_interface
1089
1008
== if_tag.getAttribute("name")):
1090
1009
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)
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)
1107
1039
# Add the names to the return values for the
1108
1040
# "org.freedesktop.DBus.Properties" methods
1109
1041
if (if_tag.getAttribute("name")
1127
1059
exc_info=error)
1128
1060
return xmlstring
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,
1159
def InterfacesRemoved(self, object_path, interfaces):
1162
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1163
out_signature = "s",
1164
path_keyword = 'object_path',
1165
connection_keyword = 'connection')
1166
def Introspect(self, object_path, connection):
1167
"""Overloading of standard D-Bus method.
1169
Override return argument name of GetManagedObjects to be
1170
"objpath_interfaces_and_properties"
1172
xmlstring = DBusObjectWithAnnotations(self, object_path,
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
1063
def datetime_to_dbus(dt, variant_level=0):
1200
1064
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1247
1111
interface_names.add(alt_interface)
1248
1112
# Is this a D-Bus signal?
1249
1113
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
1114
# Extract the original non-method undecorated
1115
# function by black magic
1116
nonmethod_func = (dict(
1117
zip(attribute.func_code.co_freevars,
1118
attribute.__closure__))
1119
["func"].cell_contents)
1259
1120
# Create a new, but exactly alike, function
1260
1121
# object, and decorate it to be a new D-Bus signal
1261
1122
# 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
1123
new_function = (dbus.service.signal(
1278
attribute._dbus_signature)(new_function))
1124
alt_interface, attribute._dbus_signature)
1125
(types.FunctionType(
1126
nonmethod_func.func_code,
1127
nonmethod_func.func_globals,
1128
nonmethod_func.func_name,
1129
nonmethod_func.func_defaults,
1130
nonmethod_func.func_closure)))
1279
1131
# Copy annotations, if any
1281
1133
new_function._dbus_annotations = dict(
1504
1356
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1505
1357
Client.__del__(self, *args, **kwargs)
1507
def checker_callback(self, source, condition,
1508
connection, command, *args, **kwargs):
1509
ret = Client.checker_callback(self, source, condition,
1510
connection, command, *args,
1512
exitstatus = self.last_checker_status
1359
def checker_callback(self, pid, condition, command,
1361
self.checker_callback_tag = None
1363
if os.WIFEXITED(condition):
1364
exitstatus = os.WEXITSTATUS(condition)
1514
1365
# Emit D-Bus signal
1515
1366
self.CheckerCompleted(dbus.Int16(exitstatus),
1516
# This is specific to GNU libC
1517
dbus.Int64(exitstatus << 8),
1367
dbus.Int64(condition),
1518
1368
dbus.String(command))
1520
1370
# Emit D-Bus signal
1521
1371
self.CheckerCompleted(dbus.Int16(-1),
1523
# This is specific to GNU libC
1525
| self.last_checker_signal),
1372
dbus.Int64(condition),
1526
1373
dbus.String(command))
1375
return Client.checker_callback(self, pid, condition, command,
1529
1378
def start_checker(self, *args, **kwargs):
1530
1379
old_checker_pid = getattr(self.checker, "pid", None)
1668
1513
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1670
1515
# Name - property
1672
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1673
1516
@dbus_service_property(_interface, signature="s", access="read")
1674
1517
def Name_dbus_property(self):
1675
1518
return dbus.String(self.name)
1677
1520
# Fingerprint - property
1679
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1680
1521
@dbus_service_property(_interface, signature="s", access="read")
1681
1522
def Fingerprint_dbus_property(self):
1682
1523
return dbus.String(self.fingerprint)
1813
1652
self.stop_checker()
1815
1654
# ObjectPath - property
1817
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const",
1818
"org.freedesktop.DBus.Deprecated": "true"})
1819
1655
@dbus_service_property(_interface, signature="o", access="read")
1820
1656
def ObjectPath_dbus_property(self):
1821
1657
return self.dbus_object_path # is already a dbus.ObjectPath
1823
1659
# Secret = property
1825
{"org.freedesktop.DBus.Property.EmitsChangedSignal":
1827
1660
@dbus_service_property(_interface,
1828
1661
signature="ay",
1829
1662
access="write",
2350
2182
# avoid excessive use of external libraries.
2352
2184
# New type for defining tokens, syntax, and semantics all-in-one
2185
Token = collections.namedtuple("Token",
2186
("regexp", # To match token; if
2187
# "value" is not None,
2188
# must have a "group"
2190
"value", # datetime.timedelta or
2192
"followers")) # Tokens valid after
2353
2194
Token = collections.namedtuple("Token", (
2354
2195
"regexp", # To match token; if "value" is not None, must have
2355
2196
# a "group" containing digits
2911
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2913
2758
@dbus.service.signal(_interface, signature="os")
2914
2759
def ClientRemoved(self, objpath, name):
2918
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2920
2763
@dbus.service.method(_interface, out_signature="ao")
2921
2764
def GetAllClients(self):
2923
2766
return dbus.Array(c.dbus_object_path for c in
2924
2767
tcp_server.clients.itervalues())
2926
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2928
2769
@dbus.service.method(_interface,
2929
2770
out_signature="a{oa{sv}}")
2930
2771
def GetAllClientsWithProperties(self):
2932
2773
return dbus.Dictionary(
2933
{ c.dbus_object_path: c.GetAll(
2934
"se.recompile.Mandos.Client")
2774
{ c.dbus_object_path: c.GetAll("")
2935
2775
for c in tcp_server.clients.itervalues() },
2936
2776
signature="oa{sv}")
2942
2782
if c.dbus_object_path == object_path:
2943
2783
del tcp_server.clients[c.name]
2944
2784
c.remove_from_connection()
2945
# Don't signal the disabling
2785
# Don't signal anything except ClientRemoved
2946
2786
c.disable(quiet=True)
2947
# Emit D-Bus signal for removal
2948
self.client_removed_signal(c)
2788
self.ClientRemoved(object_path, c.name)
2950
2790
raise KeyError(object_path)
2954
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
2955
out_signature = "a{oa{sa{sv}}}")
2956
def GetManagedObjects(self):
2958
return dbus.Dictionary(
2959
{ client.dbus_object_path:
2961
{ interface: client.GetAll(interface)
2963
client._get_all_interface_names()})
2964
for client in tcp_server.clients.values()})
2966
def client_added_signal(self, client):
2967
"""Send the new standard signal and the old signal"""
2969
# New standard signal
2970
self.InterfacesAdded(
2971
client.dbus_object_path,
2973
{ interface: client.GetAll(interface)
2975
client._get_all_interface_names()}))
2977
self.ClientAdded(client.dbus_object_path)
2979
def client_removed_signal(self, client):
2980
"""Send the new standard signal and the old signal"""
2982
# New standard signal
2983
self.InterfacesRemoved(
2984
client.dbus_object_path,
2985
client._get_all_interface_names())
2987
self.ClientRemoved(client.dbus_object_path,
2990
2794
mandos_dbus_service = MandosDBusService()
3056
2860
name, client = tcp_server.clients.popitem()
3058
2862
client.remove_from_connection()
3059
# Don't signal the disabling
2863
# Don't signal anything except ClientRemoved
3060
2864
client.disable(quiet=True)
3061
# Emit D-Bus signal for removal
3062
mandos_dbus_service.client_removed_signal(client)
2867
mandos_dbus_service.ClientRemoved(
2868
client.dbus_object_path, client.name)
3063
2869
client_settings.clear()
3065
2871
atexit.register(cleanup)
3067
2873
for client in tcp_server.clients.itervalues():
3069
# Emit D-Bus signal for adding
3070
mandos_dbus_service.client_added_signal(client)
2876
mandos_dbus_service.ClientAdded(client.dbus_object_path)
3071
2877
# Need to initiate checking of clients
3072
2878
if client.enabled:
3073
2879
client.init_checker()