172
189
def password_encode(self, password):
173
190
# Passphrase can not be empty and can not contain newlines or
174
191
# NUL bytes. So we prefix it and hex encode it.
175
return b"mandos" + binascii.hexlify(password)
192
encoded = b"mandos" + binascii.hexlify(password)
193
if len(encoded) > 2048:
194
# GnuPG can't handle long passwords, so encode differently
195
encoded = (b"mandos" + password.replace(b"\\", b"\\\\")
196
.replace(b"\n", b"\\n")
197
.replace(b"\0", b"\\x00"))
177
200
def encrypt(self, data, password):
178
self.gnupg.passphrase = self.password_encode(password)
179
with open(os.devnull, "w") as devnull:
181
proc = self.gnupg.run(['--symmetric'],
182
create_fhs=['stdin', 'stdout'],
183
attach_fhs={'stderr': devnull})
184
with contextlib.closing(proc.handles['stdin']) as f:
186
with contextlib.closing(proc.handles['stdout']) as f:
187
ciphertext = f.read()
191
self.gnupg.passphrase = None
201
passphrase = self.password_encode(password)
202
with tempfile.NamedTemporaryFile(
203
dir=self.tempdir) as passfile:
204
passfile.write(passphrase)
206
proc = subprocess.Popen(['gpg', '--symmetric',
210
stdin = subprocess.PIPE,
211
stdout = subprocess.PIPE,
212
stderr = subprocess.PIPE)
213
ciphertext, err = proc.communicate(input = data)
214
if proc.returncode != 0:
192
216
return ciphertext
194
218
def decrypt(self, data, password):
195
self.gnupg.passphrase = self.password_encode(password)
196
with open(os.devnull, "w") as devnull:
198
proc = self.gnupg.run(['--decrypt'],
199
create_fhs=['stdin', 'stdout'],
200
attach_fhs={'stderr': devnull})
201
with contextlib.closing(proc.handles['stdin']) as f:
203
with contextlib.closing(proc.handles['stdout']) as f:
204
decrypted_plaintext = f.read()
208
self.gnupg.passphrase = None
219
passphrase = self.password_encode(password)
220
with tempfile.NamedTemporaryFile(
221
dir = self.tempdir) as passfile:
222
passfile.write(passphrase)
224
proc = subprocess.Popen(['gpg', '--decrypt',
228
stdin = subprocess.PIPE,
229
stdout = subprocess.PIPE,
230
stderr = subprocess.PIPE)
231
decrypted_plaintext, err = proc.communicate(input = data)
232
if proc.returncode != 0:
209
234
return decrypted_plaintext
213
237
class AvahiError(Exception):
214
238
def __init__(self, value, *args, **kwargs):
215
239
self.value = value
216
super(AvahiError, self).__init__(value, *args, **kwargs)
217
def __unicode__(self):
218
return unicode(repr(self.value))
240
return super(AvahiError, self).__init__(value, *args,
220
244
class AvahiServiceError(AvahiError):
223
248
class AvahiGroupError(AvahiError):
654
710
If a checker already exists, leave it running and do
656
712
# The reason for not killing a running checker is that if we
657
# did that, then if a checker (for some reason) started
658
# running slowly and taking more than 'interval' time, the
659
# client would inevitably timeout, since no checker would get
660
# a chance to run to completion. If we instead leave running
713
# did that, and if a checker (for some reason) started running
714
# slowly and taking more than 'interval' time, then the client
715
# would inevitably timeout, since no checker would get a
716
# chance to run to completion. If we instead leave running
661
717
# checkers alone, the checker would have to take more time
662
718
# than 'timeout' for the client to be disabled, which is as it
665
# If a checker exists, make sure it is not a zombie
667
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
668
except (AttributeError, OSError) as error:
669
if (isinstance(error, OSError)
670
and error.errno != errno.ECHILD):
674
logger.warning("Checker was a zombie")
675
gobject.source_remove(self.checker_callback_tag)
676
self.checker_callback(pid, status,
677
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")
678
725
# Start a new checker if needed
679
726
if self.checker is None:
727
# Escape attributes for the shell
729
attr: re.escape(str(getattr(self, attr)))
730
for attr in self.runtime_expansions }
681
# In case checker_command has exactly one % operator
682
command = self.checker_command % self.host
684
# Escape attributes for the shell
685
escaped_attrs = dict(
687
re.escape(unicode(str(getattr(self, attr, "")),
691
self.runtime_expansions)
694
command = self.checker_command % escaped_attrs
695
except TypeError as error:
696
logger.error('Could not format string "%s":'
697
' %s', self.checker_command, error)
698
return True # Try again later
732
command = self.checker_command % escaped_attrs
733
except TypeError as error:
734
logger.error('Could not format string "%s"',
735
self.checker_command,
737
return True # Try again later
699
738
self.current_checker_command = command
701
logger.info("Starting checker %r for %s",
703
# We don't need to redirect stdout and stderr, since
704
# in normal mode, that is already done by daemon(),
705
# and in debug mode we don't want to. (Stdin is
706
# always replaced by /dev/null.)
707
self.checker = subprocess.Popen(command,
710
self.checker_callback_tag = (gobject.child_watch_add
712
self.checker_callback,
714
# The checker may have completed before the gobject
715
# watch was added. Check for this.
716
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
718
gobject.source_remove(self.checker_callback_tag)
719
self.checker_callback(pid, status, command)
720
except OSError as error:
721
logger.error("Failed to start subprocess: %s",
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)
723
764
# Re-run this periodically if run by gobject.timeout_add
825
885
If called like _is_dbus_thing("method") it returns a function
826
886
suitable for use as predicate to inspect.getmembers().
828
return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
888
return lambda obj: getattr(obj, "_dbus_is_{}".format(thing),
831
891
def _get_all_dbus_things(self, thing):
832
892
"""Returns a generator of (name, attribute) pairs
834
return ((athing.__get__(self)._dbus_name,
894
return ((getattr(athing.__get__(self), "_dbus_name", name),
835
895
athing.__get__(self))
836
896
for cls in self.__class__.__mro__
837
897
for name, athing in
838
inspect.getmembers(cls,
839
self._is_dbus_thing(thing)))
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.
841
971
def _get_dbus_property(self, interface_name, property_name):
842
972
"""Returns a bound method if one exists which is a D-Bus
843
973
property with the specified name and interface.
845
for cls in self.__class__.__mro__:
846
for name, value in (inspect.getmembers
848
self._is_dbus_thing("property"))):
975
for cls in self.__class__.__mro__:
976
for name, value in inspect.getmembers(
977
cls, self._is_dbus_thing("property")):
849
978
if (value._dbus_name == property_name
850
979
and value._dbus_interface == interface_name):
851
980
return value.__get__(self)
853
982
# No such property
854
raise DBusPropertyNotFound(self.dbus_object_path + ":"
855
+ interface_name + "."
858
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
983
raise DBusPropertyNotFound("{}:{}.{}".format(
984
self.dbus_object_path, interface_name, property_name))
987
def _get_all_interface_names(cls):
988
"""Get a sequence of all interfaces supported by an object"""
989
return (name for name in set(getattr(getattr(x, attr),
990
"_dbus_interface", None)
991
for x in (inspect.getmro(cls))
995
@dbus.service.method(dbus.PROPERTIES_IFACE,
859
997
out_signature="v")
860
998
def Get(self, interface_name, property_name):
861
999
"""Standard D-Bus property Get() method, see D-Bus standard.
968
1124
except (AttributeError, xml.dom.DOMException,
969
1125
xml.parsers.expat.ExpatError) as error:
970
1126
logger.error("Failed to override Introspection method",
975
def datetime_to_dbus (dt, variant_level=0):
1131
dbus.OBJECT_MANAGER_IFACE
1132
except AttributeError:
1133
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1135
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1136
"""A D-Bus object with an ObjectManager.
1138
Classes inheriting from this exposes the standard
1139
GetManagedObjects call and the InterfacesAdded and
1140
InterfacesRemoved signals on the standard
1141
"org.freedesktop.DBus.ObjectManager" interface.
1143
Note: No signals are sent automatically; they must be sent
1146
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1147
out_signature = "a{oa{sa{sv}}}")
1148
def GetManagedObjects(self):
1149
"""This function must be overridden"""
1150
raise NotImplementedError()
1152
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1153
signature = "oa{sa{sv}}")
1154
def InterfacesAdded(self, object_path, interfaces_and_properties):
1157
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
1158
def InterfacesRemoved(self, object_path, interfaces):
1161
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1162
out_signature = "s",
1163
path_keyword = 'object_path',
1164
connection_keyword = 'connection')
1165
def Introspect(self, object_path, connection):
1166
"""Overloading of standard D-Bus method.
1168
Override return argument name of GetManagedObjects to be
1169
"objpath_interfaces_and_properties"
1171
xmlstring = DBusObjectWithAnnotations.Introspect(self,
1175
document = xml.dom.minidom.parseString(xmlstring)
1177
for if_tag in document.getElementsByTagName("interface"):
1178
# Fix argument name for the GetManagedObjects method
1179
if (if_tag.getAttribute("name")
1180
== dbus.OBJECT_MANAGER_IFACE):
1181
for cn in if_tag.getElementsByTagName("method"):
1182
if (cn.getAttribute("name")
1183
== "GetManagedObjects"):
1184
for arg in cn.getElementsByTagName("arg"):
1185
if (arg.getAttribute("direction")
1189
"objpath_interfaces"
1191
xmlstring = document.toxml("utf-8")
1193
except (AttributeError, xml.dom.DOMException,
1194
xml.parsers.expat.ExpatError) as error:
1195
logger.error("Failed to override Introspection method",
1199
def datetime_to_dbus(dt, variant_level=0):
976
1200
"""Convert a UTC datetime.datetime() to a D-Bus type."""
978
1202
return dbus.String("", variant_level = variant_level)
979
return dbus.String(dt.isoformat(),
980
variant_level=variant_level)
983
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
985
"""Applied to an empty subclass of a D-Bus object, this metaclass
986
will add additional D-Bus attributes matching a certain pattern.
1203
return dbus.String(dt.isoformat(), variant_level=variant_level)
1206
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1207
"""A class decorator; applied to a subclass of
1208
dbus.service.Object, it will add alternate D-Bus attributes with
1209
interface names according to the "alt_interface_names" mapping.
1212
@alternate_dbus_interfaces({"org.example.Interface":
1213
"net.example.AlternateInterface"})
1214
class SampleDBusObject(dbus.service.Object):
1215
@dbus.service.method("org.example.Interface")
1216
def SampleDBusMethod():
1219
The above "SampleDBusMethod" on "SampleDBusObject" will be
1220
reachable via two interfaces: "org.example.Interface" and
1221
"net.example.AlternateInterface", the latter of which will have
1222
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1223
"true", unless "deprecate" is passed with a False value.
1225
This works for methods and signals, and also for D-Bus properties
1226
(from DBusObjectWithProperties) and interfaces (from the
1227
dbus_interface_annotations decorator).
988
def __new__(mcs, name, bases, attr):
989
# Go through all the base classes which could have D-Bus
990
# methods, signals, or properties in them
991
old_interface_names = []
992
for base in (b for b in bases
993
if issubclass(b, dbus.service.Object)):
994
# Go though all attributes of the base class
995
for attrname, attribute in inspect.getmembers(base):
1231
for orig_interface_name, alt_interface_name in (
1232
alt_interface_names.items()):
1234
interface_names = set()
1235
# Go though all attributes of the class
1236
for attrname, attribute in inspect.getmembers(cls):
996
1237
# Ignore non-D-Bus attributes, and D-Bus attributes
997
1238
# with the wrong interface name
998
1239
if (not hasattr(attribute, "_dbus_interface")
999
or not attribute._dbus_interface
1000
.startswith("se.recompile.Mandos")):
1240
or not attribute._dbus_interface.startswith(
1241
orig_interface_name)):
1002
1243
# Create an alternate D-Bus interface name based on
1003
1244
# the current name
1004
alt_interface = (attribute._dbus_interface
1005
.replace("se.recompile.Mandos",
1006
"se.bsnet.fukt.Mandos"))
1007
if alt_interface != attribute._dbus_interface:
1008
old_interface_names.append(alt_interface)
1245
alt_interface = attribute._dbus_interface.replace(
1246
orig_interface_name, alt_interface_name)
1247
interface_names.add(alt_interface)
1009
1248
# Is this a D-Bus signal?
1010
1249
if getattr(attribute, "_dbus_is_signal", False):
1011
# Extract the original non-method function by
1013
nonmethod_func = (dict(
1250
if sys.version_info.major == 2:
1251
# Extract the original non-method undecorated
1252
# function by black magic
1253
nonmethod_func = (dict(
1014
1254
zip(attribute.func_code.co_freevars,
1015
attribute.__closure__))["func"]
1255
attribute.__closure__))
1256
["func"].cell_contents)
1258
nonmethod_func = attribute
1017
1259
# Create a new, but exactly alike, function
1018
1260
# object, and decorate it to be a new D-Bus signal
1019
1261
# with the alternate D-Bus interface name
1020
new_function = (dbus.service.signal
1022
attribute._dbus_signature)
1023
(types.FunctionType(
1024
nonmethod_func.func_code,
1025
nonmethod_func.func_globals,
1026
nonmethod_func.func_name,
1027
nonmethod_func.func_defaults,
1028
nonmethod_func.func_closure)))
1262
if sys.version_info.major == 2:
1263
new_function = types.FunctionType(
1264
nonmethod_func.func_code,
1265
nonmethod_func.func_globals,
1266
nonmethod_func.func_name,
1267
nonmethod_func.func_defaults,
1268
nonmethod_func.func_closure)
1270
new_function = types.FunctionType(
1271
nonmethod_func.__code__,
1272
nonmethod_func.__globals__,
1273
nonmethod_func.__name__,
1274
nonmethod_func.__defaults__,
1275
nonmethod_func.__closure__)
1276
new_function = (dbus.service.signal(
1278
attribute._dbus_signature)(new_function))
1279
# Copy annotations, if any
1281
new_function._dbus_annotations = dict(
1282
attribute._dbus_annotations)
1283
except AttributeError:
1029
1285
# Define a creator of a function to call both the
1030
# old and new functions, so both the old and new
1031
# signals gets sent when the function is called
1286
# original and alternate functions, so both the
1287
# original and alternate signals gets sent when
1288
# the function is called
1032
1289
def fixscope(func1, func2):
1033
1290
"""This function is a scope container to pass
1034
1291
func1 and func2 to the "call_both" function
1035
1292
outside of its arguments"""
1294
@functools.wraps(func2)
1036
1295
def call_both(*args, **kwargs):
1037
1296
"""This function will emit two D-Bus
1038
1297
signals by calling func1 and func2"""
1039
1298
func1(*args, **kwargs)
1040
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)
1041
1305
return call_both
1042
1306
# Create the "call_both" function and add it to
1044
attr[attrname] = fixscope(attribute,
1308
attr[attrname] = fixscope(attribute, new_function)
1046
1309
# Is this a D-Bus method?
1047
1310
elif getattr(attribute, "_dbus_is_method", False):
1048
1311
# Create a new, but exactly alike, function
1049
1312
# object. Decorate it to be a new D-Bus method
1050
1313
# with the alternate D-Bus interface name. Add it
1051
1314
# to the class.
1052
attr[attrname] = (dbus.service.method
1054
attribute._dbus_in_signature,
1055
attribute._dbus_out_signature)
1057
(attribute.func_code,
1058
attribute.func_globals,
1059
attribute.func_name,
1060
attribute.func_defaults,
1061
attribute.func_closure)))
1316
dbus.service.method(
1318
attribute._dbus_in_signature,
1319
attribute._dbus_out_signature)
1320
(types.FunctionType(attribute.func_code,
1321
attribute.func_globals,
1322
attribute.func_name,
1323
attribute.func_defaults,
1324
attribute.func_closure)))
1325
# Copy annotations, if any
1327
attr[attrname]._dbus_annotations = dict(
1328
attribute._dbus_annotations)
1329
except AttributeError:
1062
1331
# Is this a D-Bus property?
1063
1332
elif getattr(attribute, "_dbus_is_property", False):
1064
1333
# Create a new, but exactly alike, function
1065
1334
# object, and decorate it to be a new D-Bus
1066
1335
# property with the alternate D-Bus interface
1067
1336
# name. Add it to the class.
1068
attr[attrname] = (dbus_service_property
1070
attribute._dbus_signature,
1071
attribute._dbus_access,
1073
._dbus_get_args_options
1076
(attribute.func_code,
1077
attribute.func_globals,
1078
attribute.func_name,
1079
attribute.func_defaults,
1080
attribute.func_closure)))
1337
attr[attrname] = (dbus_service_property(
1338
alt_interface, attribute._dbus_signature,
1339
attribute._dbus_access,
1340
attribute._dbus_get_args_options
1342
(types.FunctionType(
1343
attribute.func_code,
1344
attribute.func_globals,
1345
attribute.func_name,
1346
attribute.func_defaults,
1347
attribute.func_closure)))
1348
# Copy annotations, if any
1350
attr[attrname]._dbus_annotations = dict(
1351
attribute._dbus_annotations)
1352
except AttributeError:
1081
1354
# Is this a D-Bus interface?
1082
1355
elif getattr(attribute, "_dbus_is_interface", False):
1083
1356
# Create a new, but exactly alike, function
1084
1357
# object. Decorate it to be a new D-Bus interface
1085
1358
# with the alternate D-Bus interface name. Add it
1086
1359
# to the class.
1087
attr[attrname] = (dbus_interface_annotations
1090
(attribute.func_code,
1091
attribute.func_globals,
1092
attribute.func_name,
1093
attribute.func_defaults,
1094
attribute.func_closure)))
1095
# Deprecate all old interfaces
1096
basename="_AlternateDBusNamesMetaclass_interface_annotation{0}"
1097
for old_interface_name in old_interface_names:
1098
@dbus_interface_annotations(old_interface_name)
1100
return { "org.freedesktop.DBus.Deprecated": "true" }
1101
# Find an unused name
1102
for aname in (basename.format(i) for i in
1104
if aname not in attr:
1107
return type.__new__(mcs, name, bases, attr)
1361
dbus_interface_annotations(alt_interface)
1362
(types.FunctionType(attribute.func_code,
1363
attribute.func_globals,
1364
attribute.func_name,
1365
attribute.func_defaults,
1366
attribute.func_closure)))
1368
# Deprecate all alternate interfaces
1369
iname="_AlternateDBusNames_interface_annotation{}"
1370
for interface_name in interface_names:
1372
@dbus_interface_annotations(interface_name)
1374
return { "org.freedesktop.DBus.Deprecated":
1376
# Find an unused name
1377
for aname in (iname.format(i)
1378
for i in itertools.count()):
1379
if aname not in attr:
1383
# Replace the class with a new subclass of it with
1384
# methods, signals, etc. as created above.
1385
cls = type(b"{}Alternate".format(cls.__name__),
1392
@alternate_dbus_interfaces({"se.recompile.Mandos":
1393
"se.bsnet.fukt.Mandos"})
1110
1394
class ClientDBus(Client, DBusObjectWithProperties):
1111
1395
"""A Client class using D-Bus
1439
1745
return datetime_to_dbus(self.last_approval_request)
1441
1747
# Timeout - property
1442
@dbus_service_property(_interface, signature="t",
1748
@dbus_service_property(_interface,
1443
1750
access="readwrite")
1444
1751
def Timeout_dbus_property(self, value=None):
1445
1752
if value is None: # get
1446
return dbus.UInt64(self.timeout_milliseconds())
1753
return dbus.UInt64(self.timeout.total_seconds() * 1000)
1754
old_timeout = self.timeout
1447
1755
self.timeout = datetime.timedelta(0, 0, 0, value)
1448
# Reschedule timeout
1756
# Reschedule disabling
1449
1757
if self.enabled:
1450
1758
now = datetime.datetime.utcnow()
1451
time_to_die = timedelta_to_milliseconds(
1452
(self.last_checked_ok + self.timeout) - now)
1453
if time_to_die <= 0:
1759
self.expires += self.timeout - old_timeout
1760
if self.expires <= now:
1454
1761
# The timeout has passed
1457
self.expires = (now +
1458
datetime.timedelta(milliseconds =
1460
1764
if (getattr(self, "disable_initiator_tag", None)
1463
1767
gobject.source_remove(self.disable_initiator_tag)
1464
self.disable_initiator_tag = (gobject.timeout_add
1768
self.disable_initiator_tag = gobject.timeout_add(
1769
int((self.expires - now).total_seconds() * 1000),
1468
1772
# ExtendedTimeout - property
1469
@dbus_service_property(_interface, signature="t",
1773
@dbus_service_property(_interface,
1470
1775
access="readwrite")
1471
1776
def ExtendedTimeout_dbus_property(self, value=None):
1472
1777
if value is None: # get
1473
return dbus.UInt64(self.extended_timeout_milliseconds())
1778
return dbus.UInt64(self.extended_timeout.total_seconds()
1474
1780
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1476
1782
# Interval - property
1477
@dbus_service_property(_interface, signature="t",
1783
@dbus_service_property(_interface,
1478
1785
access="readwrite")
1479
1786
def Interval_dbus_property(self, value=None):
1480
1787
if value is None: # get
1481
return dbus.UInt64(self.interval_milliseconds())
1788
return dbus.UInt64(self.interval.total_seconds() * 1000)
1482
1789
self.interval = datetime.timedelta(0, 0, 0, value)
1483
1790
if getattr(self, "checker_initiator_tag", None) is None:
1485
1792
if self.enabled:
1486
1793
# Reschedule checker run
1487
1794
gobject.source_remove(self.checker_initiator_tag)
1488
self.checker_initiator_tag = (gobject.timeout_add
1489
(value, self.start_checker))
1490
self.start_checker() # Start one now, too
1795
self.checker_initiator_tag = gobject.timeout_add(
1796
value, self.start_checker)
1797
self.start_checker() # Start one now, too
1492
1799
# Checker - property
1493
@dbus_service_property(_interface, signature="s",
1800
@dbus_service_property(_interface,
1494
1802
access="readwrite")
1495
1803
def Checker_dbus_property(self, value=None):
1496
1804
if value is None: # get
1497
1805
return dbus.String(self.checker_command)
1498
self.checker_command = unicode(value)
1806
self.checker_command = str(value)
1500
1808
# CheckerRunning - property
1501
@dbus_service_property(_interface, signature="b",
1809
@dbus_service_property(_interface,
1502
1811
access="readwrite")
1503
1812
def CheckerRunning_dbus_property(self, value=None):
1504
1813
if value is None: # get
1732
2044
def fingerprint(openpgp):
1733
2045
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1734
2046
# New GnuTLS "datum" with the OpenPGP public key
1735
datum = (gnutls.library.types
1736
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1739
ctypes.c_uint(len(openpgp))))
2047
datum = gnutls.library.types.gnutls_datum_t(
2048
ctypes.cast(ctypes.c_char_p(openpgp),
2049
ctypes.POINTER(ctypes.c_ubyte)),
2050
ctypes.c_uint(len(openpgp)))
1740
2051
# New empty GnuTLS certificate
1741
2052
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1742
(gnutls.library.functions
1743
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
2053
gnutls.library.functions.gnutls_openpgp_crt_init(
1744
2055
# Import the OpenPGP public key into the certificate
1745
(gnutls.library.functions
1746
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1747
gnutls.library.constants
1748
.GNUTLS_OPENPGP_FMT_RAW))
2056
gnutls.library.functions.gnutls_openpgp_crt_import(
2057
crt, ctypes.byref(datum),
2058
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
1749
2059
# Verify the self signature in the key
1750
2060
crtverify = ctypes.c_uint()
1751
(gnutls.library.functions
1752
.gnutls_openpgp_crt_verify_self(crt, 0,
1753
ctypes.byref(crtverify)))
2061
gnutls.library.functions.gnutls_openpgp_crt_verify_self(
2062
crt, 0, ctypes.byref(crtverify))
1754
2063
if crtverify.value != 0:
1755
2064
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1756
raise (gnutls.errors.CertificateSecurityError
2065
raise gnutls.errors.CertificateSecurityError(
1758
2067
# New buffer for the fingerprint
1759
2068
buf = ctypes.create_string_buffer(20)
1760
2069
buf_len = ctypes.c_size_t()
1761
2070
# Get the fingerprint from the certificate into the buffer
1762
(gnutls.library.functions
1763
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1764
ctypes.byref(buf_len)))
2071
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint(
2072
crt, ctypes.byref(buf), ctypes.byref(buf_len))
1765
2073
# Deinit the certificate
1766
2074
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1767
2075
# Convert the buffer to a Python bytestring
2331
def rfc3339_duration_to_delta(duration):
2332
"""Parse an RFC 3339 "duration" and return a datetime.timedelta
2334
>>> rfc3339_duration_to_delta("P7D")
2335
datetime.timedelta(7)
2336
>>> rfc3339_duration_to_delta("PT60S")
2337
datetime.timedelta(0, 60)
2338
>>> rfc3339_duration_to_delta("PT60M")
2339
datetime.timedelta(0, 3600)
2340
>>> rfc3339_duration_to_delta("PT24H")
2341
datetime.timedelta(1)
2342
>>> rfc3339_duration_to_delta("P1W")
2343
datetime.timedelta(7)
2344
>>> rfc3339_duration_to_delta("PT5M30S")
2345
datetime.timedelta(0, 330)
2346
>>> rfc3339_duration_to_delta("P1DT3M20S")
2347
datetime.timedelta(1, 200)
2350
# Parsing an RFC 3339 duration with regular expressions is not
2351
# possible - there would have to be multiple places for the same
2352
# values, like seconds. The current code, while more esoteric, is
2353
# cleaner without depending on a parsing library. If Python had a
2354
# built-in library for parsing we would use it, but we'd like to
2355
# avoid excessive use of external libraries.
2357
# New type for defining tokens, syntax, and semantics all-in-one
2358
Token = collections.namedtuple("Token", (
2359
"regexp", # To match token; if "value" is not None, must have
2360
# a "group" containing digits
2361
"value", # datetime.timedelta or None
2362
"followers")) # Tokens valid after this token
2363
# RFC 3339 "duration" tokens, syntax, and semantics; taken from
2364
# the "duration" ABNF definition in RFC 3339, Appendix A.
2365
token_end = Token(re.compile(r"$"), None, frozenset())
2366
token_second = Token(re.compile(r"(\d+)S"),
2367
datetime.timedelta(seconds=1),
2368
frozenset((token_end, )))
2369
token_minute = Token(re.compile(r"(\d+)M"),
2370
datetime.timedelta(minutes=1),
2371
frozenset((token_second, token_end)))
2372
token_hour = Token(re.compile(r"(\d+)H"),
2373
datetime.timedelta(hours=1),
2374
frozenset((token_minute, token_end)))
2375
token_time = Token(re.compile(r"T"),
2377
frozenset((token_hour, token_minute,
2379
token_day = Token(re.compile(r"(\d+)D"),
2380
datetime.timedelta(days=1),
2381
frozenset((token_time, token_end)))
2382
token_month = Token(re.compile(r"(\d+)M"),
2383
datetime.timedelta(weeks=4),
2384
frozenset((token_day, token_end)))
2385
token_year = Token(re.compile(r"(\d+)Y"),
2386
datetime.timedelta(weeks=52),
2387
frozenset((token_month, token_end)))
2388
token_week = Token(re.compile(r"(\d+)W"),
2389
datetime.timedelta(weeks=1),
2390
frozenset((token_end, )))
2391
token_duration = Token(re.compile(r"P"), None,
2392
frozenset((token_year, token_month,
2393
token_day, token_time,
2395
# Define starting values
2396
value = datetime.timedelta() # Value so far
2398
followers = frozenset((token_duration, )) # Following valid tokens
2399
s = duration # String left to parse
2400
# Loop until end token is found
2401
while found_token is not token_end:
2402
# Search for any currently valid tokens
2403
for token in followers:
2404
match = token.regexp.match(s)
2405
if match is not None:
2407
if token.value is not None:
2408
# Value found, parse digits
2409
factor = int(match.group(1), 10)
2410
# Add to value so far
2411
value += factor * token.value
2412
# Strip token from string
2413
s = token.regexp.sub("", s, 1)
2416
# Set valid next tokens
2417
followers = found_token.followers
2420
# No currently valid tokens were found
2421
raise ValueError("Invalid RFC 3339 duration: {!r}"
1994
2427
def string_to_delta(interval):
1995
2428
"""Parse a string and return a datetime.timedelta
2110
2558
"debug": "False",
2112
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
2560
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2561
":+SIGN-DSA-SHA256",
2113
2562
"servicename": "Mandos",
2114
2563
"use_dbus": "True",
2115
2564
"use_ipv6": "True",
2116
2565
"debuglevel": "",
2117
2566
"restore": "True",
2118
"statedir": "/var/lib/mandos"
2568
"statedir": "/var/lib/mandos",
2569
"foreground": "False",
2121
2573
# Parse config file for server-global settings
2122
2574
server_config = configparser.SafeConfigParser(server_defaults)
2123
2575
del server_defaults
2124
server_config.read(os.path.join(options.configdir,
2576
server_config.read(os.path.join(options.configdir, "mandos.conf"))
2126
2577
# Convert the SafeConfigParser object to a dict
2127
2578
server_settings = server_config.defaults()
2128
2579
# Use the appropriate methods on the non-string config options
2129
for option in ("debug", "use_dbus", "use_ipv6"):
2580
for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
2130
2581
server_settings[option] = server_config.getboolean("DEFAULT",
2132
2583
if server_settings["port"]:
2133
2584
server_settings["port"] = server_config.getint("DEFAULT",
2586
if server_settings["socket"]:
2587
server_settings["socket"] = server_config.getint("DEFAULT",
2589
# Later, stdin will, and stdout and stderr might, be dup'ed
2590
# over with an opened os.devnull. But we don't want this to
2591
# happen with a supplied network socket.
2592
if 0 <= server_settings["socket"] <= 2:
2593
server_settings["socket"] = os.dup(server_settings
2135
2595
del server_config
2137
2597
# Override the settings from the config file with command line
2138
2598
# options, if set.
2139
2599
for option in ("interface", "address", "port", "debug",
2140
"priority", "servicename", "configdir",
2141
"use_dbus", "use_ipv6", "debuglevel", "restore",
2600
"priority", "servicename", "configdir", "use_dbus",
2601
"use_ipv6", "debuglevel", "restore", "statedir",
2602
"socket", "foreground", "zeroconf"):
2143
2603
value = getattr(options, option)
2144
2604
if value is not None:
2145
2605
server_settings[option] = value
2147
2607
# Force all strings to be unicode
2148
2608
for option in server_settings.keys():
2149
if type(server_settings[option]) is str:
2150
server_settings[option] = unicode(server_settings[option])
2609
if isinstance(server_settings[option], bytes):
2610
server_settings[option] = (server_settings[option]
2612
# Force all boolean options to be boolean
2613
for option in ("debug", "use_dbus", "use_ipv6", "restore",
2614
"foreground", "zeroconf"):
2615
server_settings[option] = bool(server_settings[option])
2616
# Debug implies foreground
2617
if server_settings["debug"]:
2618
server_settings["foreground"] = True
2151
2619
# Now we have our good server settings in "server_settings"
2153
2621
##################################################################
2623
if (not server_settings["zeroconf"]
2624
and not (server_settings["port"]
2625
or server_settings["socket"] != "")):
2626
parser.error("Needs port or socket to work without Zeroconf")
2155
2628
# For convenience
2156
2629
debug = server_settings["debug"]
2157
2630
debuglevel = server_settings["debuglevel"]
2259
2738
bus_name = dbus.service.BusName("se.recompile.Mandos",
2260
bus, do_not_queue=True)
2261
old_bus_name = (dbus.service.BusName
2262
("se.bsnet.fukt.Mandos", bus,
2264
except dbus.exceptions.NameExistsException as e:
2265
logger.error(unicode(e) + ", disabling D-Bus")
2741
old_bus_name = dbus.service.BusName(
2742
"se.bsnet.fukt.Mandos", bus,
2744
except dbus.exceptions.DBusException as e:
2745
logger.error("Disabling D-Bus:", exc_info=e)
2266
2746
use_dbus = False
2267
2747
server_settings["use_dbus"] = False
2268
2748
tcp_server.use_dbus = False
2269
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2270
service = AvahiServiceToSyslog(name =
2271
server_settings["servicename"],
2272
servicetype = "_mandos._tcp",
2273
protocol = protocol, bus = bus)
2274
if server_settings["interface"]:
2275
service.interface = (if_nametoindex
2276
(str(server_settings["interface"])))
2750
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2751
service = AvahiServiceToSyslog(
2752
name = server_settings["servicename"],
2753
servicetype = "_mandos._tcp",
2754
protocol = protocol,
2756
if server_settings["interface"]:
2757
service.interface = if_nametoindex(
2758
server_settings["interface"].encode("utf-8"))
2278
2760
global multiprocessing_manager
2279
2761
multiprocessing_manager = multiprocessing.Manager()
2281
2763
client_class = Client
2283
client_class = functools.partial(ClientDBusTransitional,
2765
client_class = functools.partial(ClientDBus, bus = bus)
2286
2767
client_settings = Client.config_parser(client_config)
2287
2768
old_client_settings = {}
2288
2769
clients_data = {}
2771
# This is used to redirect stdout and stderr for checker processes
2773
wnull = open(os.devnull, "w") # A writable /dev/null
2774
# Only used if server is running in foreground but not in debug
2776
if debug or not foreground:
2290
2779
# Get client data and settings from last running state.
2291
2780
if server_settings["restore"]:
2293
2782
with open(stored_state_path, "rb") as stored_state:
2294
clients_data, old_client_settings = (pickle.load
2783
clients_data, old_client_settings = pickle.load(
2296
2785
os.remove(stored_state_path)
2297
2786
except IOError as e:
2298
logger.warning("Could not load persistent state: {0}"
2300
if e.errno != errno.ENOENT:
2787
if e.errno == errno.ENOENT:
2788
logger.warning("Could not load persistent state:"
2789
" {}".format(os.strerror(e.errno)))
2791
logger.critical("Could not load persistent state:",
2302
2794
except EOFError as e:
2303
2795
logger.warning("Could not load persistent state: "
2304
"EOFError: {0}".format(e))
2306
2799
with PGPEngine() as pgp:
2307
for client_name, client in clients_data.iteritems():
2800
for client_name, client in clients_data.items():
2801
# Skip removed clients
2802
if client_name not in client_settings:
2308
2805
# Decide which value to use after restoring saved state.
2309
2806
# We have three different values: Old config file,
2310
2807
# new config file, and saved state.