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-2016 Teddy Hogeborn
15
# Copyright © 2008-2016 Björn Påhlsson
17
# This program is free software: you can redistribute it and/or modify
18
# it under the terms of the GNU General Public License as published by
14
# Copyright © 2008-2018 Teddy Hogeborn
15
# Copyright © 2008-2018 Björn Påhlsson
17
# This file is part of Mandos.
19
# Mandos is free software: you can redistribute it and/or modify it
20
# under the terms of the GNU General Public License as published by
19
21
# the Free Software Foundation, either version 3 of the License, or
20
22
# (at your option) any later version.
22
# This program is distributed in the hope that it will be useful,
23
# but WITHOUT ANY WARRANTY; without even the implied warranty of
24
# Mandos is distributed in the hope that it will be useful, but
25
# WITHOUT ANY WARRANTY; without even the implied warranty of
24
26
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
27
# GNU General Public License for more details.
27
29
# You should have received a copy of the GNU General Public License
28
# along with this program. If not, see
29
# <http://www.gnu.org/licenses/>.
30
# along with Mandos. If not, see <http://www.gnu.org/licenses/>.
31
32
# Contact the authors at <mandos@recompile.se>.
34
35
from __future__ import (division, absolute_import, print_function,
86
87
import xml.dom.minidom
90
# Try to find the value of SO_BINDTODEVICE:
92
# This is where SO_BINDTODEVICE is in Python 3.3 (or 3.4?) and
93
# newer, and it is also the most natural place for it:
90
94
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
91
95
except AttributeError:
97
# This is where SO_BINDTODEVICE was up to and including Python
93
99
from IN import SO_BINDTODEVICE
94
100
except ImportError:
95
SO_BINDTODEVICE = None
101
# In Python 2.7 it seems to have been removed entirely.
102
# Try running the C preprocessor:
104
cc = subprocess.Popen(["cc", "--language=c", "-E",
106
stdin=subprocess.PIPE,
107
stdout=subprocess.PIPE)
108
stdout = cc.communicate(
109
"#include <sys/socket.h>\nSO_BINDTODEVICE\n")[0]
110
SO_BINDTODEVICE = int(stdout.splitlines()[-1])
111
except (OSError, ValueError, IndexError):
113
SO_BINDTODEVICE = None
97
115
if sys.version_info.major == 2:
101
119
stored_state_file = "clients.pickle"
103
121
logger = logging.getLogger()
229
247
'--passphrase-file',
231
249
+ self.gnupgargs,
232
stdin = subprocess.PIPE,
233
stdout = subprocess.PIPE,
234
stderr = subprocess.PIPE)
235
ciphertext, err = proc.communicate(input = data)
250
stdin=subprocess.PIPE,
251
stdout=subprocess.PIPE,
252
stderr=subprocess.PIPE)
253
ciphertext, err = proc.communicate(input=data)
236
254
if proc.returncode != 0:
237
255
raise PGPError(err)
238
256
return ciphertext
240
258
def decrypt(self, data, password):
241
259
passphrase = self.password_encode(password)
242
260
with tempfile.NamedTemporaryFile(
243
dir = self.tempdir) as passfile:
261
dir=self.tempdir) as passfile:
244
262
passfile.write(passphrase)
246
264
proc = subprocess.Popen([self.gpg, '--decrypt',
247
265
'--passphrase-file',
249
267
+ self.gnupgargs,
250
stdin = subprocess.PIPE,
251
stdout = subprocess.PIPE,
252
stderr = subprocess.PIPE)
253
decrypted_plaintext, err = proc.communicate(input = data)
268
stdin=subprocess.PIPE,
269
stdout=subprocess.PIPE,
270
stderr=subprocess.PIPE)
271
decrypted_plaintext, err = proc.communicate(input=data)
254
272
if proc.returncode != 0:
255
273
raise PGPError(err)
256
274
return decrypted_plaintext
258
277
# Pretend that we have an Avahi module
259
278
class Avahi(object):
260
279
"""This isn't so much a class as it is a module-like namespace.
261
280
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
281
IF_UNSPEC = -1 # avahi-common/address.h
282
PROTO_UNSPEC = -1 # avahi-common/address.h
283
PROTO_INET = 0 # avahi-common/address.h
284
PROTO_INET6 = 1 # avahi-common/address.h
266
285
DBUS_NAME = "org.freedesktop.Avahi"
267
286
DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"
268
287
DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
269
288
DBUS_PATH_SERVER = "/"
270
290
def string_array_to_txt_array(self, t):
271
291
return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
272
292
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
293
ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h
294
ENTRY_GROUP_COLLISION = 3 # avahi-common/defs.h
295
ENTRY_GROUP_FAILURE = 4 # avahi-common/defs.h
296
SERVER_INVALID = 0 # avahi-common/defs.h
297
SERVER_REGISTERING = 1 # avahi-common/defs.h
298
SERVER_RUNNING = 2 # avahi-common/defs.h
299
SERVER_COLLISION = 3 # avahi-common/defs.h
300
SERVER_FAILURE = 4 # avahi-common/defs.h
283
304
class AvahiError(Exception):
284
305
def __init__(self, value, *args, **kwargs):
285
306
self.value = value
475
496
class AvahiServiceToSyslog(AvahiService):
476
497
def rename(self, *args, **kwargs):
477
498
"""Add the new name to the syslog messages"""
478
ret = AvahiService.rename(self, *args, **kwargs)
499
ret = super(AvahiServiceToSyslog, self).rename(self, *args,
479
501
syslogger.setFormatter(logging.Formatter(
480
502
'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
481
503
.format(self.name)))
484
507
# Pretend that we have a GnuTLS module
485
508
class GnuTLS(object):
486
509
"""This isn't so much a class as it is a module-like namespace.
487
510
It is instantiated once, and simulates having a GnuTLS module."""
489
_library = ctypes.cdll.LoadLibrary(
490
ctypes.util.find_library("gnutls"))
512
library = ctypes.util.find_library("gnutls")
514
library = ctypes.util.find_library("gnutls-deb0")
515
_library = ctypes.cdll.LoadLibrary(library)
491
517
_need_version = b"3.3.0"
492
519
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))
520
# Need to use "self" here, since this method is called before
521
# the assignment to the "gnutls" global variable happens.
522
if self.check_version(self._need_version) is None:
523
raise self.Error("Needs GnuTLS {} or later"
524
.format(self._need_version))
500
526
# Unless otherwise indicated, the constants and types below are
501
527
# all from the gnutls/gnutls.h C header file.
505
531
E_INTERRUPTED = -52
510
536
CRD_CERTIFICATE = 1
511
537
E_NO_CERTIFICATE_FOUND = -49
512
538
OPENPGP_FMT_RAW = 0 # gnutls/openpgp.h
515
541
class session_int(ctypes.Structure):
517
543
session_t = ctypes.POINTER(session_int)
518
545
class certificate_credentials_st(ctypes.Structure):
520
547
certificate_credentials_t = ctypes.POINTER(
521
548
certificate_credentials_st)
522
549
certificate_type_t = ctypes.c_int
523
551
class datum_t(ctypes.Structure):
524
552
_fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
525
553
('size', ctypes.c_uint)]
526
555
class openpgp_crt_int(ctypes.Structure):
528
557
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
529
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
558
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
530
559
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
531
560
credentials_type_t = ctypes.c_int
532
561
transport_ptr_t = ctypes.c_void_p
533
562
close_request_t = ctypes.c_int
536
565
class Error(Exception):
537
566
# We need to use the class name "GnuTLS" here, since this
538
567
# exception might be raised from within GnuTLS.__init__,
539
568
# which is called before the assignment to the "gnutls"
540
569
# global variable has happened.
541
def __init__(self, message = None, code = None, args=()):
570
def __init__(self, message=None, code=None, args=()):
542
571
# Default usage is by a message string, but if a return
543
572
# code is passed, convert it to a string with
544
573
# gnutls.strerror()
613
642
return _error_code(result)
614
643
result = func(*arguments)
617
646
# Unless otherwise indicated, the function declarations below are
618
647
# all from the gnutls/gnutls.h C header file.
621
650
priority_set_direct = _library.gnutls_priority_set_direct
622
651
priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
623
652
ctypes.POINTER(ctypes.c_char_p)]
624
653
priority_set_direct.restype = _error_code
626
655
init = _library.gnutls_init
627
656
init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
628
657
init.restype = _error_code
630
659
set_default_priority = _library.gnutls_set_default_priority
631
660
set_default_priority.argtypes = [session_t]
632
661
set_default_priority.restype = _error_code
634
663
record_send = _library.gnutls_record_send
635
664
record_send.argtypes = [session_t, ctypes.c_void_p,
637
666
record_send.restype = ctypes.c_ssize_t
638
667
record_send.errcheck = _retry_on_error
640
669
certificate_allocate_credentials = (
641
670
_library.gnutls_certificate_allocate_credentials)
642
671
certificate_allocate_credentials.argtypes = [
643
672
ctypes.POINTER(certificate_credentials_t)]
644
673
certificate_allocate_credentials.restype = _error_code
646
675
certificate_free_credentials = (
647
676
_library.gnutls_certificate_free_credentials)
648
certificate_free_credentials.argtypes = [certificate_credentials_t]
677
certificate_free_credentials.argtypes = [
678
certificate_credentials_t]
649
679
certificate_free_credentials.restype = None
651
681
handshake_set_private_extensions = (
652
682
_library.gnutls_handshake_set_private_extensions)
653
683
handshake_set_private_extensions.argtypes = [session_t,
655
685
handshake_set_private_extensions.restype = None
657
687
credentials_set = _library.gnutls_credentials_set
658
688
credentials_set.argtypes = [session_t, credentials_type_t,
660
690
credentials_set.restype = _error_code
662
692
strerror = _library.gnutls_strerror
663
693
strerror.argtypes = [ctypes.c_int]
664
694
strerror.restype = ctypes.c_char_p
666
696
certificate_type_get = _library.gnutls_certificate_type_get
667
697
certificate_type_get.argtypes = [session_t]
668
698
certificate_type_get.restype = _error_code
670
700
certificate_get_peers = _library.gnutls_certificate_get_peers
671
701
certificate_get_peers.argtypes = [session_t,
672
702
ctypes.POINTER(ctypes.c_uint)]
673
703
certificate_get_peers.restype = ctypes.POINTER(datum_t)
675
705
global_set_log_level = _library.gnutls_global_set_log_level
676
706
global_set_log_level.argtypes = [ctypes.c_int]
677
707
global_set_log_level.restype = None
679
709
global_set_log_function = _library.gnutls_global_set_log_function
680
710
global_set_log_function.argtypes = [log_func]
681
711
global_set_log_function.restype = None
683
713
deinit = _library.gnutls_deinit
684
714
deinit.argtypes = [session_t]
685
715
deinit.restype = None
687
717
handshake = _library.gnutls_handshake
688
718
handshake.argtypes = [session_t]
689
719
handshake.restype = _error_code
690
720
handshake.errcheck = _retry_on_error
692
722
transport_set_ptr = _library.gnutls_transport_set_ptr
693
723
transport_set_ptr.argtypes = [session_t, transport_ptr_t]
694
724
transport_set_ptr.restype = None
696
726
bye = _library.gnutls_bye
697
727
bye.argtypes = [session_t, close_request_t]
698
728
bye.restype = _error_code
699
729
bye.errcheck = _retry_on_error
701
731
check_version = _library.gnutls_check_version
702
732
check_version.argtypes = [ctypes.c_char_p]
703
733
check_version.restype = ctypes.c_char_p
705
735
# All the function declarations below are from gnutls/openpgp.h
707
737
openpgp_crt_init = _library.gnutls_openpgp_crt_init
708
738
openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
709
739
openpgp_crt_init.restype = _error_code
711
741
openpgp_crt_import = _library.gnutls_openpgp_crt_import
712
742
openpgp_crt_import.argtypes = [openpgp_crt_t,
713
743
ctypes.POINTER(datum_t),
714
744
openpgp_crt_fmt_t]
715
745
openpgp_crt_import.restype = _error_code
717
747
openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
718
748
openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
719
749
ctypes.POINTER(ctypes.c_uint)]
720
750
openpgp_crt_verify_self.restype = _error_code
722
752
openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
723
753
openpgp_crt_deinit.argtypes = [openpgp_crt_t]
724
754
openpgp_crt_deinit.restype = None
726
756
openpgp_crt_get_fingerprint = (
727
757
_library.gnutls_openpgp_crt_get_fingerprint)
728
758
openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
1048
1080
# The exception is when not debugging but nevertheless
1049
1081
# running in the foreground; use the previously
1050
1082
# created wnull.
1051
popen_args = { "close_fds": True,
1083
popen_args = {"close_fds": True,
1054
1086
if (not self.server_settings["debug"]
1055
1087
and self.server_settings["foreground"]):
1056
1088
popen_args.update({"stdout": wnull,
1058
pipe = multiprocessing.Pipe(duplex = False)
1090
pipe = multiprocessing.Pipe(duplex=False)
1059
1091
self.checker = multiprocessing.Process(
1061
args = (pipe[1], subprocess.call, command),
1062
kwargs = popen_args)
1093
args=(pipe[1], subprocess.call, command),
1063
1095
self.checker.start()
1064
1096
self.checker_callback_tag = GLib.io_add_watch(
1065
1097
pipe[0].fileno(), GLib.IO_IN,
1066
1098
self.checker_callback, pipe[0], command)
1067
1099
# Re-run this periodically if run by GLib.timeout_add
1070
1102
def stop_checker(self):
1071
1103
"""Force the checker process, if any, to stop."""
1072
1104
if self.checker_callback_tag:
1107
1139
func._dbus_name = func.__name__
1108
1140
if func._dbus_name.endswith("_dbus_property"):
1109
1141
func._dbus_name = func._dbus_name[:-14]
1110
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
1142
func._dbus_get_args_options = {'byte_arrays': byte_arrays}
1113
1145
return decorator
1116
1148
def dbus_interface_annotations(dbus_interface):
1117
1149
"""Decorator for marking functions returning interface annotations
1121
1153
@dbus_interface_annotations("org.example.Interface")
1122
1154
def _foo(self): # Function name does not matter
1123
1155
return {"org.freedesktop.DBus.Deprecated": "true",
1124
1156
"org.freedesktop.DBus.Property.EmitsChangedSignal":
1128
1160
def decorator(func):
1129
1161
func._dbus_is_interface = True
1130
1162
func._dbus_interface = dbus_interface
1131
1163
func._dbus_name = dbus_interface
1134
1166
return decorator
1137
1169
def dbus_annotations(annotations):
1138
1170
"""Decorator to annotate D-Bus methods, signals or properties
1141
1173
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
1142
1174
"org.freedesktop.DBus.Property."
1143
1175
"EmitsChangedSignal": "false"})
1199
1231
for cls in self.__class__.__mro__
1200
1232
for name, athing in
1201
1233
inspect.getmembers(cls, self._is_dbus_thing(thing)))
1203
1235
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1204
out_signature = "s",
1205
path_keyword = 'object_path',
1206
connection_keyword = 'connection')
1237
path_keyword='object_path',
1238
connection_keyword='connection')
1207
1239
def Introspect(self, object_path, connection):
1208
1240
"""Overloading of standard D-Bus method.
1210
1242
Inserts annotation tags on methods and signals.
1212
1244
xmlstring = dbus.service.Object.Introspect(self, object_path,
1215
1247
document = xml.dom.minidom.parseString(xmlstring)
1217
1249
for if_tag in document.getElementsByTagName("interface"):
1218
1250
# Add annotation tags
1219
1251
for typ in ("method", "signal"):
1430
1462
exc_info=error)
1431
1463
return xmlstring
1434
1467
dbus.OBJECT_MANAGER_IFACE
1435
1468
except AttributeError:
1436
1469
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1438
1472
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1439
1473
"""A D-Bus object with an ObjectManager.
1441
1475
Classes inheriting from this exposes the standard
1442
1476
GetManagedObjects call and the InterfacesAdded and
1443
1477
InterfacesRemoved signals on the standard
1444
1478
"org.freedesktop.DBus.ObjectManager" interface.
1446
1480
Note: No signals are sent automatically; they must be sent
1449
1483
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1450
out_signature = "a{oa{sa{sv}}}")
1484
out_signature="a{oa{sa{sv}}}")
1451
1485
def GetManagedObjects(self):
1452
1486
"""This function must be overridden"""
1453
1487
raise NotImplementedError()
1455
1489
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1456
signature = "oa{sa{sv}}")
1490
signature="oa{sa{sv}}")
1457
1491
def InterfacesAdded(self, object_path, interfaces_and_properties):
1460
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
1494
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature="oas")
1461
1495
def InterfacesRemoved(self, object_path, interfaces):
1464
1498
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1465
out_signature = "s",
1466
path_keyword = 'object_path',
1467
connection_keyword = 'connection')
1500
path_keyword='object_path',
1501
connection_keyword='connection')
1468
1502
def Introspect(self, object_path, connection):
1469
1503
"""Overloading of standard D-Bus method.
1471
1505
Override return argument name of GetManagedObjects to be
1472
1506
"objpath_interfaces_and_properties"
1511
1546
dbus.service.Object, it will add alternate D-Bus attributes with
1512
1547
interface names according to the "alt_interface_names" mapping.
1515
1550
@alternate_dbus_interfaces({"org.example.Interface":
1516
1551
"net.example.AlternateInterface"})
1517
1552
class SampleDBusObject(dbus.service.Object):
1518
1553
@dbus.service.method("org.example.Interface")
1519
1554
def SampleDBusMethod():
1522
1557
The above "SampleDBusMethod" on "SampleDBusObject" will be
1523
1558
reachable via two interfaces: "org.example.Interface" and
1524
1559
"net.example.AlternateInterface", the latter of which will have
1525
1560
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1526
1561
"true", unless "deprecate" is passed with a False value.
1528
1563
This works for methods and signals, and also for D-Bus properties
1529
1564
(from DBusObjectWithProperties) and interfaces (from the
1530
1565
dbus_interface_annotations decorator).
1533
1568
def wrapper(cls):
1534
1569
for orig_interface_name, alt_interface_name in (
1535
1570
alt_interface_names.items()):
1677
1714
"se.bsnet.fukt.Mandos"})
1678
1715
class ClientDBus(Client, DBusObjectWithProperties):
1679
1716
"""A Client class using D-Bus
1682
1719
dbus_object_path: dbus.ObjectPath
1683
1720
bus: dbus.SystemBus()
1686
1723
runtime_expansions = (Client.runtime_expansions
1687
1724
+ ("dbus_object_path", ))
1689
1726
_interface = "se.recompile.Mandos.Client"
1691
1728
# dbus.service.Object doesn't use super(), so we can't either.
1693
def __init__(self, bus = None, *args, **kwargs):
1730
def __init__(self, bus=None, *args, **kwargs):
1695
1732
Client.__init__(self, *args, **kwargs)
1696
1733
# Only now, when this client is initialized, can it show up on
1733
1770
dbus_value = transform_func(
1734
1771
type_func(value),
1735
variant_level = variant_level)
1772
variant_level=variant_level)
1736
1773
self.PropertyChanged(dbus.String(dbus_name),
1738
1775
self.PropertiesChanged(
1740
dbus.Dictionary({ dbus.String(dbus_name):
1777
dbus.Dictionary({dbus.String(dbus_name):
1743
1780
setattr(self, attrname, value)
1745
1782
return property(lambda self: getattr(self, attrname), setter)
1747
1784
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1748
1785
approvals_pending = notifychangeproperty(dbus.Boolean,
1749
1786
"ApprovalPending",
1751
1788
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1752
1789
last_enabled = notifychangeproperty(datetime_to_dbus,
1754
1791
checker = notifychangeproperty(
1755
1792
dbus.Boolean, "CheckerRunning",
1756
type_func = lambda checker: checker is not None)
1793
type_func=lambda checker: checker is not None)
1757
1794
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1758
1795
"LastCheckedOK")
1759
1796
last_checker_status = notifychangeproperty(dbus.Int16,
1764
1801
"ApprovedByDefault")
1765
1802
approval_delay = notifychangeproperty(
1766
1803
dbus.UInt64, "ApprovalDelay",
1767
type_func = lambda td: td.total_seconds() * 1000)
1804
type_func=lambda td: td.total_seconds() * 1000)
1768
1805
approval_duration = notifychangeproperty(
1769
1806
dbus.UInt64, "ApprovalDuration",
1770
type_func = lambda td: td.total_seconds() * 1000)
1807
type_func=lambda td: td.total_seconds() * 1000)
1771
1808
host = notifychangeproperty(dbus.String, "Host")
1772
1809
timeout = notifychangeproperty(
1773
1810
dbus.UInt64, "Timeout",
1774
type_func = lambda td: td.total_seconds() * 1000)
1811
type_func=lambda td: td.total_seconds() * 1000)
1775
1812
extended_timeout = notifychangeproperty(
1776
1813
dbus.UInt64, "ExtendedTimeout",
1777
type_func = lambda td: td.total_seconds() * 1000)
1814
type_func=lambda td: td.total_seconds() * 1000)
1778
1815
interval = notifychangeproperty(
1779
1816
dbus.UInt64, "Interval",
1780
type_func = lambda td: td.total_seconds() * 1000)
1817
type_func=lambda td: td.total_seconds() * 1000)
1781
1818
checker_command = notifychangeproperty(dbus.String, "Checker")
1782
1819
secret = notifychangeproperty(dbus.ByteArray, "Secret",
1783
1820
invalidate_only=True)
1785
1822
del notifychangeproperty
1787
1824
def __del__(self, *args, **kwargs):
1789
1826
self.remove_from_connection()
1824
1861
# Emit D-Bus signal
1825
1862
self.CheckerStarted(self.current_checker_command)
1828
1865
def _reset_approved(self):
1829
1866
self.approved = None
1832
1869
def approve(self, value=True):
1833
1870
self.approved = value
1834
1871
GLib.timeout_add(int(self.approval_duration.total_seconds()
1835
1872
* 1000), self._reset_approved)
1836
1873
self.send_changedstate()
1838
## D-Bus methods, signals & properties
1875
# D-Bus methods, signals & properties
1844
1881
# CheckerCompleted - signal
1845
1882
@dbus.service.signal(_interface, signature="nxs")
1846
1883
def CheckerCompleted(self, exitcode, waitstatus, command):
1850
1887
# CheckerStarted - signal
1851
1888
@dbus.service.signal(_interface, signature="s")
1852
1889
def CheckerStarted(self, command):
1856
1893
# PropertyChanged - signal
1857
1894
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1858
1895
@dbus.service.signal(_interface, signature="sv")
1859
1896
def PropertyChanged(self, property, value):
1863
1900
# GotSecret - signal
1864
1901
@dbus.service.signal(_interface)
1865
1902
def GotSecret(self):
1868
1905
server to mandos-client
1872
1909
# Rejected - signal
1873
1910
@dbus.service.signal(_interface, signature="s")
1874
1911
def Rejected(self, reason):
1878
1915
# NeedApproval - signal
1879
1916
@dbus.service.signal(_interface, signature="tb")
1880
1917
def NeedApproval(self, timeout, default):
1882
1919
return self.need_approval()
1886
1923
# Approve - method
1887
1924
@dbus.service.method(_interface, in_signature="b")
1888
1925
def Approve(self, value):
1889
1926
self.approve(value)
1891
1928
# CheckedOK - method
1892
1929
@dbus.service.method(_interface)
1893
1930
def CheckedOK(self):
1894
1931
self.checked_ok()
1896
1933
# Enable - method
1897
1934
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1898
1935
@dbus.service.method(_interface)
1899
1936
def Enable(self):
1903
1940
# StartChecker - method
1904
1941
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1905
1942
@dbus.service.method(_interface)
1906
1943
def StartChecker(self):
1908
1945
self.start_checker()
1910
1947
# Disable - method
1911
1948
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1912
1949
@dbus.service.method(_interface)
1913
1950
def Disable(self):
1917
1954
# StopChecker - method
1918
1955
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1919
1956
@dbus.service.method(_interface)
1920
1957
def StopChecker(self):
1921
1958
self.stop_checker()
1925
1962
# ApprovalPending - property
1926
1963
@dbus_service_property(_interface, signature="b", access="read")
1927
1964
def ApprovalPending_dbus_property(self):
1928
1965
return dbus.Boolean(bool(self.approvals_pending))
1930
1967
# ApprovedByDefault - property
1931
1968
@dbus_service_property(_interface,
2012
2049
self.checked_ok()
2014
2051
return datetime_to_dbus(self.last_checked_ok)
2016
2053
# LastCheckerStatus - property
2017
2054
@dbus_service_property(_interface, signature="n", access="read")
2018
2055
def LastCheckerStatus_dbus_property(self):
2019
2056
return dbus.Int16(self.last_checker_status)
2021
2058
# Expires - property
2022
2059
@dbus_service_property(_interface, signature="s", access="read")
2023
2060
def Expires_dbus_property(self):
2024
2061
return datetime_to_dbus(self.expires)
2026
2063
# LastApprovalRequest - property
2027
2064
@dbus_service_property(_interface, signature="s", access="read")
2028
2065
def LastApprovalRequest_dbus_property(self):
2029
2066
return datetime_to_dbus(self.last_approval_request)
2031
2068
# Timeout - property
2032
2069
@dbus_service_property(_interface,
2154
2191
class ClientHandler(socketserver.BaseRequestHandler, object):
2155
2192
"""A class to handle client connections.
2157
2194
Instantiated once for each connection to handle it.
2158
2195
Note: This will run in its own forked process."""
2160
2197
def handle(self):
2161
2198
with contextlib.closing(self.server.child_pipe) as child_pipe:
2162
2199
logger.info("TCP connection from: %s",
2163
2200
str(self.client_address))
2164
2201
logger.debug("Pipe FD: %d",
2165
2202
self.server.child_pipe.fileno())
2167
2204
session = gnutls.ClientSession(self.request)
2169
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
2170
# "+AES-256-CBC", "+SHA1",
2171
# "+COMP-NULL", "+CTYPE-OPENPGP",
2206
# priority = ':'.join(("NONE", "+VERS-TLS1.1",
2207
# "+AES-256-CBC", "+SHA1",
2208
# "+COMP-NULL", "+CTYPE-OPENPGP",
2173
2210
# Use a fallback default, since this MUST be set.
2174
2211
priority = self.server.gnutls_priority
2175
2212
if priority is None:
2348
2385
class MultiprocessingMixIn(object):
2349
2386
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
2351
2388
def sub_process_main(self, request, address):
2353
2390
self.finish_request(request, address)
2354
2391
except Exception:
2355
2392
self.handle_error(request, address)
2356
2393
self.close_request(request)
2358
2395
def process_request(self, request, address):
2359
2396
"""Start a new process to process the request."""
2360
proc = multiprocessing.Process(target = self.sub_process_main,
2361
args = (request, address))
2397
proc = multiprocessing.Process(target=self.sub_process_main,
2398
args=(request, address))
2366
2403
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
2367
2404
""" adds a pipe to the MixIn """
2369
2406
def process_request(self, request, client_address):
2370
2407
"""Overrides and wraps the original process_request().
2372
2409
This function creates a new pipe in self.pipe
2374
2411
parent_pipe, self.child_pipe = multiprocessing.Pipe()
2376
2413
proc = MultiprocessingMixIn.process_request(self, request,
2377
2414
client_address)
2378
2415
self.child_pipe.close()
2379
2416
self.add_pipe(parent_pipe, proc)
2381
2418
def add_pipe(self, parent_pipe, proc):
2382
2419
"""Dummy function; override as necessary"""
2383
2420
raise NotImplementedError()
2431
2469
# socket_wrapper(), if socketfd was set.
2432
2470
socketserver.TCPServer.__init__(self, server_address,
2433
2471
RequestHandlerClass)
2435
2473
def server_bind(self):
2436
2474
"""This overrides the normal server_bind() function
2437
2475
to bind to an interface if one was specified, and also NOT to
2438
2476
bind to an address or port if they were not specified."""
2477
global SO_BINDTODEVICE
2439
2478
if self.interface is not None:
2440
2479
if SO_BINDTODEVICE is None:
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)
2480
# Fall back to a hard-coded value which seems to be
2482
logger.warning("SO_BINDTODEVICE not found, trying 25")
2483
SO_BINDTODEVICE = 25
2485
self.socket.setsockopt(
2486
socket.SOL_SOCKET, SO_BINDTODEVICE,
2487
(self.interface + "\0").encode("utf-8"))
2488
except socket.error as error:
2489
if error.errno == errno.EPERM:
2490
logger.error("No permission to bind to"
2491
" interface %s", self.interface)
2492
elif error.errno == errno.ENOPROTOOPT:
2493
logger.error("SO_BINDTODEVICE not available;"
2494
" cannot bind to interface %s",
2496
elif error.errno == errno.ENODEV:
2497
logger.error("Interface %s does not exist,"
2498
" cannot bind", self.interface)
2462
2501
# Only bind(2) the socket if we really need to.
2463
2502
if self.server_address[0] or self.server_address[1]:
2464
2503
if not self.server_address[0]:
2465
2504
if self.address_family == socket.AF_INET6:
2466
any_address = "::" # in6addr_any
2505
any_address = "::" # in6addr_any
2468
any_address = "0.0.0.0" # INADDR_ANY
2507
any_address = "0.0.0.0" # INADDR_ANY
2469
2508
self.server_address = (any_address,
2470
2509
self.server_address[1])
2471
2510
elif not self.server_address[1]:
2505
2544
self.gnutls_priority = gnutls_priority
2506
2545
IPv6_TCPServer.__init__(self, server_address,
2507
2546
RequestHandlerClass,
2508
interface = interface,
2509
use_ipv6 = use_ipv6,
2510
socketfd = socketfd)
2547
interface=interface,
2512
2551
def server_activate(self):
2513
2552
if self.enabled:
2514
2553
return socketserver.TCPServer.server_activate(self)
2516
2555
def enable(self):
2517
2556
self.enabled = True
2519
2558
def add_pipe(self, parent_pipe, proc):
2520
2559
# Call "handle_ipc" for both data and EOF events
2521
2560
GLib.io_add_watch(
2522
2561
parent_pipe.fileno(),
2523
2562
GLib.IO_IN | GLib.IO_HUP,
2524
2563
functools.partial(self.handle_ipc,
2525
parent_pipe = parent_pipe,
2564
parent_pipe=parent_pipe,
2528
2567
def handle_ipc(self, source, condition,
2529
2568
parent_pipe=None,
2531
2570
client_object=None):
2532
2571
# error, or the other end of multiprocessing.Pipe has closed
2533
2572
if condition & (GLib.IO_ERR | GLib.IO_HUP):
2534
2573
# Wait for other process to exit
2538
2577
# Read a request from the child
2539
2578
request = parent_pipe.recv()
2540
2579
command = request[0]
2542
2581
if command == 'init':
2582
fpr = request[1].decode("ascii")
2544
2583
address = request[2]
2546
2585
for c in self.clients.values():
2547
2586
if c.fingerprint == fpr:
2612
2651
>>> rfc3339_duration_to_delta("P1DT3M20S")
2613
2652
datetime.timedelta(1, 200)
2616
2655
# Parsing an RFC 3339 duration with regular expressions is not
2617
2656
# possible - there would have to be multiple places for the same
2618
2657
# values, like seconds. The current code, while more esoteric, is
2619
2658
# cleaner without depending on a parsing library. If Python had a
2620
2659
# built-in library for parsing we would use it, but we'd like to
2621
2660
# avoid excessive use of external libraries.
2623
2662
# New type for defining tokens, syntax, and semantics all-in-one
2624
2663
Token = collections.namedtuple("Token", (
2625
2664
"regexp", # To match token; if "value" is not None, must have
2809
2851
parser.add_argument("--no-zeroconf", action="store_false",
2810
2852
dest="zeroconf", help="Do not use Zeroconf",
2813
2855
options = parser.parse_args()
2815
2857
if options.check:
2817
2859
fail_count, test_count = doctest.testmod()
2818
2860
sys.exit(os.EX_OK if fail_count == 0 else 1)
2820
2862
# Default values for config file for server-global settings
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",
2863
server_defaults = {"interface": "",
2868
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2869
":+SIGN-DSA-SHA256",
2870
"servicename": "Mandos",
2876
"statedir": "/var/lib/mandos",
2877
"foreground": "False",
2839
2881
# Parse config file for server-global settings
2840
2882
server_config = configparser.SafeConfigParser(server_defaults)
2841
2883
del server_defaults
2970
3013
.format(uid, gid, os.strerror(error.errno)))
2971
3014
if error.errno != errno.EPERM:
2975
3018
# Enable all possible GnuTLS debugging
2977
3020
# "Use a log level over 10 to enable all debugging options."
2978
3021
# - GnuTLS manual
2979
3022
gnutls.global_set_log_level(11)
2981
3024
@gnutls.log_func
2982
3025
def debug_gnutls(level, string):
2983
3026
logger.debug("GnuTLS: %s", string[:-1])
2985
3028
gnutls.global_set_log_function(debug_gnutls)
2987
3030
# Redirect stdin so all checkers get /dev/null
2988
3031
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2989
3032
os.dup2(null, sys.stdin.fileno())
2993
3036
# Need to fork before connecting to D-Bus
2994
3037
if not foreground:
2995
3038
# Close all input and output, do double fork, etc.
2998
3041
# multiprocessing will use threads, so before we use GLib we need
2999
3042
# to inform GLib that threads will be used.
3000
3043
GLib.threads_init()
3002
3045
global main_loop
3003
3046
# From the Avahi example code
3004
3047
DBusGMainLoop(set_as_default=True)
3022
3065
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
3023
3066
service = AvahiServiceToSyslog(
3024
name = server_settings["servicename"],
3025
servicetype = "_mandos._tcp",
3026
protocol = protocol,
3067
name=server_settings["servicename"],
3068
servicetype="_mandos._tcp",
3028
3071
if server_settings["interface"]:
3029
3072
service.interface = if_nametoindex(
3030
3073
server_settings["interface"].encode("utf-8"))
3032
3075
global multiprocessing_manager
3033
3076
multiprocessing_manager = multiprocessing.Manager()
3035
3078
client_class = Client
3037
client_class = functools.partial(ClientDBus, bus = bus)
3080
client_class = functools.partial(ClientDBus, bus=bus)
3039
3082
client_settings = Client.config_parser(client_config)
3040
3083
old_client_settings = {}
3041
3084
clients_data = {}
3043
3086
# This is used to redirect stdout and stderr for checker processes
3045
wnull = open(os.devnull, "w") # A writable /dev/null
3088
wnull = open(os.devnull, "w") # A writable /dev/null
3046
3089
# Only used if server is running in foreground but not in debug
3048
3091
if debug or not foreground:
3051
3094
# Get client data and settings from last running state.
3052
3095
if server_settings["restore"]:
3054
3097
with open(stored_state_path, "rb") as stored_state:
3055
if sys.version_info.major == 2:
3098
if sys.version_info.major == 2:
3056
3099
clients_data, old_client_settings = pickle.load(
3059
3102
bytes_clients_data, bytes_old_client_settings = (
3060
pickle.load(stored_state, encoding = "bytes"))
3061
### Fix bytes to strings
3103
pickle.load(stored_state, encoding="bytes"))
3104
# Fix bytes to strings
3064
clients_data = { (key.decode("utf-8")
3065
if isinstance(key, bytes)
3068
bytes_clients_data.items() }
3107
clients_data = {(key.decode("utf-8")
3108
if isinstance(key, bytes)
3111
bytes_clients_data.items()}
3069
3112
del bytes_clients_data
3070
3113
for key in clients_data:
3071
value = { (k.decode("utf-8")
3072
if isinstance(k, bytes) else k): v
3074
clients_data[key].items() }
3114
value = {(k.decode("utf-8")
3115
if isinstance(k, bytes) else k): v
3117
clients_data[key].items()}
3075
3118
clients_data[key] = value
3076
3119
# .client_structure
3077
3120
value["client_structure"] = [
3078
3121
(s.decode("utf-8")
3079
3122
if isinstance(s, bytes)
3080
3123
else s) for s in
3081
value["client_structure"] ]
3124
value["client_structure"]]
3082
3125
# .name & .host
3083
3126
for k in ("name", "host"):
3084
3127
if isinstance(value[k], bytes):
3085
3128
value[k] = value[k].decode("utf-8")
3086
## old_client_settings
3129
# old_client_settings
3088
3131
old_client_settings = {
3089
3132
(key.decode("utf-8")
3090
3133
if isinstance(key, bytes)
3091
3134
else key): value
3092
3135
for key, value in
3093
bytes_old_client_settings.items() }
3136
bytes_old_client_settings.items()}
3094
3137
del bytes_old_client_settings
3096
3139
for value in old_client_settings.values():
3202
3245
pidfilename, pid)
3204
3247
del pidfilename
3206
3249
for termsig in (signal.SIGHUP, signal.SIGTERM):
3207
3250
GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig,
3208
3251
lambda: main_loop.quit() and False)
3212
3255
@alternate_dbus_interfaces(
3213
{ "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
3256
{"se.recompile.Mandos": "se.bsnet.fukt.Mandos"})
3214
3257
class MandosDBusService(DBusObjectWithObjectManager):
3215
3258
"""A D-Bus proxy object"""
3217
3260
def __init__(self):
3218
3261
dbus.service.Object.__init__(self, bus, "/")
3220
3263
_interface = "se.recompile.Mandos"
3222
3265
@dbus.service.signal(_interface, signature="o")
3223
3266
def ClientAdded(self, objpath):
3227
3270
@dbus.service.signal(_interface, signature="ss")
3228
3271
def ClientNotFound(self, fingerprint, address):
3232
3275
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3234
3277
@dbus.service.signal(_interface, signature="os")
3235
3278
def ClientRemoved(self, objpath, name):
3239
3282
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3241
3284
@dbus.service.method(_interface, out_signature="ao")
3269
3312
self.client_removed_signal(c)
3271
3314
raise KeyError(object_path)
3275
3318
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
3276
out_signature = "a{oa{sa{sv}}}")
3319
out_signature="a{oa{sa{sv}}}")
3277
3320
def GetManagedObjects(self):
3278
3321
"""D-Bus method"""
3279
3322
return dbus.Dictionary(
3280
{ client.dbus_object_path:
3282
{ interface: client.GetAll(interface)
3284
client._get_all_interface_names()})
3285
for client in tcp_server.clients.values()})
3323
{client.dbus_object_path:
3325
{interface: client.GetAll(interface)
3327
client._get_all_interface_names()})
3328
for client in tcp_server.clients.values()})
3287
3330
def client_added_signal(self, client):
3288
3331
"""Send the new standard signal and the old signal"""
3334
3378
client.encrypted_secret = pgp.encrypt(client.secret,
3336
3380
client_dict = {}
3338
3382
# A list of attributes that can not be pickled
3340
exclude = { "bus", "changedstate", "secret",
3341
"checker", "server_settings" }
3384
exclude = {"bus", "changedstate", "secret",
3385
"checker", "server_settings"}
3342
3386
for name, typ in inspect.getmembers(dbus.service
3344
3388
exclude.add(name)
3346
3390
client_dict["encrypted_secret"] = (client
3347
3391
.encrypted_secret)
3348
3392
for attr in client.client_structure:
3349
3393
if attr not in exclude:
3350
3394
client_dict[attr] = getattr(client, attr)
3352
3396
clients[client.name] = client_dict
3353
3397
del client_settings[client.name]["secret"]
3356
3400
with tempfile.NamedTemporaryFile(