395
394
logger.error(bad_states[state] + ": %r", error)
397
396
elif state == avahi.SERVER_RUNNING:
399
except dbus.exceptions.DBusException as error:
400
if (error.get_dbus_name()
401
== "org.freedesktop.Avahi.CollisionError"):
402
logger.info("Local Zeroconf service name"
404
return self.rename(remove=False)
406
logger.critical("D-Bus Exception", exc_info=error)
400
410
if error is None:
401
411
logger.debug("Unknown state: %r", state)
456
475
last_checker_status: integer between 0 and 255 reflecting exit
457
476
status of last checker. -1 reflects crashed
458
477
checker, -2 means no checker completed yet.
478
last_checker_signal: The signal which killed the last checker, if
479
last_checker_status is -1
459
480
last_enabled: datetime.datetime(); (UTC) or None
460
481
name: string; from the config file, used in log messages and
461
482
D-Bus identifiers
635
656
# Also start a new checker *right now*.
636
657
self.start_checker()
638
def checker_callback(self, pid, condition, command):
659
def checker_callback(self, source, condition, connection,
639
661
"""The checker has completed, so take appropriate actions."""
640
662
self.checker_callback_tag = None
641
663
self.checker = None
642
if os.WIFEXITED(condition):
643
self.last_checker_status = os.WEXITSTATUS(condition)
664
# Read return code from connection (see call_pipe)
665
returncode = connection.recv()
669
self.last_checker_status = returncode
670
self.last_checker_signal = None
644
671
if self.last_checker_status == 0:
645
672
logger.info("Checker for %(name)s succeeded",
649
676
logger.info("Checker for %(name)s failed", vars(self))
651
678
self.last_checker_status = -1
679
self.last_checker_signal = -returncode
652
680
logger.warning("Checker for %(name)s crashed?",
655
684
def checked_ok(self):
656
685
"""Assert that the client has been seen, alive and well."""
657
686
self.last_checked_ok = datetime.datetime.utcnow()
658
687
self.last_checker_status = 0
688
self.last_checker_signal = None
659
689
self.bump_timeout()
661
691
def bump_timeout(self, timeout=None):
687
717
# than 'timeout' for the client to be disabled, which is as it
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)
720
if self.checker is not None and not self.checker.is_alive():
721
logger.warning("Checker was not alive; joining")
704
724
# Start a new checker if needed
705
725
if self.checker is None:
706
726
# Escape attributes for the shell
716
736
return True # Try again later
717
737
self.current_checker_command = 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)
738
logger.info("Starting checker %r for %s", command,
740
# We don't need to redirect stdout and stderr, since
741
# in normal mode, that is already done by daemon(),
742
# and in debug mode we don't want to. (Stdin is
743
# always replaced by /dev/null.)
744
# The exception is when not debugging but nevertheless
745
# running in the foreground; use the previously
747
popen_args = { "close_fds": True,
750
if (not self.server_settings["debug"]
751
and self.server_settings["foreground"]):
752
popen_args.update({"stdout": wnull,
754
pipe = multiprocessing.Pipe(duplex = False)
755
self.checker = multiprocessing.Process(
757
args = (pipe[1], subprocess.call, command),
760
self.checker_callback_tag = gobject.io_add_watch(
761
pipe[0].fileno(), gobject.IO_IN,
762
self.checker_callback, pipe[0], command)
758
763
# Re-run this periodically if run by gobject.timeout_add
873
class DBusObjectWithProperties(dbus.service.Object):
874
"""A D-Bus object with properties.
873
class DBusObjectWithAnnotations(dbus.service.Object):
874
"""A D-Bus object with annotations.
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.
876
Classes inheriting from this can use the dbus_annotations
877
decorator to add annotations to methods or signals.
897
896
for name, athing in
898
897
inspect.getmembers(cls, self._is_dbus_thing(thing)))
899
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
901
path_keyword = 'object_path',
902
connection_keyword = 'connection')
903
def Introspect(self, object_path, connection):
904
"""Overloading of standard D-Bus method.
906
Inserts annotation tags on methods and signals.
908
xmlstring = dbus.service.Object.Introspect(self, object_path,
911
document = xml.dom.minidom.parseString(xmlstring)
913
for if_tag in document.getElementsByTagName("interface"):
914
# Add annotation tags
915
for typ in ("method", "signal"):
916
for tag in if_tag.getElementsByTagName(typ):
918
for name, prop in (self.
919
_get_all_dbus_things(typ)):
920
if (name == tag.getAttribute("name")
921
and prop._dbus_interface
922
== if_tag.getAttribute("name")):
923
annots.update(getattr(
924
prop, "_dbus_annotations", {}))
925
for name, value in annots.items():
926
ann_tag = document.createElement(
928
ann_tag.setAttribute("name", name)
929
ann_tag.setAttribute("value", value)
930
tag.appendChild(ann_tag)
931
# Add interface annotation tags
932
for annotation, value in dict(
933
itertools.chain.from_iterable(
934
annotations().items()
935
for name, annotations
936
in self._get_all_dbus_things("interface")
937
if name == if_tag.getAttribute("name")
939
ann_tag = document.createElement("annotation")
940
ann_tag.setAttribute("name", annotation)
941
ann_tag.setAttribute("value", value)
942
if_tag.appendChild(ann_tag)
943
# Fix argument name for the Introspect method itself
944
if (if_tag.getAttribute("name")
945
== dbus.INTROSPECTABLE_IFACE):
946
for cn in if_tag.getElementsByTagName("method"):
947
if cn.getAttribute("name") == "Introspect":
948
for arg in cn.getElementsByTagName("arg"):
949
if (arg.getAttribute("direction")
951
arg.setAttribute("name",
953
xmlstring = document.toxml("utf-8")
955
except (AttributeError, xml.dom.DOMException,
956
xml.parsers.expat.ExpatError) as error:
957
logger.error("Failed to override Introspection method",
962
class DBusObjectWithProperties(DBusObjectWithAnnotations):
963
"""A D-Bus object with properties.
965
Classes inheriting from this can use the dbus_service_property
966
decorator to expose methods as D-Bus properties. It exposes the
967
standard Get(), Set(), and GetAll() methods on the D-Bus.
900
970
def _get_dbus_property(self, interface_name, property_name):
901
971
"""Returns a bound method if one exists which is a D-Bus
902
972
property with the specified name and interface.
912
982
raise DBusPropertyNotFound("{}:{}.{}".format(
913
983
self.dbus_object_path, interface_name, property_name))
986
def _get_all_interface_names(cls):
987
"""Get a sequence of all interfaces supported by an object"""
988
return (name for name in set(getattr(getattr(x, attr),
989
"_dbus_interface", None)
990
for x in (inspect.getmro(cls))
915
994
@dbus.service.method(dbus.PROPERTIES_IFACE,
916
995
in_signature="ss",
917
996
out_signature="v")
1007
1087
if prop._dbus_interface
1008
1088
== if_tag.getAttribute("name")):
1009
1089
if_tag.appendChild(tag)
1010
# Add annotation tags
1011
for typ in ("method", "signal", "property"):
1012
for tag in if_tag.getElementsByTagName(typ):
1014
for name, prop in (self.
1015
_get_all_dbus_things(typ)):
1016
if (name == tag.getAttribute("name")
1017
and prop._dbus_interface
1018
== if_tag.getAttribute("name")):
1019
annots.update(getattr(
1020
prop, "_dbus_annotations", {}))
1021
for name, value in annots.items():
1022
ann_tag = document.createElement(
1024
ann_tag.setAttribute("name", name)
1025
ann_tag.setAttribute("value", value)
1026
tag.appendChild(ann_tag)
1027
# Add interface annotation tags
1028
for annotation, value in dict(
1029
itertools.chain.from_iterable(
1030
annotations().items()
1031
for name, annotations
1032
in self._get_all_dbus_things("interface")
1033
if name == if_tag.getAttribute("name")
1035
ann_tag = document.createElement("annotation")
1036
ann_tag.setAttribute("name", annotation)
1037
ann_tag.setAttribute("value", value)
1038
if_tag.appendChild(ann_tag)
1090
# Add annotation tags for properties
1091
for tag in if_tag.getElementsByTagName("property"):
1093
for name, prop in self._get_all_dbus_things(
1095
if (name == tag.getAttribute("name")
1096
and prop._dbus_interface
1097
== if_tag.getAttribute("name")):
1098
annots.update(getattr(
1099
prop, "_dbus_annotations", {}))
1100
for name, value in annots.items():
1101
ann_tag = document.createElement(
1103
ann_tag.setAttribute("name", name)
1104
ann_tag.setAttribute("value", value)
1105
tag.appendChild(ann_tag)
1039
1106
# Add the names to the return values for the
1040
1107
# "org.freedesktop.DBus.Properties" methods
1041
1108
if (if_tag.getAttribute("name")
1059
1126
exc_info=error)
1060
1127
return xmlstring
1130
dbus.OBJECT_MANAGER_IFACE
1131
except AttributeError:
1132
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1134
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1135
"""A D-Bus object with an ObjectManager.
1137
Classes inheriting from this exposes the standard
1138
GetManagedObjects call and the InterfacesAdded and
1139
InterfacesRemoved signals on the standard
1140
"org.freedesktop.DBus.ObjectManager" interface.
1142
Note: No signals are sent automatically; they must be sent
1145
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1146
out_signature = "a{oa{sa{sv}}}")
1147
def GetManagedObjects(self):
1148
"""This function must be overridden"""
1149
raise NotImplementedError()
1151
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1152
signature = "oa{sa{sv}}")
1153
def InterfacesAdded(self, object_path, interfaces_and_properties):
1156
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
1157
def InterfacesRemoved(self, object_path, interfaces):
1160
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1161
out_signature = "s",
1162
path_keyword = 'object_path',
1163
connection_keyword = 'connection')
1164
def Introspect(self, object_path, connection):
1165
"""Overloading of standard D-Bus method.
1167
Override return argument name of GetManagedObjects to be
1168
"objpath_interfaces_and_properties"
1170
xmlstring = DBusObjectWithAnnotations.Introspect(self,
1174
document = xml.dom.minidom.parseString(xmlstring)
1176
for if_tag in document.getElementsByTagName("interface"):
1177
# Fix argument name for the GetManagedObjects method
1178
if (if_tag.getAttribute("name")
1179
== dbus.OBJECT_MANAGER_IFACE):
1180
for cn in if_tag.getElementsByTagName("method"):
1181
if (cn.getAttribute("name")
1182
== "GetManagedObjects"):
1183
for arg in cn.getElementsByTagName("arg"):
1184
if (arg.getAttribute("direction")
1188
"objpath_interfaces"
1190
xmlstring = document.toxml("utf-8")
1192
except (AttributeError, xml.dom.DOMException,
1193
xml.parsers.expat.ExpatError) as error:
1194
logger.error("Failed to override Introspection method",
1063
1198
def datetime_to_dbus(dt, variant_level=0):
1064
1199
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1111
1246
interface_names.add(alt_interface)
1112
1247
# Is this a D-Bus signal?
1113
1248
if getattr(attribute, "_dbus_is_signal", False):
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)
1249
if sys.version_info.major == 2:
1250
# Extract the original non-method undecorated
1251
# function by black magic
1252
nonmethod_func = (dict(
1253
zip(attribute.func_code.co_freevars,
1254
attribute.__closure__))
1255
["func"].cell_contents)
1257
nonmethod_func = attribute
1120
1258
# Create a new, but exactly alike, function
1121
1259
# object, and decorate it to be a new D-Bus signal
1122
1260
# with the alternate D-Bus interface name
1261
if sys.version_info.major == 2:
1262
new_function = types.FunctionType(
1263
nonmethod_func.func_code,
1264
nonmethod_func.func_globals,
1265
nonmethod_func.func_name,
1266
nonmethod_func.func_defaults,
1267
nonmethod_func.func_closure)
1269
new_function = types.FunctionType(
1270
nonmethod_func.__code__,
1271
nonmethod_func.__globals__,
1272
nonmethod_func.__name__,
1273
nonmethod_func.__defaults__,
1274
nonmethod_func.__closure__)
1123
1275
new_function = (dbus.service.signal(
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)))
1277
attribute._dbus_signature)(new_function))
1131
1278
# Copy annotations, if any
1133
1280
new_function._dbus_annotations = dict(
1143
1290
func1 and func2 to the "call_both" function
1144
1291
outside of its arguments"""
1293
@functools.wraps(func2)
1146
1294
def call_both(*args, **kwargs):
1147
1295
"""This function will emit two D-Bus
1148
1296
signals by calling func1 and func2"""
1149
1297
func1(*args, **kwargs)
1150
1298
func2(*args, **kwargs)
1299
# Make wrapper function look like a D-Bus signal
1300
for name, attr in inspect.getmembers(func2):
1301
if name.startswith("_dbus_"):
1302
setattr(call_both, name, attr)
1152
1304
return call_both
1153
1305
# Create the "call_both" function and add it to
1356
1508
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1357
1509
Client.__del__(self, *args, **kwargs)
1359
def checker_callback(self, pid, condition, command,
1361
self.checker_callback_tag = None
1363
if os.WIFEXITED(condition):
1364
exitstatus = os.WEXITSTATUS(condition)
1511
def checker_callback(self, source, condition,
1512
connection, command, *args, **kwargs):
1513
ret = Client.checker_callback(self, source, condition,
1514
connection, command, *args,
1516
exitstatus = self.last_checker_status
1365
1518
# Emit D-Bus signal
1366
1519
self.CheckerCompleted(dbus.Int16(exitstatus),
1367
dbus.Int64(condition),
1520
# This is specific to GNU libC
1521
dbus.Int64(exitstatus << 8),
1368
1522
dbus.String(command))
1370
1524
# Emit D-Bus signal
1371
1525
self.CheckerCompleted(dbus.Int16(-1),
1372
dbus.Int64(condition),
1527
# This is specific to GNU libC
1529
| self.last_checker_signal),
1373
1530
dbus.String(command))
1375
return Client.checker_callback(self, pid, condition, command,
1378
1533
def start_checker(self, *args, **kwargs):
1379
1534
old_checker_pid = getattr(self.checker, "pid", None)
1513
1672
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1515
1674
# Name - property
1676
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1516
1677
@dbus_service_property(_interface, signature="s", access="read")
1517
1678
def Name_dbus_property(self):
1518
1679
return dbus.String(self.name)
1520
1681
# Fingerprint - property
1683
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1521
1684
@dbus_service_property(_interface, signature="s", access="read")
1522
1685
def Fingerprint_dbus_property(self):
1523
1686
return dbus.String(self.fingerprint)
1652
1817
self.stop_checker()
1654
1819
# ObjectPath - property
1821
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const",
1822
"org.freedesktop.DBus.Deprecated": "true"})
1655
1823
@dbus_service_property(_interface, signature="o", access="read")
1656
1824
def ObjectPath_dbus_property(self):
1657
1825
return self.dbus_object_path # is already a dbus.ObjectPath
1659
1827
# Secret = property
1829
{"org.freedesktop.DBus.Property.EmitsChangedSignal":
1660
1831
@dbus_service_property(_interface,
1661
1832
signature="ay",
1662
1833
access="write",
2182
2354
# avoid excessive use of external libraries.
2184
2356
# 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
2194
2357
Token = collections.namedtuple("Token", (
2195
2358
"regexp", # To match token; if "value" is not None, must have
2196
2359
# a "group" containing digits
2915
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2757
2917
@dbus.service.signal(_interface, signature="os")
2758
2918
def ClientRemoved(self, objpath, name):
2922
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2762
2924
@dbus.service.method(_interface, out_signature="ao")
2763
2925
def GetAllClients(self):
2765
2927
return dbus.Array(c.dbus_object_path for c in
2766
2928
tcp_server.clients.itervalues())
2930
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2768
2932
@dbus.service.method(_interface,
2769
2933
out_signature="a{oa{sv}}")
2770
2934
def GetAllClientsWithProperties(self):
2772
2936
return dbus.Dictionary(
2773
{ c.dbus_object_path: c.GetAll("")
2937
{ c.dbus_object_path: c.GetAll(
2938
"se.recompile.Mandos.Client")
2774
2939
for c in tcp_server.clients.itervalues() },
2775
2940
signature="oa{sv}")
2781
2946
if c.dbus_object_path == object_path:
2782
2947
del tcp_server.clients[c.name]
2783
2948
c.remove_from_connection()
2784
# Don't signal anything except ClientRemoved
2949
# Don't signal the disabling
2785
2950
c.disable(quiet=True)
2787
self.ClientRemoved(object_path, c.name)
2951
# Emit D-Bus signal for removal
2952
self.client_removed_signal(c)
2789
2954
raise KeyError(object_path)
2958
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
2959
out_signature = "a{oa{sa{sv}}}")
2960
def GetManagedObjects(self):
2962
return dbus.Dictionary(
2963
{ client.dbus_object_path:
2965
{ interface: client.GetAll(interface)
2967
client._get_all_interface_names()})
2968
for client in tcp_server.clients.values()})
2970
def client_added_signal(self, client):
2971
"""Send the new standard signal and the old signal"""
2973
# New standard signal
2974
self.InterfacesAdded(
2975
client.dbus_object_path,
2977
{ interface: client.GetAll(interface)
2979
client._get_all_interface_names()}))
2981
self.ClientAdded(client.dbus_object_path)
2983
def client_removed_signal(self, client):
2984
"""Send the new standard signal and the old signal"""
2986
# New standard signal
2987
self.InterfacesRemoved(
2988
client.dbus_object_path,
2989
client._get_all_interface_names())
2991
self.ClientRemoved(client.dbus_object_path,
2793
2994
mandos_dbus_service = MandosDBusService()
2859
3060
name, client = tcp_server.clients.popitem()
2861
3062
client.remove_from_connection()
2862
# Don't signal anything except ClientRemoved
3063
# Don't signal the disabling
2863
3064
client.disable(quiet=True)
3065
# Emit D-Bus signal for removal
2866
mandos_dbus_service.ClientRemoved(
2867
client.dbus_object_path, client.name)
3067
mandos_dbus_service.client_removed_signal(client)
2868
3068
client_settings.clear()
2870
3070
atexit.register(cleanup)
2872
3072
for client in tcp_server.clients.itervalues():
2875
mandos_dbus_service.ClientAdded(client.dbus_object_path)
3074
# Emit D-Bus signal for adding
3075
mandos_dbus_service.client_added_signal(client)
2876
3076
# Need to initiate checking of clients
2877
3077
if client.enabled:
2878
3078
client.init_checker()