174
183
def password_encode(self, password):
175
184
# Passphrase can not be empty and can not contain newlines or
176
185
# NUL bytes. So we prefix it and hex encode it.
177
return b"mandos" + binascii.hexlify(password)
186
encoded = b"mandos" + binascii.hexlify(password)
187
if len(encoded) > 2048:
188
# GnuPG can't handle long passwords, so encode differently
189
encoded = (b"mandos" + password.replace(b"\\", b"\\\\")
190
.replace(b"\n", b"\\n")
191
.replace(b"\0", b"\\x00"))
179
194
def encrypt(self, data, password):
180
self.gnupg.passphrase = self.password_encode(password)
181
with open(os.devnull, "w") as devnull:
183
proc = self.gnupg.run(['--symmetric'],
184
create_fhs=['stdin', 'stdout'],
185
attach_fhs={'stderr': devnull})
186
with contextlib.closing(proc.handles['stdin']) as f:
188
with contextlib.closing(proc.handles['stdout']) as f:
189
ciphertext = f.read()
193
self.gnupg.passphrase = None
195
passphrase = self.password_encode(password)
196
with tempfile.NamedTemporaryFile(
197
dir=self.tempdir) as passfile:
198
passfile.write(passphrase)
200
proc = subprocess.Popen(['gpg', '--symmetric',
204
stdin = subprocess.PIPE,
205
stdout = subprocess.PIPE,
206
stderr = subprocess.PIPE)
207
ciphertext, err = proc.communicate(input = data)
208
if proc.returncode != 0:
194
210
return ciphertext
196
212
def decrypt(self, data, password):
197
self.gnupg.passphrase = self.password_encode(password)
198
with open(os.devnull, "w") as devnull:
200
proc = self.gnupg.run(['--decrypt'],
201
create_fhs=['stdin', 'stdout'],
202
attach_fhs={'stderr': devnull})
203
with contextlib.closing(proc.handles['stdin']) as f:
205
with contextlib.closing(proc.handles['stdout']) as f:
206
decrypted_plaintext = f.read()
210
self.gnupg.passphrase = None
213
passphrase = self.password_encode(password)
214
with tempfile.NamedTemporaryFile(
215
dir = self.tempdir) as passfile:
216
passfile.write(passphrase)
218
proc = subprocess.Popen(['gpg', '--decrypt',
222
stdin = subprocess.PIPE,
223
stdout = subprocess.PIPE,
224
stderr = subprocess.PIPE)
225
decrypted_plaintext, err = proc.communicate(input = data)
226
if proc.returncode != 0:
211
228
return decrypted_plaintext
214
231
class AvahiError(Exception):
215
232
def __init__(self, value, *args, **kwargs):
216
233
self.value = value
217
super(AvahiError, self).__init__(value, *args, **kwargs)
218
def __unicode__(self):
219
return unicode(repr(self.value))
234
return super(AvahiError, self).__init__(value, *args,
221
238
class AvahiServiceError(AvahiError):
224
242
class AvahiGroupError(AvahiError):
376
416
follow_name_owner_changes=True),
377
417
avahi.DBUS_INTERFACE_SERVER)
378
418
self.server.connect_to_signal("StateChanged",
379
self.server_state_changed)
419
self.server_state_changed)
380
420
self.server_state_changed(self.server.GetState())
383
423
class AvahiServiceToSyslog(AvahiService):
424
def rename(self, *args, **kwargs):
385
425
"""Add the new name to the syslog messages"""
386
ret = AvahiService.rename(self)
387
syslogger.setFormatter(logging.Formatter
388
('Mandos ({0}) [%(process)d]:'
389
' %(levelname)s: %(message)s'
426
ret = AvahiService.rename(self, *args, **kwargs)
427
syslogger.setFormatter(logging.Formatter(
428
'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
394
def timedelta_to_milliseconds(td):
395
"Convert a datetime.timedelta() to milliseconds"
396
return ((td.days * 24 * 60 * 60 * 1000)
397
+ (td.seconds * 1000)
398
+ (td.microseconds // 1000))
432
# Pretend that we have a GnuTLS module
433
class GnuTLS(object):
434
"""This isn't so much a class as it is a module-like namespace.
435
It is instantiated once, and simulates having a GnuTLS module."""
437
_library = ctypes.cdll.LoadLibrary(
438
ctypes.util.find_library("gnutls"))
439
_need_version = "3.3.0"
441
# Need to use class name "GnuTLS" here, since this method is
442
# called before the assignment to the "gnutls" global variable
444
if GnuTLS.check_version(self._need_version) is None:
445
raise GnuTLS.Error("Needs GnuTLS {} or later"
446
.format(self._need_version))
448
# Unless otherwise indicated, the constants and types below are
449
# all from the gnutls/gnutls.h C header file.
459
E_NO_CERTIFICATE_FOUND = -49
460
OPENPGP_FMT_RAW = 0 # gnutls/openpgp.h
463
class session_int(ctypes.Structure):
465
session_t = ctypes.POINTER(session_int)
466
class certificate_credentials_st(ctypes.Structure):
468
certificate_credentials_t = ctypes.POINTER(
469
certificate_credentials_st)
470
certificate_type_t = ctypes.c_int
471
class datum_t(ctypes.Structure):
472
_fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
473
('size', ctypes.c_uint)]
474
class openpgp_crt_int(ctypes.Structure):
476
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
477
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
478
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
479
credentials_type_t = ctypes.c_int #
480
transport_ptr_t = ctypes.c_void_p
481
close_request_t = ctypes.c_int
484
class Error(Exception):
485
# We need to use the class name "GnuTLS" here, since this
486
# exception might be raised from within GnuTLS.__init__,
487
# which is called before the assignment to the "gnutls"
488
# global variable has happened.
489
def __init__(self, message = None, code = None, args=()):
490
# Default usage is by a message string, but if a return
491
# code is passed, convert it to a string with
494
if message is None and code is not None:
495
message = GnuTLS.strerror(code)
496
return super(GnuTLS.Error, self).__init__(
499
class CertificateSecurityError(Error):
503
class Credentials(object):
505
self._c_object = gnutls.certificate_credentials_t()
506
gnutls.certificate_allocate_credentials(
507
ctypes.byref(self._c_object))
508
self.type = gnutls.CRD_CERTIFICATE
511
gnutls.certificate_free_credentials(self._c_object)
513
class ClientSession(object):
514
def __init__(self, socket, credentials = None):
515
self._c_object = gnutls.session_t()
516
gnutls.init(ctypes.byref(self._c_object), gnutls.CLIENT)
517
gnutls.set_default_priority(self._c_object)
518
gnutls.transport_set_ptr(self._c_object, socket.fileno())
519
gnutls.handshake_set_private_extensions(self._c_object,
522
if credentials is None:
523
credentials = gnutls.Credentials()
524
gnutls.credentials_set(self._c_object, credentials.type,
525
ctypes.cast(credentials._c_object,
527
self.credentials = credentials
530
gnutls.deinit(self._c_object)
533
return gnutls.handshake(self._c_object)
535
def send(self, data):
539
data_len -= gnutls.record_send(self._c_object,
544
return gnutls.bye(self._c_object, gnutls.SHUT_RDWR)
546
# Error handling functions
547
def _error_code(result):
548
"""A function to raise exceptions on errors, suitable
549
for the 'restype' attribute on ctypes functions"""
552
if result == gnutls.E_NO_CERTIFICATE_FOUND:
553
raise gnutls.CertificateSecurityError(code = result)
554
raise gnutls.Error(code = result)
556
def _retry_on_error(result, func, arguments):
557
"""A function to retry on some errors, suitable
558
for the 'errcheck' attribute on ctypes functions"""
560
if result not in (gnutls.E_INTERRUPTED, gnutls.E_AGAIN):
561
return _error_code(result)
562
result = func(*arguments)
565
# Unless otherwise indicated, the function declarations below are
566
# all from the gnutls/gnutls.h C header file.
569
priority_set_direct = _library.gnutls_priority_set_direct
570
priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
571
ctypes.POINTER(ctypes.c_char_p)]
572
priority_set_direct.restype = _error_code
574
init = _library.gnutls_init
575
init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
576
init.restype = _error_code
578
set_default_priority = _library.gnutls_set_default_priority
579
set_default_priority.argtypes = [session_t]
580
set_default_priority.restype = _error_code
582
record_send = _library.gnutls_record_send
583
record_send.argtypes = [session_t, ctypes.c_void_p,
585
record_send.restype = ctypes.c_ssize_t
586
record_send.errcheck = _retry_on_error
588
certificate_allocate_credentials = (
589
_library.gnutls_certificate_allocate_credentials)
590
certificate_allocate_credentials.argtypes = [
591
ctypes.POINTER(certificate_credentials_t)]
592
certificate_allocate_credentials.restype = _error_code
594
certificate_free_credentials = (
595
_library.gnutls_certificate_free_credentials)
596
certificate_free_credentials.argtypes = [certificate_credentials_t]
597
certificate_free_credentials.restype = None
599
handshake_set_private_extensions = (
600
_library.gnutls_handshake_set_private_extensions)
601
handshake_set_private_extensions.argtypes = [session_t,
603
handshake_set_private_extensions.restype = None
605
credentials_set = _library.gnutls_credentials_set
606
credentials_set.argtypes = [session_t, credentials_type_t,
608
credentials_set.restype = _error_code
610
strerror = _library.gnutls_strerror
611
strerror.argtypes = [ctypes.c_int]
612
strerror.restype = ctypes.c_char_p
614
certificate_type_get = _library.gnutls_certificate_type_get
615
certificate_type_get.argtypes = [session_t]
616
certificate_type_get.restype = _error_code
618
certificate_get_peers = _library.gnutls_certificate_get_peers
619
certificate_get_peers.argtypes = [session_t,
620
ctypes.POINTER(ctypes.c_uint)]
621
certificate_get_peers.restype = ctypes.POINTER(datum_t)
623
global_set_log_level = _library.gnutls_global_set_log_level
624
global_set_log_level.argtypes = [ctypes.c_int]
625
global_set_log_level.restype = None
627
global_set_log_function = _library.gnutls_global_set_log_function
628
global_set_log_function.argtypes = [log_func]
629
global_set_log_function.restype = None
631
deinit = _library.gnutls_deinit
632
deinit.argtypes = [session_t]
633
deinit.restype = None
635
handshake = _library.gnutls_handshake
636
handshake.argtypes = [session_t]
637
handshake.restype = _error_code
638
handshake.errcheck = _retry_on_error
640
transport_set_ptr = _library.gnutls_transport_set_ptr
641
transport_set_ptr.argtypes = [session_t, transport_ptr_t]
642
transport_set_ptr.restype = None
644
bye = _library.gnutls_bye
645
bye.argtypes = [session_t, close_request_t]
646
bye.restype = _error_code
647
bye.errcheck = _retry_on_error
649
check_version = _library.gnutls_check_version
650
check_version.argtypes = [ctypes.c_char_p]
651
check_version.restype = ctypes.c_char_p
653
# All the function declarations below are from gnutls/openpgp.h
655
openpgp_crt_init = _library.gnutls_openpgp_crt_init
656
openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
657
openpgp_crt_init.restype = _error_code
659
openpgp_crt_import = _library.gnutls_openpgp_crt_import
660
openpgp_crt_import.argtypes = [openpgp_crt_t,
661
ctypes.POINTER(datum_t),
663
openpgp_crt_import.restype = _error_code
665
openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
666
openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
667
ctypes.POINTER(ctypes.c_uint)]
668
openpgp_crt_verify_self.restype = _error_code
670
openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
671
openpgp_crt_deinit.argtypes = [openpgp_crt_t]
672
openpgp_crt_deinit.restype = None
674
openpgp_crt_get_fingerprint = (
675
_library.gnutls_openpgp_crt_get_fingerprint)
676
openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
680
openpgp_crt_get_fingerprint.restype = _error_code
682
# Remove non-public functions
683
del _error_code, _retry_on_error
684
# Create the global "gnutls" object, simulating a module
687
def call_pipe(connection, # : multiprocessing.Connection
688
func, *args, **kwargs):
689
"""This function is meant to be called by multiprocessing.Process
691
This function runs func(*args, **kwargs), and writes the resulting
692
return value on the provided multiprocessing.Connection.
694
connection.send(func(*args, **kwargs))
401
697
class Client(object):
402
698
"""A representation of a client host served by this server.
610
895
# and every interval from then on.
611
896
if self.checker_initiator_tag is not None:
612
897
gobject.source_remove(self.checker_initiator_tag)
613
self.checker_initiator_tag = (gobject.timeout_add
614
(self.interval_milliseconds(),
898
self.checker_initiator_tag = gobject.timeout_add(
899
int(self.interval.total_seconds() * 1000),
616
901
# Schedule a disable() when 'timeout' has passed
617
902
if self.disable_initiator_tag is not None:
618
903
gobject.source_remove(self.disable_initiator_tag)
619
self.disable_initiator_tag = (gobject.timeout_add
620
(self.timeout_milliseconds(),
904
self.disable_initiator_tag = gobject.timeout_add(
905
int(self.timeout.total_seconds() * 1000), self.disable)
622
906
# Also start a new checker *right now*.
623
907
self.start_checker()
625
def checker_callback(self, pid, condition, command):
909
def checker_callback(self, source, condition, connection,
626
911
"""The checker has completed, so take appropriate actions."""
627
912
self.checker_callback_tag = None
628
913
self.checker = None
629
if os.WIFEXITED(condition):
630
self.last_checker_status = os.WEXITSTATUS(condition)
914
# Read return code from connection (see call_pipe)
915
returncode = connection.recv()
919
self.last_checker_status = returncode
920
self.last_checker_signal = None
631
921
if self.last_checker_status == 0:
632
922
logger.info("Checker for %(name)s succeeded",
634
924
self.checked_ok()
636
logger.info("Checker for %(name)s failed",
926
logger.info("Checker for %(name)s failed", vars(self))
639
928
self.last_checker_status = -1
929
self.last_checker_signal = -returncode
640
930
logger.warning("Checker for %(name)s crashed?",
643
934
def checked_ok(self):
644
935
"""Assert that the client has been seen, alive and well."""
645
936
self.last_checked_ok = datetime.datetime.utcnow()
646
937
self.last_checker_status = 0
938
self.last_checker_signal = None
647
939
self.bump_timeout()
649
941
def bump_timeout(self, timeout=None):
676
967
# than 'timeout' for the client to be disabled, which is as it
679
# If a checker exists, make sure it is not a zombie
681
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
682
except (AttributeError, OSError) as error:
683
if (isinstance(error, OSError)
684
and error.errno != errno.ECHILD):
688
logger.warning("Checker was a zombie")
689
gobject.source_remove(self.checker_callback_tag)
690
self.checker_callback(pid, status,
691
self.current_checker_command)
970
if self.checker is not None and not self.checker.is_alive():
971
logger.warning("Checker was not alive; joining")
692
974
# Start a new checker if needed
693
975
if self.checker is None:
694
976
# Escape attributes for the shell
695
escaped_attrs = dict(
696
(attr, re.escape(unicode(getattr(self, attr))))
698
self.runtime_expansions)
978
attr: re.escape(str(getattr(self, attr)))
979
for attr in self.runtime_expansions }
700
981
command = self.checker_command % escaped_attrs
701
982
except TypeError as error:
702
983
logger.error('Could not format string "%s"',
703
self.checker_command, exc_info=error)
704
return True # Try again later
984
self.checker_command,
986
return True # Try again later
705
987
self.current_checker_command = command
707
logger.info("Starting checker %r for %s",
709
# We don't need to redirect stdout and stderr, since
710
# in normal mode, that is already done by daemon(),
711
# and in debug mode we don't want to. (Stdin is
712
# always replaced by /dev/null.)
713
self.checker = subprocess.Popen(command,
716
except OSError as error:
717
logger.error("Failed to start subprocess",
719
self.checker_callback_tag = (gobject.child_watch_add
721
self.checker_callback,
723
# The checker may have completed before the gobject
724
# watch was added. Check for this.
725
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
727
gobject.source_remove(self.checker_callback_tag)
728
self.checker_callback(pid, status, command)
988
logger.info("Starting checker %r for %s", command,
990
# We don't need to redirect stdout and stderr, since
991
# in normal mode, that is already done by daemon(),
992
# and in debug mode we don't want to. (Stdin is
993
# always replaced by /dev/null.)
994
# The exception is when not debugging but nevertheless
995
# running in the foreground; use the previously
997
popen_args = { "close_fds": True,
1000
if (not self.server_settings["debug"]
1001
and self.server_settings["foreground"]):
1002
popen_args.update({"stdout": wnull,
1004
pipe = multiprocessing.Pipe(duplex = False)
1005
self.checker = multiprocessing.Process(
1007
args = (pipe[1], subprocess.call, command),
1008
kwargs = popen_args)
1009
self.checker.start()
1010
self.checker_callback_tag = gobject.io_add_watch(
1011
pipe[0].fileno(), gobject.IO_IN,
1012
self.checker_callback, pipe[0], command)
729
1013
# Re-run this periodically if run by gobject.timeout_add
849
1134
If called like _is_dbus_thing("method") it returns a function
850
1135
suitable for use as predicate to inspect.getmembers().
852
return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
1137
return lambda obj: getattr(obj, "_dbus_is_{}".format(thing),
855
1140
def _get_all_dbus_things(self, thing):
856
1141
"""Returns a generator of (name, attribute) pairs
858
return ((getattr(athing.__get__(self), "_dbus_name",
1143
return ((getattr(athing.__get__(self), "_dbus_name", name),
860
1144
athing.__get__(self))
861
1145
for cls in self.__class__.__mro__
862
1146
for name, athing in
863
inspect.getmembers(cls,
864
self._is_dbus_thing(thing)))
1147
inspect.getmembers(cls, self._is_dbus_thing(thing)))
1149
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1150
out_signature = "s",
1151
path_keyword = 'object_path',
1152
connection_keyword = 'connection')
1153
def Introspect(self, object_path, connection):
1154
"""Overloading of standard D-Bus method.
1156
Inserts annotation tags on methods and signals.
1158
xmlstring = dbus.service.Object.Introspect(self, object_path,
1161
document = xml.dom.minidom.parseString(xmlstring)
1163
for if_tag in document.getElementsByTagName("interface"):
1164
# Add annotation tags
1165
for typ in ("method", "signal"):
1166
for tag in if_tag.getElementsByTagName(typ):
1168
for name, prop in (self.
1169
_get_all_dbus_things(typ)):
1170
if (name == tag.getAttribute("name")
1171
and prop._dbus_interface
1172
== if_tag.getAttribute("name")):
1173
annots.update(getattr(
1174
prop, "_dbus_annotations", {}))
1175
for name, value in annots.items():
1176
ann_tag = document.createElement(
1178
ann_tag.setAttribute("name", name)
1179
ann_tag.setAttribute("value", value)
1180
tag.appendChild(ann_tag)
1181
# Add interface annotation tags
1182
for annotation, value in dict(
1183
itertools.chain.from_iterable(
1184
annotations().items()
1185
for name, annotations
1186
in self._get_all_dbus_things("interface")
1187
if name == if_tag.getAttribute("name")
1189
ann_tag = document.createElement("annotation")
1190
ann_tag.setAttribute("name", annotation)
1191
ann_tag.setAttribute("value", value)
1192
if_tag.appendChild(ann_tag)
1193
# Fix argument name for the Introspect method itself
1194
if (if_tag.getAttribute("name")
1195
== dbus.INTROSPECTABLE_IFACE):
1196
for cn in if_tag.getElementsByTagName("method"):
1197
if cn.getAttribute("name") == "Introspect":
1198
for arg in cn.getElementsByTagName("arg"):
1199
if (arg.getAttribute("direction")
1201
arg.setAttribute("name",
1203
xmlstring = document.toxml("utf-8")
1205
except (AttributeError, xml.dom.DOMException,
1206
xml.parsers.expat.ExpatError) as error:
1207
logger.error("Failed to override Introspection method",
1212
class DBusObjectWithProperties(DBusObjectWithAnnotations):
1213
"""A D-Bus object with properties.
1215
Classes inheriting from this can use the dbus_service_property
1216
decorator to expose methods as D-Bus properties. It exposes the
1217
standard Get(), Set(), and GetAll() methods on the D-Bus.
866
1220
def _get_dbus_property(self, interface_name, property_name):
867
1221
"""Returns a bound method if one exists which is a D-Bus
868
1222
property with the specified name and interface.
870
for cls in self.__class__.__mro__:
871
for name, value in (inspect.getmembers
873
self._is_dbus_thing("property"))):
1224
for cls in self.__class__.__mro__:
1225
for name, value in inspect.getmembers(
1226
cls, self._is_dbus_thing("property")):
874
1227
if (value._dbus_name == property_name
875
1228
and value._dbus_interface == interface_name):
876
1229
return value.__get__(self)
878
1231
# No such property
879
raise DBusPropertyNotFound(self.dbus_object_path + ":"
880
+ interface_name + "."
883
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
1232
raise DBusPropertyNotFound("{}:{}.{}".format(
1233
self.dbus_object_path, interface_name, property_name))
1236
def _get_all_interface_names(cls):
1237
"""Get a sequence of all interfaces supported by an object"""
1238
return (name for name in set(getattr(getattr(x, attr),
1239
"_dbus_interface", None)
1240
for x in (inspect.getmro(cls))
1242
if name is not None)
1244
@dbus.service.method(dbus.PROPERTIES_IFACE,
884
1246
out_signature="v")
885
1247
def Get(self, interface_name, property_name):
886
1248
"""Standard D-Bus property Get() method, see D-Bus standard.
1015
1376
exc_info=error)
1016
1377
return xmlstring
1380
dbus.OBJECT_MANAGER_IFACE
1381
except AttributeError:
1382
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1384
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1385
"""A D-Bus object with an ObjectManager.
1387
Classes inheriting from this exposes the standard
1388
GetManagedObjects call and the InterfacesAdded and
1389
InterfacesRemoved signals on the standard
1390
"org.freedesktop.DBus.ObjectManager" interface.
1392
Note: No signals are sent automatically; they must be sent
1395
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1396
out_signature = "a{oa{sa{sv}}}")
1397
def GetManagedObjects(self):
1398
"""This function must be overridden"""
1399
raise NotImplementedError()
1401
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1402
signature = "oa{sa{sv}}")
1403
def InterfacesAdded(self, object_path, interfaces_and_properties):
1406
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
1407
def InterfacesRemoved(self, object_path, interfaces):
1410
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1411
out_signature = "s",
1412
path_keyword = 'object_path',
1413
connection_keyword = 'connection')
1414
def Introspect(self, object_path, connection):
1415
"""Overloading of standard D-Bus method.
1417
Override return argument name of GetManagedObjects to be
1418
"objpath_interfaces_and_properties"
1420
xmlstring = DBusObjectWithAnnotations.Introspect(self,
1424
document = xml.dom.minidom.parseString(xmlstring)
1426
for if_tag in document.getElementsByTagName("interface"):
1427
# Fix argument name for the GetManagedObjects method
1428
if (if_tag.getAttribute("name")
1429
== dbus.OBJECT_MANAGER_IFACE):
1430
for cn in if_tag.getElementsByTagName("method"):
1431
if (cn.getAttribute("name")
1432
== "GetManagedObjects"):
1433
for arg in cn.getElementsByTagName("arg"):
1434
if (arg.getAttribute("direction")
1438
"objpath_interfaces"
1440
xmlstring = document.toxml("utf-8")
1442
except (AttributeError, xml.dom.DOMException,
1443
xml.parsers.expat.ExpatError) as error:
1444
logger.error("Failed to override Introspection method",
1019
1448
def datetime_to_dbus(dt, variant_level=0):
1020
1449
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1022
1451
return dbus.String("", variant_level = variant_level)
1023
return dbus.String(dt.isoformat(),
1024
variant_level=variant_level)
1452
return dbus.String(dt.isoformat(), variant_level=variant_level)
1027
1455
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1057
1486
# Ignore non-D-Bus attributes, and D-Bus attributes
1058
1487
# with the wrong interface name
1059
1488
if (not hasattr(attribute, "_dbus_interface")
1060
or not attribute._dbus_interface
1061
.startswith(orig_interface_name)):
1489
or not attribute._dbus_interface.startswith(
1490
orig_interface_name)):
1063
1492
# Create an alternate D-Bus interface name based on
1064
1493
# the current name
1065
alt_interface = (attribute._dbus_interface
1066
.replace(orig_interface_name,
1067
alt_interface_name))
1494
alt_interface = attribute._dbus_interface.replace(
1495
orig_interface_name, alt_interface_name)
1068
1496
interface_names.add(alt_interface)
1069
1497
# Is this a D-Bus signal?
1070
1498
if getattr(attribute, "_dbus_is_signal", False):
1071
# Extract the original non-method function by
1073
nonmethod_func = (dict(
1499
if sys.version_info.major == 2:
1500
# Extract the original non-method undecorated
1501
# function by black magic
1502
nonmethod_func = (dict(
1074
1503
zip(attribute.func_code.co_freevars,
1075
attribute.__closure__))["func"]
1504
attribute.__closure__))
1505
["func"].cell_contents)
1507
nonmethod_func = attribute
1077
1508
# Create a new, but exactly alike, function
1078
1509
# object, and decorate it to be a new D-Bus signal
1079
1510
# with the alternate D-Bus interface name
1080
new_function = (dbus.service.signal
1082
attribute._dbus_signature)
1083
(types.FunctionType(
1084
nonmethod_func.func_code,
1085
nonmethod_func.func_globals,
1086
nonmethod_func.func_name,
1087
nonmethod_func.func_defaults,
1088
nonmethod_func.func_closure)))
1511
if sys.version_info.major == 2:
1512
new_function = types.FunctionType(
1513
nonmethod_func.func_code,
1514
nonmethod_func.func_globals,
1515
nonmethod_func.func_name,
1516
nonmethod_func.func_defaults,
1517
nonmethod_func.func_closure)
1519
new_function = types.FunctionType(
1520
nonmethod_func.__code__,
1521
nonmethod_func.__globals__,
1522
nonmethod_func.__name__,
1523
nonmethod_func.__defaults__,
1524
nonmethod_func.__closure__)
1525
new_function = (dbus.service.signal(
1527
attribute._dbus_signature)(new_function))
1089
1528
# Copy annotations, if any
1091
new_function._dbus_annotations = (
1092
dict(attribute._dbus_annotations))
1530
new_function._dbus_annotations = dict(
1531
attribute._dbus_annotations)
1093
1532
except AttributeError:
1095
1534
# Define a creator of a function to call both the
1298
1758
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1299
1759
Client.__del__(self, *args, **kwargs)
1301
def checker_callback(self, pid, condition, command,
1303
self.checker_callback_tag = None
1305
if os.WIFEXITED(condition):
1306
exitstatus = os.WEXITSTATUS(condition)
1761
def checker_callback(self, source, condition,
1762
connection, command, *args, **kwargs):
1763
ret = Client.checker_callback(self, source, condition,
1764
connection, command, *args,
1766
exitstatus = self.last_checker_status
1307
1768
# Emit D-Bus signal
1308
1769
self.CheckerCompleted(dbus.Int16(exitstatus),
1309
dbus.Int64(condition),
1770
# This is specific to GNU libC
1771
dbus.Int64(exitstatus << 8),
1310
1772
dbus.String(command))
1312
1774
# Emit D-Bus signal
1313
1775
self.CheckerCompleted(dbus.Int16(-1),
1314
dbus.Int64(condition),
1777
# This is specific to GNU libC
1779
| self.last_checker_signal),
1315
1780
dbus.String(command))
1317
return Client.checker_callback(self, pid, condition, command,
1320
1783
def start_checker(self, *args, **kwargs):
1321
old_checker = self.checker
1322
if self.checker is not None:
1323
old_checker_pid = self.checker.pid
1325
old_checker_pid = None
1784
old_checker_pid = getattr(self.checker, "pid", None)
1326
1785
r = Client.start_checker(self, *args, **kwargs)
1327
1786
# Only if new checker process was started
1328
1787
if (self.checker is not None
1444
1902
self.approved_by_default = bool(value)
1446
1904
# ApprovalDelay - property
1447
@dbus_service_property(_interface, signature="t",
1905
@dbus_service_property(_interface,
1448
1907
access="readwrite")
1449
1908
def ApprovalDelay_dbus_property(self, value=None):
1450
1909
if value is None: # get
1451
return dbus.UInt64(self.approval_delay_milliseconds())
1910
return dbus.UInt64(self.approval_delay.total_seconds()
1452
1912
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1454
1914
# ApprovalDuration - property
1455
@dbus_service_property(_interface, signature="t",
1915
@dbus_service_property(_interface,
1456
1917
access="readwrite")
1457
1918
def ApprovalDuration_dbus_property(self, value=None):
1458
1919
if value is None: # get
1459
return dbus.UInt64(timedelta_to_milliseconds(
1460
self.approval_duration))
1920
return dbus.UInt64(self.approval_duration.total_seconds()
1461
1922
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1463
1924
# Name - property
1926
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1464
1927
@dbus_service_property(_interface, signature="s", access="read")
1465
1928
def Name_dbus_property(self):
1466
1929
return dbus.String(self.name)
1468
1931
# Fingerprint - property
1933
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1469
1934
@dbus_service_property(_interface, signature="s", access="read")
1470
1935
def Fingerprint_dbus_property(self):
1471
1936
return dbus.String(self.fingerprint)
1473
1938
# Host - property
1474
@dbus_service_property(_interface, signature="s",
1939
@dbus_service_property(_interface,
1475
1941
access="readwrite")
1476
1942
def Host_dbus_property(self, value=None):
1477
1943
if value is None: # get
1478
1944
return dbus.String(self.host)
1479
self.host = unicode(value)
1945
self.host = str(value)
1481
1947
# Created - property
1949
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1482
1950
@dbus_service_property(_interface, signature="s", access="read")
1483
1951
def Created_dbus_property(self):
1484
1952
return datetime_to_dbus(self.created)
1546
2016
gobject.source_remove(self.disable_initiator_tag)
1547
self.disable_initiator_tag = (
1548
gobject.timeout_add(
1549
timedelta_to_milliseconds(self.expires - now),
2017
self.disable_initiator_tag = gobject.timeout_add(
2018
int((self.expires - now).total_seconds() * 1000),
1552
2021
# ExtendedTimeout - property
1553
@dbus_service_property(_interface, signature="t",
2022
@dbus_service_property(_interface,
1554
2024
access="readwrite")
1555
2025
def ExtendedTimeout_dbus_property(self, value=None):
1556
2026
if value is None: # get
1557
return dbus.UInt64(self.extended_timeout_milliseconds())
2027
return dbus.UInt64(self.extended_timeout.total_seconds()
1558
2029
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1560
2031
# Interval - property
1561
@dbus_service_property(_interface, signature="t",
2032
@dbus_service_property(_interface,
1562
2034
access="readwrite")
1563
2035
def Interval_dbus_property(self, value=None):
1564
2036
if value is None: # get
1565
return dbus.UInt64(self.interval_milliseconds())
2037
return dbus.UInt64(self.interval.total_seconds() * 1000)
1566
2038
self.interval = datetime.timedelta(0, 0, 0, value)
1567
2039
if getattr(self, "checker_initiator_tag", None) is None:
1569
2041
if self.enabled:
1570
2042
# Reschedule checker run
1571
2043
gobject.source_remove(self.checker_initiator_tag)
1572
self.checker_initiator_tag = (gobject.timeout_add
1573
(value, self.start_checker))
1574
self.start_checker() # Start one now, too
2044
self.checker_initiator_tag = gobject.timeout_add(
2045
value, self.start_checker)
2046
self.start_checker() # Start one now, too
1576
2048
# Checker - property
1577
@dbus_service_property(_interface, signature="s",
2049
@dbus_service_property(_interface,
1578
2051
access="readwrite")
1579
2052
def Checker_dbus_property(self, value=None):
1580
2053
if value is None: # get
1581
2054
return dbus.String(self.checker_command)
1582
self.checker_command = unicode(value)
2055
self.checker_command = str(value)
1584
2057
# CheckerRunning - property
1585
@dbus_service_property(_interface, signature="b",
2058
@dbus_service_property(_interface,
1586
2060
access="readwrite")
1587
2061
def CheckerRunning_dbus_property(self, value=None):
1588
2062
if value is None: # get
1814
2277
def fingerprint(openpgp):
1815
2278
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1816
2279
# New GnuTLS "datum" with the OpenPGP public key
1817
datum = (gnutls.library.types
1818
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1821
ctypes.c_uint(len(openpgp))))
2280
datum = gnutls.datum_t(
2281
ctypes.cast(ctypes.c_char_p(openpgp),
2282
ctypes.POINTER(ctypes.c_ubyte)),
2283
ctypes.c_uint(len(openpgp)))
1822
2284
# New empty GnuTLS certificate
1823
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1824
(gnutls.library.functions
1825
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
2285
crt = gnutls.openpgp_crt_t()
2286
gnutls.openpgp_crt_init(ctypes.byref(crt))
1826
2287
# Import the OpenPGP public key into the certificate
1827
(gnutls.library.functions
1828
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1829
gnutls.library.constants
1830
.GNUTLS_OPENPGP_FMT_RAW))
2288
gnutls.openpgp_crt_import(crt, ctypes.byref(datum),
2289
gnutls.OPENPGP_FMT_RAW)
1831
2290
# Verify the self signature in the key
1832
2291
crtverify = ctypes.c_uint()
1833
(gnutls.library.functions
1834
.gnutls_openpgp_crt_verify_self(crt, 0,
1835
ctypes.byref(crtverify)))
2292
gnutls.openpgp_crt_verify_self(crt, 0,
2293
ctypes.byref(crtverify))
1836
2294
if crtverify.value != 0:
1837
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1838
raise (gnutls.errors.CertificateSecurityError
2295
gnutls.openpgp_crt_deinit(crt)
2296
raise gnutls.CertificateSecurityError("Verify failed")
1840
2297
# New buffer for the fingerprint
1841
2298
buf = ctypes.create_string_buffer(20)
1842
2299
buf_len = ctypes.c_size_t()
1843
2300
# Get the fingerprint from the certificate into the buffer
1844
(gnutls.library.functions
1845
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1846
ctypes.byref(buf_len)))
2301
gnutls.openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
2302
ctypes.byref(buf_len))
1847
2303
# Deinit the certificate
1848
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
2304
gnutls.openpgp_crt_deinit(crt)
1849
2305
# Convert the buffer to a Python bytestring
1850
2306
fpr = ctypes.string_at(buf, buf_len.value)
1851
2307
# Convert the bytestring to hexadecimal notation
2561
def rfc3339_duration_to_delta(duration):
2562
"""Parse an RFC 3339 "duration" and return a datetime.timedelta
2564
>>> rfc3339_duration_to_delta("P7D")
2565
datetime.timedelta(7)
2566
>>> rfc3339_duration_to_delta("PT60S")
2567
datetime.timedelta(0, 60)
2568
>>> rfc3339_duration_to_delta("PT60M")
2569
datetime.timedelta(0, 3600)
2570
>>> rfc3339_duration_to_delta("PT24H")
2571
datetime.timedelta(1)
2572
>>> rfc3339_duration_to_delta("P1W")
2573
datetime.timedelta(7)
2574
>>> rfc3339_duration_to_delta("PT5M30S")
2575
datetime.timedelta(0, 330)
2576
>>> rfc3339_duration_to_delta("P1DT3M20S")
2577
datetime.timedelta(1, 200)
2580
# Parsing an RFC 3339 duration with regular expressions is not
2581
# possible - there would have to be multiple places for the same
2582
# values, like seconds. The current code, while more esoteric, is
2583
# cleaner without depending on a parsing library. If Python had a
2584
# built-in library for parsing we would use it, but we'd like to
2585
# avoid excessive use of external libraries.
2587
# New type for defining tokens, syntax, and semantics all-in-one
2588
Token = collections.namedtuple("Token", (
2589
"regexp", # To match token; if "value" is not None, must have
2590
# a "group" containing digits
2591
"value", # datetime.timedelta or None
2592
"followers")) # Tokens valid after this token
2593
# RFC 3339 "duration" tokens, syntax, and semantics; taken from
2594
# the "duration" ABNF definition in RFC 3339, Appendix A.
2595
token_end = Token(re.compile(r"$"), None, frozenset())
2596
token_second = Token(re.compile(r"(\d+)S"),
2597
datetime.timedelta(seconds=1),
2598
frozenset((token_end, )))
2599
token_minute = Token(re.compile(r"(\d+)M"),
2600
datetime.timedelta(minutes=1),
2601
frozenset((token_second, token_end)))
2602
token_hour = Token(re.compile(r"(\d+)H"),
2603
datetime.timedelta(hours=1),
2604
frozenset((token_minute, token_end)))
2605
token_time = Token(re.compile(r"T"),
2607
frozenset((token_hour, token_minute,
2609
token_day = Token(re.compile(r"(\d+)D"),
2610
datetime.timedelta(days=1),
2611
frozenset((token_time, token_end)))
2612
token_month = Token(re.compile(r"(\d+)M"),
2613
datetime.timedelta(weeks=4),
2614
frozenset((token_day, token_end)))
2615
token_year = Token(re.compile(r"(\d+)Y"),
2616
datetime.timedelta(weeks=52),
2617
frozenset((token_month, token_end)))
2618
token_week = Token(re.compile(r"(\d+)W"),
2619
datetime.timedelta(weeks=1),
2620
frozenset((token_end, )))
2621
token_duration = Token(re.compile(r"P"), None,
2622
frozenset((token_year, token_month,
2623
token_day, token_time,
2625
# Define starting values
2626
value = datetime.timedelta() # Value so far
2628
followers = frozenset((token_duration, )) # Following valid tokens
2629
s = duration # String left to parse
2630
# Loop until end token is found
2631
while found_token is not token_end:
2632
# Search for any currently valid tokens
2633
for token in followers:
2634
match = token.regexp.match(s)
2635
if match is not None:
2637
if token.value is not None:
2638
# Value found, parse digits
2639
factor = int(match.group(1), 10)
2640
# Add to value so far
2641
value += factor * token.value
2642
# Strip token from string
2643
s = token.regexp.sub("", s, 1)
2646
# Set valid next tokens
2647
followers = found_token.followers
2650
# No currently valid tokens were found
2651
raise ValueError("Invalid RFC 3339 duration: {!r}"
2096
2657
def string_to_delta(interval):
2097
2658
"""Parse a string and return a datetime.timedelta
2253
2827
# Override the settings from the config file with command line
2254
2828
# options, if set.
2255
2829
for option in ("interface", "address", "port", "debug",
2256
"priority", "servicename", "configdir",
2257
"use_dbus", "use_ipv6", "debuglevel", "restore",
2258
"statedir", "socket"):
2830
"priority", "servicename", "configdir", "use_dbus",
2831
"use_ipv6", "debuglevel", "restore", "statedir",
2832
"socket", "foreground", "zeroconf"):
2259
2833
value = getattr(options, option)
2260
2834
if value is not None:
2261
2835
server_settings[option] = value
2263
2837
# Force all strings to be unicode
2264
2838
for option in server_settings.keys():
2265
if type(server_settings[option]) is str:
2266
server_settings[option] = unicode(server_settings[option])
2839
if isinstance(server_settings[option], bytes):
2840
server_settings[option] = (server_settings[option]
2842
# Force all boolean options to be boolean
2843
for option in ("debug", "use_dbus", "use_ipv6", "restore",
2844
"foreground", "zeroconf"):
2845
server_settings[option] = bool(server_settings[option])
2846
# Debug implies foreground
2847
if server_settings["debug"]:
2848
server_settings["foreground"] = True
2267
2849
# Now we have our good server settings in "server_settings"
2269
2851
##################################################################
2853
if (not server_settings["zeroconf"]
2854
and not (server_settings["port"]
2855
or server_settings["socket"] != "")):
2856
parser.error("Needs port or socket to work without Zeroconf")
2271
2858
# For convenience
2272
2859
debug = server_settings["debug"]
2273
2860
debuglevel = server_settings["debuglevel"]