382
395
logger.error(bad_states[state] + ": %r", error)
384
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)
387
411
if error is None:
388
412
logger.debug("Unknown state: %r", state)
443
476
last_checker_status: integer between 0 and 255 reflecting exit
444
477
status of last checker. -1 reflects crashed
445
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
446
481
last_enabled: datetime.datetime(); (UTC) or None
447
482
name: string; from the config file, used in log messages and
448
483
D-Bus identifiers
622
657
# Also start a new checker *right now*.
623
658
self.start_checker()
625
def checker_callback(self, pid, condition, command):
660
def checker_callback(self, source, condition, connection,
626
662
"""The checker has completed, so take appropriate actions."""
627
663
self.checker_callback_tag = None
628
664
self.checker = None
629
if os.WIFEXITED(condition):
630
self.last_checker_status = os.WEXITSTATUS(condition)
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
631
672
if self.last_checker_status == 0:
632
673
logger.info("Checker for %(name)s succeeded",
636
677
logger.info("Checker for %(name)s failed", vars(self))
638
679
self.last_checker_status = -1
680
self.last_checker_signal = -returncode
639
681
logger.warning("Checker for %(name)s crashed?",
642
685
def checked_ok(self):
643
686
"""Assert that the client has been seen, alive and well."""
644
687
self.last_checked_ok = datetime.datetime.utcnow()
645
688
self.last_checker_status = 0
689
self.last_checker_signal = None
646
690
self.bump_timeout()
648
692
def bump_timeout(self, timeout=None):
674
718
# than 'timeout' for the client to be disabled, which is as it
677
# If a checker exists, make sure it is not a zombie
679
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
680
except AttributeError:
682
except OSError as error:
683
if error.errno != errno.ECHILD:
687
logger.warning("Checker was a zombie")
688
gobject.source_remove(self.checker_callback_tag)
689
self.checker_callback(pid, status,
690
self.current_checker_command)
721
if self.checker is not None and not self.checker.is_alive():
722
logger.warning("Checker was not alive; joining")
691
725
# Start a new checker if needed
692
726
if self.checker is None:
693
727
# Escape attributes for the shell
703
737
return True # Try again later
704
738
self.current_checker_command = command
706
logger.info("Starting checker %r for %s", command,
708
# We don't need to redirect stdout and stderr, since
709
# in normal mode, that is already done by daemon(),
710
# and in debug mode we don't want to. (Stdin is
711
# always replaced by /dev/null.)
712
# The exception is when not debugging but nevertheless
713
# running in the foreground; use the previously
716
if (not self.server_settings["debug"]
717
and self.server_settings["foreground"]):
718
popen_args.update({"stdout": wnull,
720
self.checker = subprocess.Popen(command,
725
except OSError as error:
726
logger.error("Failed to start subprocess",
729
self.checker_callback_tag = gobject.child_watch_add(
730
self.checker.pid, self.checker_callback, data=command)
731
# The checker may have completed before the gobject
732
# watch was added. Check for this.
734
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
735
except OSError as error:
736
if error.errno == errno.ECHILD:
737
# This should never happen
738
logger.error("Child process vanished",
743
gobject.source_remove(self.checker_callback_tag)
744
self.checker_callback(pid, status, 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)
745
764
# Re-run this periodically if run by gobject.timeout_add
860
class DBusObjectWithProperties(dbus.service.Object):
861
"""A D-Bus object with properties.
874
class DBusObjectWithAnnotations(dbus.service.Object):
875
"""A D-Bus object with annotations.
863
Classes inheriting from this can use the dbus_service_property
864
decorator to expose methods as D-Bus properties. It exposes the
865
standard Get(), Set(), and GetAll() methods on the D-Bus.
877
Classes inheriting from this can use the dbus_annotations
878
decorator to add annotations to methods or signals.
884
897
for name, athing in
885
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.
887
971
def _get_dbus_property(self, interface_name, property_name):
888
972
"""Returns a bound method if one exists which is a D-Bus
889
973
property with the specified name and interface.
899
983
raise DBusPropertyNotFound("{}:{}.{}".format(
900
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))
902
995
@dbus.service.method(dbus.PROPERTIES_IFACE,
903
996
in_signature="ss",
904
997
out_signature="v")
994
1088
if prop._dbus_interface
995
1089
== if_tag.getAttribute("name")):
996
1090
if_tag.appendChild(tag)
997
# Add annotation tags
998
for typ in ("method", "signal", "property"):
999
for tag in if_tag.getElementsByTagName(typ):
1001
for name, prop in (self.
1002
_get_all_dbus_things(typ)):
1003
if (name == tag.getAttribute("name")
1004
and prop._dbus_interface
1005
== if_tag.getAttribute("name")):
1006
annots.update(getattr(
1007
prop, "_dbus_annotations", {}))
1008
for name, value in annots.items():
1009
ann_tag = document.createElement(
1011
ann_tag.setAttribute("name", name)
1012
ann_tag.setAttribute("value", value)
1013
tag.appendChild(ann_tag)
1014
# Add interface annotation tags
1015
for annotation, value in dict(
1016
itertools.chain.from_iterable(
1017
annotations().items()
1018
for name, annotations
1019
in self._get_all_dbus_things("interface")
1020
if name == if_tag.getAttribute("name")
1022
ann_tag = document.createElement("annotation")
1023
ann_tag.setAttribute("name", annotation)
1024
ann_tag.setAttribute("value", value)
1025
if_tag.appendChild(ann_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)
1026
1107
# Add the names to the return values for the
1027
1108
# "org.freedesktop.DBus.Properties" methods
1028
1109
if (if_tag.getAttribute("name")
1046
1127
exc_info=error)
1047
1128
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, 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",
1050
1199
def datetime_to_dbus(dt, variant_level=0):
1051
1200
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1098
1247
interface_names.add(alt_interface)
1099
1248
# Is this a D-Bus signal?
1100
1249
if getattr(attribute, "_dbus_is_signal", False):
1101
# Extract the original non-method undecorated
1102
# function by black magic
1103
nonmethod_func = (dict(
1104
zip(attribute.func_code.co_freevars,
1105
attribute.__closure__))
1106
["func"].cell_contents)
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
1107
1259
# Create a new, but exactly alike, function
1108
1260
# object, and decorate it to be a new D-Bus signal
1109
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__)
1110
1276
new_function = (dbus.service.signal(
1111
alt_interface, attribute._dbus_signature)
1112
(types.FunctionType(
1113
nonmethod_func.func_code,
1114
nonmethod_func.func_globals,
1115
nonmethod_func.func_name,
1116
nonmethod_func.func_defaults,
1117
nonmethod_func.func_closure)))
1278
attribute._dbus_signature)(new_function))
1118
1279
# Copy annotations, if any
1120
1281
new_function._dbus_annotations = dict(
1130
1291
func1 and func2 to the "call_both" function
1131
1292
outside of its arguments"""
1294
@functools.wraps(func2)
1133
1295
def call_both(*args, **kwargs):
1134
1296
"""This function will emit two D-Bus
1135
1297
signals by calling func1 and func2"""
1136
1298
func1(*args, **kwargs)
1137
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)
1139
1305
return call_both
1140
1306
# Create the "call_both" function and add it to
1343
1509
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1344
1510
Client.__del__(self, *args, **kwargs)
1346
def checker_callback(self, pid, condition, command,
1348
self.checker_callback_tag = None
1350
if os.WIFEXITED(condition):
1351
exitstatus = os.WEXITSTATUS(condition)
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
1352
1519
# Emit D-Bus signal
1353
1520
self.CheckerCompleted(dbus.Int16(exitstatus),
1354
dbus.Int64(condition),
1521
# This is specific to GNU libC
1522
dbus.Int64(exitstatus << 8),
1355
1523
dbus.String(command))
1357
1525
# Emit D-Bus signal
1358
1526
self.CheckerCompleted(dbus.Int16(-1),
1359
dbus.Int64(condition),
1528
# This is specific to GNU libC
1530
| self.last_checker_signal),
1360
1531
dbus.String(command))
1362
return Client.checker_callback(self, pid, condition, command,
1365
1534
def start_checker(self, *args, **kwargs):
1366
1535
old_checker_pid = getattr(self.checker, "pid", None)
1500
1673
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1502
1675
# Name - property
1677
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1503
1678
@dbus_service_property(_interface, signature="s", access="read")
1504
1679
def Name_dbus_property(self):
1505
1680
return dbus.String(self.name)
1507
1682
# Fingerprint - property
1684
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1508
1685
@dbus_service_property(_interface, signature="s", access="read")
1509
1686
def Fingerprint_dbus_property(self):
1510
1687
return dbus.String(self.fingerprint)
1639
1818
self.stop_checker()
1641
1820
# ObjectPath - property
1822
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const",
1823
"org.freedesktop.DBus.Deprecated": "true"})
1642
1824
@dbus_service_property(_interface, signature="o", access="read")
1643
1825
def ObjectPath_dbus_property(self):
1644
1826
return self.dbus_object_path # is already a dbus.ObjectPath
1646
1828
# Secret = property
1830
{"org.freedesktop.DBus.Property.EmitsChangedSignal":
1647
1832
@dbus_service_property(_interface,
1648
1833
signature="ay",
1649
1834
access="write",
2169
2355
# avoid excessive use of external libraries.
2171
2357
# New type for defining tokens, syntax, and semantics all-in-one
2172
Token = collections.namedtuple("Token",
2173
("regexp", # To match token; if
2174
# "value" is not None,
2175
# must have a "group"
2177
"value", # datetime.timedelta or
2179
"followers")) # Tokens valid after
2181
2358
Token = collections.namedtuple("Token", (
2182
2359
"regexp", # To match token; if "value" is not None, must have
2183
2360
# a "group" containing digits
2916
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2744
2918
@dbus.service.signal(_interface, signature="os")
2745
2919
def ClientRemoved(self, objpath, name):
2923
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2749
2925
@dbus.service.method(_interface, out_signature="ao")
2750
2926
def GetAllClients(self):
2752
2928
return dbus.Array(c.dbus_object_path for c in
2753
2929
tcp_server.clients.itervalues())
2931
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2755
2933
@dbus.service.method(_interface,
2756
2934
out_signature="a{oa{sv}}")
2757
2935
def GetAllClientsWithProperties(self):
2759
2937
return dbus.Dictionary(
2760
{ c.dbus_object_path: c.GetAll("")
2938
{ c.dbus_object_path: c.GetAll(
2939
"se.recompile.Mandos.Client")
2761
2940
for c in tcp_server.clients.itervalues() },
2762
2941
signature="oa{sv}")
2768
2947
if c.dbus_object_path == object_path:
2769
2948
del tcp_server.clients[c.name]
2770
2949
c.remove_from_connection()
2771
# Don't signal anything except ClientRemoved
2950
# Don't signal the disabling
2772
2951
c.disable(quiet=True)
2774
self.ClientRemoved(object_path, c.name)
2952
# Emit D-Bus signal for removal
2953
self.client_removed_signal(c)
2776
2955
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,
2780
2995
mandos_dbus_service = MandosDBusService()
2846
3061
name, client = tcp_server.clients.popitem()
2848
3063
client.remove_from_connection()
2849
# Don't signal anything except ClientRemoved
3064
# Don't signal the disabling
2850
3065
client.disable(quiet=True)
2853
mandos_dbus_service.ClientRemoved(
2854
client.dbus_object_path, client.name)
3066
# Emit D-Bus signal for removal
3067
mandos_dbus_service.client_removed_signal(client)
2855
3068
client_settings.clear()
2857
3070
atexit.register(cleanup)
2859
3072
for client in tcp_server.clients.itervalues():
2862
mandos_dbus_service.ClientAdded(client.dbus_object_path)
3074
# Emit D-Bus signal for adding
3075
mandos_dbus_service.client_added_signal(client)
2863
3076
# Need to initiate checking of clients
2864
3077
if client.enabled:
2865
3078
client.init_checker()