88
100
except ImportError:
89
101
SO_BINDTODEVICE = None
103
if sys.version_info.major == 2:
92
107
stored_state_file = "clients.pickle"
94
109
logger = logging.getLogger()
98
if_nametoindex = (ctypes.cdll.LoadLibrary
99
(ctypes.util.find_library("c"))
113
if_nametoindex = ctypes.cdll.LoadLibrary(
114
ctypes.util.find_library("c")).if_nametoindex
101
115
except (OSError, AttributeError):
102
117
def if_nametoindex(interface):
103
118
"Get an interface index the hard way, i.e. using fcntl()"
104
119
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
105
120
with contextlib.closing(socket.socket()) as s:
106
121
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
107
struct.pack(str("16s16x"),
109
interface_index = struct.unpack(str("I"),
122
struct.pack(b"16s16x", interface))
123
interface_index = struct.unpack("I", ifreq[16:20])[0]
111
124
return interface_index
276
296
self.entry_group_state_changed_match = None
298
def rename(self, remove=True):
279
299
"""Derived from the Avahi example code"""
280
300
if self.rename_count >= self.max_renames:
281
301
logger.critical("No suitable Zeroconf service name found"
282
302
" after %i retries, exiting.",
283
303
self.rename_count)
284
304
raise AvahiServiceError("Too many renames")
285
self.name = unicode(self.server
286
.GetAlternativeServiceName(self.name))
306
self.server.GetAlternativeServiceName(self.name))
307
self.rename_count += 1
287
308
logger.info("Changing Zeroconf service name to %r ...",
292
314
except dbus.exceptions.DBusException as error:
293
logger.critical("D-Bus Exception", exc_info=error)
296
self.rename_count += 1
315
if (error.get_dbus_name()
316
== "org.freedesktop.Avahi.CollisionError"):
317
logger.info("Local Zeroconf service name collision.")
318
return self.rename(remove=False)
320
logger.critical("D-Bus Exception", exc_info=error)
298
324
def remove(self):
299
325
"""Derived from the Avahi example code"""
355
380
def server_state_changed(self, state, error=None):
356
381
"""Derived from the Avahi example code"""
357
382
logger.debug("Avahi server state change: %i", state)
358
bad_states = { avahi.SERVER_INVALID:
359
"Zeroconf server invalid",
360
avahi.SERVER_REGISTERING: None,
361
avahi.SERVER_COLLISION:
362
"Zeroconf server name collision",
363
avahi.SERVER_FAILURE:
364
"Zeroconf server failure" }
384
avahi.SERVER_INVALID: "Zeroconf server invalid",
385
avahi.SERVER_REGISTERING: None,
386
avahi.SERVER_COLLISION: "Zeroconf server name collision",
387
avahi.SERVER_FAILURE: "Zeroconf server failure",
365
389
if state in bad_states:
366
390
if bad_states[state] is not None:
367
391
if error is None:
386
421
follow_name_owner_changes=True),
387
422
avahi.DBUS_INTERFACE_SERVER)
388
423
self.server.connect_to_signal("StateChanged",
389
self.server_state_changed)
424
self.server_state_changed)
390
425
self.server_state_changed(self.server.GetState())
393
428
class AvahiServiceToSyslog(AvahiService):
429
def rename(self, *args, **kwargs):
395
430
"""Add the new name to the syslog messages"""
396
ret = AvahiService.rename(self)
397
syslogger.setFormatter(logging.Formatter
398
('Mandos ({}) [%(process)d]:'
399
' %(levelname)s: %(message)s'
431
ret = AvahiService.rename(self, *args, **kwargs)
432
syslogger.setFormatter(logging.Formatter(
433
'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
437
def call_pipe(connection, # : multiprocessing.Connection
438
func, *args, **kwargs):
439
"""This function is meant to be called by multiprocessing.Process
441
This function runs func(*args, **kwargs), and writes the resulting
442
return value on the provided multiprocessing.Connection.
444
connection.send(func(*args, **kwargs))
404
447
class Client(object):
405
448
"""A representation of a client host served by this server.
450
495
"fingerprint", "host", "interval",
451
496
"last_approval_request", "last_checked_ok",
452
497
"last_enabled", "name", "timeout")
453
client_defaults = { "timeout": "PT5M",
454
"extended_timeout": "PT15M",
456
"checker": "fping -q -- %%(host)s",
458
"approval_delay": "PT0S",
459
"approval_duration": "PT1S",
460
"approved_by_default": "True",
500
"extended_timeout": "PT15M",
502
"checker": "fping -q -- %%(host)s",
504
"approval_delay": "PT0S",
505
"approval_duration": "PT1S",
506
"approved_by_default": "True",
465
511
def config_parser(config):
543
589
self.current_checker_command = None
544
590
self.approved = None
545
591
self.approvals_pending = 0
546
self.changedstate = (multiprocessing_manager
547
.Condition(multiprocessing_manager
549
self.client_structure = [attr for attr in
550
self.__dict__.iterkeys()
592
self.changedstate = multiprocessing_manager.Condition(
593
multiprocessing_manager.Lock())
594
self.client_structure = [attr
595
for attr in self.__dict__.iterkeys()
551
596
if not attr.startswith("_")]
552
597
self.client_structure.append("client_structure")
554
for name, t in inspect.getmembers(type(self),
599
for name, t in inspect.getmembers(
600
type(self), lambda obj: isinstance(obj, property)):
558
601
if not name.startswith("_"):
559
602
self.client_structure.append(name)
602
645
# and every interval from then on.
603
646
if self.checker_initiator_tag is not None:
604
647
gobject.source_remove(self.checker_initiator_tag)
605
self.checker_initiator_tag = (gobject.timeout_add
607
.total_seconds() * 1000),
648
self.checker_initiator_tag = gobject.timeout_add(
649
int(self.interval.total_seconds() * 1000),
609
651
# Schedule a disable() when 'timeout' has passed
610
652
if self.disable_initiator_tag is not None:
611
653
gobject.source_remove(self.disable_initiator_tag)
612
self.disable_initiator_tag = (gobject.timeout_add
614
.total_seconds() * 1000),
654
self.disable_initiator_tag = gobject.timeout_add(
655
int(self.timeout.total_seconds() * 1000), self.disable)
616
656
# Also start a new checker *right now*.
617
657
self.start_checker()
619
def checker_callback(self, pid, condition, command):
659
def checker_callback(self, source, condition, connection,
620
661
"""The checker has completed, so take appropriate actions."""
621
662
self.checker_callback_tag = None
622
663
self.checker = None
623
if os.WIFEXITED(condition):
624
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
625
671
if self.last_checker_status == 0:
626
672
logger.info("Checker for %(name)s succeeded",
628
674
self.checked_ok()
630
logger.info("Checker for %(name)s failed",
676
logger.info("Checker for %(name)s failed", vars(self))
633
678
self.last_checker_status = -1
679
self.last_checker_signal = -returncode
634
680
logger.warning("Checker for %(name)s crashed?",
637
684
def checked_ok(self):
638
685
"""Assert that the client has been seen, alive and well."""
639
686
self.last_checked_ok = datetime.datetime.utcnow()
640
687
self.last_checker_status = 0
688
self.last_checker_signal = None
641
689
self.bump_timeout()
643
691
def bump_timeout(self, timeout=None):
670
717
# than 'timeout' for the client to be disabled, which is as it
673
# If a checker exists, make sure it is not a zombie
675
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
676
except AttributeError:
678
except OSError as error:
679
if error.errno != errno.ECHILD:
683
logger.warning("Checker was a zombie")
684
gobject.source_remove(self.checker_callback_tag)
685
self.checker_callback(pid, status,
686
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")
687
724
# Start a new checker if needed
688
725
if self.checker is None:
689
726
# Escape attributes for the shell
690
escaped_attrs = { attr:
691
re.escape(unicode(getattr(self,
693
for attr in self.runtime_expansions }
728
attr: re.escape(str(getattr(self, attr)))
729
for attr in self.runtime_expansions }
695
731
command = self.checker_command % escaped_attrs
696
732
except TypeError as error:
697
733
logger.error('Could not format string "%s"',
698
self.checker_command, exc_info=error)
699
return True # Try again later
734
self.checker_command,
736
return True # Try again later
700
737
self.current_checker_command = command
702
logger.info("Starting checker %r for %s",
704
# We don't need to redirect stdout and stderr, since
705
# in normal mode, that is already done by daemon(),
706
# and in debug mode we don't want to. (Stdin is
707
# always replaced by /dev/null.)
708
# The exception is when not debugging but nevertheless
709
# running in the foreground; use the previously
712
if (not self.server_settings["debug"]
713
and self.server_settings["foreground"]):
714
popen_args.update({"stdout": wnull,
716
self.checker = subprocess.Popen(command,
720
except OSError as error:
721
logger.error("Failed to start subprocess",
724
self.checker_callback_tag = (gobject.child_watch_add
726
self.checker_callback,
728
# The checker may have completed before the gobject
729
# watch was added. Check for this.
731
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
732
except OSError as error:
733
if error.errno == errno.ECHILD:
734
# This should never happen
735
logger.error("Child process vanished",
740
gobject.source_remove(self.checker_callback_tag)
741
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)
742
763
# Re-run this periodically if run by gobject.timeout_add
814
834
"""Decorator to annotate D-Bus methods, signals or properties
837
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
838
"org.freedesktop.DBus.Property."
839
"EmitsChangedSignal": "false"})
817
840
@dbus_service_property("org.example.Interface", signature="b",
819
@dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
820
"org.freedesktop.DBus.Property."
821
"EmitsChangedSignal": "false"})
822
842
def Property_dbus_property(self):
823
843
return dbus.Boolean(False)
845
See also the DBusObjectWithAnnotations class.
825
848
def decorator(func):
826
849
func._dbus_annotations = annotations
831
855
class DBusPropertyException(dbus.exceptions.DBusException):
832
856
"""A base class for D-Bus property-related exceptions
834
def __unicode__(self):
835
return unicode(str(self))
838
861
class DBusPropertyAccessException(DBusPropertyException):
868
890
def _get_all_dbus_things(self, thing):
869
891
"""Returns a generator of (name, attribute) pairs
871
return ((getattr(athing.__get__(self), "_dbus_name",
893
return ((getattr(athing.__get__(self), "_dbus_name", name),
873
894
athing.__get__(self))
874
895
for cls in self.__class__.__mro__
875
896
for name, athing in
876
inspect.getmembers(cls,
877
self._is_dbus_thing(thing)))
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.
879
970
def _get_dbus_property(self, interface_name, property_name):
880
971
"""Returns a bound method if one exists which is a D-Bus
881
972
property with the specified name and interface.
883
for cls in self.__class__.__mro__:
884
for name, value in (inspect.getmembers
886
self._is_dbus_thing("property"))):
974
for cls in self.__class__.__mro__:
975
for name, value in inspect.getmembers(
976
cls, self._is_dbus_thing("property")):
887
977
if (value._dbus_name == property_name
888
978
and value._dbus_interface == interface_name):
889
979
return value.__get__(self)
891
981
# No such property
892
raise DBusPropertyNotFound(self.dbus_object_path + ":"
893
+ interface_name + "."
896
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
982
raise DBusPropertyNotFound("{}:{}.{}".format(
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))
994
@dbus.service.method(dbus.PROPERTIES_IFACE,
897
996
out_signature="v")
898
997
def Get(self, interface_name, property_name):
899
998
"""Standard D-Bus property Get() method, see D-Bus standard.
959
1067
Inserts property tags and interface annotation tags.
961
xmlstring = dbus.service.Object.Introspect(self, object_path,
1069
xmlstring = DBusObjectWithAnnotations.Introspect(self,
964
1073
document = xml.dom.minidom.parseString(xmlstring)
965
1075
def make_tag(document, name, prop):
966
1076
e = document.createElement("property")
967
1077
e.setAttribute("name", name)
968
1078
e.setAttribute("type", prop._dbus_signature)
969
1079
e.setAttribute("access", prop._dbus_access)
971
1082
for if_tag in document.getElementsByTagName("interface"):
972
1083
# Add property tags
973
1084
for tag in (make_tag(document, name, prop)
976
1087
if prop._dbus_interface
977
1088
== if_tag.getAttribute("name")):
978
1089
if_tag.appendChild(tag)
979
# Add annotation tags
980
for typ in ("method", "signal", "property"):
981
for tag in if_tag.getElementsByTagName(typ):
983
for name, prop in (self.
984
_get_all_dbus_things(typ)):
985
if (name == tag.getAttribute("name")
986
and prop._dbus_interface
987
== if_tag.getAttribute("name")):
988
annots.update(getattr
992
for name, value in annots.items():
993
ann_tag = document.createElement(
995
ann_tag.setAttribute("name", name)
996
ann_tag.setAttribute("value", value)
997
tag.appendChild(ann_tag)
998
# Add interface annotation tags
999
for annotation, value in dict(
1000
itertools.chain.from_iterable(
1001
annotations().items()
1002
for name, annotations in
1003
self._get_all_dbus_things("interface")
1004
if name == if_tag.getAttribute("name")
1006
ann_tag = document.createElement("annotation")
1007
ann_tag.setAttribute("name", annotation)
1008
ann_tag.setAttribute("value", value)
1009
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)
1010
1106
# Add the names to the return values for the
1011
1107
# "org.freedesktop.DBus.Properties" methods
1012
1108
if (if_tag.getAttribute("name")
1030
1126
exc_info=error)
1031
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",
1034
1198
def datetime_to_dbus(dt, variant_level=0):
1035
1199
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1037
1201
return dbus.String("", variant_level = variant_level)
1038
return dbus.String(dt.isoformat(),
1039
variant_level=variant_level)
1202
return dbus.String(dt.isoformat(), variant_level=variant_level)
1042
1205
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1072
1236
# Ignore non-D-Bus attributes, and D-Bus attributes
1073
1237
# with the wrong interface name
1074
1238
if (not hasattr(attribute, "_dbus_interface")
1075
or not attribute._dbus_interface
1076
.startswith(orig_interface_name)):
1239
or not attribute._dbus_interface.startswith(
1240
orig_interface_name)):
1078
1242
# Create an alternate D-Bus interface name based on
1079
1243
# the current name
1080
alt_interface = (attribute._dbus_interface
1081
.replace(orig_interface_name,
1082
alt_interface_name))
1244
alt_interface = attribute._dbus_interface.replace(
1245
orig_interface_name, alt_interface_name)
1083
1246
interface_names.add(alt_interface)
1084
1247
# Is this a D-Bus signal?
1085
1248
if getattr(attribute, "_dbus_is_signal", False):
1086
# Extract the original non-method undecorated
1087
# function by black magic
1088
nonmethod_func = (dict(
1249
if sys.version_info.major == 2:
1250
# Extract the original non-method undecorated
1251
# function by black magic
1252
nonmethod_func = (dict(
1089
1253
zip(attribute.func_code.co_freevars,
1090
attribute.__closure__))["func"]
1254
attribute.__closure__))
1255
["func"].cell_contents)
1257
nonmethod_func = attribute
1092
1258
# Create a new, but exactly alike, function
1093
1259
# object, and decorate it to be a new D-Bus signal
1094
1260
# with the alternate D-Bus interface name
1095
new_function = (dbus.service.signal
1097
attribute._dbus_signature)
1098
(types.FunctionType(
1099
nonmethod_func.func_code,
1100
nonmethod_func.func_globals,
1101
nonmethod_func.func_name,
1102
nonmethod_func.func_defaults,
1103
nonmethod_func.func_closure)))
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__)
1275
new_function = (dbus.service.signal(
1277
attribute._dbus_signature)(new_function))
1104
1278
# Copy annotations, if any
1106
new_function._dbus_annotations = (
1107
dict(attribute._dbus_annotations))
1280
new_function._dbus_annotations = dict(
1281
attribute._dbus_annotations)
1108
1282
except AttributeError:
1110
1284
# Define a creator of a function to call both the
1130
1311
# object. Decorate it to be a new D-Bus method
1131
1312
# with the alternate D-Bus interface name. Add it
1132
1313
# to the class.
1133
attr[attrname] = (dbus.service.method
1135
attribute._dbus_in_signature,
1136
attribute._dbus_out_signature)
1138
(attribute.func_code,
1139
attribute.func_globals,
1140
attribute.func_name,
1141
attribute.func_defaults,
1142
attribute.func_closure)))
1315
dbus.service.method(
1317
attribute._dbus_in_signature,
1318
attribute._dbus_out_signature)
1319
(types.FunctionType(attribute.func_code,
1320
attribute.func_globals,
1321
attribute.func_name,
1322
attribute.func_defaults,
1323
attribute.func_closure)))
1143
1324
# Copy annotations, if any
1145
attr[attrname]._dbus_annotations = (
1146
dict(attribute._dbus_annotations))
1326
attr[attrname]._dbus_annotations = dict(
1327
attribute._dbus_annotations)
1147
1328
except AttributeError:
1149
1330
# Is this a D-Bus property?
1152
1333
# object, and decorate it to be a new D-Bus
1153
1334
# property with the alternate D-Bus interface
1154
1335
# name. Add it to the class.
1155
attr[attrname] = (dbus_service_property
1157
attribute._dbus_signature,
1158
attribute._dbus_access,
1160
._dbus_get_args_options
1163
(attribute.func_code,
1164
attribute.func_globals,
1165
attribute.func_name,
1166
attribute.func_defaults,
1167
attribute.func_closure)))
1336
attr[attrname] = (dbus_service_property(
1337
alt_interface, attribute._dbus_signature,
1338
attribute._dbus_access,
1339
attribute._dbus_get_args_options
1341
(types.FunctionType(
1342
attribute.func_code,
1343
attribute.func_globals,
1344
attribute.func_name,
1345
attribute.func_defaults,
1346
attribute.func_closure)))
1168
1347
# Copy annotations, if any
1170
attr[attrname]._dbus_annotations = (
1171
dict(attribute._dbus_annotations))
1349
attr[attrname]._dbus_annotations = dict(
1350
attribute._dbus_annotations)
1172
1351
except AttributeError:
1174
1353
# Is this a D-Bus interface?
1228
1410
Client.__init__(self, *args, **kwargs)
1229
1411
# Only now, when this client is initialized, can it show up on
1231
client_object_name = unicode(self.name).translate(
1413
client_object_name = str(self.name).translate(
1232
1414
{ord("."): ord("_"),
1233
1415
ord("-"): ord("_")})
1234
self.dbus_object_path = (dbus.ObjectPath
1235
("/clients/" + client_object_name))
1416
self.dbus_object_path = dbus.ObjectPath(
1417
"/clients/" + client_object_name)
1236
1418
DBusObjectWithProperties.__init__(self, self.bus,
1237
1419
self.dbus_object_path)
1239
def notifychangeproperty(transform_func,
1240
dbus_name, type_func=lambda x: x,
1421
def notifychangeproperty(transform_func, dbus_name,
1422
type_func=lambda x: x,
1424
invalidate_only=False,
1425
_interface=_interface):
1242
1426
""" Modify a variable so that it's a property which announces
1243
1427
its changes to DBus.
1250
1434
variant_level: D-Bus variant level. Default: 1
1252
1436
attrname = "_{}".format(dbus_name)
1253
1438
def setter(self, value):
1254
1439
if hasattr(self, "dbus_object_path"):
1255
1440
if (not hasattr(self, attrname) or
1256
1441
type_func(getattr(self, attrname, None))
1257
1442
!= type_func(value)):
1258
dbus_value = transform_func(type_func(value),
1261
self.PropertyChanged(dbus.String(dbus_name),
1444
self.PropertiesChanged(
1445
_interface, dbus.Dictionary(),
1446
dbus.Array((dbus_name, )))
1448
dbus_value = transform_func(
1450
variant_level = variant_level)
1451
self.PropertyChanged(dbus.String(dbus_name),
1453
self.PropertiesChanged(
1455
dbus.Dictionary({ dbus.String(dbus_name):
1263
1458
setattr(self, attrname, value)
1265
1460
return property(lambda self: getattr(self, attrname), setter)
1282
1477
datetime_to_dbus, "LastApprovalRequest")
1283
1478
approved_by_default = notifychangeproperty(dbus.Boolean,
1284
1479
"ApprovedByDefault")
1285
approval_delay = notifychangeproperty(dbus.UInt64,
1288
lambda td: td.total_seconds()
1480
approval_delay = notifychangeproperty(
1481
dbus.UInt64, "ApprovalDelay",
1482
type_func = lambda td: td.total_seconds() * 1000)
1290
1483
approval_duration = notifychangeproperty(
1291
1484
dbus.UInt64, "ApprovalDuration",
1292
1485
type_func = lambda td: td.total_seconds() * 1000)
1293
1486
host = notifychangeproperty(dbus.String, "Host")
1294
timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1295
type_func = lambda td:
1296
td.total_seconds() * 1000)
1487
timeout = notifychangeproperty(
1488
dbus.UInt64, "Timeout",
1489
type_func = lambda td: td.total_seconds() * 1000)
1297
1490
extended_timeout = notifychangeproperty(
1298
1491
dbus.UInt64, "ExtendedTimeout",
1299
1492
type_func = lambda td: td.total_seconds() * 1000)
1300
interval = notifychangeproperty(dbus.UInt64,
1303
lambda td: td.total_seconds()
1493
interval = notifychangeproperty(
1494
dbus.UInt64, "Interval",
1495
type_func = lambda td: td.total_seconds() * 1000)
1305
1496
checker_command = notifychangeproperty(dbus.String, "Checker")
1497
secret = notifychangeproperty(dbus.ByteArray, "Secret",
1498
invalidate_only=True)
1307
1500
del notifychangeproperty
1315
1508
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1316
1509
Client.__del__(self, *args, **kwargs)
1318
def checker_callback(self, pid, condition, command,
1320
self.checker_callback_tag = None
1322
if os.WIFEXITED(condition):
1323
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
1324
1518
# Emit D-Bus signal
1325
1519
self.CheckerCompleted(dbus.Int16(exitstatus),
1326
dbus.Int64(condition),
1520
# This is specific to GNU libC
1521
dbus.Int64(exitstatus << 8),
1327
1522
dbus.String(command))
1329
1524
# Emit D-Bus signal
1330
1525
self.CheckerCompleted(dbus.Int16(-1),
1331
dbus.Int64(condition),
1527
# This is specific to GNU libC
1529
| self.last_checker_signal),
1332
1530
dbus.String(command))
1334
return Client.checker_callback(self, pid, condition, command,
1337
1533
def start_checker(self, *args, **kwargs):
1338
1534
old_checker_pid = getattr(self.checker, "pid", None)
1474
1672
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1476
1674
# Name - property
1676
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1477
1677
@dbus_service_property(_interface, signature="s", access="read")
1478
1678
def Name_dbus_property(self):
1479
1679
return dbus.String(self.name)
1481
1681
# Fingerprint - property
1683
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1482
1684
@dbus_service_property(_interface, signature="s", access="read")
1483
1685
def Fingerprint_dbus_property(self):
1484
1686
return dbus.String(self.fingerprint)
1486
1688
# Host - property
1487
@dbus_service_property(_interface, signature="s",
1689
@dbus_service_property(_interface,
1488
1691
access="readwrite")
1489
1692
def Host_dbus_property(self, value=None):
1490
1693
if value is None: # get
1491
1694
return dbus.String(self.host)
1492
self.host = unicode(value)
1695
self.host = str(value)
1494
1697
# Created - property
1699
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1495
1700
@dbus_service_property(_interface, signature="s", access="read")
1496
1701
def Created_dbus_property(self):
1497
1702
return datetime_to_dbus(self.created)
1583
1791
if self.enabled:
1584
1792
# Reschedule checker run
1585
1793
gobject.source_remove(self.checker_initiator_tag)
1586
self.checker_initiator_tag = (gobject.timeout_add
1587
(value, self.start_checker))
1588
self.start_checker() # Start one now, too
1794
self.checker_initiator_tag = gobject.timeout_add(
1795
value, self.start_checker)
1796
self.start_checker() # Start one now, too
1590
1798
# Checker - property
1591
@dbus_service_property(_interface, signature="s",
1799
@dbus_service_property(_interface,
1592
1801
access="readwrite")
1593
1802
def Checker_dbus_property(self, value=None):
1594
1803
if value is None: # get
1595
1804
return dbus.String(self.checker_command)
1596
self.checker_command = unicode(value)
1805
self.checker_command = str(value)
1598
1807
# CheckerRunning - property
1599
@dbus_service_property(_interface, signature="b",
1808
@dbus_service_property(_interface,
1600
1810
access="readwrite")
1601
1811
def CheckerRunning_dbus_property(self, value=None):
1602
1812
if value is None: # get
1826
2043
def fingerprint(openpgp):
1827
2044
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1828
2045
# New GnuTLS "datum" with the OpenPGP public key
1829
datum = (gnutls.library.types
1830
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1833
ctypes.c_uint(len(openpgp))))
2046
datum = gnutls.library.types.gnutls_datum_t(
2047
ctypes.cast(ctypes.c_char_p(openpgp),
2048
ctypes.POINTER(ctypes.c_ubyte)),
2049
ctypes.c_uint(len(openpgp)))
1834
2050
# New empty GnuTLS certificate
1835
2051
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1836
(gnutls.library.functions
1837
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
2052
gnutls.library.functions.gnutls_openpgp_crt_init(
1838
2054
# Import the OpenPGP public key into the certificate
1839
(gnutls.library.functions
1840
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1841
gnutls.library.constants
1842
.GNUTLS_OPENPGP_FMT_RAW))
2055
gnutls.library.functions.gnutls_openpgp_crt_import(
2056
crt, ctypes.byref(datum),
2057
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
1843
2058
# Verify the self signature in the key
1844
2059
crtverify = ctypes.c_uint()
1845
(gnutls.library.functions
1846
.gnutls_openpgp_crt_verify_self(crt, 0,
1847
ctypes.byref(crtverify)))
2060
gnutls.library.functions.gnutls_openpgp_crt_verify_self(
2061
crt, 0, ctypes.byref(crtverify))
1848
2062
if crtverify.value != 0:
1849
2063
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1850
raise (gnutls.errors.CertificateSecurityError
2064
raise gnutls.errors.CertificateSecurityError(
1852
2066
# New buffer for the fingerprint
1853
2067
buf = ctypes.create_string_buffer(20)
1854
2068
buf_len = ctypes.c_size_t()
1855
2069
# Get the fingerprint from the certificate into the buffer
1856
(gnutls.library.functions
1857
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1858
ctypes.byref(buf_len)))
2070
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint(
2071
crt, ctypes.byref(buf), ctypes.byref(buf_len))
1859
2072
# Deinit the certificate
1860
2073
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1861
2074
# Convert the buffer to a Python bytestring
2029
2252
def add_pipe(self, parent_pipe, proc):
2030
2253
# Call "handle_ipc" for both data and EOF events
2031
gobject.io_add_watch(parent_pipe.fileno(),
2032
gobject.IO_IN | gobject.IO_HUP,
2033
functools.partial(self.handle_ipc,
2254
gobject.io_add_watch(
2255
parent_pipe.fileno(),
2256
gobject.IO_IN | gobject.IO_HUP,
2257
functools.partial(self.handle_ipc,
2258
parent_pipe = parent_pipe,
2038
def handle_ipc(self, source, condition, parent_pipe=None,
2039
proc = None, client_object=None):
2261
def handle_ipc(self, source, condition,
2264
client_object=None):
2040
2265
# error, or the other end of multiprocessing.Pipe has closed
2041
2266
if condition & (gobject.IO_ERR | gobject.IO_HUP):
2042
2267
# Wait for other process to exit
2129
2354
# avoid excessive use of external libraries.
2131
2356
# New type for defining tokens, syntax, and semantics all-in-one
2132
Token = collections.namedtuple("Token",
2133
("regexp", # To match token; if
2134
# "value" is not None,
2135
# must have a "group"
2137
"value", # datetime.timedelta or
2139
"followers")) # Tokens valid after
2357
Token = collections.namedtuple("Token", (
2358
"regexp", # To match token; if "value" is not None, must have
2359
# a "group" containing digits
2360
"value", # datetime.timedelta or None
2361
"followers")) # Tokens valid after this token
2141
2362
# RFC 3339 "duration" tokens, syntax, and semantics; taken from
2142
2363
# the "duration" ABNF definition in RFC 3339, Appendix A.
2143
2364
token_end = Token(re.compile(r"$"), None, frozenset())
2144
2365
token_second = Token(re.compile(r"(\d+)S"),
2145
2366
datetime.timedelta(seconds=1),
2146
frozenset((token_end,)))
2367
frozenset((token_end, )))
2147
2368
token_minute = Token(re.compile(r"(\d+)M"),
2148
2369
datetime.timedelta(minutes=1),
2149
2370
frozenset((token_second, token_end)))
2374
2596
# Override the settings from the config file with command line
2375
2597
# options, if set.
2376
2598
for option in ("interface", "address", "port", "debug",
2377
"priority", "servicename", "configdir",
2378
"use_dbus", "use_ipv6", "debuglevel", "restore",
2379
"statedir", "socket", "foreground", "zeroconf"):
2599
"priority", "servicename", "configdir", "use_dbus",
2600
"use_ipv6", "debuglevel", "restore", "statedir",
2601
"socket", "foreground", "zeroconf"):
2380
2602
value = getattr(options, option)
2381
2603
if value is not None:
2382
2604
server_settings[option] = value
2384
2606
# Force all strings to be unicode
2385
2607
for option in server_settings.keys():
2386
if type(server_settings[option]) is str:
2387
server_settings[option] = unicode(server_settings[option])
2608
if isinstance(server_settings[option], bytes):
2609
server_settings[option] = (server_settings[option]
2388
2611
# Force all boolean options to be boolean
2389
2612
for option in ("debug", "use_dbus", "use_ipv6", "restore",
2390
2613
"foreground", "zeroconf"):
2440
2661
socketfd = None
2441
2662
if server_settings["socket"] != "":
2442
2663
socketfd = server_settings["socket"]
2443
tcp_server = MandosServer((server_settings["address"],
2444
server_settings["port"]),
2446
interface=(server_settings["interface"]
2450
server_settings["priority"],
2664
tcp_server = MandosServer(
2665
(server_settings["address"], server_settings["port"]),
2667
interface=(server_settings["interface"] or None),
2669
gnutls_priority=server_settings["priority"],
2453
2672
if not foreground:
2454
2673
pidfilename = "/run/mandos.pid"
2455
2674
if not os.path.isdir("/run/."):
2456
2675
pidfilename = "/var/run/mandos.pid"
2459
pidfile = open(pidfilename, "w")
2678
pidfile = codecs.open(pidfilename, "w", encoding="utf-8")
2460
2679
except IOError as e:
2461
2680
logger.error("Could not open file %r", pidfilename,
2518
2737
bus_name = dbus.service.BusName("se.recompile.Mandos",
2519
bus, do_not_queue=True)
2520
old_bus_name = (dbus.service.BusName
2521
("se.bsnet.fukt.Mandos", bus,
2523
except dbus.exceptions.NameExistsException as e:
2740
old_bus_name = dbus.service.BusName(
2741
"se.bsnet.fukt.Mandos", bus,
2743
except dbus.exceptions.DBusException as e:
2524
2744
logger.error("Disabling D-Bus:", exc_info=e)
2525
2745
use_dbus = False
2526
2746
server_settings["use_dbus"] = False
2527
2747
tcp_server.use_dbus = False
2529
2749
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2530
service = AvahiServiceToSyslog(name =
2531
server_settings["servicename"],
2532
servicetype = "_mandos._tcp",
2533
protocol = protocol, bus = bus)
2750
service = AvahiServiceToSyslog(
2751
name = server_settings["servicename"],
2752
servicetype = "_mandos._tcp",
2753
protocol = protocol,
2534
2755
if server_settings["interface"]:
2535
service.interface = (if_nametoindex
2536
(str(server_settings["interface"])))
2756
service.interface = if_nametoindex(
2757
server_settings["interface"].encode("utf-8"))
2538
2759
global multiprocessing_manager
2539
2760
multiprocessing_manager = multiprocessing.Manager()
2558
2779
if server_settings["restore"]:
2560
2781
with open(stored_state_path, "rb") as stored_state:
2561
clients_data, old_client_settings = (pickle.load
2782
clients_data, old_client_settings = pickle.load(
2563
2784
os.remove(stored_state_path)
2564
2785
except IOError as e:
2565
2786
if e.errno == errno.ENOENT:
2566
logger.warning("Could not load persistent state: {}"
2567
.format(os.strerror(e.errno)))
2787
logger.warning("Could not load persistent state:"
2788
" {}".format(os.strerror(e.errno)))
2569
2790
logger.critical("Could not load persistent state:",
2572
2793
except EOFError as e:
2573
2794
logger.warning("Could not load persistent state: "
2574
"EOFError:", exc_info=e)
2576
2798
with PGPEngine() as pgp:
2577
2799
for client_name, client in clients_data.items():
2606
2828
if not client["last_checked_ok"]:
2607
2829
logger.warning(
2608
2830
"disabling client {} - Client never "
2609
"performed a successful checker"
2610
.format(client_name))
2831
"performed a successful checker".format(
2611
2833
client["enabled"] = False
2612
2834
elif client["last_checker_status"] != 0:
2613
2835
logger.warning(
2614
2836
"disabling client {} - Client last"
2615
" checker failed with error code {}"
2616
.format(client_name,
2617
client["last_checker_status"]))
2837
" checker failed with error code"
2840
client["last_checker_status"]))
2618
2841
client["enabled"] = False
2620
client["expires"] = (datetime.datetime
2622
+ client["timeout"])
2843
client["expires"] = (
2844
datetime.datetime.utcnow()
2845
+ client["timeout"])
2623
2846
logger.debug("Last checker succeeded,"
2624
" keeping {} enabled"
2625
.format(client_name))
2847
" keeping {} enabled".format(
2627
client["secret"] = (
2628
pgp.decrypt(client["encrypted_secret"],
2629
client_settings[client_name]
2850
client["secret"] = pgp.decrypt(
2851
client["encrypted_secret"],
2852
client_settings[client_name]["secret"])
2631
2853
except PGPError:
2632
2854
# If decryption fails, we use secret from new settings
2633
logger.debug("Failed to decrypt {} old secret"
2634
.format(client_name))
2635
client["secret"] = (
2636
client_settings[client_name]["secret"])
2855
logger.debug("Failed to decrypt {} old secret".format(
2857
client["secret"] = (client_settings[client_name]
2638
2860
# Add/remove clients based on new changes made to config
2639
2861
for client_name in (set(old_client_settings)
2668
2891
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2671
@alternate_dbus_interfaces({"se.recompile.Mandos":
2672
"se.bsnet.fukt.Mandos"})
2673
class MandosDBusService(DBusObjectWithProperties):
2895
@alternate_dbus_interfaces(
2896
{ "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
2897
class MandosDBusService(DBusObjectWithObjectManager):
2674
2898
"""A D-Bus proxy object"""
2675
2900
def __init__(self):
2676
2901
dbus.service.Object.__init__(self, bus, "/")
2677
2903
_interface = "se.recompile.Mandos"
2679
@dbus_interface_annotations(_interface)
2681
return { "org.freedesktop.DBus.Property"
2682
".EmitsChangedSignal":
2685
2905
@dbus.service.signal(_interface, signature="o")
2686
2906
def ClientAdded(self, objpath):
2915
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2695
2917
@dbus.service.signal(_interface, signature="os")
2696
2918
def ClientRemoved(self, objpath, name):
2922
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2700
2924
@dbus.service.method(_interface, out_signature="ao")
2701
2925
def GetAllClients(self):
2703
return dbus.Array(c.dbus_object_path
2927
return dbus.Array(c.dbus_object_path for c in
2705
2928
tcp_server.clients.itervalues())
2930
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2707
2932
@dbus.service.method(_interface,
2708
2933
out_signature="a{oa{sv}}")
2709
2934
def GetAllClientsWithProperties(self):
2711
2936
return dbus.Dictionary(
2712
((c.dbus_object_path, c.GetAll(""))
2713
for c in tcp_server.clients.itervalues()),
2937
{ c.dbus_object_path: c.GetAll(
2938
"se.recompile.Mandos.Client")
2939
for c in tcp_server.clients.itervalues() },
2714
2940
signature="oa{sv}")
2716
2942
@dbus.service.method(_interface, in_signature="o")
2720
2946
if c.dbus_object_path == object_path:
2721
2947
del tcp_server.clients[c.name]
2722
2948
c.remove_from_connection()
2723
# Don't signal anything except ClientRemoved
2949
# Don't signal the disabling
2724
2950
c.disable(quiet=True)
2726
self.ClientRemoved(object_path, c.name)
2951
# Emit D-Bus signal for removal
2952
self.client_removed_signal(c)
2728
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,
2732
2994
mandos_dbus_service = MandosDBusService()
2770
3032
del client_settings[client.name]["secret"]
2773
with (tempfile.NamedTemporaryFile
2774
(mode='wb', suffix=".pickle", prefix='clients-',
2775
dir=os.path.dirname(stored_state_path),
2776
delete=False)) as stored_state:
3035
with tempfile.NamedTemporaryFile(
3039
dir=os.path.dirname(stored_state_path),
3040
delete=False) as stored_state:
2777
3041
pickle.dump((clients, client_settings), stored_state)
2778
tempname=stored_state.name
3042
tempname = stored_state.name
2779
3043
os.rename(tempname, stored_state_path)
2780
3044
except (IOError, OSError) as e: