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
# Copyright © 2008-2017 Teddy Hogeborn
15
# Copyright © 2008-2017 Björn Påhlsson
14
# Copyright © 2008-2016 Teddy Hogeborn
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
86
86
import xml.dom.minidom
89
# Try to find the value of SO_BINDTODEVICE:
91
# This is where SO_BINDTODEVICE is in Python 3.3 (or 3.4?) and
92
# newer, and it is also the most natural place for it:
93
90
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
94
91
except AttributeError:
96
# This is where SO_BINDTODEVICE was up to and including Python
98
93
from IN import SO_BINDTODEVICE
99
94
except ImportError:
100
# In Python 2.7 it seems to have been removed entirely.
101
# Try running the C preprocessor:
103
cc = subprocess.Popen(["cc", "--language=c", "-E",
105
stdin=subprocess.PIPE,
106
stdout=subprocess.PIPE)
107
stdout = cc.communicate(
108
"#include <sys/socket.h>\nSO_BINDTODEVICE\n")[0]
109
SO_BINDTODEVICE = int(stdout.splitlines()[-1])
110
except (OSError, ValueError, IndexError):
112
SO_BINDTODEVICE = None
95
SO_BINDTODEVICE = None
114
97
if sys.version_info.major == 2:
118
101
stored_state_file = "clients.pickle"
120
103
logger = logging.getLogger()
246
229
'--passphrase-file',
248
231
+ self.gnupgargs,
249
stdin=subprocess.PIPE,
250
stdout=subprocess.PIPE,
251
stderr=subprocess.PIPE)
252
ciphertext, err = proc.communicate(input=data)
232
stdin = subprocess.PIPE,
233
stdout = subprocess.PIPE,
234
stderr = subprocess.PIPE)
235
ciphertext, err = proc.communicate(input = data)
253
236
if proc.returncode != 0:
254
237
raise PGPError(err)
255
238
return ciphertext
257
240
def decrypt(self, data, password):
258
241
passphrase = self.password_encode(password)
259
242
with tempfile.NamedTemporaryFile(
260
dir=self.tempdir) as passfile:
243
dir = self.tempdir) as passfile:
261
244
passfile.write(passphrase)
263
246
proc = subprocess.Popen([self.gpg, '--decrypt',
264
247
'--passphrase-file',
266
249
+ self.gnupgargs,
267
stdin=subprocess.PIPE,
268
stdout=subprocess.PIPE,
269
stderr=subprocess.PIPE)
270
decrypted_plaintext, err = proc.communicate(input=data)
250
stdin = subprocess.PIPE,
251
stdout = subprocess.PIPE,
252
stderr = subprocess.PIPE)
253
decrypted_plaintext, err = proc.communicate(input = data)
271
254
if proc.returncode != 0:
272
255
raise PGPError(err)
273
256
return decrypted_plaintext
276
258
# Pretend that we have an Avahi module
277
259
class Avahi(object):
278
260
"""This isn't so much a class as it is a module-like namespace.
279
261
It is instantiated once, and simulates having an Avahi module."""
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
262
IF_UNSPEC = -1 # avahi-common/address.h
263
PROTO_UNSPEC = -1 # avahi-common/address.h
264
PROTO_INET = 0 # avahi-common/address.h
265
PROTO_INET6 = 1 # avahi-common/address.h
284
266
DBUS_NAME = "org.freedesktop.Avahi"
285
267
DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"
286
268
DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
287
269
DBUS_PATH_SERVER = "/"
289
270
def string_array_to_txt_array(self, t):
290
271
return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
291
272
for s in t), signature="ay")
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
273
ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h
274
ENTRY_GROUP_COLLISION = 3 # avahi-common/defs.h
275
ENTRY_GROUP_FAILURE = 4 # avahi-common/defs.h
276
SERVER_INVALID = 0 # avahi-common/defs.h
277
SERVER_REGISTERING = 1 # avahi-common/defs.h
278
SERVER_RUNNING = 2 # avahi-common/defs.h
279
SERVER_COLLISION = 3 # avahi-common/defs.h
280
SERVER_FAILURE = 4 # avahi-common/defs.h
303
283
class AvahiError(Exception):
304
284
def __init__(self, value, *args, **kwargs):
305
285
self.value = value
501
481
.format(self.name)))
505
484
# Pretend that we have a GnuTLS module
506
485
class GnuTLS(object):
507
486
"""This isn't so much a class as it is a module-like namespace.
508
487
It is instantiated once, and simulates having a GnuTLS module."""
510
library = ctypes.util.find_library("gnutls")
512
library = ctypes.util.find_library("gnutls-deb0")
513
_library = ctypes.cdll.LoadLibrary(library)
489
_library = ctypes.cdll.LoadLibrary(
490
ctypes.util.find_library("gnutls"))
515
491
_need_version = b"3.3.0"
517
492
def __init__(self):
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))
493
# Need to use class name "GnuTLS" here, since this method is
494
# called before the assignment to the "gnutls" global variable
496
if GnuTLS.check_version(self._need_version) is None:
497
raise GnuTLS.Error("Needs GnuTLS {} or later"
498
.format(self._need_version))
524
500
# Unless otherwise indicated, the constants and types below are
525
501
# all from the gnutls/gnutls.h C header file.
529
505
E_INTERRUPTED = -52
534
510
CRD_CERTIFICATE = 1
535
511
E_NO_CERTIFICATE_FOUND = -49
536
512
OPENPGP_FMT_RAW = 0 # gnutls/openpgp.h
539
515
class session_int(ctypes.Structure):
541
517
session_t = ctypes.POINTER(session_int)
543
518
class certificate_credentials_st(ctypes.Structure):
545
520
certificate_credentials_t = ctypes.POINTER(
546
521
certificate_credentials_st)
547
522
certificate_type_t = ctypes.c_int
549
523
class datum_t(ctypes.Structure):
550
524
_fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
551
525
('size', ctypes.c_uint)]
553
526
class openpgp_crt_int(ctypes.Structure):
555
528
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
556
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
529
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
557
530
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
558
531
credentials_type_t = ctypes.c_int
559
532
transport_ptr_t = ctypes.c_void_p
560
533
close_request_t = ctypes.c_int
563
536
class Error(Exception):
564
537
# We need to use the class name "GnuTLS" here, since this
565
538
# exception might be raised from within GnuTLS.__init__,
566
539
# which is called before the assignment to the "gnutls"
567
540
# global variable has happened.
568
def __init__(self, message=None, code=None, args=()):
541
def __init__(self, message = None, code = None, args=()):
569
542
# Default usage is by a message string, but if a return
570
543
# code is passed, convert it to a string with
571
544
# gnutls.strerror()
640
613
return _error_code(result)
641
614
result = func(*arguments)
644
617
# Unless otherwise indicated, the function declarations below are
645
618
# all from the gnutls/gnutls.h C header file.
648
621
priority_set_direct = _library.gnutls_priority_set_direct
649
622
priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
650
623
ctypes.POINTER(ctypes.c_char_p)]
651
624
priority_set_direct.restype = _error_code
653
626
init = _library.gnutls_init
654
627
init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
655
628
init.restype = _error_code
657
630
set_default_priority = _library.gnutls_set_default_priority
658
631
set_default_priority.argtypes = [session_t]
659
632
set_default_priority.restype = _error_code
661
634
record_send = _library.gnutls_record_send
662
635
record_send.argtypes = [session_t, ctypes.c_void_p,
664
637
record_send.restype = ctypes.c_ssize_t
665
638
record_send.errcheck = _retry_on_error
667
640
certificate_allocate_credentials = (
668
641
_library.gnutls_certificate_allocate_credentials)
669
642
certificate_allocate_credentials.argtypes = [
670
643
ctypes.POINTER(certificate_credentials_t)]
671
644
certificate_allocate_credentials.restype = _error_code
673
646
certificate_free_credentials = (
674
647
_library.gnutls_certificate_free_credentials)
675
certificate_free_credentials.argtypes = [
676
certificate_credentials_t]
648
certificate_free_credentials.argtypes = [certificate_credentials_t]
677
649
certificate_free_credentials.restype = None
679
651
handshake_set_private_extensions = (
680
652
_library.gnutls_handshake_set_private_extensions)
681
653
handshake_set_private_extensions.argtypes = [session_t,
683
655
handshake_set_private_extensions.restype = None
685
657
credentials_set = _library.gnutls_credentials_set
686
658
credentials_set.argtypes = [session_t, credentials_type_t,
688
660
credentials_set.restype = _error_code
690
662
strerror = _library.gnutls_strerror
691
663
strerror.argtypes = [ctypes.c_int]
692
664
strerror.restype = ctypes.c_char_p
694
666
certificate_type_get = _library.gnutls_certificate_type_get
695
667
certificate_type_get.argtypes = [session_t]
696
668
certificate_type_get.restype = _error_code
698
670
certificate_get_peers = _library.gnutls_certificate_get_peers
699
671
certificate_get_peers.argtypes = [session_t,
700
672
ctypes.POINTER(ctypes.c_uint)]
701
673
certificate_get_peers.restype = ctypes.POINTER(datum_t)
703
675
global_set_log_level = _library.gnutls_global_set_log_level
704
676
global_set_log_level.argtypes = [ctypes.c_int]
705
677
global_set_log_level.restype = None
707
679
global_set_log_function = _library.gnutls_global_set_log_function
708
680
global_set_log_function.argtypes = [log_func]
709
681
global_set_log_function.restype = None
711
683
deinit = _library.gnutls_deinit
712
684
deinit.argtypes = [session_t]
713
685
deinit.restype = None
715
687
handshake = _library.gnutls_handshake
716
688
handshake.argtypes = [session_t]
717
689
handshake.restype = _error_code
718
690
handshake.errcheck = _retry_on_error
720
692
transport_set_ptr = _library.gnutls_transport_set_ptr
721
693
transport_set_ptr.argtypes = [session_t, transport_ptr_t]
722
694
transport_set_ptr.restype = None
724
696
bye = _library.gnutls_bye
725
697
bye.argtypes = [session_t, close_request_t]
726
698
bye.restype = _error_code
727
699
bye.errcheck = _retry_on_error
729
701
check_version = _library.gnutls_check_version
730
702
check_version.argtypes = [ctypes.c_char_p]
731
703
check_version.restype = ctypes.c_char_p
733
705
# All the function declarations below are from gnutls/openpgp.h
735
707
openpgp_crt_init = _library.gnutls_openpgp_crt_init
736
708
openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
737
709
openpgp_crt_init.restype = _error_code
739
711
openpgp_crt_import = _library.gnutls_openpgp_crt_import
740
712
openpgp_crt_import.argtypes = [openpgp_crt_t,
741
713
ctypes.POINTER(datum_t),
742
714
openpgp_crt_fmt_t]
743
715
openpgp_crt_import.restype = _error_code
745
717
openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
746
718
openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
747
719
ctypes.POINTER(ctypes.c_uint)]
748
720
openpgp_crt_verify_self.restype = _error_code
750
722
openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
751
723
openpgp_crt_deinit.argtypes = [openpgp_crt_t]
752
724
openpgp_crt_deinit.restype = None
754
726
openpgp_crt_get_fingerprint = (
755
727
_library.gnutls_openpgp_crt_get_fingerprint)
756
728
openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
1078
1048
# The exception is when not debugging but nevertheless
1079
1049
# running in the foreground; use the previously
1080
1050
# created wnull.
1081
popen_args = {"close_fds": True,
1051
popen_args = { "close_fds": True,
1084
1054
if (not self.server_settings["debug"]
1085
1055
and self.server_settings["foreground"]):
1086
1056
popen_args.update({"stdout": wnull,
1088
pipe = multiprocessing.Pipe(duplex=False)
1058
pipe = multiprocessing.Pipe(duplex = False)
1089
1059
self.checker = multiprocessing.Process(
1091
args=(pipe[1], subprocess.call, command),
1061
args = (pipe[1], subprocess.call, command),
1062
kwargs = popen_args)
1093
1063
self.checker.start()
1094
1064
self.checker_callback_tag = GLib.io_add_watch(
1095
1065
pipe[0].fileno(), GLib.IO_IN,
1096
1066
self.checker_callback, pipe[0], command)
1097
1067
# Re-run this periodically if run by GLib.timeout_add
1100
1070
def stop_checker(self):
1101
1071
"""Force the checker process, if any, to stop."""
1102
1072
if self.checker_callback_tag:
1137
1107
func._dbus_name = func.__name__
1138
1108
if func._dbus_name.endswith("_dbus_property"):
1139
1109
func._dbus_name = func._dbus_name[:-14]
1140
func._dbus_get_args_options = {'byte_arrays': byte_arrays}
1110
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
1143
1113
return decorator
1146
1116
def dbus_interface_annotations(dbus_interface):
1147
1117
"""Decorator for marking functions returning interface annotations
1151
1121
@dbus_interface_annotations("org.example.Interface")
1152
1122
def _foo(self): # Function name does not matter
1153
1123
return {"org.freedesktop.DBus.Deprecated": "true",
1154
1124
"org.freedesktop.DBus.Property.EmitsChangedSignal":
1158
1128
def decorator(func):
1159
1129
func._dbus_is_interface = True
1160
1130
func._dbus_interface = dbus_interface
1161
1131
func._dbus_name = dbus_interface
1164
1134
return decorator
1167
1137
def dbus_annotations(annotations):
1168
1138
"""Decorator to annotate D-Bus methods, signals or properties
1171
1141
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
1172
1142
"org.freedesktop.DBus.Property."
1173
1143
"EmitsChangedSignal": "false"})
1229
1199
for cls in self.__class__.__mro__
1230
1200
for name, athing in
1231
1201
inspect.getmembers(cls, self._is_dbus_thing(thing)))
1233
1203
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1235
path_keyword='object_path',
1236
connection_keyword='connection')
1204
out_signature = "s",
1205
path_keyword = 'object_path',
1206
connection_keyword = 'connection')
1237
1207
def Introspect(self, object_path, connection):
1238
1208
"""Overloading of standard D-Bus method.
1240
1210
Inserts annotation tags on methods and signals.
1242
1212
xmlstring = dbus.service.Object.Introspect(self, object_path,
1245
1215
document = xml.dom.minidom.parseString(xmlstring)
1247
1217
for if_tag in document.getElementsByTagName("interface"):
1248
1218
# Add annotation tags
1249
1219
for typ in ("method", "signal"):
1460
1430
exc_info=error)
1461
1431
return xmlstring
1465
1434
dbus.OBJECT_MANAGER_IFACE
1466
1435
except AttributeError:
1467
1436
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1470
1438
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1471
1439
"""A D-Bus object with an ObjectManager.
1473
1441
Classes inheriting from this exposes the standard
1474
1442
GetManagedObjects call and the InterfacesAdded and
1475
1443
InterfacesRemoved signals on the standard
1476
1444
"org.freedesktop.DBus.ObjectManager" interface.
1478
1446
Note: No signals are sent automatically; they must be sent
1481
1449
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1482
out_signature="a{oa{sa{sv}}}")
1450
out_signature = "a{oa{sa{sv}}}")
1483
1451
def GetManagedObjects(self):
1484
1452
"""This function must be overridden"""
1485
1453
raise NotImplementedError()
1487
1455
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1488
signature="oa{sa{sv}}")
1456
signature = "oa{sa{sv}}")
1489
1457
def InterfacesAdded(self, object_path, interfaces_and_properties):
1492
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature="oas")
1460
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
1493
1461
def InterfacesRemoved(self, object_path, interfaces):
1496
1464
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1498
path_keyword='object_path',
1499
connection_keyword='connection')
1465
out_signature = "s",
1466
path_keyword = 'object_path',
1467
connection_keyword = 'connection')
1500
1468
def Introspect(self, object_path, connection):
1501
1469
"""Overloading of standard D-Bus method.
1503
1471
Override return argument name of GetManagedObjects to be
1504
1472
"objpath_interfaces_and_properties"
1544
1511
dbus.service.Object, it will add alternate D-Bus attributes with
1545
1512
interface names according to the "alt_interface_names" mapping.
1548
1515
@alternate_dbus_interfaces({"org.example.Interface":
1549
1516
"net.example.AlternateInterface"})
1550
1517
class SampleDBusObject(dbus.service.Object):
1551
1518
@dbus.service.method("org.example.Interface")
1552
1519
def SampleDBusMethod():
1555
1522
The above "SampleDBusMethod" on "SampleDBusObject" will be
1556
1523
reachable via two interfaces: "org.example.Interface" and
1557
1524
"net.example.AlternateInterface", the latter of which will have
1558
1525
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1559
1526
"true", unless "deprecate" is passed with a False value.
1561
1528
This works for methods and signals, and also for D-Bus properties
1562
1529
(from DBusObjectWithProperties) and interfaces (from the
1563
1530
dbus_interface_annotations decorator).
1566
1533
def wrapper(cls):
1567
1534
for orig_interface_name, alt_interface_name in (
1568
1535
alt_interface_names.items()):
1712
1677
"se.bsnet.fukt.Mandos"})
1713
1678
class ClientDBus(Client, DBusObjectWithProperties):
1714
1679
"""A Client class using D-Bus
1717
1682
dbus_object_path: dbus.ObjectPath
1718
1683
bus: dbus.SystemBus()
1721
1686
runtime_expansions = (Client.runtime_expansions
1722
1687
+ ("dbus_object_path", ))
1724
1689
_interface = "se.recompile.Mandos.Client"
1726
1691
# dbus.service.Object doesn't use super(), so we can't either.
1728
def __init__(self, bus=None, *args, **kwargs):
1693
def __init__(self, bus = None, *args, **kwargs):
1730
1695
Client.__init__(self, *args, **kwargs)
1731
1696
# Only now, when this client is initialized, can it show up on
1768
1733
dbus_value = transform_func(
1769
1734
type_func(value),
1770
variant_level=variant_level)
1735
variant_level = variant_level)
1771
1736
self.PropertyChanged(dbus.String(dbus_name),
1773
1738
self.PropertiesChanged(
1775
dbus.Dictionary({dbus.String(dbus_name):
1740
dbus.Dictionary({ dbus.String(dbus_name):
1778
1743
setattr(self, attrname, value)
1780
1745
return property(lambda self: getattr(self, attrname), setter)
1782
1747
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1783
1748
approvals_pending = notifychangeproperty(dbus.Boolean,
1784
1749
"ApprovalPending",
1786
1751
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1787
1752
last_enabled = notifychangeproperty(datetime_to_dbus,
1789
1754
checker = notifychangeproperty(
1790
1755
dbus.Boolean, "CheckerRunning",
1791
type_func=lambda checker: checker is not None)
1756
type_func = lambda checker: checker is not None)
1792
1757
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1793
1758
"LastCheckedOK")
1794
1759
last_checker_status = notifychangeproperty(dbus.Int16,
1799
1764
"ApprovedByDefault")
1800
1765
approval_delay = notifychangeproperty(
1801
1766
dbus.UInt64, "ApprovalDelay",
1802
type_func=lambda td: td.total_seconds() * 1000)
1767
type_func = lambda td: td.total_seconds() * 1000)
1803
1768
approval_duration = notifychangeproperty(
1804
1769
dbus.UInt64, "ApprovalDuration",
1805
type_func=lambda td: td.total_seconds() * 1000)
1770
type_func = lambda td: td.total_seconds() * 1000)
1806
1771
host = notifychangeproperty(dbus.String, "Host")
1807
1772
timeout = notifychangeproperty(
1808
1773
dbus.UInt64, "Timeout",
1809
type_func=lambda td: td.total_seconds() * 1000)
1774
type_func = lambda td: td.total_seconds() * 1000)
1810
1775
extended_timeout = notifychangeproperty(
1811
1776
dbus.UInt64, "ExtendedTimeout",
1812
type_func=lambda td: td.total_seconds() * 1000)
1777
type_func = lambda td: td.total_seconds() * 1000)
1813
1778
interval = notifychangeproperty(
1814
1779
dbus.UInt64, "Interval",
1815
type_func=lambda td: td.total_seconds() * 1000)
1780
type_func = lambda td: td.total_seconds() * 1000)
1816
1781
checker_command = notifychangeproperty(dbus.String, "Checker")
1817
1782
secret = notifychangeproperty(dbus.ByteArray, "Secret",
1818
1783
invalidate_only=True)
1820
1785
del notifychangeproperty
1822
1787
def __del__(self, *args, **kwargs):
1824
1789
self.remove_from_connection()
1859
1824
# Emit D-Bus signal
1860
1825
self.CheckerStarted(self.current_checker_command)
1863
1828
def _reset_approved(self):
1864
1829
self.approved = None
1867
1832
def approve(self, value=True):
1868
1833
self.approved = value
1869
1834
GLib.timeout_add(int(self.approval_duration.total_seconds()
1870
1835
* 1000), self._reset_approved)
1871
1836
self.send_changedstate()
1873
# D-Bus methods, signals & properties
1838
## D-Bus methods, signals & properties
1879
1844
# CheckerCompleted - signal
1880
1845
@dbus.service.signal(_interface, signature="nxs")
1881
1846
def CheckerCompleted(self, exitcode, waitstatus, command):
1885
1850
# CheckerStarted - signal
1886
1851
@dbus.service.signal(_interface, signature="s")
1887
1852
def CheckerStarted(self, command):
1891
1856
# PropertyChanged - signal
1892
1857
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1893
1858
@dbus.service.signal(_interface, signature="sv")
1894
1859
def PropertyChanged(self, property, value):
1898
1863
# GotSecret - signal
1899
1864
@dbus.service.signal(_interface)
1900
1865
def GotSecret(self):
1903
1868
server to mandos-client
1907
1872
# Rejected - signal
1908
1873
@dbus.service.signal(_interface, signature="s")
1909
1874
def Rejected(self, reason):
1913
1878
# NeedApproval - signal
1914
1879
@dbus.service.signal(_interface, signature="tb")
1915
1880
def NeedApproval(self, timeout, default):
1917
1882
return self.need_approval()
1921
1886
# Approve - method
1922
1887
@dbus.service.method(_interface, in_signature="b")
1923
1888
def Approve(self, value):
1924
1889
self.approve(value)
1926
1891
# CheckedOK - method
1927
1892
@dbus.service.method(_interface)
1928
1893
def CheckedOK(self):
1929
1894
self.checked_ok()
1931
1896
# Enable - method
1932
1897
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1933
1898
@dbus.service.method(_interface)
1934
1899
def Enable(self):
1938
1903
# StartChecker - method
1939
1904
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1940
1905
@dbus.service.method(_interface)
1941
1906
def StartChecker(self):
1943
1908
self.start_checker()
1945
1910
# Disable - method
1946
1911
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1947
1912
@dbus.service.method(_interface)
1948
1913
def Disable(self):
1952
1917
# StopChecker - method
1953
1918
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1954
1919
@dbus.service.method(_interface)
1955
1920
def StopChecker(self):
1956
1921
self.stop_checker()
1960
1925
# ApprovalPending - property
1961
1926
@dbus_service_property(_interface, signature="b", access="read")
1962
1927
def ApprovalPending_dbus_property(self):
1963
1928
return dbus.Boolean(bool(self.approvals_pending))
1965
1930
# ApprovedByDefault - property
1966
1931
@dbus_service_property(_interface,
2047
2012
self.checked_ok()
2049
2014
return datetime_to_dbus(self.last_checked_ok)
2051
2016
# LastCheckerStatus - property
2052
2017
@dbus_service_property(_interface, signature="n", access="read")
2053
2018
def LastCheckerStatus_dbus_property(self):
2054
2019
return dbus.Int16(self.last_checker_status)
2056
2021
# Expires - property
2057
2022
@dbus_service_property(_interface, signature="s", access="read")
2058
2023
def Expires_dbus_property(self):
2059
2024
return datetime_to_dbus(self.expires)
2061
2026
# LastApprovalRequest - property
2062
2027
@dbus_service_property(_interface, signature="s", access="read")
2063
2028
def LastApprovalRequest_dbus_property(self):
2064
2029
return datetime_to_dbus(self.last_approval_request)
2066
2031
# Timeout - property
2067
2032
@dbus_service_property(_interface,
2189
2154
class ClientHandler(socketserver.BaseRequestHandler, object):
2190
2155
"""A class to handle client connections.
2192
2157
Instantiated once for each connection to handle it.
2193
2158
Note: This will run in its own forked process."""
2195
2160
def handle(self):
2196
2161
with contextlib.closing(self.server.child_pipe) as child_pipe:
2197
2162
logger.info("TCP connection from: %s",
2198
2163
str(self.client_address))
2199
2164
logger.debug("Pipe FD: %d",
2200
2165
self.server.child_pipe.fileno())
2202
2167
session = gnutls.ClientSession(self.request)
2204
# priority = ':'.join(("NONE", "+VERS-TLS1.1",
2205
# "+AES-256-CBC", "+SHA1",
2206
# "+COMP-NULL", "+CTYPE-OPENPGP",
2169
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
2170
# "+AES-256-CBC", "+SHA1",
2171
# "+COMP-NULL", "+CTYPE-OPENPGP",
2208
2173
# Use a fallback default, since this MUST be set.
2209
2174
priority = self.server.gnutls_priority
2210
2175
if priority is None:
2383
2348
class MultiprocessingMixIn(object):
2384
2349
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
2386
2351
def sub_process_main(self, request, address):
2388
2353
self.finish_request(request, address)
2389
2354
except Exception:
2390
2355
self.handle_error(request, address)
2391
2356
self.close_request(request)
2393
2358
def process_request(self, request, address):
2394
2359
"""Start a new process to process the request."""
2395
proc = multiprocessing.Process(target=self.sub_process_main,
2396
args=(request, address))
2360
proc = multiprocessing.Process(target = self.sub_process_main,
2361
args = (request, address))
2401
2366
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
2402
2367
""" adds a pipe to the MixIn """
2404
2369
def process_request(self, request, client_address):
2405
2370
"""Overrides and wraps the original process_request().
2407
2372
This function creates a new pipe in self.pipe
2409
2374
parent_pipe, self.child_pipe = multiprocessing.Pipe()
2411
2376
proc = MultiprocessingMixIn.process_request(self, request,
2412
2377
client_address)
2413
2378
self.child_pipe.close()
2414
2379
self.add_pipe(parent_pipe, proc)
2416
2381
def add_pipe(self, parent_pipe, proc):
2417
2382
"""Dummy function; override as necessary"""
2418
2383
raise NotImplementedError()
2467
2431
# socket_wrapper(), if socketfd was set.
2468
2432
socketserver.TCPServer.__init__(self, server_address,
2469
2433
RequestHandlerClass)
2471
2435
def server_bind(self):
2472
2436
"""This overrides the normal server_bind() function
2473
2437
to bind to an interface if one was specified, and also NOT to
2474
2438
bind to an address or port if they were not specified."""
2475
global SO_BINDTODEVICE
2476
2439
if self.interface is not None:
2477
2440
if SO_BINDTODEVICE is None:
2478
# Fall back to a hard-coded value which seems to be
2480
logger.warning("SO_BINDTODEVICE not found, trying 25")
2481
SO_BINDTODEVICE = 25
2483
self.socket.setsockopt(
2484
socket.SOL_SOCKET, SO_BINDTODEVICE,
2485
(self.interface + "\0").encode("utf-8"))
2486
except socket.error as error:
2487
if error.errno == errno.EPERM:
2488
logger.error("No permission to bind to"
2489
" interface %s", self.interface)
2490
elif error.errno == errno.ENOPROTOOPT:
2491
logger.error("SO_BINDTODEVICE not available;"
2492
" cannot bind to interface %s",
2494
elif error.errno == errno.ENODEV:
2495
logger.error("Interface %s does not exist,"
2496
" cannot bind", self.interface)
2441
logger.error("SO_BINDTODEVICE does not exist;"
2442
" cannot bind to interface %s",
2446
self.socket.setsockopt(
2447
socket.SOL_SOCKET, SO_BINDTODEVICE,
2448
(self.interface + "\0").encode("utf-8"))
2449
except socket.error as error:
2450
if error.errno == errno.EPERM:
2451
logger.error("No permission to bind to"
2452
" interface %s", self.interface)
2453
elif error.errno == errno.ENOPROTOOPT:
2454
logger.error("SO_BINDTODEVICE not available;"
2455
" cannot bind to interface %s",
2457
elif error.errno == errno.ENODEV:
2458
logger.error("Interface %s does not exist,"
2459
" cannot bind", self.interface)
2499
2462
# Only bind(2) the socket if we really need to.
2500
2463
if self.server_address[0] or self.server_address[1]:
2501
2464
if not self.server_address[0]:
2502
2465
if self.address_family == socket.AF_INET6:
2503
any_address = "::" # in6addr_any
2466
any_address = "::" # in6addr_any
2505
any_address = "0.0.0.0" # INADDR_ANY
2468
any_address = "0.0.0.0" # INADDR_ANY
2506
2469
self.server_address = (any_address,
2507
2470
self.server_address[1])
2508
2471
elif not self.server_address[1]:
2542
2505
self.gnutls_priority = gnutls_priority
2543
2506
IPv6_TCPServer.__init__(self, server_address,
2544
2507
RequestHandlerClass,
2545
interface=interface,
2508
interface = interface,
2509
use_ipv6 = use_ipv6,
2510
socketfd = socketfd)
2549
2512
def server_activate(self):
2550
2513
if self.enabled:
2551
2514
return socketserver.TCPServer.server_activate(self)
2553
2516
def enable(self):
2554
2517
self.enabled = True
2556
2519
def add_pipe(self, parent_pipe, proc):
2557
2520
# Call "handle_ipc" for both data and EOF events
2558
2521
GLib.io_add_watch(
2559
2522
parent_pipe.fileno(),
2560
2523
GLib.IO_IN | GLib.IO_HUP,
2561
2524
functools.partial(self.handle_ipc,
2562
parent_pipe=parent_pipe,
2525
parent_pipe = parent_pipe,
2565
2528
def handle_ipc(self, source, condition,
2566
2529
parent_pipe=None,
2568
2531
client_object=None):
2569
2532
# error, or the other end of multiprocessing.Pipe has closed
2570
2533
if condition & (GLib.IO_ERR | GLib.IO_HUP):
2571
2534
# Wait for other process to exit
2575
2538
# Read a request from the child
2576
2539
request = parent_pipe.recv()
2577
2540
command = request[0]
2579
2542
if command == 'init':
2580
2543
fpr = request[1]
2581
2544
address = request[2]
2583
2546
for c in self.clients.values():
2584
2547
if c.fingerprint == fpr:
2649
2612
>>> rfc3339_duration_to_delta("P1DT3M20S")
2650
2613
datetime.timedelta(1, 200)
2653
2616
# Parsing an RFC 3339 duration with regular expressions is not
2654
2617
# possible - there would have to be multiple places for the same
2655
2618
# values, like seconds. The current code, while more esoteric, is
2656
2619
# cleaner without depending on a parsing library. If Python had a
2657
2620
# built-in library for parsing we would use it, but we'd like to
2658
2621
# avoid excessive use of external libraries.
2660
2623
# New type for defining tokens, syntax, and semantics all-in-one
2661
2624
Token = collections.namedtuple("Token", (
2662
2625
"regexp", # To match token; if "value" is not None, must have
2849
2809
parser.add_argument("--no-zeroconf", action="store_false",
2850
2810
dest="zeroconf", help="Do not use Zeroconf",
2853
2813
options = parser.parse_args()
2855
2815
if options.check:
2857
2817
fail_count, test_count = doctest.testmod()
2858
2818
sys.exit(os.EX_OK if fail_count == 0 else 1)
2860
2820
# Default values for config file for server-global settings
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",
2821
server_defaults = { "interface": "",
2826
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2827
":+SIGN-DSA-SHA256",
2828
"servicename": "Mandos",
2834
"statedir": "/var/lib/mandos",
2835
"foreground": "False",
2879
2839
# Parse config file for server-global settings
2880
2840
server_config = configparser.SafeConfigParser(server_defaults)
2881
2841
del server_defaults
3011
2970
.format(uid, gid, os.strerror(error.errno)))
3012
2971
if error.errno != errno.EPERM:
3016
2975
# Enable all possible GnuTLS debugging
3018
2977
# "Use a log level over 10 to enable all debugging options."
3019
2978
# - GnuTLS manual
3020
2979
gnutls.global_set_log_level(11)
3022
2981
@gnutls.log_func
3023
2982
def debug_gnutls(level, string):
3024
2983
logger.debug("GnuTLS: %s", string[:-1])
3026
2985
gnutls.global_set_log_function(debug_gnutls)
3028
2987
# Redirect stdin so all checkers get /dev/null
3029
2988
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
3030
2989
os.dup2(null, sys.stdin.fileno())
3034
2993
# Need to fork before connecting to D-Bus
3035
2994
if not foreground:
3036
2995
# Close all input and output, do double fork, etc.
3039
2998
# multiprocessing will use threads, so before we use GLib we need
3040
2999
# to inform GLib that threads will be used.
3041
3000
GLib.threads_init()
3043
3002
global main_loop
3044
3003
# From the Avahi example code
3045
3004
DBusGMainLoop(set_as_default=True)
3063
3022
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
3064
3023
service = AvahiServiceToSyslog(
3065
name=server_settings["servicename"],
3066
servicetype="_mandos._tcp",
3024
name = server_settings["servicename"],
3025
servicetype = "_mandos._tcp",
3026
protocol = protocol,
3069
3028
if server_settings["interface"]:
3070
3029
service.interface = if_nametoindex(
3071
3030
server_settings["interface"].encode("utf-8"))
3073
3032
global multiprocessing_manager
3074
3033
multiprocessing_manager = multiprocessing.Manager()
3076
3035
client_class = Client
3078
client_class = functools.partial(ClientDBus, bus=bus)
3037
client_class = functools.partial(ClientDBus, bus = bus)
3080
3039
client_settings = Client.config_parser(client_config)
3081
3040
old_client_settings = {}
3082
3041
clients_data = {}
3084
3043
# This is used to redirect stdout and stderr for checker processes
3086
wnull = open(os.devnull, "w") # A writable /dev/null
3045
wnull = open(os.devnull, "w") # A writable /dev/null
3087
3046
# Only used if server is running in foreground but not in debug
3089
3048
if debug or not foreground:
3092
3051
# Get client data and settings from last running state.
3093
3052
if server_settings["restore"]:
3095
3054
with open(stored_state_path, "rb") as stored_state:
3096
if sys.version_info.major == 2:
3055
if sys.version_info.major == 2:
3097
3056
clients_data, old_client_settings = pickle.load(
3100
3059
bytes_clients_data, bytes_old_client_settings = (
3101
pickle.load(stored_state, encoding="bytes"))
3102
# Fix bytes to strings
3060
pickle.load(stored_state, encoding = "bytes"))
3061
### Fix bytes to strings
3105
clients_data = {(key.decode("utf-8")
3106
if isinstance(key, bytes)
3109
bytes_clients_data.items()}
3064
clients_data = { (key.decode("utf-8")
3065
if isinstance(key, bytes)
3068
bytes_clients_data.items() }
3110
3069
del bytes_clients_data
3111
3070
for key in clients_data:
3112
value = {(k.decode("utf-8")
3113
if isinstance(k, bytes) else k): v
3115
clients_data[key].items()}
3071
value = { (k.decode("utf-8")
3072
if isinstance(k, bytes) else k): v
3074
clients_data[key].items() }
3116
3075
clients_data[key] = value
3117
3076
# .client_structure
3118
3077
value["client_structure"] = [
3119
3078
(s.decode("utf-8")
3120
3079
if isinstance(s, bytes)
3121
3080
else s) for s in
3122
value["client_structure"]]
3081
value["client_structure"] ]
3123
3082
# .name & .host
3124
3083
for k in ("name", "host"):
3125
3084
if isinstance(value[k], bytes):
3126
3085
value[k] = value[k].decode("utf-8")
3127
# old_client_settings
3086
## old_client_settings
3129
3088
old_client_settings = {
3130
3089
(key.decode("utf-8")
3131
3090
if isinstance(key, bytes)
3132
3091
else key): value
3133
3092
for key, value in
3134
bytes_old_client_settings.items()}
3093
bytes_old_client_settings.items() }
3135
3094
del bytes_old_client_settings
3137
3096
for value in old_client_settings.values():
3243
3202
pidfilename, pid)
3245
3204
del pidfilename
3247
3206
for termsig in (signal.SIGHUP, signal.SIGTERM):
3248
3207
GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig,
3249
3208
lambda: main_loop.quit() and False)
3253
3212
@alternate_dbus_interfaces(
3254
{"se.recompile.Mandos": "se.bsnet.fukt.Mandos"})
3213
{ "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
3255
3214
class MandosDBusService(DBusObjectWithObjectManager):
3256
3215
"""A D-Bus proxy object"""
3258
3217
def __init__(self):
3259
3218
dbus.service.Object.__init__(self, bus, "/")
3261
3220
_interface = "se.recompile.Mandos"
3263
3222
@dbus.service.signal(_interface, signature="o")
3264
3223
def ClientAdded(self, objpath):
3268
3227
@dbus.service.signal(_interface, signature="ss")
3269
3228
def ClientNotFound(self, fingerprint, address):
3273
3232
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3275
3234
@dbus.service.signal(_interface, signature="os")
3276
3235
def ClientRemoved(self, objpath, name):
3280
3239
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3282
3241
@dbus.service.method(_interface, out_signature="ao")
3310
3269
self.client_removed_signal(c)
3312
3271
raise KeyError(object_path)
3316
3275
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
3317
out_signature="a{oa{sa{sv}}}")
3276
out_signature = "a{oa{sa{sv}}}")
3318
3277
def GetManagedObjects(self):
3319
3278
"""D-Bus method"""
3320
3279
return dbus.Dictionary(
3321
{client.dbus_object_path:
3323
{interface: client.GetAll(interface)
3325
client._get_all_interface_names()})
3326
for client in tcp_server.clients.values()})
3280
{ client.dbus_object_path:
3282
{ interface: client.GetAll(interface)
3284
client._get_all_interface_names()})
3285
for client in tcp_server.clients.values()})
3328
3287
def client_added_signal(self, client):
3329
3288
"""Send the new standard signal and the old signal"""
3376
3334
client.encrypted_secret = pgp.encrypt(client.secret,
3378
3336
client_dict = {}
3380
3338
# A list of attributes that can not be pickled
3382
exclude = {"bus", "changedstate", "secret",
3383
"checker", "server_settings"}
3340
exclude = { "bus", "changedstate", "secret",
3341
"checker", "server_settings" }
3384
3342
for name, typ in inspect.getmembers(dbus.service
3386
3344
exclude.add(name)
3388
3346
client_dict["encrypted_secret"] = (client
3389
3347
.encrypted_secret)
3390
3348
for attr in client.client_structure:
3391
3349
if attr not in exclude:
3392
3350
client_dict[attr] = getattr(client, attr)
3394
3352
clients[client.name] = client_dict
3395
3353
del client_settings[client.name]["secret"]
3398
3356
with tempfile.NamedTemporaryFile(