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
498
501
.format(self.name)))
501
505
# Pretend that we have a GnuTLS module
502
506
class GnuTLS(object):
503
507
"""This isn't so much a class as it is a module-like namespace.
504
508
It is instantiated once, and simulates having a GnuTLS module."""
506
_library = ctypes.cdll.LoadLibrary(
507
ctypes.util.find_library("gnutls"))
510
library = ctypes.util.find_library("gnutls")
512
library = ctypes.util.find_library("gnutls-deb0")
513
_library = ctypes.cdll.LoadLibrary(library)
508
515
_need_version = b"3.3.0"
509
517
def __init__(self):
510
# Need to use class name "GnuTLS" here, since this method is
511
# called before the assignment to the "gnutls" global variable
513
if GnuTLS.check_version(self._need_version) is None:
514
raise GnuTLS.Error("Needs GnuTLS {} or later"
515
.format(self._need_version))
518
# Need to use "self" here, since this method is called before
519
# the assignment to the "gnutls" global variable happens.
520
if self.check_version(self._need_version) is None:
521
raise self.Error("Needs GnuTLS {} or later"
522
.format(self._need_version))
517
524
# Unless otherwise indicated, the constants and types below are
518
525
# all from the gnutls/gnutls.h C header file.
522
529
E_INTERRUPTED = -52
527
534
CRD_CERTIFICATE = 1
528
535
E_NO_CERTIFICATE_FOUND = -49
529
536
OPENPGP_FMT_RAW = 0 # gnutls/openpgp.h
532
539
class session_int(ctypes.Structure):
534
541
session_t = ctypes.POINTER(session_int)
535
543
class certificate_credentials_st(ctypes.Structure):
537
545
certificate_credentials_t = ctypes.POINTER(
538
546
certificate_credentials_st)
539
547
certificate_type_t = ctypes.c_int
540
549
class datum_t(ctypes.Structure):
541
550
_fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
542
551
('size', ctypes.c_uint)]
543
553
class openpgp_crt_int(ctypes.Structure):
545
555
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
546
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
556
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
547
557
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
548
558
credentials_type_t = ctypes.c_int
549
559
transport_ptr_t = ctypes.c_void_p
550
560
close_request_t = ctypes.c_int
553
563
class Error(Exception):
554
564
# We need to use the class name "GnuTLS" here, since this
555
565
# exception might be raised from within GnuTLS.__init__,
556
566
# which is called before the assignment to the "gnutls"
557
567
# global variable has happened.
558
def __init__(self, message = None, code = None, args=()):
568
def __init__(self, message=None, code=None, args=()):
559
569
# Default usage is by a message string, but if a return
560
570
# code is passed, convert it to a string with
561
571
# gnutls.strerror()
630
640
return _error_code(result)
631
641
result = func(*arguments)
634
644
# Unless otherwise indicated, the function declarations below are
635
645
# all from the gnutls/gnutls.h C header file.
638
648
priority_set_direct = _library.gnutls_priority_set_direct
639
649
priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
640
650
ctypes.POINTER(ctypes.c_char_p)]
641
651
priority_set_direct.restype = _error_code
643
653
init = _library.gnutls_init
644
654
init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
645
655
init.restype = _error_code
647
657
set_default_priority = _library.gnutls_set_default_priority
648
658
set_default_priority.argtypes = [session_t]
649
659
set_default_priority.restype = _error_code
651
661
record_send = _library.gnutls_record_send
652
662
record_send.argtypes = [session_t, ctypes.c_void_p,
654
664
record_send.restype = ctypes.c_ssize_t
655
665
record_send.errcheck = _retry_on_error
657
667
certificate_allocate_credentials = (
658
668
_library.gnutls_certificate_allocate_credentials)
659
669
certificate_allocate_credentials.argtypes = [
660
670
ctypes.POINTER(certificate_credentials_t)]
661
671
certificate_allocate_credentials.restype = _error_code
663
673
certificate_free_credentials = (
664
674
_library.gnutls_certificate_free_credentials)
665
certificate_free_credentials.argtypes = [certificate_credentials_t]
675
certificate_free_credentials.argtypes = [
676
certificate_credentials_t]
666
677
certificate_free_credentials.restype = None
668
679
handshake_set_private_extensions = (
669
680
_library.gnutls_handshake_set_private_extensions)
670
681
handshake_set_private_extensions.argtypes = [session_t,
672
683
handshake_set_private_extensions.restype = None
674
685
credentials_set = _library.gnutls_credentials_set
675
686
credentials_set.argtypes = [session_t, credentials_type_t,
677
688
credentials_set.restype = _error_code
679
690
strerror = _library.gnutls_strerror
680
691
strerror.argtypes = [ctypes.c_int]
681
692
strerror.restype = ctypes.c_char_p
683
694
certificate_type_get = _library.gnutls_certificate_type_get
684
695
certificate_type_get.argtypes = [session_t]
685
696
certificate_type_get.restype = _error_code
687
698
certificate_get_peers = _library.gnutls_certificate_get_peers
688
699
certificate_get_peers.argtypes = [session_t,
689
700
ctypes.POINTER(ctypes.c_uint)]
690
701
certificate_get_peers.restype = ctypes.POINTER(datum_t)
692
703
global_set_log_level = _library.gnutls_global_set_log_level
693
704
global_set_log_level.argtypes = [ctypes.c_int]
694
705
global_set_log_level.restype = None
696
707
global_set_log_function = _library.gnutls_global_set_log_function
697
708
global_set_log_function.argtypes = [log_func]
698
709
global_set_log_function.restype = None
700
711
deinit = _library.gnutls_deinit
701
712
deinit.argtypes = [session_t]
702
713
deinit.restype = None
704
715
handshake = _library.gnutls_handshake
705
716
handshake.argtypes = [session_t]
706
717
handshake.restype = _error_code
707
718
handshake.errcheck = _retry_on_error
709
720
transport_set_ptr = _library.gnutls_transport_set_ptr
710
721
transport_set_ptr.argtypes = [session_t, transport_ptr_t]
711
722
transport_set_ptr.restype = None
713
724
bye = _library.gnutls_bye
714
725
bye.argtypes = [session_t, close_request_t]
715
726
bye.restype = _error_code
716
727
bye.errcheck = _retry_on_error
718
729
check_version = _library.gnutls_check_version
719
730
check_version.argtypes = [ctypes.c_char_p]
720
731
check_version.restype = ctypes.c_char_p
722
733
# All the function declarations below are from gnutls/openpgp.h
724
735
openpgp_crt_init = _library.gnutls_openpgp_crt_init
725
736
openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
726
737
openpgp_crt_init.restype = _error_code
728
739
openpgp_crt_import = _library.gnutls_openpgp_crt_import
729
740
openpgp_crt_import.argtypes = [openpgp_crt_t,
730
741
ctypes.POINTER(datum_t),
731
742
openpgp_crt_fmt_t]
732
743
openpgp_crt_import.restype = _error_code
734
745
openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
735
746
openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
736
747
ctypes.POINTER(ctypes.c_uint)]
737
748
openpgp_crt_verify_self.restype = _error_code
739
750
openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
740
751
openpgp_crt_deinit.argtypes = [openpgp_crt_t]
741
752
openpgp_crt_deinit.restype = None
743
754
openpgp_crt_get_fingerprint = (
744
755
_library.gnutls_openpgp_crt_get_fingerprint)
745
756
openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
1065
1078
# The exception is when not debugging but nevertheless
1066
1079
# running in the foreground; use the previously
1067
1080
# created wnull.
1068
popen_args = { "close_fds": True,
1081
popen_args = {"close_fds": True,
1071
1084
if (not self.server_settings["debug"]
1072
1085
and self.server_settings["foreground"]):
1073
1086
popen_args.update({"stdout": wnull,
1075
pipe = multiprocessing.Pipe(duplex = False)
1088
pipe = multiprocessing.Pipe(duplex=False)
1076
1089
self.checker = multiprocessing.Process(
1078
args = (pipe[1], subprocess.call, command),
1079
kwargs = popen_args)
1091
args=(pipe[1], subprocess.call, command),
1080
1093
self.checker.start()
1081
1094
self.checker_callback_tag = GLib.io_add_watch(
1082
1095
pipe[0].fileno(), GLib.IO_IN,
1083
1096
self.checker_callback, pipe[0], command)
1084
1097
# Re-run this periodically if run by GLib.timeout_add
1087
1100
def stop_checker(self):
1088
1101
"""Force the checker process, if any, to stop."""
1089
1102
if self.checker_callback_tag:
1124
1137
func._dbus_name = func.__name__
1125
1138
if func._dbus_name.endswith("_dbus_property"):
1126
1139
func._dbus_name = func._dbus_name[:-14]
1127
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
1140
func._dbus_get_args_options = {'byte_arrays': byte_arrays}
1130
1143
return decorator
1133
1146
def dbus_interface_annotations(dbus_interface):
1134
1147
"""Decorator for marking functions returning interface annotations
1138
1151
@dbus_interface_annotations("org.example.Interface")
1139
1152
def _foo(self): # Function name does not matter
1140
1153
return {"org.freedesktop.DBus.Deprecated": "true",
1141
1154
"org.freedesktop.DBus.Property.EmitsChangedSignal":
1145
1158
def decorator(func):
1146
1159
func._dbus_is_interface = True
1147
1160
func._dbus_interface = dbus_interface
1148
1161
func._dbus_name = dbus_interface
1151
1164
return decorator
1154
1167
def dbus_annotations(annotations):
1155
1168
"""Decorator to annotate D-Bus methods, signals or properties
1158
1171
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
1159
1172
"org.freedesktop.DBus.Property."
1160
1173
"EmitsChangedSignal": "false"})
1216
1229
for cls in self.__class__.__mro__
1217
1230
for name, athing in
1218
1231
inspect.getmembers(cls, self._is_dbus_thing(thing)))
1220
1233
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1221
out_signature = "s",
1222
path_keyword = 'object_path',
1223
connection_keyword = 'connection')
1235
path_keyword='object_path',
1236
connection_keyword='connection')
1224
1237
def Introspect(self, object_path, connection):
1225
1238
"""Overloading of standard D-Bus method.
1227
1240
Inserts annotation tags on methods and signals.
1229
1242
xmlstring = dbus.service.Object.Introspect(self, object_path,
1232
1245
document = xml.dom.minidom.parseString(xmlstring)
1234
1247
for if_tag in document.getElementsByTagName("interface"):
1235
1248
# Add annotation tags
1236
1249
for typ in ("method", "signal"):
1452
1465
except AttributeError:
1453
1466
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1455
1469
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1456
1470
"""A D-Bus object with an ObjectManager.
1458
1472
Classes inheriting from this exposes the standard
1459
1473
GetManagedObjects call and the InterfacesAdded and
1460
1474
InterfacesRemoved signals on the standard
1461
1475
"org.freedesktop.DBus.ObjectManager" interface.
1463
1477
Note: No signals are sent automatically; they must be sent
1466
1480
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1467
out_signature = "a{oa{sa{sv}}}")
1481
out_signature="a{oa{sa{sv}}}")
1468
1482
def GetManagedObjects(self):
1469
1483
"""This function must be overridden"""
1470
1484
raise NotImplementedError()
1472
1486
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1473
signature = "oa{sa{sv}}")
1487
signature="oa{sa{sv}}")
1474
1488
def InterfacesAdded(self, object_path, interfaces_and_properties):
1477
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
1491
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature="oas")
1478
1492
def InterfacesRemoved(self, object_path, interfaces):
1481
1495
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1482
out_signature = "s",
1483
path_keyword = 'object_path',
1484
connection_keyword = 'connection')
1497
path_keyword='object_path',
1498
connection_keyword='connection')
1485
1499
def Introspect(self, object_path, connection):
1486
1500
"""Overloading of standard D-Bus method.
1488
1502
Override return argument name of GetManagedObjects to be
1489
1503
"objpath_interfaces_and_properties"
1528
1543
dbus.service.Object, it will add alternate D-Bus attributes with
1529
1544
interface names according to the "alt_interface_names" mapping.
1532
1547
@alternate_dbus_interfaces({"org.example.Interface":
1533
1548
"net.example.AlternateInterface"})
1534
1549
class SampleDBusObject(dbus.service.Object):
1535
1550
@dbus.service.method("org.example.Interface")
1536
1551
def SampleDBusMethod():
1539
1554
The above "SampleDBusMethod" on "SampleDBusObject" will be
1540
1555
reachable via two interfaces: "org.example.Interface" and
1541
1556
"net.example.AlternateInterface", the latter of which will have
1542
1557
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1543
1558
"true", unless "deprecate" is passed with a False value.
1545
1560
This works for methods and signals, and also for D-Bus properties
1546
1561
(from DBusObjectWithProperties) and interfaces (from the
1547
1562
dbus_interface_annotations decorator).
1550
1565
def wrapper(cls):
1551
1566
for orig_interface_name, alt_interface_name in (
1552
1567
alt_interface_names.items()):
1694
1711
"se.bsnet.fukt.Mandos"})
1695
1712
class ClientDBus(Client, DBusObjectWithProperties):
1696
1713
"""A Client class using D-Bus
1699
1716
dbus_object_path: dbus.ObjectPath
1700
1717
bus: dbus.SystemBus()
1703
1720
runtime_expansions = (Client.runtime_expansions
1704
1721
+ ("dbus_object_path", ))
1706
1723
_interface = "se.recompile.Mandos.Client"
1708
1725
# dbus.service.Object doesn't use super(), so we can't either.
1710
def __init__(self, bus = None, *args, **kwargs):
1727
def __init__(self, bus=None, *args, **kwargs):
1712
1729
Client.__init__(self, *args, **kwargs)
1713
1730
# Only now, when this client is initialized, can it show up on
1750
1767
dbus_value = transform_func(
1751
1768
type_func(value),
1752
variant_level = variant_level)
1769
variant_level=variant_level)
1753
1770
self.PropertyChanged(dbus.String(dbus_name),
1755
1772
self.PropertiesChanged(
1757
dbus.Dictionary({ dbus.String(dbus_name):
1774
dbus.Dictionary({dbus.String(dbus_name):
1760
1777
setattr(self, attrname, value)
1762
1779
return property(lambda self: getattr(self, attrname), setter)
1764
1781
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1765
1782
approvals_pending = notifychangeproperty(dbus.Boolean,
1766
1783
"ApprovalPending",
1768
1785
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1769
1786
last_enabled = notifychangeproperty(datetime_to_dbus,
1771
1788
checker = notifychangeproperty(
1772
1789
dbus.Boolean, "CheckerRunning",
1773
type_func = lambda checker: checker is not None)
1790
type_func=lambda checker: checker is not None)
1774
1791
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1775
1792
"LastCheckedOK")
1776
1793
last_checker_status = notifychangeproperty(dbus.Int16,
1781
1798
"ApprovedByDefault")
1782
1799
approval_delay = notifychangeproperty(
1783
1800
dbus.UInt64, "ApprovalDelay",
1784
type_func = lambda td: td.total_seconds() * 1000)
1801
type_func=lambda td: td.total_seconds() * 1000)
1785
1802
approval_duration = notifychangeproperty(
1786
1803
dbus.UInt64, "ApprovalDuration",
1787
type_func = lambda td: td.total_seconds() * 1000)
1804
type_func=lambda td: td.total_seconds() * 1000)
1788
1805
host = notifychangeproperty(dbus.String, "Host")
1789
1806
timeout = notifychangeproperty(
1790
1807
dbus.UInt64, "Timeout",
1791
type_func = lambda td: td.total_seconds() * 1000)
1808
type_func=lambda td: td.total_seconds() * 1000)
1792
1809
extended_timeout = notifychangeproperty(
1793
1810
dbus.UInt64, "ExtendedTimeout",
1794
type_func = lambda td: td.total_seconds() * 1000)
1811
type_func=lambda td: td.total_seconds() * 1000)
1795
1812
interval = notifychangeproperty(
1796
1813
dbus.UInt64, "Interval",
1797
type_func = lambda td: td.total_seconds() * 1000)
1814
type_func=lambda td: td.total_seconds() * 1000)
1798
1815
checker_command = notifychangeproperty(dbus.String, "Checker")
1799
1816
secret = notifychangeproperty(dbus.ByteArray, "Secret",
1800
1817
invalidate_only=True)
1802
1819
del notifychangeproperty
1804
1821
def __del__(self, *args, **kwargs):
1806
1823
self.remove_from_connection()
1841
1858
# Emit D-Bus signal
1842
1859
self.CheckerStarted(self.current_checker_command)
1845
1862
def _reset_approved(self):
1846
1863
self.approved = None
1849
1866
def approve(self, value=True):
1850
1867
self.approved = value
1851
1868
GLib.timeout_add(int(self.approval_duration.total_seconds()
1852
1869
* 1000), self._reset_approved)
1853
1870
self.send_changedstate()
1855
## D-Bus methods, signals & properties
1872
# D-Bus methods, signals & properties
1861
1878
# CheckerCompleted - signal
1862
1879
@dbus.service.signal(_interface, signature="nxs")
1863
1880
def CheckerCompleted(self, exitcode, waitstatus, command):
1867
1884
# CheckerStarted - signal
1868
1885
@dbus.service.signal(_interface, signature="s")
1869
1886
def CheckerStarted(self, command):
1873
1890
# PropertyChanged - signal
1874
1891
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1875
1892
@dbus.service.signal(_interface, signature="sv")
1876
1893
def PropertyChanged(self, property, value):
1880
1897
# GotSecret - signal
1881
1898
@dbus.service.signal(_interface)
1882
1899
def GotSecret(self):
1885
1902
server to mandos-client
1889
1906
# Rejected - signal
1890
1907
@dbus.service.signal(_interface, signature="s")
1891
1908
def Rejected(self, reason):
1895
1912
# NeedApproval - signal
1896
1913
@dbus.service.signal(_interface, signature="tb")
1897
1914
def NeedApproval(self, timeout, default):
1899
1916
return self.need_approval()
1903
1920
# Approve - method
1904
1921
@dbus.service.method(_interface, in_signature="b")
1905
1922
def Approve(self, value):
1906
1923
self.approve(value)
1908
1925
# CheckedOK - method
1909
1926
@dbus.service.method(_interface)
1910
1927
def CheckedOK(self):
1911
1928
self.checked_ok()
1913
1930
# Enable - method
1914
1931
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1915
1932
@dbus.service.method(_interface)
1916
1933
def Enable(self):
1920
1937
# StartChecker - method
1921
1938
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1922
1939
@dbus.service.method(_interface)
1923
1940
def StartChecker(self):
1925
1942
self.start_checker()
1927
1944
# Disable - method
1928
1945
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1929
1946
@dbus.service.method(_interface)
1930
1947
def Disable(self):
1934
1951
# StopChecker - method
1935
1952
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1936
1953
@dbus.service.method(_interface)
1937
1954
def StopChecker(self):
1938
1955
self.stop_checker()
1942
1959
# ApprovalPending - property
1943
1960
@dbus_service_property(_interface, signature="b", access="read")
1944
1961
def ApprovalPending_dbus_property(self):
1945
1962
return dbus.Boolean(bool(self.approvals_pending))
1947
1964
# ApprovedByDefault - property
1948
1965
@dbus_service_property(_interface,
2029
2046
self.checked_ok()
2031
2048
return datetime_to_dbus(self.last_checked_ok)
2033
2050
# LastCheckerStatus - property
2034
2051
@dbus_service_property(_interface, signature="n", access="read")
2035
2052
def LastCheckerStatus_dbus_property(self):
2036
2053
return dbus.Int16(self.last_checker_status)
2038
2055
# Expires - property
2039
2056
@dbus_service_property(_interface, signature="s", access="read")
2040
2057
def Expires_dbus_property(self):
2041
2058
return datetime_to_dbus(self.expires)
2043
2060
# LastApprovalRequest - property
2044
2061
@dbus_service_property(_interface, signature="s", access="read")
2045
2062
def LastApprovalRequest_dbus_property(self):
2046
2063
return datetime_to_dbus(self.last_approval_request)
2048
2065
# Timeout - property
2049
2066
@dbus_service_property(_interface,
2171
2188
class ClientHandler(socketserver.BaseRequestHandler, object):
2172
2189
"""A class to handle client connections.
2174
2191
Instantiated once for each connection to handle it.
2175
2192
Note: This will run in its own forked process."""
2177
2194
def handle(self):
2178
2195
with contextlib.closing(self.server.child_pipe) as child_pipe:
2179
2196
logger.info("TCP connection from: %s",
2180
2197
str(self.client_address))
2181
2198
logger.debug("Pipe FD: %d",
2182
2199
self.server.child_pipe.fileno())
2184
2201
session = gnutls.ClientSession(self.request)
2186
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
2187
# "+AES-256-CBC", "+SHA1",
2188
# "+COMP-NULL", "+CTYPE-OPENPGP",
2203
# priority = ':'.join(("NONE", "+VERS-TLS1.1",
2204
# "+AES-256-CBC", "+SHA1",
2205
# "+COMP-NULL", "+CTYPE-OPENPGP",
2190
2207
# Use a fallback default, since this MUST be set.
2191
2208
priority = self.server.gnutls_priority
2192
2209
if priority is None:
2365
2382
class MultiprocessingMixIn(object):
2366
2383
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
2368
2385
def sub_process_main(self, request, address):
2370
2387
self.finish_request(request, address)
2371
2388
except Exception:
2372
2389
self.handle_error(request, address)
2373
2390
self.close_request(request)
2375
2392
def process_request(self, request, address):
2376
2393
"""Start a new process to process the request."""
2377
proc = multiprocessing.Process(target = self.sub_process_main,
2378
args = (request, address))
2394
proc = multiprocessing.Process(target=self.sub_process_main,
2395
args=(request, address))
2383
2400
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
2384
2401
""" adds a pipe to the MixIn """
2386
2403
def process_request(self, request, client_address):
2387
2404
"""Overrides and wraps the original process_request().
2389
2406
This function creates a new pipe in self.pipe
2391
2408
parent_pipe, self.child_pipe = multiprocessing.Pipe()
2393
2410
proc = MultiprocessingMixIn.process_request(self, request,
2394
2411
client_address)
2395
2412
self.child_pipe.close()
2396
2413
self.add_pipe(parent_pipe, proc)
2398
2415
def add_pipe(self, parent_pipe, proc):
2399
2416
"""Dummy function; override as necessary"""
2400
2417
raise NotImplementedError()
2522
2541
self.gnutls_priority = gnutls_priority
2523
2542
IPv6_TCPServer.__init__(self, server_address,
2524
2543
RequestHandlerClass,
2525
interface = interface,
2526
use_ipv6 = use_ipv6,
2527
socketfd = socketfd)
2544
interface=interface,
2529
2548
def server_activate(self):
2530
2549
if self.enabled:
2531
2550
return socketserver.TCPServer.server_activate(self)
2533
2552
def enable(self):
2534
2553
self.enabled = True
2536
2555
def add_pipe(self, parent_pipe, proc):
2537
2556
# Call "handle_ipc" for both data and EOF events
2538
2557
GLib.io_add_watch(
2539
2558
parent_pipe.fileno(),
2540
2559
GLib.IO_IN | GLib.IO_HUP,
2541
2560
functools.partial(self.handle_ipc,
2542
parent_pipe = parent_pipe,
2561
parent_pipe=parent_pipe,
2545
2564
def handle_ipc(self, source, condition,
2546
2565
parent_pipe=None,
2548
2567
client_object=None):
2549
2568
# error, or the other end of multiprocessing.Pipe has closed
2550
2569
if condition & (GLib.IO_ERR | GLib.IO_HUP):
2551
2570
# Wait for other process to exit
2555
2574
# Read a request from the child
2556
2575
request = parent_pipe.recv()
2557
2576
command = request[0]
2559
2578
if command == 'init':
2560
2579
fpr = request[1]
2561
2580
address = request[2]
2563
2582
for c in self.clients.values():
2564
2583
if c.fingerprint == fpr:
2629
2648
>>> rfc3339_duration_to_delta("P1DT3M20S")
2630
2649
datetime.timedelta(1, 200)
2633
2652
# Parsing an RFC 3339 duration with regular expressions is not
2634
2653
# possible - there would have to be multiple places for the same
2635
2654
# values, like seconds. The current code, while more esoteric, is
2636
2655
# cleaner without depending on a parsing library. If Python had a
2637
2656
# built-in library for parsing we would use it, but we'd like to
2638
2657
# avoid excessive use of external libraries.
2640
2659
# New type for defining tokens, syntax, and semantics all-in-one
2641
2660
Token = collections.namedtuple("Token", (
2642
2661
"regexp", # To match token; if "value" is not None, must have
2826
2848
parser.add_argument("--no-zeroconf", action="store_false",
2827
2849
dest="zeroconf", help="Do not use Zeroconf",
2830
2852
options = parser.parse_args()
2832
2854
if options.check:
2834
2856
fail_count, test_count = doctest.testmod()
2835
2857
sys.exit(os.EX_OK if fail_count == 0 else 1)
2837
2859
# Default values for config file for server-global settings
2838
server_defaults = { "interface": "",
2843
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2844
":+SIGN-DSA-SHA256",
2845
"servicename": "Mandos",
2851
"statedir": "/var/lib/mandos",
2852
"foreground": "False",
2860
server_defaults = {"interface": "",
2865
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2866
":+SIGN-DSA-SHA256",
2867
"servicename": "Mandos",
2873
"statedir": "/var/lib/mandos",
2874
"foreground": "False",
2856
2878
# Parse config file for server-global settings
2857
2879
server_config = configparser.SafeConfigParser(server_defaults)
2858
2880
del server_defaults
2987
3009
.format(uid, gid, os.strerror(error.errno)))
2988
3010
if error.errno != errno.EPERM:
2992
3014
# Enable all possible GnuTLS debugging
2994
3016
# "Use a log level over 10 to enable all debugging options."
2995
3017
# - GnuTLS manual
2996
3018
gnutls.global_set_log_level(11)
2998
3020
@gnutls.log_func
2999
3021
def debug_gnutls(level, string):
3000
3022
logger.debug("GnuTLS: %s", string[:-1])
3002
3024
gnutls.global_set_log_function(debug_gnutls)
3004
3026
# Redirect stdin so all checkers get /dev/null
3005
3027
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
3006
3028
os.dup2(null, sys.stdin.fileno())
3010
3032
# Need to fork before connecting to D-Bus
3011
3033
if not foreground:
3012
3034
# Close all input and output, do double fork, etc.
3015
3037
# multiprocessing will use threads, so before we use GLib we need
3016
3038
# to inform GLib that threads will be used.
3017
3039
GLib.threads_init()
3019
3041
global main_loop
3020
3042
# From the Avahi example code
3021
3043
DBusGMainLoop(set_as_default=True)
3039
3061
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
3040
3062
service = AvahiServiceToSyslog(
3041
name = server_settings["servicename"],
3042
servicetype = "_mandos._tcp",
3043
protocol = protocol,
3063
name=server_settings["servicename"],
3064
servicetype="_mandos._tcp",
3045
3067
if server_settings["interface"]:
3046
3068
service.interface = if_nametoindex(
3047
3069
server_settings["interface"].encode("utf-8"))
3049
3071
global multiprocessing_manager
3050
3072
multiprocessing_manager = multiprocessing.Manager()
3052
3074
client_class = Client
3054
client_class = functools.partial(ClientDBus, bus = bus)
3076
client_class = functools.partial(ClientDBus, bus=bus)
3056
3078
client_settings = Client.config_parser(client_config)
3057
3079
old_client_settings = {}
3058
3080
clients_data = {}
3060
3082
# This is used to redirect stdout and stderr for checker processes
3062
wnull = open(os.devnull, "w") # A writable /dev/null
3084
wnull = open(os.devnull, "w") # A writable /dev/null
3063
3085
# Only used if server is running in foreground but not in debug
3065
3087
if debug or not foreground:
3068
3090
# Get client data and settings from last running state.
3069
3091
if server_settings["restore"]:
3071
3093
with open(stored_state_path, "rb") as stored_state:
3072
if sys.version_info.major == 2:
3094
if sys.version_info.major == 2:
3073
3095
clients_data, old_client_settings = pickle.load(
3076
3098
bytes_clients_data, bytes_old_client_settings = (
3077
pickle.load(stored_state, encoding = "bytes"))
3078
### Fix bytes to strings
3099
pickle.load(stored_state, encoding="bytes"))
3100
# Fix bytes to strings
3081
clients_data = { (key.decode("utf-8")
3082
if isinstance(key, bytes)
3085
bytes_clients_data.items() }
3103
clients_data = {(key.decode("utf-8")
3104
if isinstance(key, bytes)
3107
bytes_clients_data.items()}
3086
3108
del bytes_clients_data
3087
3109
for key in clients_data:
3088
value = { (k.decode("utf-8")
3089
if isinstance(k, bytes) else k): v
3091
clients_data[key].items() }
3110
value = {(k.decode("utf-8")
3111
if isinstance(k, bytes) else k): v
3113
clients_data[key].items()}
3092
3114
clients_data[key] = value
3093
3115
# .client_structure
3094
3116
value["client_structure"] = [
3095
3117
(s.decode("utf-8")
3096
3118
if isinstance(s, bytes)
3097
3119
else s) for s in
3098
value["client_structure"] ]
3120
value["client_structure"]]
3099
3121
# .name & .host
3100
3122
for k in ("name", "host"):
3101
3123
if isinstance(value[k], bytes):
3102
3124
value[k] = value[k].decode("utf-8")
3103
## old_client_settings
3125
# old_client_settings
3105
3127
old_client_settings = {
3106
3128
(key.decode("utf-8")
3107
3129
if isinstance(key, bytes)
3108
3130
else key): value
3109
3131
for key, value in
3110
bytes_old_client_settings.items() }
3132
bytes_old_client_settings.items()}
3111
3133
del bytes_old_client_settings
3113
3135
for value in old_client_settings.values():
3219
3241
pidfilename, pid)
3221
3243
del pidfilename
3223
3245
for termsig in (signal.SIGHUP, signal.SIGTERM):
3224
3246
GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig,
3225
3247
lambda: main_loop.quit() and False)
3229
3251
@alternate_dbus_interfaces(
3230
{ "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
3252
{"se.recompile.Mandos": "se.bsnet.fukt.Mandos"})
3231
3253
class MandosDBusService(DBusObjectWithObjectManager):
3232
3254
"""A D-Bus proxy object"""
3234
3256
def __init__(self):
3235
3257
dbus.service.Object.__init__(self, bus, "/")
3237
3259
_interface = "se.recompile.Mandos"
3239
3261
@dbus.service.signal(_interface, signature="o")
3240
3262
def ClientAdded(self, objpath):
3244
3266
@dbus.service.signal(_interface, signature="ss")
3245
3267
def ClientNotFound(self, fingerprint, address):
3249
3271
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3251
3273
@dbus.service.signal(_interface, signature="os")
3252
3274
def ClientRemoved(self, objpath, name):
3256
3278
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3258
3280
@dbus.service.method(_interface, out_signature="ao")
3286
3308
self.client_removed_signal(c)
3288
3310
raise KeyError(object_path)
3292
3314
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
3293
out_signature = "a{oa{sa{sv}}}")
3315
out_signature="a{oa{sa{sv}}}")
3294
3316
def GetManagedObjects(self):
3295
3317
"""D-Bus method"""
3296
3318
return dbus.Dictionary(
3297
{ client.dbus_object_path:
3299
{ interface: client.GetAll(interface)
3301
client._get_all_interface_names()})
3302
for client in tcp_server.clients.values()})
3319
{client.dbus_object_path:
3321
{interface: client.GetAll(interface)
3323
client._get_all_interface_names()})
3324
for client in tcp_server.clients.values()})
3304
3326
def client_added_signal(self, client):
3305
3327
"""Send the new standard signal and the old signal"""
3351
3374
client.encrypted_secret = pgp.encrypt(client.secret,
3353
3376
client_dict = {}
3355
3378
# A list of attributes that can not be pickled
3357
exclude = { "bus", "changedstate", "secret",
3358
"checker", "server_settings" }
3380
exclude = {"bus", "changedstate", "secret",
3381
"checker", "server_settings"}
3359
3382
for name, typ in inspect.getmembers(dbus.service
3361
3384
exclude.add(name)
3363
3386
client_dict["encrypted_secret"] = (client
3364
3387
.encrypted_secret)
3365
3388
for attr in client.client_structure:
3366
3389
if attr not in exclude:
3367
3390
client_dict[attr] = getattr(client, attr)
3369
3392
clients[client.name] = client_dict
3370
3393
del client_settings[client.name]["secret"]
3373
3396
with tempfile.NamedTemporaryFile(