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