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(*args, **kwargs)
479
500
syslogger.setFormatter(logging.Formatter(
480
501
'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
481
502
.format(self.name)))
484
506
# Pretend that we have a GnuTLS module
485
507
class GnuTLS(object):
486
508
"""This isn't so much a class as it is a module-like namespace.
487
509
It is instantiated once, and simulates having a GnuTLS module."""
489
_library = ctypes.cdll.LoadLibrary(
490
ctypes.util.find_library("gnutls"))
511
library = ctypes.util.find_library("gnutls")
513
library = ctypes.util.find_library("gnutls-deb0")
514
_library = ctypes.cdll.LoadLibrary(library)
491
516
_need_version = b"3.3.0"
492
518
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))
519
# Need to use "self" here, since this method is called before
520
# the assignment to the "gnutls" global variable happens.
521
if self.check_version(self._need_version) is None:
522
raise self.Error("Needs GnuTLS {} or later"
523
.format(self._need_version))
500
525
# Unless otherwise indicated, the constants and types below are
501
526
# all from the gnutls/gnutls.h C header file.
505
530
E_INTERRUPTED = -52
510
535
CRD_CERTIFICATE = 1
511
536
E_NO_CERTIFICATE_FOUND = -49
512
537
OPENPGP_FMT_RAW = 0 # gnutls/openpgp.h
515
540
class session_int(ctypes.Structure):
517
542
session_t = ctypes.POINTER(session_int)
518
544
class certificate_credentials_st(ctypes.Structure):
520
546
certificate_credentials_t = ctypes.POINTER(
521
547
certificate_credentials_st)
522
548
certificate_type_t = ctypes.c_int
523
550
class datum_t(ctypes.Structure):
524
551
_fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
525
552
('size', ctypes.c_uint)]
526
554
class openpgp_crt_int(ctypes.Structure):
528
556
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
529
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
557
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
530
558
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
531
559
credentials_type_t = ctypes.c_int
532
560
transport_ptr_t = ctypes.c_void_p
533
561
close_request_t = ctypes.c_int
536
564
class Error(Exception):
537
565
# We need to use the class name "GnuTLS" here, since this
538
566
# exception might be raised from within GnuTLS.__init__,
539
567
# which is called before the assignment to the "gnutls"
540
568
# global variable has happened.
541
def __init__(self, message = None, code = None, args=()):
569
def __init__(self, message=None, code=None, args=()):
542
570
# Default usage is by a message string, but if a return
543
571
# code is passed, convert it to a string with
544
572
# gnutls.strerror()
613
641
return _error_code(result)
614
642
result = func(*arguments)
617
645
# Unless otherwise indicated, the function declarations below are
618
646
# all from the gnutls/gnutls.h C header file.
621
649
priority_set_direct = _library.gnutls_priority_set_direct
622
650
priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
623
651
ctypes.POINTER(ctypes.c_char_p)]
624
652
priority_set_direct.restype = _error_code
626
654
init = _library.gnutls_init
627
655
init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
628
656
init.restype = _error_code
630
658
set_default_priority = _library.gnutls_set_default_priority
631
659
set_default_priority.argtypes = [session_t]
632
660
set_default_priority.restype = _error_code
634
662
record_send = _library.gnutls_record_send
635
663
record_send.argtypes = [session_t, ctypes.c_void_p,
637
665
record_send.restype = ctypes.c_ssize_t
638
666
record_send.errcheck = _retry_on_error
640
668
certificate_allocate_credentials = (
641
669
_library.gnutls_certificate_allocate_credentials)
642
670
certificate_allocate_credentials.argtypes = [
643
671
ctypes.POINTER(certificate_credentials_t)]
644
672
certificate_allocate_credentials.restype = _error_code
646
674
certificate_free_credentials = (
647
675
_library.gnutls_certificate_free_credentials)
648
certificate_free_credentials.argtypes = [certificate_credentials_t]
676
certificate_free_credentials.argtypes = [
677
certificate_credentials_t]
649
678
certificate_free_credentials.restype = None
651
680
handshake_set_private_extensions = (
652
681
_library.gnutls_handshake_set_private_extensions)
653
682
handshake_set_private_extensions.argtypes = [session_t,
655
684
handshake_set_private_extensions.restype = None
657
686
credentials_set = _library.gnutls_credentials_set
658
687
credentials_set.argtypes = [session_t, credentials_type_t,
660
689
credentials_set.restype = _error_code
662
691
strerror = _library.gnutls_strerror
663
692
strerror.argtypes = [ctypes.c_int]
664
693
strerror.restype = ctypes.c_char_p
666
695
certificate_type_get = _library.gnutls_certificate_type_get
667
696
certificate_type_get.argtypes = [session_t]
668
697
certificate_type_get.restype = _error_code
670
699
certificate_get_peers = _library.gnutls_certificate_get_peers
671
700
certificate_get_peers.argtypes = [session_t,
672
701
ctypes.POINTER(ctypes.c_uint)]
673
702
certificate_get_peers.restype = ctypes.POINTER(datum_t)
675
704
global_set_log_level = _library.gnutls_global_set_log_level
676
705
global_set_log_level.argtypes = [ctypes.c_int]
677
706
global_set_log_level.restype = None
679
708
global_set_log_function = _library.gnutls_global_set_log_function
680
709
global_set_log_function.argtypes = [log_func]
681
710
global_set_log_function.restype = None
683
712
deinit = _library.gnutls_deinit
684
713
deinit.argtypes = [session_t]
685
714
deinit.restype = None
687
716
handshake = _library.gnutls_handshake
688
717
handshake.argtypes = [session_t]
689
718
handshake.restype = _error_code
690
719
handshake.errcheck = _retry_on_error
692
721
transport_set_ptr = _library.gnutls_transport_set_ptr
693
722
transport_set_ptr.argtypes = [session_t, transport_ptr_t]
694
723
transport_set_ptr.restype = None
696
725
bye = _library.gnutls_bye
697
726
bye.argtypes = [session_t, close_request_t]
698
727
bye.restype = _error_code
699
728
bye.errcheck = _retry_on_error
701
730
check_version = _library.gnutls_check_version
702
731
check_version.argtypes = [ctypes.c_char_p]
703
732
check_version.restype = ctypes.c_char_p
705
734
# All the function declarations below are from gnutls/openpgp.h
707
736
openpgp_crt_init = _library.gnutls_openpgp_crt_init
708
737
openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
709
738
openpgp_crt_init.restype = _error_code
711
740
openpgp_crt_import = _library.gnutls_openpgp_crt_import
712
741
openpgp_crt_import.argtypes = [openpgp_crt_t,
713
742
ctypes.POINTER(datum_t),
714
743
openpgp_crt_fmt_t]
715
744
openpgp_crt_import.restype = _error_code
717
746
openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
718
747
openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
719
748
ctypes.POINTER(ctypes.c_uint)]
720
749
openpgp_crt_verify_self.restype = _error_code
722
751
openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
723
752
openpgp_crt_deinit.argtypes = [openpgp_crt_t]
724
753
openpgp_crt_deinit.restype = None
726
755
openpgp_crt_get_fingerprint = (
727
756
_library.gnutls_openpgp_crt_get_fingerprint)
728
757
openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
1048
1079
# The exception is when not debugging but nevertheless
1049
1080
# running in the foreground; use the previously
1050
1081
# created wnull.
1051
popen_args = { "close_fds": True,
1082
popen_args = {"close_fds": True,
1054
1085
if (not self.server_settings["debug"]
1055
1086
and self.server_settings["foreground"]):
1056
1087
popen_args.update({"stdout": wnull,
1058
pipe = multiprocessing.Pipe(duplex = False)
1089
pipe = multiprocessing.Pipe(duplex=False)
1059
1090
self.checker = multiprocessing.Process(
1061
args = (pipe[1], subprocess.call, command),
1062
kwargs = popen_args)
1092
args=(pipe[1], subprocess.call, command),
1063
1094
self.checker.start()
1064
1095
self.checker_callback_tag = GLib.io_add_watch(
1065
1096
pipe[0].fileno(), GLib.IO_IN,
1066
1097
self.checker_callback, pipe[0], command)
1067
1098
# Re-run this periodically if run by GLib.timeout_add
1070
1101
def stop_checker(self):
1071
1102
"""Force the checker process, if any, to stop."""
1072
1103
if self.checker_callback_tag:
1107
1138
func._dbus_name = func.__name__
1108
1139
if func._dbus_name.endswith("_dbus_property"):
1109
1140
func._dbus_name = func._dbus_name[:-14]
1110
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
1141
func._dbus_get_args_options = {'byte_arrays': byte_arrays}
1113
1144
return decorator
1116
1147
def dbus_interface_annotations(dbus_interface):
1117
1148
"""Decorator for marking functions returning interface annotations
1121
1152
@dbus_interface_annotations("org.example.Interface")
1122
1153
def _foo(self): # Function name does not matter
1123
1154
return {"org.freedesktop.DBus.Deprecated": "true",
1124
1155
"org.freedesktop.DBus.Property.EmitsChangedSignal":
1128
1159
def decorator(func):
1129
1160
func._dbus_is_interface = True
1130
1161
func._dbus_interface = dbus_interface
1131
1162
func._dbus_name = dbus_interface
1134
1165
return decorator
1137
1168
def dbus_annotations(annotations):
1138
1169
"""Decorator to annotate D-Bus methods, signals or properties
1141
1172
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
1142
1173
"org.freedesktop.DBus.Property."
1143
1174
"EmitsChangedSignal": "false"})
1199
1230
for cls in self.__class__.__mro__
1200
1231
for name, athing in
1201
1232
inspect.getmembers(cls, self._is_dbus_thing(thing)))
1203
1234
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1204
out_signature = "s",
1205
path_keyword = 'object_path',
1206
connection_keyword = 'connection')
1236
path_keyword='object_path',
1237
connection_keyword='connection')
1207
1238
def Introspect(self, object_path, connection):
1208
1239
"""Overloading of standard D-Bus method.
1210
1241
Inserts annotation tags on methods and signals.
1212
1243
xmlstring = dbus.service.Object.Introspect(self, object_path,
1215
1246
document = xml.dom.minidom.parseString(xmlstring)
1217
1248
for if_tag in document.getElementsByTagName("interface"):
1218
1249
# Add annotation tags
1219
1250
for typ in ("method", "signal"):
1430
1461
exc_info=error)
1431
1462
return xmlstring
1434
1466
dbus.OBJECT_MANAGER_IFACE
1435
1467
except AttributeError:
1436
1468
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1438
1471
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1439
1472
"""A D-Bus object with an ObjectManager.
1441
1474
Classes inheriting from this exposes the standard
1442
1475
GetManagedObjects call and the InterfacesAdded and
1443
1476
InterfacesRemoved signals on the standard
1444
1477
"org.freedesktop.DBus.ObjectManager" interface.
1446
1479
Note: No signals are sent automatically; they must be sent
1449
1482
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1450
out_signature = "a{oa{sa{sv}}}")
1483
out_signature="a{oa{sa{sv}}}")
1451
1484
def GetManagedObjects(self):
1452
1485
"""This function must be overridden"""
1453
1486
raise NotImplementedError()
1455
1488
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1456
signature = "oa{sa{sv}}")
1489
signature="oa{sa{sv}}")
1457
1490
def InterfacesAdded(self, object_path, interfaces_and_properties):
1460
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
1493
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature="oas")
1461
1494
def InterfacesRemoved(self, object_path, interfaces):
1464
1497
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1465
out_signature = "s",
1466
path_keyword = 'object_path',
1467
connection_keyword = 'connection')
1499
path_keyword='object_path',
1500
connection_keyword='connection')
1468
1501
def Introspect(self, object_path, connection):
1469
1502
"""Overloading of standard D-Bus method.
1471
1504
Override return argument name of GetManagedObjects to be
1472
1505
"objpath_interfaces_and_properties"
1511
1545
dbus.service.Object, it will add alternate D-Bus attributes with
1512
1546
interface names according to the "alt_interface_names" mapping.
1515
1549
@alternate_dbus_interfaces({"org.example.Interface":
1516
1550
"net.example.AlternateInterface"})
1517
1551
class SampleDBusObject(dbus.service.Object):
1518
1552
@dbus.service.method("org.example.Interface")
1519
1553
def SampleDBusMethod():
1522
1556
The above "SampleDBusMethod" on "SampleDBusObject" will be
1523
1557
reachable via two interfaces: "org.example.Interface" and
1524
1558
"net.example.AlternateInterface", the latter of which will have
1525
1559
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1526
1560
"true", unless "deprecate" is passed with a False value.
1528
1562
This works for methods and signals, and also for D-Bus properties
1529
1563
(from DBusObjectWithProperties) and interfaces (from the
1530
1564
dbus_interface_annotations decorator).
1533
1567
def wrapper(cls):
1534
1568
for orig_interface_name, alt_interface_name in (
1535
1569
alt_interface_names.items()):
1677
1713
"se.bsnet.fukt.Mandos"})
1678
1714
class ClientDBus(Client, DBusObjectWithProperties):
1679
1715
"""A Client class using D-Bus
1682
1718
dbus_object_path: dbus.ObjectPath
1683
1719
bus: dbus.SystemBus()
1686
1722
runtime_expansions = (Client.runtime_expansions
1687
1723
+ ("dbus_object_path", ))
1689
1725
_interface = "se.recompile.Mandos.Client"
1691
1727
# dbus.service.Object doesn't use super(), so we can't either.
1693
def __init__(self, bus = None, *args, **kwargs):
1729
def __init__(self, bus=None, *args, **kwargs):
1695
1731
Client.__init__(self, *args, **kwargs)
1696
1732
# Only now, when this client is initialized, can it show up on
1733
1769
dbus_value = transform_func(
1734
1770
type_func(value),
1735
variant_level = variant_level)
1771
variant_level=variant_level)
1736
1772
self.PropertyChanged(dbus.String(dbus_name),
1738
1774
self.PropertiesChanged(
1740
dbus.Dictionary({ dbus.String(dbus_name):
1776
dbus.Dictionary({dbus.String(dbus_name):
1743
1779
setattr(self, attrname, value)
1745
1781
return property(lambda self: getattr(self, attrname), setter)
1747
1783
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1748
1784
approvals_pending = notifychangeproperty(dbus.Boolean,
1749
1785
"ApprovalPending",
1751
1787
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1752
1788
last_enabled = notifychangeproperty(datetime_to_dbus,
1754
1790
checker = notifychangeproperty(
1755
1791
dbus.Boolean, "CheckerRunning",
1756
type_func = lambda checker: checker is not None)
1792
type_func=lambda checker: checker is not None)
1757
1793
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1758
1794
"LastCheckedOK")
1759
1795
last_checker_status = notifychangeproperty(dbus.Int16,
1764
1800
"ApprovedByDefault")
1765
1801
approval_delay = notifychangeproperty(
1766
1802
dbus.UInt64, "ApprovalDelay",
1767
type_func = lambda td: td.total_seconds() * 1000)
1803
type_func=lambda td: td.total_seconds() * 1000)
1768
1804
approval_duration = notifychangeproperty(
1769
1805
dbus.UInt64, "ApprovalDuration",
1770
type_func = lambda td: td.total_seconds() * 1000)
1806
type_func=lambda td: td.total_seconds() * 1000)
1771
1807
host = notifychangeproperty(dbus.String, "Host")
1772
1808
timeout = notifychangeproperty(
1773
1809
dbus.UInt64, "Timeout",
1774
type_func = lambda td: td.total_seconds() * 1000)
1810
type_func=lambda td: td.total_seconds() * 1000)
1775
1811
extended_timeout = notifychangeproperty(
1776
1812
dbus.UInt64, "ExtendedTimeout",
1777
type_func = lambda td: td.total_seconds() * 1000)
1813
type_func=lambda td: td.total_seconds() * 1000)
1778
1814
interval = notifychangeproperty(
1779
1815
dbus.UInt64, "Interval",
1780
type_func = lambda td: td.total_seconds() * 1000)
1816
type_func=lambda td: td.total_seconds() * 1000)
1781
1817
checker_command = notifychangeproperty(dbus.String, "Checker")
1782
1818
secret = notifychangeproperty(dbus.ByteArray, "Secret",
1783
1819
invalidate_only=True)
1785
1821
del notifychangeproperty
1787
1823
def __del__(self, *args, **kwargs):
1789
1825
self.remove_from_connection()
1824
1860
# Emit D-Bus signal
1825
1861
self.CheckerStarted(self.current_checker_command)
1828
1864
def _reset_approved(self):
1829
1865
self.approved = None
1832
1868
def approve(self, value=True):
1833
1869
self.approved = value
1834
1870
GLib.timeout_add(int(self.approval_duration.total_seconds()
1835
1871
* 1000), self._reset_approved)
1836
1872
self.send_changedstate()
1838
## D-Bus methods, signals & properties
1874
# D-Bus methods, signals & properties
1844
1880
# CheckerCompleted - signal
1845
1881
@dbus.service.signal(_interface, signature="nxs")
1846
1882
def CheckerCompleted(self, exitcode, waitstatus, command):
1850
1886
# CheckerStarted - signal
1851
1887
@dbus.service.signal(_interface, signature="s")
1852
1888
def CheckerStarted(self, command):
1856
1892
# PropertyChanged - signal
1857
1893
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1858
1894
@dbus.service.signal(_interface, signature="sv")
1859
1895
def PropertyChanged(self, property, value):
1863
1899
# GotSecret - signal
1864
1900
@dbus.service.signal(_interface)
1865
1901
def GotSecret(self):
1868
1904
server to mandos-client
1872
1908
# Rejected - signal
1873
1909
@dbus.service.signal(_interface, signature="s")
1874
1910
def Rejected(self, reason):
1878
1914
# NeedApproval - signal
1879
1915
@dbus.service.signal(_interface, signature="tb")
1880
1916
def NeedApproval(self, timeout, default):
1882
1918
return self.need_approval()
1886
1922
# Approve - method
1887
1923
@dbus.service.method(_interface, in_signature="b")
1888
1924
def Approve(self, value):
1889
1925
self.approve(value)
1891
1927
# CheckedOK - method
1892
1928
@dbus.service.method(_interface)
1893
1929
def CheckedOK(self):
1894
1930
self.checked_ok()
1896
1932
# Enable - method
1897
1933
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1898
1934
@dbus.service.method(_interface)
1899
1935
def Enable(self):
1903
1939
# StartChecker - method
1904
1940
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1905
1941
@dbus.service.method(_interface)
1906
1942
def StartChecker(self):
1908
1944
self.start_checker()
1910
1946
# Disable - method
1911
1947
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1912
1948
@dbus.service.method(_interface)
1913
1949
def Disable(self):
1917
1953
# StopChecker - method
1918
1954
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1919
1955
@dbus.service.method(_interface)
1920
1956
def StopChecker(self):
1921
1957
self.stop_checker()
1925
1961
# ApprovalPending - property
1926
1962
@dbus_service_property(_interface, signature="b", access="read")
1927
1963
def ApprovalPending_dbus_property(self):
1928
1964
return dbus.Boolean(bool(self.approvals_pending))
1930
1966
# ApprovedByDefault - property
1931
1967
@dbus_service_property(_interface,
2012
2048
self.checked_ok()
2014
2050
return datetime_to_dbus(self.last_checked_ok)
2016
2052
# LastCheckerStatus - property
2017
2053
@dbus_service_property(_interface, signature="n", access="read")
2018
2054
def LastCheckerStatus_dbus_property(self):
2019
2055
return dbus.Int16(self.last_checker_status)
2021
2057
# Expires - property
2022
2058
@dbus_service_property(_interface, signature="s", access="read")
2023
2059
def Expires_dbus_property(self):
2024
2060
return datetime_to_dbus(self.expires)
2026
2062
# LastApprovalRequest - property
2027
2063
@dbus_service_property(_interface, signature="s", access="read")
2028
2064
def LastApprovalRequest_dbus_property(self):
2029
2065
return datetime_to_dbus(self.last_approval_request)
2031
2067
# Timeout - property
2032
2068
@dbus_service_property(_interface,
2154
2190
class ClientHandler(socketserver.BaseRequestHandler, object):
2155
2191
"""A class to handle client connections.
2157
2193
Instantiated once for each connection to handle it.
2158
2194
Note: This will run in its own forked process."""
2160
2196
def handle(self):
2161
2197
with contextlib.closing(self.server.child_pipe) as child_pipe:
2162
2198
logger.info("TCP connection from: %s",
2163
2199
str(self.client_address))
2164
2200
logger.debug("Pipe FD: %d",
2165
2201
self.server.child_pipe.fileno())
2167
2203
session = gnutls.ClientSession(self.request)
2169
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
2170
# "+AES-256-CBC", "+SHA1",
2171
# "+COMP-NULL", "+CTYPE-OPENPGP",
2205
# priority = ':'.join(("NONE", "+VERS-TLS1.1",
2206
# "+AES-256-CBC", "+SHA1",
2207
# "+COMP-NULL", "+CTYPE-OPENPGP",
2173
2209
# Use a fallback default, since this MUST be set.
2174
2210
priority = self.server.gnutls_priority
2175
2211
if priority is None:
2176
2212
priority = "NORMAL"
2177
gnutls.priority_set_direct(session._c_object, priority,
2213
gnutls.priority_set_direct(session._c_object,
2214
priority.encode("utf-8"),
2180
2217
# Start communication using the Mandos protocol
2181
2218
# Get protocol number
2182
2219
line = self.request.makefile().readline()
2347
2384
class MultiprocessingMixIn(object):
2348
2385
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
2350
2387
def sub_process_main(self, request, address):
2352
2389
self.finish_request(request, address)
2353
2390
except Exception:
2354
2391
self.handle_error(request, address)
2355
2392
self.close_request(request)
2357
2394
def process_request(self, request, address):
2358
2395
"""Start a new process to process the request."""
2359
proc = multiprocessing.Process(target = self.sub_process_main,
2360
args = (request, address))
2396
proc = multiprocessing.Process(target=self.sub_process_main,
2397
args=(request, address))
2365
2402
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
2366
2403
""" adds a pipe to the MixIn """
2368
2405
def process_request(self, request, client_address):
2369
2406
"""Overrides and wraps the original process_request().
2371
2408
This function creates a new pipe in self.pipe
2373
2410
parent_pipe, self.child_pipe = multiprocessing.Pipe()
2375
2412
proc = MultiprocessingMixIn.process_request(self, request,
2376
2413
client_address)
2377
2414
self.child_pipe.close()
2378
2415
self.add_pipe(parent_pipe, proc)
2380
2417
def add_pipe(self, parent_pipe, proc):
2381
2418
"""Dummy function; override as necessary"""
2382
2419
raise NotImplementedError()
2430
2468
# socket_wrapper(), if socketfd was set.
2431
2469
socketserver.TCPServer.__init__(self, server_address,
2432
2470
RequestHandlerClass)
2434
2472
def server_bind(self):
2435
2473
"""This overrides the normal server_bind() function
2436
2474
to bind to an interface if one was specified, and also NOT to
2437
2475
bind to an address or port if they were not specified."""
2476
global SO_BINDTODEVICE
2438
2477
if self.interface is not None:
2439
2478
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)
2479
# Fall back to a hard-coded value which seems to be
2481
logger.warning("SO_BINDTODEVICE not found, trying 25")
2482
SO_BINDTODEVICE = 25
2484
self.socket.setsockopt(
2485
socket.SOL_SOCKET, SO_BINDTODEVICE,
2486
(self.interface + "\0").encode("utf-8"))
2487
except socket.error as error:
2488
if error.errno == errno.EPERM:
2489
logger.error("No permission to bind to"
2490
" interface %s", self.interface)
2491
elif error.errno == errno.ENOPROTOOPT:
2492
logger.error("SO_BINDTODEVICE not available;"
2493
" cannot bind to interface %s",
2495
elif error.errno == errno.ENODEV:
2496
logger.error("Interface %s does not exist,"
2497
" cannot bind", self.interface)
2461
2500
# Only bind(2) the socket if we really need to.
2462
2501
if self.server_address[0] or self.server_address[1]:
2463
2502
if not self.server_address[0]:
2464
2503
if self.address_family == socket.AF_INET6:
2465
any_address = "::" # in6addr_any
2504
any_address = "::" # in6addr_any
2467
any_address = "0.0.0.0" # INADDR_ANY
2506
any_address = "0.0.0.0" # INADDR_ANY
2468
2507
self.server_address = (any_address,
2469
2508
self.server_address[1])
2470
2509
elif not self.server_address[1]:
2504
2543
self.gnutls_priority = gnutls_priority
2505
2544
IPv6_TCPServer.__init__(self, server_address,
2506
2545
RequestHandlerClass,
2507
interface = interface,
2508
use_ipv6 = use_ipv6,
2509
socketfd = socketfd)
2546
interface=interface,
2511
2550
def server_activate(self):
2512
2551
if self.enabled:
2513
2552
return socketserver.TCPServer.server_activate(self)
2515
2554
def enable(self):
2516
2555
self.enabled = True
2518
2557
def add_pipe(self, parent_pipe, proc):
2519
2558
# Call "handle_ipc" for both data and EOF events
2520
2559
GLib.io_add_watch(
2521
2560
parent_pipe.fileno(),
2522
2561
GLib.IO_IN | GLib.IO_HUP,
2523
2562
functools.partial(self.handle_ipc,
2524
parent_pipe = parent_pipe,
2563
parent_pipe=parent_pipe,
2527
2566
def handle_ipc(self, source, condition,
2528
2567
parent_pipe=None,
2530
2569
client_object=None):
2531
2570
# error, or the other end of multiprocessing.Pipe has closed
2532
2571
if condition & (GLib.IO_ERR | GLib.IO_HUP):
2533
2572
# Wait for other process to exit
2537
2576
# Read a request from the child
2538
2577
request = parent_pipe.recv()
2539
2578
command = request[0]
2541
2580
if command == 'init':
2581
fpr = request[1].decode("ascii")
2543
2582
address = request[2]
2545
2584
for c in self.clients.values():
2546
2585
if c.fingerprint == fpr:
2611
2650
>>> rfc3339_duration_to_delta("P1DT3M20S")
2612
2651
datetime.timedelta(1, 200)
2615
2654
# Parsing an RFC 3339 duration with regular expressions is not
2616
2655
# possible - there would have to be multiple places for the same
2617
2656
# values, like seconds. The current code, while more esoteric, is
2618
2657
# cleaner without depending on a parsing library. If Python had a
2619
2658
# built-in library for parsing we would use it, but we'd like to
2620
2659
# avoid excessive use of external libraries.
2622
2661
# New type for defining tokens, syntax, and semantics all-in-one
2623
2662
Token = collections.namedtuple("Token", (
2624
2663
"regexp", # To match token; if "value" is not None, must have
2808
2850
parser.add_argument("--no-zeroconf", action="store_false",
2809
2851
dest="zeroconf", help="Do not use Zeroconf",
2812
2854
options = parser.parse_args()
2814
2856
if options.check:
2816
2858
fail_count, test_count = doctest.testmod()
2817
2859
sys.exit(os.EX_OK if fail_count == 0 else 1)
2819
2861
# 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",
2862
server_defaults = {"interface": "",
2867
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2868
":+SIGN-DSA-SHA256",
2869
"servicename": "Mandos",
2875
"statedir": "/var/lib/mandos",
2876
"foreground": "False",
2838
2880
# Parse config file for server-global settings
2839
2881
server_config = configparser.SafeConfigParser(server_defaults)
2840
2882
del server_defaults
2969
3012
.format(uid, gid, os.strerror(error.errno)))
2970
3013
if error.errno != errno.EPERM:
2974
3017
# Enable all possible GnuTLS debugging
2976
3019
# "Use a log level over 10 to enable all debugging options."
2977
3020
# - GnuTLS manual
2978
3021
gnutls.global_set_log_level(11)
2980
3023
@gnutls.log_func
2981
3024
def debug_gnutls(level, string):
2982
3025
logger.debug("GnuTLS: %s", string[:-1])
2984
3027
gnutls.global_set_log_function(debug_gnutls)
2986
3029
# Redirect stdin so all checkers get /dev/null
2987
3030
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2988
3031
os.dup2(null, sys.stdin.fileno())
2992
3035
# Need to fork before connecting to D-Bus
2993
3036
if not foreground:
2994
3037
# Close all input and output, do double fork, etc.
2997
3040
# multiprocessing will use threads, so before we use GLib we need
2998
3041
# to inform GLib that threads will be used.
2999
3042
GLib.threads_init()
3001
3044
global main_loop
3002
3045
# From the Avahi example code
3003
3046
DBusGMainLoop(set_as_default=True)
3021
3064
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
3022
3065
service = AvahiServiceToSyslog(
3023
name = server_settings["servicename"],
3024
servicetype = "_mandos._tcp",
3025
protocol = protocol,
3066
name=server_settings["servicename"],
3067
servicetype="_mandos._tcp",
3027
3070
if server_settings["interface"]:
3028
3071
service.interface = if_nametoindex(
3029
3072
server_settings["interface"].encode("utf-8"))
3031
3074
global multiprocessing_manager
3032
3075
multiprocessing_manager = multiprocessing.Manager()
3034
3077
client_class = Client
3036
client_class = functools.partial(ClientDBus, bus = bus)
3079
client_class = functools.partial(ClientDBus, bus=bus)
3038
3081
client_settings = Client.config_parser(client_config)
3039
3082
old_client_settings = {}
3040
3083
clients_data = {}
3042
3085
# This is used to redirect stdout and stderr for checker processes
3044
wnull = open(os.devnull, "w") # A writable /dev/null
3087
wnull = open(os.devnull, "w") # A writable /dev/null
3045
3088
# Only used if server is running in foreground but not in debug
3047
3090
if debug or not foreground:
3050
3093
# Get client data and settings from last running state.
3051
3094
if server_settings["restore"]:
3053
3096
with open(stored_state_path, "rb") as stored_state:
3054
if sys.version_info.major == 2:
3097
if sys.version_info.major == 2:
3055
3098
clients_data, old_client_settings = pickle.load(
3058
3101
bytes_clients_data, bytes_old_client_settings = (
3059
pickle.load(stored_state, encoding = "bytes"))
3060
### Fix bytes to strings
3102
pickle.load(stored_state, encoding="bytes"))
3103
# Fix bytes to strings
3063
clients_data = { (key.decode("utf-8")
3064
if isinstance(key, bytes)
3067
bytes_clients_data.items() }
3106
clients_data = {(key.decode("utf-8")
3107
if isinstance(key, bytes)
3110
bytes_clients_data.items()}
3068
3111
del bytes_clients_data
3069
3112
for key in clients_data:
3070
value = { (k.decode("utf-8")
3071
if isinstance(k, bytes) else k): v
3073
clients_data[key].items() }
3113
value = {(k.decode("utf-8")
3114
if isinstance(k, bytes) else k): v
3116
clients_data[key].items()}
3074
3117
clients_data[key] = value
3075
3118
# .client_structure
3076
3119
value["client_structure"] = [
3077
3120
(s.decode("utf-8")
3078
3121
if isinstance(s, bytes)
3079
3122
else s) for s in
3080
value["client_structure"] ]
3123
value["client_structure"]]
3081
3124
# .name & .host
3082
3125
for k in ("name", "host"):
3083
3126
if isinstance(value[k], bytes):
3084
3127
value[k] = value[k].decode("utf-8")
3085
## old_client_settings
3128
# old_client_settings
3087
3130
old_client_settings = {
3088
3131
(key.decode("utf-8")
3089
3132
if isinstance(key, bytes)
3090
3133
else key): value
3091
3134
for key, value in
3092
bytes_old_client_settings.items() }
3135
bytes_old_client_settings.items()}
3093
3136
del bytes_old_client_settings
3095
3138
for value in old_client_settings.values():
3201
3244
pidfilename, pid)
3203
3246
del pidfilename
3205
3248
for termsig in (signal.SIGHUP, signal.SIGTERM):
3206
3249
GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig,
3207
3250
lambda: main_loop.quit() and False)
3211
3254
@alternate_dbus_interfaces(
3212
{ "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
3255
{"se.recompile.Mandos": "se.bsnet.fukt.Mandos"})
3213
3256
class MandosDBusService(DBusObjectWithObjectManager):
3214
3257
"""A D-Bus proxy object"""
3216
3259
def __init__(self):
3217
3260
dbus.service.Object.__init__(self, bus, "/")
3219
3262
_interface = "se.recompile.Mandos"
3221
3264
@dbus.service.signal(_interface, signature="o")
3222
3265
def ClientAdded(self, objpath):
3226
3269
@dbus.service.signal(_interface, signature="ss")
3227
3270
def ClientNotFound(self, fingerprint, address):
3231
3274
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3233
3276
@dbus.service.signal(_interface, signature="os")
3234
3277
def ClientRemoved(self, objpath, name):
3238
3281
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3240
3283
@dbus.service.method(_interface, out_signature="ao")
3268
3311
self.client_removed_signal(c)
3270
3313
raise KeyError(object_path)
3274
3317
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
3275
out_signature = "a{oa{sa{sv}}}")
3318
out_signature="a{oa{sa{sv}}}")
3276
3319
def GetManagedObjects(self):
3277
3320
"""D-Bus method"""
3278
3321
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()})
3322
{client.dbus_object_path:
3324
{interface: client.GetAll(interface)
3326
client._get_all_interface_names()})
3327
for client in tcp_server.clients.values()})
3286
3329
def client_added_signal(self, client):
3287
3330
"""Send the new standard signal and the old signal"""
3329
3377
client.encrypted_secret = pgp.encrypt(client.secret,
3331
3379
client_dict = {}
3333
3381
# A list of attributes that can not be pickled
3335
exclude = { "bus", "changedstate", "secret",
3336
"checker", "server_settings" }
3383
exclude = {"bus", "changedstate", "secret",
3384
"checker", "server_settings"}
3337
3385
for name, typ in inspect.getmembers(dbus.service
3339
3387
exclude.add(name)
3341
3389
client_dict["encrypted_secret"] = (client
3342
3390
.encrypted_secret)
3343
3391
for attr in client.client_structure:
3344
3392
if attr not in exclude:
3345
3393
client_dict[attr] = getattr(client, attr)
3347
3395
clients[client.name] = client_dict
3348
3396
del client_settings[client.name]["secret"]
3351
3399
with tempfile.NamedTemporaryFile(