2
2
# -*- mode: python; coding: utf-8 -*-
4
4
# Mandos server - give out binary blobs to connecting clients.
6
6
# This program is partly derived from an example program for an Avahi
7
7
# service publisher, downloaded from
8
8
# <http://avahi.org/wiki/PythonPublishExample>. This includes the
9
9
# methods "add", "remove", "server_state_changed",
10
10
# "entry_group_state_changed", "cleanup", and "activate" in the
11
11
# "AvahiService" class, and some lines in "main".
13
13
# Everything else is
14
14
# Copyright © 2008-2016 Teddy Hogeborn
15
15
# Copyright © 2008-2016 Björn Påhlsson
17
17
# This program is free software: you can redistribute it and/or modify
18
18
# it under the terms of the GNU General Public License as published by
19
19
# the Free Software Foundation, either version 3 of the License, or
246
246
'--passphrase-file',
248
248
+ self.gnupgargs,
249
stdin = subprocess.PIPE,
250
stdout = subprocess.PIPE,
251
stderr = subprocess.PIPE)
252
ciphertext, err = proc.communicate(input = data)
249
stdin=subprocess.PIPE,
250
stdout=subprocess.PIPE,
251
stderr=subprocess.PIPE)
252
ciphertext, err = proc.communicate(input=data)
253
253
if proc.returncode != 0:
254
254
raise PGPError(err)
255
255
return ciphertext
257
257
def decrypt(self, data, password):
258
258
passphrase = self.password_encode(password)
259
259
with tempfile.NamedTemporaryFile(
260
dir = self.tempdir) as passfile:
260
dir=self.tempdir) as passfile:
261
261
passfile.write(passphrase)
263
263
proc = subprocess.Popen([self.gpg, '--decrypt',
264
264
'--passphrase-file',
266
266
+ self.gnupgargs,
267
stdin = subprocess.PIPE,
268
stdout = subprocess.PIPE,
269
stderr = subprocess.PIPE)
270
decrypted_plaintext, err = proc.communicate(input = data)
267
stdin=subprocess.PIPE,
268
stdout=subprocess.PIPE,
269
stderr=subprocess.PIPE)
270
decrypted_plaintext, err = proc.communicate(input=data)
271
271
if proc.returncode != 0:
272
272
raise PGPError(err)
273
273
return decrypted_plaintext
275
276
# Pretend that we have an Avahi module
276
277
class Avahi(object):
277
278
"""This isn't so much a class as it is a module-like namespace.
278
279
It is instantiated once, and simulates having an Avahi module."""
279
IF_UNSPEC = -1 # avahi-common/address.h
280
PROTO_UNSPEC = -1 # avahi-common/address.h
281
PROTO_INET = 0 # avahi-common/address.h
282
PROTO_INET6 = 1 # avahi-common/address.h
280
IF_UNSPEC = -1 # avahi-common/address.h
281
PROTO_UNSPEC = -1 # avahi-common/address.h
282
PROTO_INET = 0 # avahi-common/address.h
283
PROTO_INET6 = 1 # avahi-common/address.h
283
284
DBUS_NAME = "org.freedesktop.Avahi"
284
285
DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"
285
286
DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
286
287
DBUS_PATH_SERVER = "/"
287
289
def string_array_to_txt_array(self, t):
288
290
return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
289
291
for s in t), signature="ay")
290
ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h
291
ENTRY_GROUP_COLLISION = 3 # avahi-common/defs.h
292
ENTRY_GROUP_FAILURE = 4 # avahi-common/defs.h
293
SERVER_INVALID = 0 # avahi-common/defs.h
294
SERVER_REGISTERING = 1 # avahi-common/defs.h
295
SERVER_RUNNING = 2 # avahi-common/defs.h
296
SERVER_COLLISION = 3 # avahi-common/defs.h
297
SERVER_FAILURE = 4 # avahi-common/defs.h
292
ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h
293
ENTRY_GROUP_COLLISION = 3 # avahi-common/defs.h
294
ENTRY_GROUP_FAILURE = 4 # avahi-common/defs.h
295
SERVER_INVALID = 0 # avahi-common/defs.h
296
SERVER_REGISTERING = 1 # avahi-common/defs.h
297
SERVER_RUNNING = 2 # avahi-common/defs.h
298
SERVER_COLLISION = 3 # avahi-common/defs.h
299
SERVER_FAILURE = 4 # avahi-common/defs.h
300
303
class AvahiError(Exception):
301
304
def __init__(self, value, *args, **kwargs):
302
305
self.value = value
527
532
CRD_CERTIFICATE = 1
528
533
E_NO_CERTIFICATE_FOUND = -49
529
534
OPENPGP_FMT_RAW = 0 # gnutls/openpgp.h
532
537
class session_int(ctypes.Structure):
534
539
session_t = ctypes.POINTER(session_int)
535
541
class certificate_credentials_st(ctypes.Structure):
537
543
certificate_credentials_t = ctypes.POINTER(
538
544
certificate_credentials_st)
539
545
certificate_type_t = ctypes.c_int
540
547
class datum_t(ctypes.Structure):
541
548
_fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
542
549
('size', ctypes.c_uint)]
543
551
class openpgp_crt_int(ctypes.Structure):
545
553
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
546
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
554
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
547
555
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
548
556
credentials_type_t = ctypes.c_int
549
557
transport_ptr_t = ctypes.c_void_p
550
558
close_request_t = ctypes.c_int
553
561
class Error(Exception):
554
562
# We need to use the class name "GnuTLS" here, since this
555
563
# exception might be raised from within GnuTLS.__init__,
556
564
# which is called before the assignment to the "gnutls"
557
565
# global variable has happened.
558
def __init__(self, message = None, code = None, args=()):
566
def __init__(self, message=None, code=None, args=()):
559
567
# Default usage is by a message string, but if a return
560
568
# code is passed, convert it to a string with
561
569
# gnutls.strerror()
630
638
return _error_code(result)
631
639
result = func(*arguments)
634
642
# Unless otherwise indicated, the function declarations below are
635
643
# all from the gnutls/gnutls.h C header file.
638
646
priority_set_direct = _library.gnutls_priority_set_direct
639
647
priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
640
648
ctypes.POINTER(ctypes.c_char_p)]
641
649
priority_set_direct.restype = _error_code
643
651
init = _library.gnutls_init
644
652
init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
645
653
init.restype = _error_code
647
655
set_default_priority = _library.gnutls_set_default_priority
648
656
set_default_priority.argtypes = [session_t]
649
657
set_default_priority.restype = _error_code
651
659
record_send = _library.gnutls_record_send
652
660
record_send.argtypes = [session_t, ctypes.c_void_p,
654
662
record_send.restype = ctypes.c_ssize_t
655
663
record_send.errcheck = _retry_on_error
657
665
certificate_allocate_credentials = (
658
666
_library.gnutls_certificate_allocate_credentials)
659
667
certificate_allocate_credentials.argtypes = [
660
668
ctypes.POINTER(certificate_credentials_t)]
661
669
certificate_allocate_credentials.restype = _error_code
663
671
certificate_free_credentials = (
664
672
_library.gnutls_certificate_free_credentials)
665
certificate_free_credentials.argtypes = [certificate_credentials_t]
673
certificate_free_credentials.argtypes = [
674
certificate_credentials_t]
666
675
certificate_free_credentials.restype = None
668
677
handshake_set_private_extensions = (
669
678
_library.gnutls_handshake_set_private_extensions)
670
679
handshake_set_private_extensions.argtypes = [session_t,
672
681
handshake_set_private_extensions.restype = None
674
683
credentials_set = _library.gnutls_credentials_set
675
684
credentials_set.argtypes = [session_t, credentials_type_t,
677
686
credentials_set.restype = _error_code
679
688
strerror = _library.gnutls_strerror
680
689
strerror.argtypes = [ctypes.c_int]
681
690
strerror.restype = ctypes.c_char_p
683
692
certificate_type_get = _library.gnutls_certificate_type_get
684
693
certificate_type_get.argtypes = [session_t]
685
694
certificate_type_get.restype = _error_code
687
696
certificate_get_peers = _library.gnutls_certificate_get_peers
688
697
certificate_get_peers.argtypes = [session_t,
689
698
ctypes.POINTER(ctypes.c_uint)]
690
699
certificate_get_peers.restype = ctypes.POINTER(datum_t)
692
701
global_set_log_level = _library.gnutls_global_set_log_level
693
702
global_set_log_level.argtypes = [ctypes.c_int]
694
703
global_set_log_level.restype = None
696
705
global_set_log_function = _library.gnutls_global_set_log_function
697
706
global_set_log_function.argtypes = [log_func]
698
707
global_set_log_function.restype = None
700
709
deinit = _library.gnutls_deinit
701
710
deinit.argtypes = [session_t]
702
711
deinit.restype = None
704
713
handshake = _library.gnutls_handshake
705
714
handshake.argtypes = [session_t]
706
715
handshake.restype = _error_code
707
716
handshake.errcheck = _retry_on_error
709
718
transport_set_ptr = _library.gnutls_transport_set_ptr
710
719
transport_set_ptr.argtypes = [session_t, transport_ptr_t]
711
720
transport_set_ptr.restype = None
713
722
bye = _library.gnutls_bye
714
723
bye.argtypes = [session_t, close_request_t]
715
724
bye.restype = _error_code
716
725
bye.errcheck = _retry_on_error
718
727
check_version = _library.gnutls_check_version
719
728
check_version.argtypes = [ctypes.c_char_p]
720
729
check_version.restype = ctypes.c_char_p
722
731
# All the function declarations below are from gnutls/openpgp.h
724
733
openpgp_crt_init = _library.gnutls_openpgp_crt_init
725
734
openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
726
735
openpgp_crt_init.restype = _error_code
728
737
openpgp_crt_import = _library.gnutls_openpgp_crt_import
729
738
openpgp_crt_import.argtypes = [openpgp_crt_t,
730
739
ctypes.POINTER(datum_t),
731
740
openpgp_crt_fmt_t]
732
741
openpgp_crt_import.restype = _error_code
734
743
openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
735
744
openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
736
745
ctypes.POINTER(ctypes.c_uint)]
737
746
openpgp_crt_verify_self.restype = _error_code
739
748
openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
740
749
openpgp_crt_deinit.argtypes = [openpgp_crt_t]
741
750
openpgp_crt_deinit.restype = None
743
752
openpgp_crt_get_fingerprint = (
744
753
_library.gnutls_openpgp_crt_get_fingerprint)
745
754
openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
1065
1076
# The exception is when not debugging but nevertheless
1066
1077
# running in the foreground; use the previously
1067
1078
# created wnull.
1068
popen_args = { "close_fds": True,
1079
popen_args = {"close_fds": True,
1071
1082
if (not self.server_settings["debug"]
1072
1083
and self.server_settings["foreground"]):
1073
1084
popen_args.update({"stdout": wnull,
1075
pipe = multiprocessing.Pipe(duplex = False)
1086
pipe = multiprocessing.Pipe(duplex=False)
1076
1087
self.checker = multiprocessing.Process(
1078
args = (pipe[1], subprocess.call, command),
1079
kwargs = popen_args)
1089
args=(pipe[1], subprocess.call, command),
1080
1091
self.checker.start()
1081
1092
self.checker_callback_tag = GLib.io_add_watch(
1082
1093
pipe[0].fileno(), GLib.IO_IN,
1083
1094
self.checker_callback, pipe[0], command)
1084
1095
# Re-run this periodically if run by GLib.timeout_add
1087
1098
def stop_checker(self):
1088
1099
"""Force the checker process, if any, to stop."""
1089
1100
if self.checker_callback_tag:
1124
1135
func._dbus_name = func.__name__
1125
1136
if func._dbus_name.endswith("_dbus_property"):
1126
1137
func._dbus_name = func._dbus_name[:-14]
1127
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
1138
func._dbus_get_args_options = {'byte_arrays': byte_arrays}
1130
1141
return decorator
1133
1144
def dbus_interface_annotations(dbus_interface):
1134
1145
"""Decorator for marking functions returning interface annotations
1138
1149
@dbus_interface_annotations("org.example.Interface")
1139
1150
def _foo(self): # Function name does not matter
1140
1151
return {"org.freedesktop.DBus.Deprecated": "true",
1141
1152
"org.freedesktop.DBus.Property.EmitsChangedSignal":
1145
1156
def decorator(func):
1146
1157
func._dbus_is_interface = True
1147
1158
func._dbus_interface = dbus_interface
1148
1159
func._dbus_name = dbus_interface
1151
1162
return decorator
1154
1165
def dbus_annotations(annotations):
1155
1166
"""Decorator to annotate D-Bus methods, signals or properties
1158
1169
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
1159
1170
"org.freedesktop.DBus.Property."
1160
1171
"EmitsChangedSignal": "false"})
1216
1227
for cls in self.__class__.__mro__
1217
1228
for name, athing in
1218
1229
inspect.getmembers(cls, self._is_dbus_thing(thing)))
1220
1231
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1221
out_signature = "s",
1222
path_keyword = 'object_path',
1223
connection_keyword = 'connection')
1233
path_keyword='object_path',
1234
connection_keyword='connection')
1224
1235
def Introspect(self, object_path, connection):
1225
1236
"""Overloading of standard D-Bus method.
1227
1238
Inserts annotation tags on methods and signals.
1229
1240
xmlstring = dbus.service.Object.Introspect(self, object_path,
1232
1243
document = xml.dom.minidom.parseString(xmlstring)
1234
1245
for if_tag in document.getElementsByTagName("interface"):
1235
1246
# Add annotation tags
1236
1247
for typ in ("method", "signal"):
1452
1463
except AttributeError:
1453
1464
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1455
1467
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1456
1468
"""A D-Bus object with an ObjectManager.
1458
1470
Classes inheriting from this exposes the standard
1459
1471
GetManagedObjects call and the InterfacesAdded and
1460
1472
InterfacesRemoved signals on the standard
1461
1473
"org.freedesktop.DBus.ObjectManager" interface.
1463
1475
Note: No signals are sent automatically; they must be sent
1466
1478
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1467
out_signature = "a{oa{sa{sv}}}")
1479
out_signature="a{oa{sa{sv}}}")
1468
1480
def GetManagedObjects(self):
1469
1481
"""This function must be overridden"""
1470
1482
raise NotImplementedError()
1472
1484
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1473
signature = "oa{sa{sv}}")
1485
signature="oa{sa{sv}}")
1474
1486
def InterfacesAdded(self, object_path, interfaces_and_properties):
1477
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
1489
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature="oas")
1478
1490
def InterfacesRemoved(self, object_path, interfaces):
1481
1493
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1482
out_signature = "s",
1483
path_keyword = 'object_path',
1484
connection_keyword = 'connection')
1495
path_keyword='object_path',
1496
connection_keyword='connection')
1485
1497
def Introspect(self, object_path, connection):
1486
1498
"""Overloading of standard D-Bus method.
1488
1500
Override return argument name of GetManagedObjects to be
1489
1501
"objpath_interfaces_and_properties"
1528
1541
dbus.service.Object, it will add alternate D-Bus attributes with
1529
1542
interface names according to the "alt_interface_names" mapping.
1532
1545
@alternate_dbus_interfaces({"org.example.Interface":
1533
1546
"net.example.AlternateInterface"})
1534
1547
class SampleDBusObject(dbus.service.Object):
1535
1548
@dbus.service.method("org.example.Interface")
1536
1549
def SampleDBusMethod():
1539
1552
The above "SampleDBusMethod" on "SampleDBusObject" will be
1540
1553
reachable via two interfaces: "org.example.Interface" and
1541
1554
"net.example.AlternateInterface", the latter of which will have
1542
1555
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1543
1556
"true", unless "deprecate" is passed with a False value.
1545
1558
This works for methods and signals, and also for D-Bus properties
1546
1559
(from DBusObjectWithProperties) and interfaces (from the
1547
1560
dbus_interface_annotations decorator).
1550
1563
def wrapper(cls):
1551
1564
for orig_interface_name, alt_interface_name in (
1552
1565
alt_interface_names.items()):
1694
1709
"se.bsnet.fukt.Mandos"})
1695
1710
class ClientDBus(Client, DBusObjectWithProperties):
1696
1711
"""A Client class using D-Bus
1699
1714
dbus_object_path: dbus.ObjectPath
1700
1715
bus: dbus.SystemBus()
1703
1718
runtime_expansions = (Client.runtime_expansions
1704
1719
+ ("dbus_object_path", ))
1706
1721
_interface = "se.recompile.Mandos.Client"
1708
1723
# dbus.service.Object doesn't use super(), so we can't either.
1710
def __init__(self, bus = None, *args, **kwargs):
1725
def __init__(self, bus=None, *args, **kwargs):
1712
1727
Client.__init__(self, *args, **kwargs)
1713
1728
# Only now, when this client is initialized, can it show up on
1750
1765
dbus_value = transform_func(
1751
1766
type_func(value),
1752
variant_level = variant_level)
1767
variant_level=variant_level)
1753
1768
self.PropertyChanged(dbus.String(dbus_name),
1755
1770
self.PropertiesChanged(
1757
dbus.Dictionary({ dbus.String(dbus_name):
1772
dbus.Dictionary({dbus.String(dbus_name):
1760
1775
setattr(self, attrname, value)
1762
1777
return property(lambda self: getattr(self, attrname), setter)
1764
1779
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1765
1780
approvals_pending = notifychangeproperty(dbus.Boolean,
1766
1781
"ApprovalPending",
1768
1783
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1769
1784
last_enabled = notifychangeproperty(datetime_to_dbus,
1771
1786
checker = notifychangeproperty(
1772
1787
dbus.Boolean, "CheckerRunning",
1773
type_func = lambda checker: checker is not None)
1788
type_func=lambda checker: checker is not None)
1774
1789
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1775
1790
"LastCheckedOK")
1776
1791
last_checker_status = notifychangeproperty(dbus.Int16,
1781
1796
"ApprovedByDefault")
1782
1797
approval_delay = notifychangeproperty(
1783
1798
dbus.UInt64, "ApprovalDelay",
1784
type_func = lambda td: td.total_seconds() * 1000)
1799
type_func=lambda td: td.total_seconds() * 1000)
1785
1800
approval_duration = notifychangeproperty(
1786
1801
dbus.UInt64, "ApprovalDuration",
1787
type_func = lambda td: td.total_seconds() * 1000)
1802
type_func=lambda td: td.total_seconds() * 1000)
1788
1803
host = notifychangeproperty(dbus.String, "Host")
1789
1804
timeout = notifychangeproperty(
1790
1805
dbus.UInt64, "Timeout",
1791
type_func = lambda td: td.total_seconds() * 1000)
1806
type_func=lambda td: td.total_seconds() * 1000)
1792
1807
extended_timeout = notifychangeproperty(
1793
1808
dbus.UInt64, "ExtendedTimeout",
1794
type_func = lambda td: td.total_seconds() * 1000)
1809
type_func=lambda td: td.total_seconds() * 1000)
1795
1810
interval = notifychangeproperty(
1796
1811
dbus.UInt64, "Interval",
1797
type_func = lambda td: td.total_seconds() * 1000)
1812
type_func=lambda td: td.total_seconds() * 1000)
1798
1813
checker_command = notifychangeproperty(dbus.String, "Checker")
1799
1814
secret = notifychangeproperty(dbus.ByteArray, "Secret",
1800
1815
invalidate_only=True)
1802
1817
del notifychangeproperty
1804
1819
def __del__(self, *args, **kwargs):
1806
1821
self.remove_from_connection()
1841
1856
# Emit D-Bus signal
1842
1857
self.CheckerStarted(self.current_checker_command)
1845
1860
def _reset_approved(self):
1846
1861
self.approved = None
1849
1864
def approve(self, value=True):
1850
1865
self.approved = value
1851
1866
GLib.timeout_add(int(self.approval_duration.total_seconds()
1852
1867
* 1000), self._reset_approved)
1853
1868
self.send_changedstate()
1855
## D-Bus methods, signals & properties
1870
# D-Bus methods, signals & properties
1861
1876
# CheckerCompleted - signal
1862
1877
@dbus.service.signal(_interface, signature="nxs")
1863
1878
def CheckerCompleted(self, exitcode, waitstatus, command):
1867
1882
# CheckerStarted - signal
1868
1883
@dbus.service.signal(_interface, signature="s")
1869
1884
def CheckerStarted(self, command):
1873
1888
# PropertyChanged - signal
1874
1889
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1875
1890
@dbus.service.signal(_interface, signature="sv")
1876
1891
def PropertyChanged(self, property, value):
1880
1895
# GotSecret - signal
1881
1896
@dbus.service.signal(_interface)
1882
1897
def GotSecret(self):
1885
1900
server to mandos-client
1889
1904
# Rejected - signal
1890
1905
@dbus.service.signal(_interface, signature="s")
1891
1906
def Rejected(self, reason):
1895
1910
# NeedApproval - signal
1896
1911
@dbus.service.signal(_interface, signature="tb")
1897
1912
def NeedApproval(self, timeout, default):
1899
1914
return self.need_approval()
1903
1918
# Approve - method
1904
1919
@dbus.service.method(_interface, in_signature="b")
1905
1920
def Approve(self, value):
1906
1921
self.approve(value)
1908
1923
# CheckedOK - method
1909
1924
@dbus.service.method(_interface)
1910
1925
def CheckedOK(self):
1911
1926
self.checked_ok()
1913
1928
# Enable - method
1914
1929
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1915
1930
@dbus.service.method(_interface)
1916
1931
def Enable(self):
1920
1935
# StartChecker - method
1921
1936
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1922
1937
@dbus.service.method(_interface)
1923
1938
def StartChecker(self):
1925
1940
self.start_checker()
1927
1942
# Disable - method
1928
1943
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1929
1944
@dbus.service.method(_interface)
1930
1945
def Disable(self):
1934
1949
# StopChecker - method
1935
1950
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1936
1951
@dbus.service.method(_interface)
1937
1952
def StopChecker(self):
1938
1953
self.stop_checker()
1942
1957
# ApprovalPending - property
1943
1958
@dbus_service_property(_interface, signature="b", access="read")
1944
1959
def ApprovalPending_dbus_property(self):
1945
1960
return dbus.Boolean(bool(self.approvals_pending))
1947
1962
# ApprovedByDefault - property
1948
1963
@dbus_service_property(_interface,
2029
2044
self.checked_ok()
2031
2046
return datetime_to_dbus(self.last_checked_ok)
2033
2048
# LastCheckerStatus - property
2034
2049
@dbus_service_property(_interface, signature="n", access="read")
2035
2050
def LastCheckerStatus_dbus_property(self):
2036
2051
return dbus.Int16(self.last_checker_status)
2038
2053
# Expires - property
2039
2054
@dbus_service_property(_interface, signature="s", access="read")
2040
2055
def Expires_dbus_property(self):
2041
2056
return datetime_to_dbus(self.expires)
2043
2058
# LastApprovalRequest - property
2044
2059
@dbus_service_property(_interface, signature="s", access="read")
2045
2060
def LastApprovalRequest_dbus_property(self):
2046
2061
return datetime_to_dbus(self.last_approval_request)
2048
2063
# Timeout - property
2049
2064
@dbus_service_property(_interface,
2171
2186
class ClientHandler(socketserver.BaseRequestHandler, object):
2172
2187
"""A class to handle client connections.
2174
2189
Instantiated once for each connection to handle it.
2175
2190
Note: This will run in its own forked process."""
2177
2192
def handle(self):
2178
2193
with contextlib.closing(self.server.child_pipe) as child_pipe:
2179
2194
logger.info("TCP connection from: %s",
2180
2195
str(self.client_address))
2181
2196
logger.debug("Pipe FD: %d",
2182
2197
self.server.child_pipe.fileno())
2184
2199
session = gnutls.ClientSession(self.request)
2186
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
2187
# "+AES-256-CBC", "+SHA1",
2188
# "+COMP-NULL", "+CTYPE-OPENPGP",
2201
# priority = ':'.join(("NONE", "+VERS-TLS1.1",
2202
# "+AES-256-CBC", "+SHA1",
2203
# "+COMP-NULL", "+CTYPE-OPENPGP",
2190
2205
# Use a fallback default, since this MUST be set.
2191
2206
priority = self.server.gnutls_priority
2192
2207
if priority is None:
2365
2380
class MultiprocessingMixIn(object):
2366
2381
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
2368
2383
def sub_process_main(self, request, address):
2370
2385
self.finish_request(request, address)
2371
2386
except Exception:
2372
2387
self.handle_error(request, address)
2373
2388
self.close_request(request)
2375
2390
def process_request(self, request, address):
2376
2391
"""Start a new process to process the request."""
2377
proc = multiprocessing.Process(target = self.sub_process_main,
2378
args = (request, address))
2392
proc = multiprocessing.Process(target=self.sub_process_main,
2393
args=(request, address))
2383
2398
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
2384
2399
""" adds a pipe to the MixIn """
2386
2401
def process_request(self, request, client_address):
2387
2402
"""Overrides and wraps the original process_request().
2389
2404
This function creates a new pipe in self.pipe
2391
2406
parent_pipe, self.child_pipe = multiprocessing.Pipe()
2393
2408
proc = MultiprocessingMixIn.process_request(self, request,
2394
2409
client_address)
2395
2410
self.child_pipe.close()
2396
2411
self.add_pipe(parent_pipe, proc)
2398
2413
def add_pipe(self, parent_pipe, proc):
2399
2414
"""Dummy function; override as necessary"""
2400
2415
raise NotImplementedError()
2523
2539
self.gnutls_priority = gnutls_priority
2524
2540
IPv6_TCPServer.__init__(self, server_address,
2525
2541
RequestHandlerClass,
2526
interface = interface,
2527
use_ipv6 = use_ipv6,
2528
socketfd = socketfd)
2542
interface=interface,
2530
2546
def server_activate(self):
2531
2547
if self.enabled:
2532
2548
return socketserver.TCPServer.server_activate(self)
2534
2550
def enable(self):
2535
2551
self.enabled = True
2537
2553
def add_pipe(self, parent_pipe, proc):
2538
2554
# Call "handle_ipc" for both data and EOF events
2539
2555
GLib.io_add_watch(
2540
2556
parent_pipe.fileno(),
2541
2557
GLib.IO_IN | GLib.IO_HUP,
2542
2558
functools.partial(self.handle_ipc,
2543
parent_pipe = parent_pipe,
2559
parent_pipe=parent_pipe,
2546
2562
def handle_ipc(self, source, condition,
2547
2563
parent_pipe=None,
2549
2565
client_object=None):
2550
2566
# error, or the other end of multiprocessing.Pipe has closed
2551
2567
if condition & (GLib.IO_ERR | GLib.IO_HUP):
2552
2568
# Wait for other process to exit
2556
2572
# Read a request from the child
2557
2573
request = parent_pipe.recv()
2558
2574
command = request[0]
2560
2576
if command == 'init':
2561
2577
fpr = request[1]
2562
2578
address = request[2]
2564
2580
for c in self.clients.values():
2565
2581
if c.fingerprint == fpr:
2630
2646
>>> rfc3339_duration_to_delta("P1DT3M20S")
2631
2647
datetime.timedelta(1, 200)
2634
2650
# Parsing an RFC 3339 duration with regular expressions is not
2635
2651
# possible - there would have to be multiple places for the same
2636
2652
# values, like seconds. The current code, while more esoteric, is
2637
2653
# cleaner without depending on a parsing library. If Python had a
2638
2654
# built-in library for parsing we would use it, but we'd like to
2639
2655
# avoid excessive use of external libraries.
2641
2657
# New type for defining tokens, syntax, and semantics all-in-one
2642
2658
Token = collections.namedtuple("Token", (
2643
2659
"regexp", # To match token; if "value" is not None, must have
2827
2846
parser.add_argument("--no-zeroconf", action="store_false",
2828
2847
dest="zeroconf", help="Do not use Zeroconf",
2831
2850
options = parser.parse_args()
2833
2852
if options.check:
2835
2854
fail_count, test_count = doctest.testmod()
2836
2855
sys.exit(os.EX_OK if fail_count == 0 else 1)
2838
2857
# Default values for config file for server-global settings
2839
server_defaults = { "interface": "",
2844
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2845
":+SIGN-DSA-SHA256",
2846
"servicename": "Mandos",
2852
"statedir": "/var/lib/mandos",
2853
"foreground": "False",
2858
server_defaults = {"interface": "",
2863
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2864
":+SIGN-DSA-SHA256",
2865
"servicename": "Mandos",
2871
"statedir": "/var/lib/mandos",
2872
"foreground": "False",
2857
2876
# Parse config file for server-global settings
2858
2877
server_config = configparser.SafeConfigParser(server_defaults)
2859
2878
del server_defaults
2988
3007
.format(uid, gid, os.strerror(error.errno)))
2989
3008
if error.errno != errno.EPERM:
2993
3012
# Enable all possible GnuTLS debugging
2995
3014
# "Use a log level over 10 to enable all debugging options."
2996
3015
# - GnuTLS manual
2997
3016
gnutls.global_set_log_level(11)
2999
3018
@gnutls.log_func
3000
3019
def debug_gnutls(level, string):
3001
3020
logger.debug("GnuTLS: %s", string[:-1])
3003
3022
gnutls.global_set_log_function(debug_gnutls)
3005
3024
# Redirect stdin so all checkers get /dev/null
3006
3025
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
3007
3026
os.dup2(null, sys.stdin.fileno())
3011
3030
# Need to fork before connecting to D-Bus
3012
3031
if not foreground:
3013
3032
# Close all input and output, do double fork, etc.
3016
3035
# multiprocessing will use threads, so before we use GLib we need
3017
3036
# to inform GLib that threads will be used.
3018
3037
GLib.threads_init()
3020
3039
global main_loop
3021
3040
# From the Avahi example code
3022
3041
DBusGMainLoop(set_as_default=True)
3040
3059
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
3041
3060
service = AvahiServiceToSyslog(
3042
name = server_settings["servicename"],
3043
servicetype = "_mandos._tcp",
3044
protocol = protocol,
3061
name=server_settings["servicename"],
3062
servicetype="_mandos._tcp",
3046
3065
if server_settings["interface"]:
3047
3066
service.interface = if_nametoindex(
3048
3067
server_settings["interface"].encode("utf-8"))
3050
3069
global multiprocessing_manager
3051
3070
multiprocessing_manager = multiprocessing.Manager()
3053
3072
client_class = Client
3055
client_class = functools.partial(ClientDBus, bus = bus)
3074
client_class = functools.partial(ClientDBus, bus=bus)
3057
3076
client_settings = Client.config_parser(client_config)
3058
3077
old_client_settings = {}
3059
3078
clients_data = {}
3061
3080
# This is used to redirect stdout and stderr for checker processes
3063
wnull = open(os.devnull, "w") # A writable /dev/null
3082
wnull = open(os.devnull, "w") # A writable /dev/null
3064
3083
# Only used if server is running in foreground but not in debug
3066
3085
if debug or not foreground:
3069
3088
# Get client data and settings from last running state.
3070
3089
if server_settings["restore"]:
3072
3091
with open(stored_state_path, "rb") as stored_state:
3073
if sys.version_info.major == 2:
3092
if sys.version_info.major == 2:
3074
3093
clients_data, old_client_settings = pickle.load(
3077
3096
bytes_clients_data, bytes_old_client_settings = (
3078
pickle.load(stored_state, encoding = "bytes"))
3079
### Fix bytes to strings
3097
pickle.load(stored_state, encoding="bytes"))
3098
# Fix bytes to strings
3082
clients_data = { (key.decode("utf-8")
3083
if isinstance(key, bytes)
3086
bytes_clients_data.items() }
3101
clients_data = {(key.decode("utf-8")
3102
if isinstance(key, bytes)
3105
bytes_clients_data.items()}
3087
3106
del bytes_clients_data
3088
3107
for key in clients_data:
3089
value = { (k.decode("utf-8")
3090
if isinstance(k, bytes) else k): v
3092
clients_data[key].items() }
3108
value = {(k.decode("utf-8")
3109
if isinstance(k, bytes) else k): v
3111
clients_data[key].items()}
3093
3112
clients_data[key] = value
3094
3113
# .client_structure
3095
3114
value["client_structure"] = [
3096
3115
(s.decode("utf-8")
3097
3116
if isinstance(s, bytes)
3098
3117
else s) for s in
3099
value["client_structure"] ]
3118
value["client_structure"]]
3100
3119
# .name & .host
3101
3120
for k in ("name", "host"):
3102
3121
if isinstance(value[k], bytes):
3103
3122
value[k] = value[k].decode("utf-8")
3104
## old_client_settings
3123
# old_client_settings
3106
3125
old_client_settings = {
3107
3126
(key.decode("utf-8")
3108
3127
if isinstance(key, bytes)
3109
3128
else key): value
3110
3129
for key, value in
3111
bytes_old_client_settings.items() }
3130
bytes_old_client_settings.items()}
3112
3131
del bytes_old_client_settings
3114
3133
for value in old_client_settings.values():
3220
3239
pidfilename, pid)
3222
3241
del pidfilename
3224
3243
for termsig in (signal.SIGHUP, signal.SIGTERM):
3225
3244
GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig,
3226
3245
lambda: main_loop.quit() and False)
3230
3249
@alternate_dbus_interfaces(
3231
{ "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
3250
{"se.recompile.Mandos": "se.bsnet.fukt.Mandos"})
3232
3251
class MandosDBusService(DBusObjectWithObjectManager):
3233
3252
"""A D-Bus proxy object"""
3235
3254
def __init__(self):
3236
3255
dbus.service.Object.__init__(self, bus, "/")
3238
3257
_interface = "se.recompile.Mandos"
3240
3259
@dbus.service.signal(_interface, signature="o")
3241
3260
def ClientAdded(self, objpath):
3245
3264
@dbus.service.signal(_interface, signature="ss")
3246
3265
def ClientNotFound(self, fingerprint, address):
3250
3269
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3252
3271
@dbus.service.signal(_interface, signature="os")
3253
3272
def ClientRemoved(self, objpath, name):
3257
3276
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3259
3278
@dbus.service.method(_interface, out_signature="ao")
3287
3306
self.client_removed_signal(c)
3289
3308
raise KeyError(object_path)
3293
3312
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
3294
out_signature = "a{oa{sa{sv}}}")
3313
out_signature="a{oa{sa{sv}}}")
3295
3314
def GetManagedObjects(self):
3296
3315
"""D-Bus method"""
3297
3316
return dbus.Dictionary(
3298
{ client.dbus_object_path:
3300
{ interface: client.GetAll(interface)
3302
client._get_all_interface_names()})
3303
for client in tcp_server.clients.values()})
3317
{client.dbus_object_path:
3319
{interface: client.GetAll(interface)
3321
client._get_all_interface_names()})
3322
for client in tcp_server.clients.values()})
3305
3324
def client_added_signal(self, client):
3306
3325
"""Send the new standard signal and the old signal"""
3352
3372
client.encrypted_secret = pgp.encrypt(client.secret,
3354
3374
client_dict = {}
3356
3376
# A list of attributes that can not be pickled
3358
exclude = { "bus", "changedstate", "secret",
3359
"checker", "server_settings" }
3378
exclude = {"bus", "changedstate", "secret",
3379
"checker", "server_settings"}
3360
3380
for name, typ in inspect.getmembers(dbus.service
3362
3382
exclude.add(name)
3364
3384
client_dict["encrypted_secret"] = (client
3365
3385
.encrypted_secret)
3366
3386
for attr in client.client_structure:
3367
3387
if attr not in exclude:
3368
3388
client_dict[attr] = getattr(client, attr)
3370
3390
clients[client.name] = client_dict
3371
3391
del client_settings[client.name]["secret"]
3374
3394
with tempfile.NamedTemporaryFile(