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
535
CRD_CERTIFICATE = 1
528
536
E_NO_CERTIFICATE_FOUND = -49
529
537
OPENPGP_FMT_RAW = 0 # gnutls/openpgp.h
532
540
class session_int(ctypes.Structure):
534
542
session_t = ctypes.POINTER(session_int)
535
544
class certificate_credentials_st(ctypes.Structure):
537
546
certificate_credentials_t = ctypes.POINTER(
538
547
certificate_credentials_st)
539
548
certificate_type_t = ctypes.c_int
540
550
class datum_t(ctypes.Structure):
541
551
_fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
542
552
('size', ctypes.c_uint)]
543
554
class openpgp_crt_int(ctypes.Structure):
545
556
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
546
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
557
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
547
558
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
548
559
credentials_type_t = ctypes.c_int
549
560
transport_ptr_t = ctypes.c_void_p
550
561
close_request_t = ctypes.c_int
553
564
class Error(Exception):
554
565
# We need to use the class name "GnuTLS" here, since this
555
566
# exception might be raised from within GnuTLS.__init__,
556
567
# which is called before the assignment to the "gnutls"
557
568
# global variable has happened.
558
def __init__(self, message = None, code = None, args=()):
569
def __init__(self, message=None, code=None, args=()):
559
570
# Default usage is by a message string, but if a return
560
571
# code is passed, convert it to a string with
561
572
# gnutls.strerror()
630
641
return _error_code(result)
631
642
result = func(*arguments)
634
645
# Unless otherwise indicated, the function declarations below are
635
646
# all from the gnutls/gnutls.h C header file.
638
649
priority_set_direct = _library.gnutls_priority_set_direct
639
650
priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
640
651
ctypes.POINTER(ctypes.c_char_p)]
641
652
priority_set_direct.restype = _error_code
643
654
init = _library.gnutls_init
644
655
init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
645
656
init.restype = _error_code
647
658
set_default_priority = _library.gnutls_set_default_priority
648
659
set_default_priority.argtypes = [session_t]
649
660
set_default_priority.restype = _error_code
651
662
record_send = _library.gnutls_record_send
652
663
record_send.argtypes = [session_t, ctypes.c_void_p,
654
665
record_send.restype = ctypes.c_ssize_t
655
666
record_send.errcheck = _retry_on_error
657
668
certificate_allocate_credentials = (
658
669
_library.gnutls_certificate_allocate_credentials)
659
670
certificate_allocate_credentials.argtypes = [
660
671
ctypes.POINTER(certificate_credentials_t)]
661
672
certificate_allocate_credentials.restype = _error_code
663
674
certificate_free_credentials = (
664
675
_library.gnutls_certificate_free_credentials)
665
certificate_free_credentials.argtypes = [certificate_credentials_t]
676
certificate_free_credentials.argtypes = [
677
certificate_credentials_t]
666
678
certificate_free_credentials.restype = None
668
680
handshake_set_private_extensions = (
669
681
_library.gnutls_handshake_set_private_extensions)
670
682
handshake_set_private_extensions.argtypes = [session_t,
672
684
handshake_set_private_extensions.restype = None
674
686
credentials_set = _library.gnutls_credentials_set
675
687
credentials_set.argtypes = [session_t, credentials_type_t,
677
689
credentials_set.restype = _error_code
679
691
strerror = _library.gnutls_strerror
680
692
strerror.argtypes = [ctypes.c_int]
681
693
strerror.restype = ctypes.c_char_p
683
695
certificate_type_get = _library.gnutls_certificate_type_get
684
696
certificate_type_get.argtypes = [session_t]
685
697
certificate_type_get.restype = _error_code
687
699
certificate_get_peers = _library.gnutls_certificate_get_peers
688
700
certificate_get_peers.argtypes = [session_t,
689
701
ctypes.POINTER(ctypes.c_uint)]
690
702
certificate_get_peers.restype = ctypes.POINTER(datum_t)
692
704
global_set_log_level = _library.gnutls_global_set_log_level
693
705
global_set_log_level.argtypes = [ctypes.c_int]
694
706
global_set_log_level.restype = None
696
708
global_set_log_function = _library.gnutls_global_set_log_function
697
709
global_set_log_function.argtypes = [log_func]
698
710
global_set_log_function.restype = None
700
712
deinit = _library.gnutls_deinit
701
713
deinit.argtypes = [session_t]
702
714
deinit.restype = None
704
716
handshake = _library.gnutls_handshake
705
717
handshake.argtypes = [session_t]
706
718
handshake.restype = _error_code
707
719
handshake.errcheck = _retry_on_error
709
721
transport_set_ptr = _library.gnutls_transport_set_ptr
710
722
transport_set_ptr.argtypes = [session_t, transport_ptr_t]
711
723
transport_set_ptr.restype = None
713
725
bye = _library.gnutls_bye
714
726
bye.argtypes = [session_t, close_request_t]
715
727
bye.restype = _error_code
716
728
bye.errcheck = _retry_on_error
718
730
check_version = _library.gnutls_check_version
719
731
check_version.argtypes = [ctypes.c_char_p]
720
732
check_version.restype = ctypes.c_char_p
722
734
# All the function declarations below are from gnutls/openpgp.h
724
736
openpgp_crt_init = _library.gnutls_openpgp_crt_init
725
737
openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
726
738
openpgp_crt_init.restype = _error_code
728
740
openpgp_crt_import = _library.gnutls_openpgp_crt_import
729
741
openpgp_crt_import.argtypes = [openpgp_crt_t,
730
742
ctypes.POINTER(datum_t),
731
743
openpgp_crt_fmt_t]
732
744
openpgp_crt_import.restype = _error_code
734
746
openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
735
747
openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
736
748
ctypes.POINTER(ctypes.c_uint)]
737
749
openpgp_crt_verify_self.restype = _error_code
739
751
openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
740
752
openpgp_crt_deinit.argtypes = [openpgp_crt_t]
741
753
openpgp_crt_deinit.restype = None
743
755
openpgp_crt_get_fingerprint = (
744
756
_library.gnutls_openpgp_crt_get_fingerprint)
745
757
openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
1065
1079
# The exception is when not debugging but nevertheless
1066
1080
# running in the foreground; use the previously
1067
1081
# created wnull.
1068
popen_args = { "close_fds": True,
1082
popen_args = {"close_fds": True,
1071
1085
if (not self.server_settings["debug"]
1072
1086
and self.server_settings["foreground"]):
1073
1087
popen_args.update({"stdout": wnull,
1075
pipe = multiprocessing.Pipe(duplex = False)
1089
pipe = multiprocessing.Pipe(duplex=False)
1076
1090
self.checker = multiprocessing.Process(
1078
args = (pipe[1], subprocess.call, command),
1079
kwargs = popen_args)
1092
args=(pipe[1], subprocess.call, command),
1080
1094
self.checker.start()
1081
1095
self.checker_callback_tag = GLib.io_add_watch(
1082
1096
pipe[0].fileno(), GLib.IO_IN,
1083
1097
self.checker_callback, pipe[0], command)
1084
1098
# Re-run this periodically if run by GLib.timeout_add
1087
1101
def stop_checker(self):
1088
1102
"""Force the checker process, if any, to stop."""
1089
1103
if self.checker_callback_tag:
1124
1138
func._dbus_name = func.__name__
1125
1139
if func._dbus_name.endswith("_dbus_property"):
1126
1140
func._dbus_name = func._dbus_name[:-14]
1127
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
1141
func._dbus_get_args_options = {'byte_arrays': byte_arrays}
1130
1144
return decorator
1133
1147
def dbus_interface_annotations(dbus_interface):
1134
1148
"""Decorator for marking functions returning interface annotations
1138
1152
@dbus_interface_annotations("org.example.Interface")
1139
1153
def _foo(self): # Function name does not matter
1140
1154
return {"org.freedesktop.DBus.Deprecated": "true",
1141
1155
"org.freedesktop.DBus.Property.EmitsChangedSignal":
1145
1159
def decorator(func):
1146
1160
func._dbus_is_interface = True
1147
1161
func._dbus_interface = dbus_interface
1148
1162
func._dbus_name = dbus_interface
1151
1165
return decorator
1154
1168
def dbus_annotations(annotations):
1155
1169
"""Decorator to annotate D-Bus methods, signals or properties
1158
1172
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
1159
1173
"org.freedesktop.DBus.Property."
1160
1174
"EmitsChangedSignal": "false"})
1216
1230
for cls in self.__class__.__mro__
1217
1231
for name, athing in
1218
1232
inspect.getmembers(cls, self._is_dbus_thing(thing)))
1220
1234
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1221
out_signature = "s",
1222
path_keyword = 'object_path',
1223
connection_keyword = 'connection')
1236
path_keyword='object_path',
1237
connection_keyword='connection')
1224
1238
def Introspect(self, object_path, connection):
1225
1239
"""Overloading of standard D-Bus method.
1227
1241
Inserts annotation tags on methods and signals.
1229
1243
xmlstring = dbus.service.Object.Introspect(self, object_path,
1232
1246
document = xml.dom.minidom.parseString(xmlstring)
1234
1248
for if_tag in document.getElementsByTagName("interface"):
1235
1249
# Add annotation tags
1236
1250
for typ in ("method", "signal"):
1452
1466
except AttributeError:
1453
1467
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1455
1470
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1456
1471
"""A D-Bus object with an ObjectManager.
1458
1473
Classes inheriting from this exposes the standard
1459
1474
GetManagedObjects call and the InterfacesAdded and
1460
1475
InterfacesRemoved signals on the standard
1461
1476
"org.freedesktop.DBus.ObjectManager" interface.
1463
1478
Note: No signals are sent automatically; they must be sent
1466
1481
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1467
out_signature = "a{oa{sa{sv}}}")
1482
out_signature="a{oa{sa{sv}}}")
1468
1483
def GetManagedObjects(self):
1469
1484
"""This function must be overridden"""
1470
1485
raise NotImplementedError()
1472
1487
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1473
signature = "oa{sa{sv}}")
1488
signature="oa{sa{sv}}")
1474
1489
def InterfacesAdded(self, object_path, interfaces_and_properties):
1477
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
1492
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature="oas")
1478
1493
def InterfacesRemoved(self, object_path, interfaces):
1481
1496
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1482
out_signature = "s",
1483
path_keyword = 'object_path',
1484
connection_keyword = 'connection')
1498
path_keyword='object_path',
1499
connection_keyword='connection')
1485
1500
def Introspect(self, object_path, connection):
1486
1501
"""Overloading of standard D-Bus method.
1488
1503
Override return argument name of GetManagedObjects to be
1489
1504
"objpath_interfaces_and_properties"
1528
1544
dbus.service.Object, it will add alternate D-Bus attributes with
1529
1545
interface names according to the "alt_interface_names" mapping.
1532
1548
@alternate_dbus_interfaces({"org.example.Interface":
1533
1549
"net.example.AlternateInterface"})
1534
1550
class SampleDBusObject(dbus.service.Object):
1535
1551
@dbus.service.method("org.example.Interface")
1536
1552
def SampleDBusMethod():
1539
1555
The above "SampleDBusMethod" on "SampleDBusObject" will be
1540
1556
reachable via two interfaces: "org.example.Interface" and
1541
1557
"net.example.AlternateInterface", the latter of which will have
1542
1558
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1543
1559
"true", unless "deprecate" is passed with a False value.
1545
1561
This works for methods and signals, and also for D-Bus properties
1546
1562
(from DBusObjectWithProperties) and interfaces (from the
1547
1563
dbus_interface_annotations decorator).
1550
1566
def wrapper(cls):
1551
1567
for orig_interface_name, alt_interface_name in (
1552
1568
alt_interface_names.items()):
1694
1712
"se.bsnet.fukt.Mandos"})
1695
1713
class ClientDBus(Client, DBusObjectWithProperties):
1696
1714
"""A Client class using D-Bus
1699
1717
dbus_object_path: dbus.ObjectPath
1700
1718
bus: dbus.SystemBus()
1703
1721
runtime_expansions = (Client.runtime_expansions
1704
1722
+ ("dbus_object_path", ))
1706
1724
_interface = "se.recompile.Mandos.Client"
1708
1726
# dbus.service.Object doesn't use super(), so we can't either.
1710
def __init__(self, bus = None, *args, **kwargs):
1728
def __init__(self, bus=None, *args, **kwargs):
1712
1730
Client.__init__(self, *args, **kwargs)
1713
1731
# Only now, when this client is initialized, can it show up on
1750
1768
dbus_value = transform_func(
1751
1769
type_func(value),
1752
variant_level = variant_level)
1770
variant_level=variant_level)
1753
1771
self.PropertyChanged(dbus.String(dbus_name),
1755
1773
self.PropertiesChanged(
1757
dbus.Dictionary({ dbus.String(dbus_name):
1775
dbus.Dictionary({dbus.String(dbus_name):
1760
1778
setattr(self, attrname, value)
1762
1780
return property(lambda self: getattr(self, attrname), setter)
1764
1782
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1765
1783
approvals_pending = notifychangeproperty(dbus.Boolean,
1766
1784
"ApprovalPending",
1768
1786
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1769
1787
last_enabled = notifychangeproperty(datetime_to_dbus,
1771
1789
checker = notifychangeproperty(
1772
1790
dbus.Boolean, "CheckerRunning",
1773
type_func = lambda checker: checker is not None)
1791
type_func=lambda checker: checker is not None)
1774
1792
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1775
1793
"LastCheckedOK")
1776
1794
last_checker_status = notifychangeproperty(dbus.Int16,
1781
1799
"ApprovedByDefault")
1782
1800
approval_delay = notifychangeproperty(
1783
1801
dbus.UInt64, "ApprovalDelay",
1784
type_func = lambda td: td.total_seconds() * 1000)
1802
type_func=lambda td: td.total_seconds() * 1000)
1785
1803
approval_duration = notifychangeproperty(
1786
1804
dbus.UInt64, "ApprovalDuration",
1787
type_func = lambda td: td.total_seconds() * 1000)
1805
type_func=lambda td: td.total_seconds() * 1000)
1788
1806
host = notifychangeproperty(dbus.String, "Host")
1789
1807
timeout = notifychangeproperty(
1790
1808
dbus.UInt64, "Timeout",
1791
type_func = lambda td: td.total_seconds() * 1000)
1809
type_func=lambda td: td.total_seconds() * 1000)
1792
1810
extended_timeout = notifychangeproperty(
1793
1811
dbus.UInt64, "ExtendedTimeout",
1794
type_func = lambda td: td.total_seconds() * 1000)
1812
type_func=lambda td: td.total_seconds() * 1000)
1795
1813
interval = notifychangeproperty(
1796
1814
dbus.UInt64, "Interval",
1797
type_func = lambda td: td.total_seconds() * 1000)
1815
type_func=lambda td: td.total_seconds() * 1000)
1798
1816
checker_command = notifychangeproperty(dbus.String, "Checker")
1799
1817
secret = notifychangeproperty(dbus.ByteArray, "Secret",
1800
1818
invalidate_only=True)
1802
1820
del notifychangeproperty
1804
1822
def __del__(self, *args, **kwargs):
1806
1824
self.remove_from_connection()
1841
1859
# Emit D-Bus signal
1842
1860
self.CheckerStarted(self.current_checker_command)
1845
1863
def _reset_approved(self):
1846
1864
self.approved = None
1849
1867
def approve(self, value=True):
1850
1868
self.approved = value
1851
1869
GLib.timeout_add(int(self.approval_duration.total_seconds()
1852
1870
* 1000), self._reset_approved)
1853
1871
self.send_changedstate()
1855
## D-Bus methods, signals & properties
1873
# D-Bus methods, signals & properties
1861
1879
# CheckerCompleted - signal
1862
1880
@dbus.service.signal(_interface, signature="nxs")
1863
1881
def CheckerCompleted(self, exitcode, waitstatus, command):
1867
1885
# CheckerStarted - signal
1868
1886
@dbus.service.signal(_interface, signature="s")
1869
1887
def CheckerStarted(self, command):
1873
1891
# PropertyChanged - signal
1874
1892
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1875
1893
@dbus.service.signal(_interface, signature="sv")
1876
1894
def PropertyChanged(self, property, value):
1880
1898
# GotSecret - signal
1881
1899
@dbus.service.signal(_interface)
1882
1900
def GotSecret(self):
1885
1903
server to mandos-client
1889
1907
# Rejected - signal
1890
1908
@dbus.service.signal(_interface, signature="s")
1891
1909
def Rejected(self, reason):
1895
1913
# NeedApproval - signal
1896
1914
@dbus.service.signal(_interface, signature="tb")
1897
1915
def NeedApproval(self, timeout, default):
1899
1917
return self.need_approval()
1903
1921
# Approve - method
1904
1922
@dbus.service.method(_interface, in_signature="b")
1905
1923
def Approve(self, value):
1906
1924
self.approve(value)
1908
1926
# CheckedOK - method
1909
1927
@dbus.service.method(_interface)
1910
1928
def CheckedOK(self):
1911
1929
self.checked_ok()
1913
1931
# Enable - method
1914
1932
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1915
1933
@dbus.service.method(_interface)
1916
1934
def Enable(self):
1920
1938
# StartChecker - method
1921
1939
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1922
1940
@dbus.service.method(_interface)
1923
1941
def StartChecker(self):
1925
1943
self.start_checker()
1927
1945
# Disable - method
1928
1946
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1929
1947
@dbus.service.method(_interface)
1930
1948
def Disable(self):
1934
1952
# StopChecker - method
1935
1953
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1936
1954
@dbus.service.method(_interface)
1937
1955
def StopChecker(self):
1938
1956
self.stop_checker()
1942
1960
# ApprovalPending - property
1943
1961
@dbus_service_property(_interface, signature="b", access="read")
1944
1962
def ApprovalPending_dbus_property(self):
1945
1963
return dbus.Boolean(bool(self.approvals_pending))
1947
1965
# ApprovedByDefault - property
1948
1966
@dbus_service_property(_interface,
2029
2047
self.checked_ok()
2031
2049
return datetime_to_dbus(self.last_checked_ok)
2033
2051
# LastCheckerStatus - property
2034
2052
@dbus_service_property(_interface, signature="n", access="read")
2035
2053
def LastCheckerStatus_dbus_property(self):
2036
2054
return dbus.Int16(self.last_checker_status)
2038
2056
# Expires - property
2039
2057
@dbus_service_property(_interface, signature="s", access="read")
2040
2058
def Expires_dbus_property(self):
2041
2059
return datetime_to_dbus(self.expires)
2043
2061
# LastApprovalRequest - property
2044
2062
@dbus_service_property(_interface, signature="s", access="read")
2045
2063
def LastApprovalRequest_dbus_property(self):
2046
2064
return datetime_to_dbus(self.last_approval_request)
2048
2066
# Timeout - property
2049
2067
@dbus_service_property(_interface,
2171
2189
class ClientHandler(socketserver.BaseRequestHandler, object):
2172
2190
"""A class to handle client connections.
2174
2192
Instantiated once for each connection to handle it.
2175
2193
Note: This will run in its own forked process."""
2177
2195
def handle(self):
2178
2196
with contextlib.closing(self.server.child_pipe) as child_pipe:
2179
2197
logger.info("TCP connection from: %s",
2180
2198
str(self.client_address))
2181
2199
logger.debug("Pipe FD: %d",
2182
2200
self.server.child_pipe.fileno())
2184
2202
session = gnutls.ClientSession(self.request)
2186
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
2187
# "+AES-256-CBC", "+SHA1",
2188
# "+COMP-NULL", "+CTYPE-OPENPGP",
2204
# priority = ':'.join(("NONE", "+VERS-TLS1.1",
2205
# "+AES-256-CBC", "+SHA1",
2206
# "+COMP-NULL", "+CTYPE-OPENPGP",
2190
2208
# Use a fallback default, since this MUST be set.
2191
2209
priority = self.server.gnutls_priority
2192
2210
if priority is None:
2365
2383
class MultiprocessingMixIn(object):
2366
2384
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
2368
2386
def sub_process_main(self, request, address):
2370
2388
self.finish_request(request, address)
2371
2389
except Exception:
2372
2390
self.handle_error(request, address)
2373
2391
self.close_request(request)
2375
2393
def process_request(self, request, address):
2376
2394
"""Start a new process to process the request."""
2377
proc = multiprocessing.Process(target = self.sub_process_main,
2378
args = (request, address))
2395
proc = multiprocessing.Process(target=self.sub_process_main,
2396
args=(request, address))
2383
2401
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
2384
2402
""" adds a pipe to the MixIn """
2386
2404
def process_request(self, request, client_address):
2387
2405
"""Overrides and wraps the original process_request().
2389
2407
This function creates a new pipe in self.pipe
2391
2409
parent_pipe, self.child_pipe = multiprocessing.Pipe()
2393
2411
proc = MultiprocessingMixIn.process_request(self, request,
2394
2412
client_address)
2395
2413
self.child_pipe.close()
2396
2414
self.add_pipe(parent_pipe, proc)
2398
2416
def add_pipe(self, parent_pipe, proc):
2399
2417
"""Dummy function; override as necessary"""
2400
2418
raise NotImplementedError()
2523
2542
self.gnutls_priority = gnutls_priority
2524
2543
IPv6_TCPServer.__init__(self, server_address,
2525
2544
RequestHandlerClass,
2526
interface = interface,
2527
use_ipv6 = use_ipv6,
2528
socketfd = socketfd)
2545
interface=interface,
2530
2549
def server_activate(self):
2531
2550
if self.enabled:
2532
2551
return socketserver.TCPServer.server_activate(self)
2534
2553
def enable(self):
2535
2554
self.enabled = True
2537
2556
def add_pipe(self, parent_pipe, proc):
2538
2557
# Call "handle_ipc" for both data and EOF events
2539
2558
GLib.io_add_watch(
2540
2559
parent_pipe.fileno(),
2541
2560
GLib.IO_IN | GLib.IO_HUP,
2542
2561
functools.partial(self.handle_ipc,
2543
parent_pipe = parent_pipe,
2562
parent_pipe=parent_pipe,
2546
2565
def handle_ipc(self, source, condition,
2547
2566
parent_pipe=None,
2549
2568
client_object=None):
2550
2569
# error, or the other end of multiprocessing.Pipe has closed
2551
2570
if condition & (GLib.IO_ERR | GLib.IO_HUP):
2552
2571
# Wait for other process to exit
2556
2575
# Read a request from the child
2557
2576
request = parent_pipe.recv()
2558
2577
command = request[0]
2560
2579
if command == 'init':
2561
2580
fpr = request[1]
2562
2581
address = request[2]
2564
2583
for c in self.clients.values():
2565
2584
if c.fingerprint == fpr:
2630
2649
>>> rfc3339_duration_to_delta("P1DT3M20S")
2631
2650
datetime.timedelta(1, 200)
2634
2653
# Parsing an RFC 3339 duration with regular expressions is not
2635
2654
# possible - there would have to be multiple places for the same
2636
2655
# values, like seconds. The current code, while more esoteric, is
2637
2656
# cleaner without depending on a parsing library. If Python had a
2638
2657
# built-in library for parsing we would use it, but we'd like to
2639
2658
# avoid excessive use of external libraries.
2641
2660
# New type for defining tokens, syntax, and semantics all-in-one
2642
2661
Token = collections.namedtuple("Token", (
2643
2662
"regexp", # To match token; if "value" is not None, must have
2827
2849
parser.add_argument("--no-zeroconf", action="store_false",
2828
2850
dest="zeroconf", help="Do not use Zeroconf",
2831
2853
options = parser.parse_args()
2833
2855
if options.check:
2835
2857
fail_count, test_count = doctest.testmod()
2836
2858
sys.exit(os.EX_OK if fail_count == 0 else 1)
2838
2860
# 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",
2861
server_defaults = {"interface": "",
2866
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2867
":+SIGN-DSA-SHA256",
2868
"servicename": "Mandos",
2874
"statedir": "/var/lib/mandos",
2875
"foreground": "False",
2857
2879
# Parse config file for server-global settings
2858
2880
server_config = configparser.SafeConfigParser(server_defaults)
2859
2881
del server_defaults
2988
3010
.format(uid, gid, os.strerror(error.errno)))
2989
3011
if error.errno != errno.EPERM:
2993
3015
# Enable all possible GnuTLS debugging
2995
3017
# "Use a log level over 10 to enable all debugging options."
2996
3018
# - GnuTLS manual
2997
3019
gnutls.global_set_log_level(11)
2999
3021
@gnutls.log_func
3000
3022
def debug_gnutls(level, string):
3001
3023
logger.debug("GnuTLS: %s", string[:-1])
3003
3025
gnutls.global_set_log_function(debug_gnutls)
3005
3027
# Redirect stdin so all checkers get /dev/null
3006
3028
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
3007
3029
os.dup2(null, sys.stdin.fileno())
3011
3033
# Need to fork before connecting to D-Bus
3012
3034
if not foreground:
3013
3035
# Close all input and output, do double fork, etc.
3016
3038
# multiprocessing will use threads, so before we use GLib we need
3017
3039
# to inform GLib that threads will be used.
3018
3040
GLib.threads_init()
3020
3042
global main_loop
3021
3043
# From the Avahi example code
3022
3044
DBusGMainLoop(set_as_default=True)
3040
3062
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
3041
3063
service = AvahiServiceToSyslog(
3042
name = server_settings["servicename"],
3043
servicetype = "_mandos._tcp",
3044
protocol = protocol,
3064
name=server_settings["servicename"],
3065
servicetype="_mandos._tcp",
3046
3068
if server_settings["interface"]:
3047
3069
service.interface = if_nametoindex(
3048
3070
server_settings["interface"].encode("utf-8"))
3050
3072
global multiprocessing_manager
3051
3073
multiprocessing_manager = multiprocessing.Manager()
3053
3075
client_class = Client
3055
client_class = functools.partial(ClientDBus, bus = bus)
3077
client_class = functools.partial(ClientDBus, bus=bus)
3057
3079
client_settings = Client.config_parser(client_config)
3058
3080
old_client_settings = {}
3059
3081
clients_data = {}
3061
3083
# This is used to redirect stdout and stderr for checker processes
3063
wnull = open(os.devnull, "w") # A writable /dev/null
3085
wnull = open(os.devnull, "w") # A writable /dev/null
3064
3086
# Only used if server is running in foreground but not in debug
3066
3088
if debug or not foreground:
3069
3091
# Get client data and settings from last running state.
3070
3092
if server_settings["restore"]:
3072
3094
with open(stored_state_path, "rb") as stored_state:
3073
if sys.version_info.major == 2:
3095
if sys.version_info.major == 2:
3074
3096
clients_data, old_client_settings = pickle.load(
3077
3099
bytes_clients_data, bytes_old_client_settings = (
3078
pickle.load(stored_state, encoding = "bytes"))
3079
### Fix bytes to strings
3100
pickle.load(stored_state, encoding="bytes"))
3101
# Fix bytes to strings
3082
clients_data = { (key.decode("utf-8")
3083
if isinstance(key, bytes)
3086
bytes_clients_data.items() }
3104
clients_data = {(key.decode("utf-8")
3105
if isinstance(key, bytes)
3108
bytes_clients_data.items()}
3087
3109
del bytes_clients_data
3088
3110
for key in clients_data:
3089
value = { (k.decode("utf-8")
3090
if isinstance(k, bytes) else k): v
3092
clients_data[key].items() }
3111
value = {(k.decode("utf-8")
3112
if isinstance(k, bytes) else k): v
3114
clients_data[key].items()}
3093
3115
clients_data[key] = value
3094
3116
# .client_structure
3095
3117
value["client_structure"] = [
3096
3118
(s.decode("utf-8")
3097
3119
if isinstance(s, bytes)
3098
3120
else s) for s in
3099
value["client_structure"] ]
3121
value["client_structure"]]
3100
3122
# .name & .host
3101
3123
for k in ("name", "host"):
3102
3124
if isinstance(value[k], bytes):
3103
3125
value[k] = value[k].decode("utf-8")
3104
## old_client_settings
3126
# old_client_settings
3106
3128
old_client_settings = {
3107
3129
(key.decode("utf-8")
3108
3130
if isinstance(key, bytes)
3109
3131
else key): value
3110
3132
for key, value in
3111
bytes_old_client_settings.items() }
3133
bytes_old_client_settings.items()}
3112
3134
del bytes_old_client_settings
3114
3136
for value in old_client_settings.values():
3220
3242
pidfilename, pid)
3222
3244
del pidfilename
3224
3246
for termsig in (signal.SIGHUP, signal.SIGTERM):
3225
3247
GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig,
3226
3248
lambda: main_loop.quit() and False)
3230
3252
@alternate_dbus_interfaces(
3231
{ "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
3253
{"se.recompile.Mandos": "se.bsnet.fukt.Mandos"})
3232
3254
class MandosDBusService(DBusObjectWithObjectManager):
3233
3255
"""A D-Bus proxy object"""
3235
3257
def __init__(self):
3236
3258
dbus.service.Object.__init__(self, bus, "/")
3238
3260
_interface = "se.recompile.Mandos"
3240
3262
@dbus.service.signal(_interface, signature="o")
3241
3263
def ClientAdded(self, objpath):
3245
3267
@dbus.service.signal(_interface, signature="ss")
3246
3268
def ClientNotFound(self, fingerprint, address):
3250
3272
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3252
3274
@dbus.service.signal(_interface, signature="os")
3253
3275
def ClientRemoved(self, objpath, name):
3257
3279
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3259
3281
@dbus.service.method(_interface, out_signature="ao")
3287
3309
self.client_removed_signal(c)
3289
3311
raise KeyError(object_path)
3293
3315
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
3294
out_signature = "a{oa{sa{sv}}}")
3316
out_signature="a{oa{sa{sv}}}")
3295
3317
def GetManagedObjects(self):
3296
3318
"""D-Bus method"""
3297
3319
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()})
3320
{client.dbus_object_path:
3322
{interface: client.GetAll(interface)
3324
client._get_all_interface_names()})
3325
for client in tcp_server.clients.values()})
3305
3327
def client_added_signal(self, client):
3306
3328
"""Send the new standard signal and the old signal"""
3352
3375
client.encrypted_secret = pgp.encrypt(client.secret,
3354
3377
client_dict = {}
3356
3379
# A list of attributes that can not be pickled
3358
exclude = { "bus", "changedstate", "secret",
3359
"checker", "server_settings" }
3381
exclude = {"bus", "changedstate", "secret",
3382
"checker", "server_settings"}
3360
3383
for name, typ in inspect.getmembers(dbus.service
3362
3385
exclude.add(name)
3364
3387
client_dict["encrypted_secret"] = (client
3365
3388
.encrypted_secret)
3366
3389
for attr in client.client_structure:
3367
3390
if attr not in exclude:
3368
3391
client_dict[attr] = getattr(client, attr)
3370
3393
clients[client.name] = client_dict
3371
3394
del client_settings[client.name]["secret"]
3374
3397
with tempfile.NamedTemporaryFile(