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,
81
82
import dbus.service
83
from gi.repository import GObject
85
import gobject as GObject
83
from gi.repository import GLib
86
84
from dbus.mainloop.glib import DBusGMainLoop
89
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:
93
94
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
94
95
except AttributeError:
97
# This is where SO_BINDTODEVICE was up to and including Python
96
99
from IN import SO_BINDTODEVICE
97
100
except ImportError:
98
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
100
115
if sys.version_info.major == 2:
104
119
stored_state_file = "clients.pickle"
106
121
logger = logging.getLogger()
230
247
'--passphrase-file',
232
249
+ self.gnupgargs,
233
stdin = subprocess.PIPE,
234
stdout = subprocess.PIPE,
235
stderr = subprocess.PIPE)
236
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)
237
254
if proc.returncode != 0:
238
255
raise PGPError(err)
239
256
return ciphertext
241
258
def decrypt(self, data, password):
242
259
passphrase = self.password_encode(password)
243
260
with tempfile.NamedTemporaryFile(
244
dir = self.tempdir) as passfile:
261
dir=self.tempdir) as passfile:
245
262
passfile.write(passphrase)
247
264
proc = subprocess.Popen([self.gpg, '--decrypt',
248
265
'--passphrase-file',
250
267
+ self.gnupgargs,
251
stdin = subprocess.PIPE,
252
stdout = subprocess.PIPE,
253
stderr = subprocess.PIPE)
254
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)
255
272
if proc.returncode != 0:
256
273
raise PGPError(err)
257
274
return decrypted_plaintext
259
277
# Pretend that we have an Avahi module
260
278
class Avahi(object):
261
279
"""This isn't so much a class as it is a module-like namespace.
262
280
It is instantiated once, and simulates having an Avahi module."""
263
IF_UNSPEC = -1 # avahi-common/address.h
264
PROTO_UNSPEC = -1 # avahi-common/address.h
265
PROTO_INET = 0 # avahi-common/address.h
266
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
267
285
DBUS_NAME = "org.freedesktop.Avahi"
268
286
DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"
269
287
DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
270
288
DBUS_PATH_SERVER = "/"
271
290
def string_array_to_txt_array(self, t):
272
291
return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
273
292
for s in t), signature="ay")
274
ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h
275
ENTRY_GROUP_COLLISION = 3 # avahi-common/defs.h
276
ENTRY_GROUP_FAILURE = 4 # avahi-common/defs.h
277
SERVER_INVALID = 0 # avahi-common/defs.h
278
SERVER_REGISTERING = 1 # avahi-common/defs.h
279
SERVER_RUNNING = 2 # avahi-common/defs.h
280
SERVER_COLLISION = 3 # avahi-common/defs.h
281
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
284
304
class AvahiError(Exception):
285
305
def __init__(self, value, *args, **kwargs):
286
306
self.value = value
476
496
class AvahiServiceToSyslog(AvahiService):
477
497
def rename(self, *args, **kwargs):
478
498
"""Add the new name to the syslog messages"""
479
ret = AvahiService.rename(self, *args, **kwargs)
499
ret = super(AvahiServiceToSyslog, self).rename(*args, **kwargs)
480
500
syslogger.setFormatter(logging.Formatter(
481
501
'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
482
502
.format(self.name)))
485
506
# Pretend that we have a GnuTLS module
486
507
class GnuTLS(object):
487
508
"""This isn't so much a class as it is a module-like namespace.
488
509
It is instantiated once, and simulates having a GnuTLS module."""
490
_library = ctypes.cdll.LoadLibrary(
491
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)
492
516
_need_version = b"3.3.0"
493
518
def __init__(self):
494
# Need to use class name "GnuTLS" here, since this method is
495
# called before the assignment to the "gnutls" global variable
497
if GnuTLS.check_version(self._need_version) is None:
498
raise GnuTLS.Error("Needs GnuTLS {} or later"
499
.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))
501
525
# Unless otherwise indicated, the constants and types below are
502
526
# all from the gnutls/gnutls.h C header file.
506
530
E_INTERRUPTED = -52
511
535
CRD_CERTIFICATE = 1
512
536
E_NO_CERTIFICATE_FOUND = -49
513
537
OPENPGP_FMT_RAW = 0 # gnutls/openpgp.h
516
540
class session_int(ctypes.Structure):
518
542
session_t = ctypes.POINTER(session_int)
519
544
class certificate_credentials_st(ctypes.Structure):
521
546
certificate_credentials_t = ctypes.POINTER(
522
547
certificate_credentials_st)
523
548
certificate_type_t = ctypes.c_int
524
550
class datum_t(ctypes.Structure):
525
551
_fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
526
552
('size', ctypes.c_uint)]
527
554
class openpgp_crt_int(ctypes.Structure):
529
556
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
530
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
557
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
531
558
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
532
559
credentials_type_t = ctypes.c_int
533
560
transport_ptr_t = ctypes.c_void_p
534
561
close_request_t = ctypes.c_int
537
564
class Error(Exception):
538
565
# We need to use the class name "GnuTLS" here, since this
539
566
# exception might be raised from within GnuTLS.__init__,
540
567
# which is called before the assignment to the "gnutls"
541
568
# global variable has happened.
542
def __init__(self, message = None, code = None, args=()):
569
def __init__(self, message=None, code=None, args=()):
543
570
# Default usage is by a message string, but if a return
544
571
# code is passed, convert it to a string with
545
572
# gnutls.strerror()
614
641
return _error_code(result)
615
642
result = func(*arguments)
618
645
# Unless otherwise indicated, the function declarations below are
619
646
# all from the gnutls/gnutls.h C header file.
622
649
priority_set_direct = _library.gnutls_priority_set_direct
623
650
priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
624
651
ctypes.POINTER(ctypes.c_char_p)]
625
652
priority_set_direct.restype = _error_code
627
654
init = _library.gnutls_init
628
655
init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
629
656
init.restype = _error_code
631
658
set_default_priority = _library.gnutls_set_default_priority
632
659
set_default_priority.argtypes = [session_t]
633
660
set_default_priority.restype = _error_code
635
662
record_send = _library.gnutls_record_send
636
663
record_send.argtypes = [session_t, ctypes.c_void_p,
638
665
record_send.restype = ctypes.c_ssize_t
639
666
record_send.errcheck = _retry_on_error
641
668
certificate_allocate_credentials = (
642
669
_library.gnutls_certificate_allocate_credentials)
643
670
certificate_allocate_credentials.argtypes = [
644
671
ctypes.POINTER(certificate_credentials_t)]
645
672
certificate_allocate_credentials.restype = _error_code
647
674
certificate_free_credentials = (
648
675
_library.gnutls_certificate_free_credentials)
649
certificate_free_credentials.argtypes = [certificate_credentials_t]
676
certificate_free_credentials.argtypes = [
677
certificate_credentials_t]
650
678
certificate_free_credentials.restype = None
652
680
handshake_set_private_extensions = (
653
681
_library.gnutls_handshake_set_private_extensions)
654
682
handshake_set_private_extensions.argtypes = [session_t,
656
684
handshake_set_private_extensions.restype = None
658
686
credentials_set = _library.gnutls_credentials_set
659
687
credentials_set.argtypes = [session_t, credentials_type_t,
661
689
credentials_set.restype = _error_code
663
691
strerror = _library.gnutls_strerror
664
692
strerror.argtypes = [ctypes.c_int]
665
693
strerror.restype = ctypes.c_char_p
667
695
certificate_type_get = _library.gnutls_certificate_type_get
668
696
certificate_type_get.argtypes = [session_t]
669
697
certificate_type_get.restype = _error_code
671
699
certificate_get_peers = _library.gnutls_certificate_get_peers
672
700
certificate_get_peers.argtypes = [session_t,
673
701
ctypes.POINTER(ctypes.c_uint)]
674
702
certificate_get_peers.restype = ctypes.POINTER(datum_t)
676
704
global_set_log_level = _library.gnutls_global_set_log_level
677
705
global_set_log_level.argtypes = [ctypes.c_int]
678
706
global_set_log_level.restype = None
680
708
global_set_log_function = _library.gnutls_global_set_log_function
681
709
global_set_log_function.argtypes = [log_func]
682
710
global_set_log_function.restype = None
684
712
deinit = _library.gnutls_deinit
685
713
deinit.argtypes = [session_t]
686
714
deinit.restype = None
688
716
handshake = _library.gnutls_handshake
689
717
handshake.argtypes = [session_t]
690
718
handshake.restype = _error_code
691
719
handshake.errcheck = _retry_on_error
693
721
transport_set_ptr = _library.gnutls_transport_set_ptr
694
722
transport_set_ptr.argtypes = [session_t, transport_ptr_t]
695
723
transport_set_ptr.restype = None
697
725
bye = _library.gnutls_bye
698
726
bye.argtypes = [session_t, close_request_t]
699
727
bye.restype = _error_code
700
728
bye.errcheck = _retry_on_error
702
730
check_version = _library.gnutls_check_version
703
731
check_version.argtypes = [ctypes.c_char_p]
704
732
check_version.restype = ctypes.c_char_p
706
734
# All the function declarations below are from gnutls/openpgp.h
708
736
openpgp_crt_init = _library.gnutls_openpgp_crt_init
709
737
openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
710
738
openpgp_crt_init.restype = _error_code
712
740
openpgp_crt_import = _library.gnutls_openpgp_crt_import
713
741
openpgp_crt_import.argtypes = [openpgp_crt_t,
714
742
ctypes.POINTER(datum_t),
715
743
openpgp_crt_fmt_t]
716
744
openpgp_crt_import.restype = _error_code
718
746
openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
719
747
openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
720
748
ctypes.POINTER(ctypes.c_uint)]
721
749
openpgp_crt_verify_self.restype = _error_code
723
751
openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
724
752
openpgp_crt_deinit.argtypes = [openpgp_crt_t]
725
753
openpgp_crt_deinit.restype = None
727
755
openpgp_crt_get_fingerprint = (
728
756
_library.gnutls_openpgp_crt_get_fingerprint)
729
757
openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
757
787
checker: subprocess.Popen(); a running checker process used
758
788
to see if the client lives.
759
789
'None' if no process is running.
760
checker_callback_tag: a GObject event source tag, or None
790
checker_callback_tag: a GLib event source tag, or None
761
791
checker_command: string; External command which is run to check
762
792
if client lives. %() expansions are done at
763
793
runtime with vars(self) as dict, so that for
764
794
instance %(name)s can be used in the command.
765
checker_initiator_tag: a GObject event source tag, or None
795
checker_initiator_tag: a GLib event source tag, or None
766
796
created: datetime.datetime(); (UTC) object creation
767
797
client_structure: Object describing what attributes a client has
768
798
and is used for storing the client at exit
769
799
current_checker_command: string; current running checker_command
770
disable_initiator_tag: a GObject event source tag, or None
800
disable_initiator_tag: a GLib event source tag, or None
772
802
fingerprint: string (40 or 32 hexadecimal digits); used to
773
803
uniquely identify the client
930
960
logger.info("Disabling client %s", self.name)
931
961
if getattr(self, "disable_initiator_tag", None) is not None:
932
GObject.source_remove(self.disable_initiator_tag)
962
GLib.source_remove(self.disable_initiator_tag)
933
963
self.disable_initiator_tag = None
934
964
self.expires = None
935
965
if getattr(self, "checker_initiator_tag", None) is not None:
936
GObject.source_remove(self.checker_initiator_tag)
966
GLib.source_remove(self.checker_initiator_tag)
937
967
self.checker_initiator_tag = None
938
968
self.stop_checker()
939
969
self.enabled = False
941
971
self.send_changedstate()
942
# Do not run this again if called by a GObject.timeout_add
972
# Do not run this again if called by a GLib.timeout_add
945
975
def __del__(self):
948
978
def init_checker(self):
949
979
# Schedule a new checker to be started an 'interval' from now,
950
980
# and every interval from then on.
951
981
if self.checker_initiator_tag is not None:
952
GObject.source_remove(self.checker_initiator_tag)
953
self.checker_initiator_tag = GObject.timeout_add(
982
GLib.source_remove(self.checker_initiator_tag)
983
self.checker_initiator_tag = GLib.timeout_add(
954
984
int(self.interval.total_seconds() * 1000),
955
985
self.start_checker)
956
986
# Schedule a disable() when 'timeout' has passed
957
987
if self.disable_initiator_tag is not None:
958
GObject.source_remove(self.disable_initiator_tag)
959
self.disable_initiator_tag = GObject.timeout_add(
988
GLib.source_remove(self.disable_initiator_tag)
989
self.disable_initiator_tag = GLib.timeout_add(
960
990
int(self.timeout.total_seconds() * 1000), self.disable)
961
991
# Also start a new checker *right now*.
962
992
self.start_checker()
964
994
def checker_callback(self, source, condition, connection,
966
996
"""The checker has completed, so take appropriate actions."""
985
1015
logger.warning("Checker for %(name)s crashed?",
989
1019
def checked_ok(self):
990
1020
"""Assert that the client has been seen, alive and well."""
991
1021
self.last_checked_ok = datetime.datetime.utcnow()
992
1022
self.last_checker_status = 0
993
1023
self.last_checker_signal = None
994
1024
self.bump_timeout()
996
1026
def bump_timeout(self, timeout=None):
997
1027
"""Bump up the timeout for this client."""
998
1028
if timeout is None:
999
1029
timeout = self.timeout
1000
1030
if self.disable_initiator_tag is not None:
1001
GObject.source_remove(self.disable_initiator_tag)
1031
GLib.source_remove(self.disable_initiator_tag)
1002
1032
self.disable_initiator_tag = None
1003
1033
if getattr(self, "enabled", False):
1004
self.disable_initiator_tag = GObject.timeout_add(
1034
self.disable_initiator_tag = GLib.timeout_add(
1005
1035
int(timeout.total_seconds() * 1000), self.disable)
1006
1036
self.expires = datetime.datetime.utcnow() + timeout
1008
1038
def need_approval(self):
1009
1039
self.last_approval_request = datetime.datetime.utcnow()
1011
1041
def start_checker(self):
1012
1042
"""Start a new checker subprocess if one is not running.
1014
1044
If a checker already exists, leave it running and do
1016
1046
# The reason for not killing a running checker is that if we
1049
1079
# The exception is when not debugging but nevertheless
1050
1080
# running in the foreground; use the previously
1051
1081
# created wnull.
1052
popen_args = { "close_fds": True,
1082
popen_args = {"close_fds": True,
1055
1085
if (not self.server_settings["debug"]
1056
1086
and self.server_settings["foreground"]):
1057
1087
popen_args.update({"stdout": wnull,
1059
pipe = multiprocessing.Pipe(duplex = False)
1089
pipe = multiprocessing.Pipe(duplex=False)
1060
1090
self.checker = multiprocessing.Process(
1062
args = (pipe[1], subprocess.call, command),
1063
kwargs = popen_args)
1092
args=(pipe[1], subprocess.call, command),
1064
1094
self.checker.start()
1065
self.checker_callback_tag = GObject.io_add_watch(
1066
pipe[0].fileno(), GObject.IO_IN,
1095
self.checker_callback_tag = GLib.io_add_watch(
1096
pipe[0].fileno(), GLib.IO_IN,
1067
1097
self.checker_callback, pipe[0], command)
1068
# Re-run this periodically if run by GObject.timeout_add
1098
# Re-run this periodically if run by GLib.timeout_add
1071
1101
def stop_checker(self):
1072
1102
"""Force the checker process, if any, to stop."""
1073
1103
if self.checker_callback_tag:
1074
GObject.source_remove(self.checker_callback_tag)
1104
GLib.source_remove(self.checker_callback_tag)
1075
1105
self.checker_callback_tag = None
1076
1106
if getattr(self, "checker", None) is None:
1108
1138
func._dbus_name = func.__name__
1109
1139
if func._dbus_name.endswith("_dbus_property"):
1110
1140
func._dbus_name = func._dbus_name[:-14]
1111
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
1141
func._dbus_get_args_options = {'byte_arrays': byte_arrays}
1114
1144
return decorator
1117
1147
def dbus_interface_annotations(dbus_interface):
1118
1148
"""Decorator for marking functions returning interface annotations
1122
1152
@dbus_interface_annotations("org.example.Interface")
1123
1153
def _foo(self): # Function name does not matter
1124
1154
return {"org.freedesktop.DBus.Deprecated": "true",
1125
1155
"org.freedesktop.DBus.Property.EmitsChangedSignal":
1129
1159
def decorator(func):
1130
1160
func._dbus_is_interface = True
1131
1161
func._dbus_interface = dbus_interface
1132
1162
func._dbus_name = dbus_interface
1135
1165
return decorator
1138
1168
def dbus_annotations(annotations):
1139
1169
"""Decorator to annotate D-Bus methods, signals or properties
1142
1172
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
1143
1173
"org.freedesktop.DBus.Property."
1144
1174
"EmitsChangedSignal": "false"})
1200
1230
for cls in self.__class__.__mro__
1201
1231
for name, athing in
1202
1232
inspect.getmembers(cls, self._is_dbus_thing(thing)))
1204
1234
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1205
out_signature = "s",
1206
path_keyword = 'object_path',
1207
connection_keyword = 'connection')
1236
path_keyword='object_path',
1237
connection_keyword='connection')
1208
1238
def Introspect(self, object_path, connection):
1209
1239
"""Overloading of standard D-Bus method.
1211
1241
Inserts annotation tags on methods and signals.
1213
1243
xmlstring = dbus.service.Object.Introspect(self, object_path,
1216
1246
document = xml.dom.minidom.parseString(xmlstring)
1218
1248
for if_tag in document.getElementsByTagName("interface"):
1219
1249
# Add annotation tags
1220
1250
for typ in ("method", "signal"):
1431
1461
exc_info=error)
1432
1462
return xmlstring
1435
1466
dbus.OBJECT_MANAGER_IFACE
1436
1467
except AttributeError:
1437
1468
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1439
1471
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1440
1472
"""A D-Bus object with an ObjectManager.
1442
1474
Classes inheriting from this exposes the standard
1443
1475
GetManagedObjects call and the InterfacesAdded and
1444
1476
InterfacesRemoved signals on the standard
1445
1477
"org.freedesktop.DBus.ObjectManager" interface.
1447
1479
Note: No signals are sent automatically; they must be sent
1450
1482
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1451
out_signature = "a{oa{sa{sv}}}")
1483
out_signature="a{oa{sa{sv}}}")
1452
1484
def GetManagedObjects(self):
1453
1485
"""This function must be overridden"""
1454
1486
raise NotImplementedError()
1456
1488
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1457
signature = "oa{sa{sv}}")
1489
signature="oa{sa{sv}}")
1458
1490
def InterfacesAdded(self, object_path, interfaces_and_properties):
1461
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
1493
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature="oas")
1462
1494
def InterfacesRemoved(self, object_path, interfaces):
1465
1497
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1466
out_signature = "s",
1467
path_keyword = 'object_path',
1468
connection_keyword = 'connection')
1499
path_keyword='object_path',
1500
connection_keyword='connection')
1469
1501
def Introspect(self, object_path, connection):
1470
1502
"""Overloading of standard D-Bus method.
1472
1504
Override return argument name of GetManagedObjects to be
1473
1505
"objpath_interfaces_and_properties"
1512
1545
dbus.service.Object, it will add alternate D-Bus attributes with
1513
1546
interface names according to the "alt_interface_names" mapping.
1516
1549
@alternate_dbus_interfaces({"org.example.Interface":
1517
1550
"net.example.AlternateInterface"})
1518
1551
class SampleDBusObject(dbus.service.Object):
1519
1552
@dbus.service.method("org.example.Interface")
1520
1553
def SampleDBusMethod():
1523
1556
The above "SampleDBusMethod" on "SampleDBusObject" will be
1524
1557
reachable via two interfaces: "org.example.Interface" and
1525
1558
"net.example.AlternateInterface", the latter of which will have
1526
1559
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1527
1560
"true", unless "deprecate" is passed with a False value.
1529
1562
This works for methods and signals, and also for D-Bus properties
1530
1563
(from DBusObjectWithProperties) and interfaces (from the
1531
1564
dbus_interface_annotations decorator).
1534
1567
def wrapper(cls):
1535
1568
for orig_interface_name, alt_interface_name in (
1536
1569
alt_interface_names.items()):
1678
1713
"se.bsnet.fukt.Mandos"})
1679
1714
class ClientDBus(Client, DBusObjectWithProperties):
1680
1715
"""A Client class using D-Bus
1683
1718
dbus_object_path: dbus.ObjectPath
1684
1719
bus: dbus.SystemBus()
1687
1722
runtime_expansions = (Client.runtime_expansions
1688
1723
+ ("dbus_object_path", ))
1690
1725
_interface = "se.recompile.Mandos.Client"
1692
1727
# dbus.service.Object doesn't use super(), so we can't either.
1694
def __init__(self, bus = None, *args, **kwargs):
1729
def __init__(self, bus=None, *args, **kwargs):
1696
1731
Client.__init__(self, *args, **kwargs)
1697
1732
# Only now, when this client is initialized, can it show up on
1734
1769
dbus_value = transform_func(
1735
1770
type_func(value),
1736
variant_level = variant_level)
1771
variant_level=variant_level)
1737
1772
self.PropertyChanged(dbus.String(dbus_name),
1739
1774
self.PropertiesChanged(
1741
dbus.Dictionary({ dbus.String(dbus_name):
1776
dbus.Dictionary({dbus.String(dbus_name):
1744
1779
setattr(self, attrname, value)
1746
1781
return property(lambda self: getattr(self, attrname), setter)
1748
1783
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1749
1784
approvals_pending = notifychangeproperty(dbus.Boolean,
1750
1785
"ApprovalPending",
1752
1787
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1753
1788
last_enabled = notifychangeproperty(datetime_to_dbus,
1755
1790
checker = notifychangeproperty(
1756
1791
dbus.Boolean, "CheckerRunning",
1757
type_func = lambda checker: checker is not None)
1792
type_func=lambda checker: checker is not None)
1758
1793
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1759
1794
"LastCheckedOK")
1760
1795
last_checker_status = notifychangeproperty(dbus.Int16,
1765
1800
"ApprovedByDefault")
1766
1801
approval_delay = notifychangeproperty(
1767
1802
dbus.UInt64, "ApprovalDelay",
1768
type_func = lambda td: td.total_seconds() * 1000)
1803
type_func=lambda td: td.total_seconds() * 1000)
1769
1804
approval_duration = notifychangeproperty(
1770
1805
dbus.UInt64, "ApprovalDuration",
1771
type_func = lambda td: td.total_seconds() * 1000)
1806
type_func=lambda td: td.total_seconds() * 1000)
1772
1807
host = notifychangeproperty(dbus.String, "Host")
1773
1808
timeout = notifychangeproperty(
1774
1809
dbus.UInt64, "Timeout",
1775
type_func = lambda td: td.total_seconds() * 1000)
1810
type_func=lambda td: td.total_seconds() * 1000)
1776
1811
extended_timeout = notifychangeproperty(
1777
1812
dbus.UInt64, "ExtendedTimeout",
1778
type_func = lambda td: td.total_seconds() * 1000)
1813
type_func=lambda td: td.total_seconds() * 1000)
1779
1814
interval = notifychangeproperty(
1780
1815
dbus.UInt64, "Interval",
1781
type_func = lambda td: td.total_seconds() * 1000)
1816
type_func=lambda td: td.total_seconds() * 1000)
1782
1817
checker_command = notifychangeproperty(dbus.String, "Checker")
1783
1818
secret = notifychangeproperty(dbus.ByteArray, "Secret",
1784
1819
invalidate_only=True)
1786
1821
del notifychangeproperty
1788
1823
def __del__(self, *args, **kwargs):
1790
1825
self.remove_from_connection()
1825
1860
# Emit D-Bus signal
1826
1861
self.CheckerStarted(self.current_checker_command)
1829
1864
def _reset_approved(self):
1830
1865
self.approved = None
1833
1868
def approve(self, value=True):
1834
1869
self.approved = value
1835
GObject.timeout_add(int(self.approval_duration.total_seconds()
1836
* 1000), self._reset_approved)
1870
GLib.timeout_add(int(self.approval_duration.total_seconds()
1871
* 1000), self._reset_approved)
1837
1872
self.send_changedstate()
1839
## D-Bus methods, signals & properties
1874
# D-Bus methods, signals & properties
1845
1880
# CheckerCompleted - signal
1846
1881
@dbus.service.signal(_interface, signature="nxs")
1847
1882
def CheckerCompleted(self, exitcode, waitstatus, command):
1851
1886
# CheckerStarted - signal
1852
1887
@dbus.service.signal(_interface, signature="s")
1853
1888
def CheckerStarted(self, command):
1857
1892
# PropertyChanged - signal
1858
1893
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1859
1894
@dbus.service.signal(_interface, signature="sv")
1860
1895
def PropertyChanged(self, property, value):
1864
1899
# GotSecret - signal
1865
1900
@dbus.service.signal(_interface)
1866
1901
def GotSecret(self):
1869
1904
server to mandos-client
1873
1908
# Rejected - signal
1874
1909
@dbus.service.signal(_interface, signature="s")
1875
1910
def Rejected(self, reason):
1879
1914
# NeedApproval - signal
1880
1915
@dbus.service.signal(_interface, signature="tb")
1881
1916
def NeedApproval(self, timeout, default):
1883
1918
return self.need_approval()
1887
1922
# Approve - method
1888
1923
@dbus.service.method(_interface, in_signature="b")
1889
1924
def Approve(self, value):
1890
1925
self.approve(value)
1892
1927
# CheckedOK - method
1893
1928
@dbus.service.method(_interface)
1894
1929
def CheckedOK(self):
1895
1930
self.checked_ok()
1897
1932
# Enable - method
1898
1933
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1899
1934
@dbus.service.method(_interface)
1900
1935
def Enable(self):
1904
1939
# StartChecker - method
1905
1940
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1906
1941
@dbus.service.method(_interface)
1907
1942
def StartChecker(self):
1909
1944
self.start_checker()
1911
1946
# Disable - method
1912
1947
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1913
1948
@dbus.service.method(_interface)
1914
1949
def Disable(self):
1918
1953
# StopChecker - method
1919
1954
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1920
1955
@dbus.service.method(_interface)
1921
1956
def StopChecker(self):
1922
1957
self.stop_checker()
1926
1961
# ApprovalPending - property
1927
1962
@dbus_service_property(_interface, signature="b", access="read")
1928
1963
def ApprovalPending_dbus_property(self):
1929
1964
return dbus.Boolean(bool(self.approvals_pending))
1931
1966
# ApprovedByDefault - property
1932
1967
@dbus_service_property(_interface,
2013
2048
self.checked_ok()
2015
2050
return datetime_to_dbus(self.last_checked_ok)
2017
2052
# LastCheckerStatus - property
2018
2053
@dbus_service_property(_interface, signature="n", access="read")
2019
2054
def LastCheckerStatus_dbus_property(self):
2020
2055
return dbus.Int16(self.last_checker_status)
2022
2057
# Expires - property
2023
2058
@dbus_service_property(_interface, signature="s", access="read")
2024
2059
def Expires_dbus_property(self):
2025
2060
return datetime_to_dbus(self.expires)
2027
2062
# LastApprovalRequest - property
2028
2063
@dbus_service_property(_interface, signature="s", access="read")
2029
2064
def LastApprovalRequest_dbus_property(self):
2030
2065
return datetime_to_dbus(self.last_approval_request)
2032
2067
# Timeout - property
2033
2068
@dbus_service_property(_interface,
2155
2190
class ClientHandler(socketserver.BaseRequestHandler, object):
2156
2191
"""A class to handle client connections.
2158
2193
Instantiated once for each connection to handle it.
2159
2194
Note: This will run in its own forked process."""
2161
2196
def handle(self):
2162
2197
with contextlib.closing(self.server.child_pipe) as child_pipe:
2163
2198
logger.info("TCP connection from: %s",
2164
2199
str(self.client_address))
2165
2200
logger.debug("Pipe FD: %d",
2166
2201
self.server.child_pipe.fileno())
2168
2203
session = gnutls.ClientSession(self.request)
2170
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
2171
# "+AES-256-CBC", "+SHA1",
2172
# "+COMP-NULL", "+CTYPE-OPENPGP",
2205
# priority = ':'.join(("NONE", "+VERS-TLS1.1",
2206
# "+AES-256-CBC", "+SHA1",
2207
# "+COMP-NULL", "+CTYPE-OPENPGP",
2174
2209
# Use a fallback default, since this MUST be set.
2175
2210
priority = self.server.gnutls_priority
2176
2211
if priority is None:
2177
2212
priority = "NORMAL"
2178
gnutls.priority_set_direct(session._c_object, priority,
2213
gnutls.priority_set_direct(session._c_object,
2214
priority.encode("utf-8"),
2181
2217
# Start communication using the Mandos protocol
2182
2218
# Get protocol number
2183
2219
line = self.request.makefile().readline()
2348
2384
class MultiprocessingMixIn(object):
2349
2385
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
2351
2387
def sub_process_main(self, request, address):
2353
2389
self.finish_request(request, address)
2354
2390
except Exception:
2355
2391
self.handle_error(request, address)
2356
2392
self.close_request(request)
2358
2394
def process_request(self, request, address):
2359
2395
"""Start a new process to process the request."""
2360
proc = multiprocessing.Process(target = self.sub_process_main,
2361
args = (request, address))
2396
proc = multiprocessing.Process(target=self.sub_process_main,
2397
args=(request, address))
2366
2402
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
2367
2403
""" adds a pipe to the MixIn """
2369
2405
def process_request(self, request, client_address):
2370
2406
"""Overrides and wraps the original process_request().
2372
2408
This function creates a new pipe in self.pipe
2374
2410
parent_pipe, self.child_pipe = multiprocessing.Pipe()
2376
2412
proc = MultiprocessingMixIn.process_request(self, request,
2377
2413
client_address)
2378
2414
self.child_pipe.close()
2379
2415
self.add_pipe(parent_pipe, proc)
2381
2417
def add_pipe(self, parent_pipe, proc):
2382
2418
"""Dummy function; override as necessary"""
2383
2419
raise NotImplementedError()
2431
2468
# socket_wrapper(), if socketfd was set.
2432
2469
socketserver.TCPServer.__init__(self, server_address,
2433
2470
RequestHandlerClass)
2435
2472
def server_bind(self):
2436
2473
"""This overrides the normal server_bind() function
2437
2474
to bind to an interface if one was specified, and also NOT to
2438
2475
bind to an address or port if they were not specified."""
2476
global SO_BINDTODEVICE
2439
2477
if self.interface is not None:
2440
2478
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)
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)
2462
2500
# Only bind(2) the socket if we really need to.
2463
2501
if self.server_address[0] or self.server_address[1]:
2464
2502
if not self.server_address[0]:
2465
2503
if self.address_family == socket.AF_INET6:
2466
any_address = "::" # in6addr_any
2504
any_address = "::" # in6addr_any
2468
any_address = "0.0.0.0" # INADDR_ANY
2506
any_address = "0.0.0.0" # INADDR_ANY
2469
2507
self.server_address = (any_address,
2470
2508
self.server_address[1])
2471
2509
elif not self.server_address[1]:
2505
2543
self.gnutls_priority = gnutls_priority
2506
2544
IPv6_TCPServer.__init__(self, server_address,
2507
2545
RequestHandlerClass,
2508
interface = interface,
2509
use_ipv6 = use_ipv6,
2510
socketfd = socketfd)
2546
interface=interface,
2512
2550
def server_activate(self):
2513
2551
if self.enabled:
2514
2552
return socketserver.TCPServer.server_activate(self)
2516
2554
def enable(self):
2517
2555
self.enabled = True
2519
2557
def add_pipe(self, parent_pipe, proc):
2520
2558
# Call "handle_ipc" for both data and EOF events
2521
GObject.io_add_watch(
2522
2560
parent_pipe.fileno(),
2523
GObject.IO_IN | GObject.IO_HUP,
2561
GLib.IO_IN | GLib.IO_HUP,
2524
2562
functools.partial(self.handle_ipc,
2525
parent_pipe = parent_pipe,
2563
parent_pipe=parent_pipe,
2528
2566
def handle_ipc(self, source, condition,
2529
2567
parent_pipe=None,
2531
2569
client_object=None):
2532
2570
# error, or the other end of multiprocessing.Pipe has closed
2533
if condition & (GObject.IO_ERR | GObject.IO_HUP):
2571
if condition & (GLib.IO_ERR | GLib.IO_HUP):
2534
2572
# Wait for other process to exit
2538
2576
# Read a request from the child
2539
2577
request = parent_pipe.recv()
2540
2578
command = request[0]
2542
2580
if command == 'init':
2581
fpr = request[1].decode("ascii")
2544
2582
address = request[2]
2546
2584
for c in self.clients.values():
2547
2585
if c.fingerprint == fpr:
2612
2650
>>> rfc3339_duration_to_delta("P1DT3M20S")
2613
2651
datetime.timedelta(1, 200)
2616
2654
# Parsing an RFC 3339 duration with regular expressions is not
2617
2655
# possible - there would have to be multiple places for the same
2618
2656
# values, like seconds. The current code, while more esoteric, is
2619
2657
# cleaner without depending on a parsing library. If Python had a
2620
2658
# built-in library for parsing we would use it, but we'd like to
2621
2659
# avoid excessive use of external libraries.
2623
2661
# New type for defining tokens, syntax, and semantics all-in-one
2624
2662
Token = collections.namedtuple("Token", (
2625
2663
"regexp", # To match token; if "value" is not None, must have
2809
2850
parser.add_argument("--no-zeroconf", action="store_false",
2810
2851
dest="zeroconf", help="Do not use Zeroconf",
2813
2854
options = parser.parse_args()
2815
2856
if options.check:
2817
2858
fail_count, test_count = doctest.testmod()
2818
2859
sys.exit(os.EX_OK if fail_count == 0 else 1)
2820
2861
# 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",
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",
2839
2880
# Parse config file for server-global settings
2840
2881
server_config = configparser.SafeConfigParser(server_defaults)
2841
2882
del server_defaults
2970
3012
.format(uid, gid, os.strerror(error.errno)))
2971
3013
if error.errno != errno.EPERM:
2975
3017
# Enable all possible GnuTLS debugging
2977
3019
# "Use a log level over 10 to enable all debugging options."
2978
3020
# - GnuTLS manual
2979
3021
gnutls.global_set_log_level(11)
2981
3023
@gnutls.log_func
2982
3024
def debug_gnutls(level, string):
2983
3025
logger.debug("GnuTLS: %s", string[:-1])
2985
3027
gnutls.global_set_log_function(debug_gnutls)
2987
3029
# Redirect stdin so all checkers get /dev/null
2988
3030
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2989
3031
os.dup2(null, sys.stdin.fileno())
2993
3035
# Need to fork before connecting to D-Bus
2994
3036
if not foreground:
2995
3037
# Close all input and output, do double fork, etc.
2998
# multiprocessing will use threads, so before we use GObject we
2999
# need to inform GObject that threads will be used.
3000
GObject.threads_init()
3040
# multiprocessing will use threads, so before we use GLib we need
3041
# to inform GLib that threads will be used.
3002
3044
global main_loop
3003
3045
# From the Avahi example code
3004
3046
DBusGMainLoop(set_as_default=True)
3005
main_loop = GObject.MainLoop()
3047
main_loop = GLib.MainLoop()
3006
3048
bus = dbus.SystemBus()
3007
3049
# End of Avahi example code
3022
3064
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
3023
3065
service = AvahiServiceToSyslog(
3024
name = server_settings["servicename"],
3025
servicetype = "_mandos._tcp",
3026
protocol = protocol,
3066
name=server_settings["servicename"],
3067
servicetype="_mandos._tcp",
3028
3070
if server_settings["interface"]:
3029
3071
service.interface = if_nametoindex(
3030
3072
server_settings["interface"].encode("utf-8"))
3032
3074
global multiprocessing_manager
3033
3075
multiprocessing_manager = multiprocessing.Manager()
3035
3077
client_class = Client
3037
client_class = functools.partial(ClientDBus, bus = bus)
3079
client_class = functools.partial(ClientDBus, bus=bus)
3039
3081
client_settings = Client.config_parser(client_config)
3040
3082
old_client_settings = {}
3041
3083
clients_data = {}
3043
3085
# This is used to redirect stdout and stderr for checker processes
3045
wnull = open(os.devnull, "w") # A writable /dev/null
3087
wnull = open(os.devnull, "w") # A writable /dev/null
3046
3088
# Only used if server is running in foreground but not in debug
3048
3090
if debug or not foreground:
3051
3093
# Get client data and settings from last running state.
3052
3094
if server_settings["restore"]:
3054
3096
with open(stored_state_path, "rb") as stored_state:
3055
if sys.version_info.major == 2:
3097
if sys.version_info.major == 2:
3056
3098
clients_data, old_client_settings = pickle.load(
3059
3101
bytes_clients_data, bytes_old_client_settings = (
3060
pickle.load(stored_state, encoding = "bytes"))
3061
### Fix bytes to strings
3102
pickle.load(stored_state, encoding="bytes"))
3103
# Fix bytes to strings
3064
clients_data = { (key.decode("utf-8")
3065
if isinstance(key, bytes)
3068
bytes_clients_data.items() }
3106
clients_data = {(key.decode("utf-8")
3107
if isinstance(key, bytes)
3110
bytes_clients_data.items()}
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()}
3136
del bytes_old_client_settings
3094
3138
for value in old_client_settings.values():
3095
value["host"] = value["host"].decode("utf-8")
3139
if isinstance(value["host"], bytes):
3140
value["host"] = (value["host"]
3096
3142
os.remove(stored_state_path)
3097
3143
except IOError as e:
3098
3144
if e.errno == errno.ENOENT:
3198
3244
pidfilename, pid)
3200
3246
del pidfilename
3202
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
3203
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
3248
for termsig in (signal.SIGHUP, signal.SIGTERM):
3249
GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig,
3250
lambda: main_loop.quit() and False)
3207
3254
@alternate_dbus_interfaces(
3208
{ "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
3255
{"se.recompile.Mandos": "se.bsnet.fukt.Mandos"})
3209
3256
class MandosDBusService(DBusObjectWithObjectManager):
3210
3257
"""A D-Bus proxy object"""
3212
3259
def __init__(self):
3213
3260
dbus.service.Object.__init__(self, bus, "/")
3215
3262
_interface = "se.recompile.Mandos"
3217
3264
@dbus.service.signal(_interface, signature="o")
3218
3265
def ClientAdded(self, objpath):
3222
3269
@dbus.service.signal(_interface, signature="ss")
3223
3270
def ClientNotFound(self, fingerprint, address):
3227
3274
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3229
3276
@dbus.service.signal(_interface, signature="os")
3230
3277
def ClientRemoved(self, objpath, name):
3234
3281
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3236
3283
@dbus.service.method(_interface, out_signature="ao")
3264
3311
self.client_removed_signal(c)
3266
3313
raise KeyError(object_path)
3270
3317
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
3271
out_signature = "a{oa{sa{sv}}}")
3318
out_signature="a{oa{sa{sv}}}")
3272
3319
def GetManagedObjects(self):
3273
3320
"""D-Bus method"""
3274
3321
return dbus.Dictionary(
3275
{ client.dbus_object_path:
3277
{ interface: client.GetAll(interface)
3279
client._get_all_interface_names()})
3280
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()})
3282
3329
def client_added_signal(self, client):
3283
3330
"""Send the new standard signal and the old signal"""
3325
3377
client.encrypted_secret = pgp.encrypt(client.secret,
3327
3379
client_dict = {}
3329
3381
# A list of attributes that can not be pickled
3331
exclude = { "bus", "changedstate", "secret",
3332
"checker", "server_settings" }
3383
exclude = {"bus", "changedstate", "secret",
3384
"checker", "server_settings"}
3333
3385
for name, typ in inspect.getmembers(dbus.service
3335
3387
exclude.add(name)
3337
3389
client_dict["encrypted_secret"] = (client
3338
3390
.encrypted_secret)
3339
3391
for attr in client.client_structure:
3340
3392
if attr not in exclude:
3341
3393
client_dict[attr] = getattr(client, attr)
3343
3395
clients[client.name] = client_dict
3344
3396
del client_settings[client.name]["secret"]
3347
3399
with tempfile.NamedTemporaryFile(