2
2
# -*- mode: python; coding: utf-8 -*-
4
4
# Mandos server - give out binary blobs to connecting clients.
6
6
# This program is partly derived from an example program for an Avahi
7
7
# service publisher, downloaded from
8
8
# <http://avahi.org/wiki/PythonPublishExample>. This includes the
9
9
# methods "add", "remove", "server_state_changed",
10
10
# "entry_group_state_changed", "cleanup", and "activate" in the
11
11
# "AvahiService" class, and some lines in "main".
13
13
# Everything else is
14
14
# Copyright © 2008-2016 Teddy Hogeborn
15
15
# Copyright © 2008-2016 Björn Påhlsson
17
17
# This program is free software: you can redistribute it and/or modify
18
18
# it under the terms of the GNU General Public License as published by
19
19
# the Free Software Foundation, either version 3 of the License, or
78
81
import dbus.service
82
from gi.repository import GObject as gobject
82
from gi.repository import GLib
84
83
from dbus.mainloop.glib import DBusGMainLoop
87
86
import xml.dom.minidom
89
# Try to find the value of SO_BINDTODEVICE:
91
# This is where SO_BINDTODEVICE is in Python 3.3 (or 3.4?) and
92
# newer, and it is also the most natural place for it:
91
93
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
92
94
except AttributeError:
96
# This is where SO_BINDTODEVICE was up to and including Python
94
98
from IN import SO_BINDTODEVICE
95
99
except ImportError:
96
SO_BINDTODEVICE = None
100
# In Python 2.7 it seems to have been removed entirely.
101
# Try running the C preprocessor:
103
cc = subprocess.Popen(["cc", "--language=c", "-E",
105
stdin=subprocess.PIPE,
106
stdout=subprocess.PIPE)
107
stdout = cc.communicate(
108
"#include <sys/socket.h>\nSO_BINDTODEVICE\n")[0]
109
SO_BINDTODEVICE = int(stdout.splitlines()[-1])
110
except (OSError, ValueError, IndexError):
112
SO_BINDTODEVICE = None
98
114
if sys.version_info.major == 2:
102
118
stored_state_file = "clients.pickle"
104
120
logger = logging.getLogger()
119
135
return interface_index
138
def copy_function(func):
139
"""Make a copy of a function"""
140
if sys.version_info.major == 2:
141
return types.FunctionType(func.func_code,
147
return types.FunctionType(func.__code__,
122
154
def initlogger(debug, level=logging.WARNING):
123
155
"""init logger and add loglevel"""
126
158
syslogger = (logging.handlers.SysLogHandler(
127
facility = logging.handlers.SysLogHandler.LOG_DAEMON,
128
address = "/dev/log"))
159
facility=logging.handlers.SysLogHandler.LOG_DAEMON,
129
161
syslogger.setFormatter(logging.Formatter
130
162
('Mandos [%(process)d]: %(levelname)s:'
132
164
logger.addHandler(syslogger)
135
167
console = logging.StreamHandler()
136
168
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
212
246
'--passphrase-file',
214
248
+ self.gnupgargs,
215
stdin = subprocess.PIPE,
216
stdout = subprocess.PIPE,
217
stderr = subprocess.PIPE)
218
ciphertext, err = proc.communicate(input = data)
249
stdin=subprocess.PIPE,
250
stdout=subprocess.PIPE,
251
stderr=subprocess.PIPE)
252
ciphertext, err = proc.communicate(input=data)
219
253
if proc.returncode != 0:
220
254
raise PGPError(err)
221
255
return ciphertext
223
257
def decrypt(self, data, password):
224
258
passphrase = self.password_encode(password)
225
259
with tempfile.NamedTemporaryFile(
226
dir = self.tempdir) as passfile:
260
dir=self.tempdir) as passfile:
227
261
passfile.write(passphrase)
229
263
proc = subprocess.Popen([self.gpg, '--decrypt',
230
264
'--passphrase-file',
232
266
+ self.gnupgargs,
233
stdin = subprocess.PIPE,
234
stdout = subprocess.PIPE,
235
stderr = subprocess.PIPE)
236
decrypted_plaintext, err = proc.communicate(input = data)
267
stdin=subprocess.PIPE,
268
stdout=subprocess.PIPE,
269
stderr=subprocess.PIPE)
270
decrypted_plaintext, err = proc.communicate(input=data)
237
271
if proc.returncode != 0:
238
272
raise PGPError(err)
239
273
return decrypted_plaintext
276
# Pretend that we have an Avahi module
278
"""This isn't so much a class as it is a module-like namespace.
279
It is instantiated once, and simulates having an Avahi module."""
280
IF_UNSPEC = -1 # avahi-common/address.h
281
PROTO_UNSPEC = -1 # avahi-common/address.h
282
PROTO_INET = 0 # avahi-common/address.h
283
PROTO_INET6 = 1 # avahi-common/address.h
284
DBUS_NAME = "org.freedesktop.Avahi"
285
DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"
286
DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
287
DBUS_PATH_SERVER = "/"
289
def string_array_to_txt_array(self, t):
290
return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
291
for s in t), signature="ay")
292
ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h
293
ENTRY_GROUP_COLLISION = 3 # avahi-common/defs.h
294
ENTRY_GROUP_FAILURE = 4 # avahi-common/defs.h
295
SERVER_INVALID = 0 # avahi-common/defs.h
296
SERVER_REGISTERING = 1 # avahi-common/defs.h
297
SERVER_RUNNING = 2 # avahi-common/defs.h
298
SERVER_COLLISION = 3 # avahi-common/defs.h
299
SERVER_FAILURE = 4 # avahi-common/defs.h
242
303
class AvahiError(Exception):
243
304
def __init__(self, value, *args, **kwargs):
244
305
self.value = value
469
535
CRD_CERTIFICATE = 1
470
536
E_NO_CERTIFICATE_FOUND = -49
471
537
OPENPGP_FMT_RAW = 0 # gnutls/openpgp.h
474
540
class session_int(ctypes.Structure):
476
542
session_t = ctypes.POINTER(session_int)
477
544
class certificate_credentials_st(ctypes.Structure):
479
546
certificate_credentials_t = ctypes.POINTER(
480
547
certificate_credentials_st)
481
548
certificate_type_t = ctypes.c_int
482
550
class datum_t(ctypes.Structure):
483
551
_fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
484
552
('size', ctypes.c_uint)]
485
554
class openpgp_crt_int(ctypes.Structure):
487
556
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
488
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
557
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
489
558
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
490
credentials_type_t = ctypes.c_int #
559
credentials_type_t = ctypes.c_int
491
560
transport_ptr_t = ctypes.c_void_p
492
561
close_request_t = ctypes.c_int
495
564
class Error(Exception):
496
565
# We need to use the class name "GnuTLS" here, since this
497
566
# exception might be raised from within GnuTLS.__init__,
498
567
# which is called before the assignment to the "gnutls"
499
568
# global variable has happened.
500
def __init__(self, message = None, code = None, args=()):
569
def __init__(self, message=None, code=None, args=()):
501
570
# Default usage is by a message string, but if a return
502
571
# code is passed, convert it to a string with
503
572
# gnutls.strerror()
572
641
return _error_code(result)
573
642
result = func(*arguments)
576
645
# Unless otherwise indicated, the function declarations below are
577
646
# all from the gnutls/gnutls.h C header file.
580
649
priority_set_direct = _library.gnutls_priority_set_direct
581
650
priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
582
651
ctypes.POINTER(ctypes.c_char_p)]
583
652
priority_set_direct.restype = _error_code
585
654
init = _library.gnutls_init
586
655
init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
587
656
init.restype = _error_code
589
658
set_default_priority = _library.gnutls_set_default_priority
590
659
set_default_priority.argtypes = [session_t]
591
660
set_default_priority.restype = _error_code
593
662
record_send = _library.gnutls_record_send
594
663
record_send.argtypes = [session_t, ctypes.c_void_p,
596
665
record_send.restype = ctypes.c_ssize_t
597
666
record_send.errcheck = _retry_on_error
599
668
certificate_allocate_credentials = (
600
669
_library.gnutls_certificate_allocate_credentials)
601
670
certificate_allocate_credentials.argtypes = [
602
671
ctypes.POINTER(certificate_credentials_t)]
603
672
certificate_allocate_credentials.restype = _error_code
605
674
certificate_free_credentials = (
606
675
_library.gnutls_certificate_free_credentials)
607
certificate_free_credentials.argtypes = [certificate_credentials_t]
676
certificate_free_credentials.argtypes = [
677
certificate_credentials_t]
608
678
certificate_free_credentials.restype = None
610
680
handshake_set_private_extensions = (
611
681
_library.gnutls_handshake_set_private_extensions)
612
682
handshake_set_private_extensions.argtypes = [session_t,
614
684
handshake_set_private_extensions.restype = None
616
686
credentials_set = _library.gnutls_credentials_set
617
687
credentials_set.argtypes = [session_t, credentials_type_t,
619
689
credentials_set.restype = _error_code
621
691
strerror = _library.gnutls_strerror
622
692
strerror.argtypes = [ctypes.c_int]
623
693
strerror.restype = ctypes.c_char_p
625
695
certificate_type_get = _library.gnutls_certificate_type_get
626
696
certificate_type_get.argtypes = [session_t]
627
697
certificate_type_get.restype = _error_code
629
699
certificate_get_peers = _library.gnutls_certificate_get_peers
630
700
certificate_get_peers.argtypes = [session_t,
631
701
ctypes.POINTER(ctypes.c_uint)]
632
702
certificate_get_peers.restype = ctypes.POINTER(datum_t)
634
704
global_set_log_level = _library.gnutls_global_set_log_level
635
705
global_set_log_level.argtypes = [ctypes.c_int]
636
706
global_set_log_level.restype = None
638
708
global_set_log_function = _library.gnutls_global_set_log_function
639
709
global_set_log_function.argtypes = [log_func]
640
710
global_set_log_function.restype = None
642
712
deinit = _library.gnutls_deinit
643
713
deinit.argtypes = [session_t]
644
714
deinit.restype = None
646
716
handshake = _library.gnutls_handshake
647
717
handshake.argtypes = [session_t]
648
718
handshake.restype = _error_code
649
719
handshake.errcheck = _retry_on_error
651
721
transport_set_ptr = _library.gnutls_transport_set_ptr
652
722
transport_set_ptr.argtypes = [session_t, transport_ptr_t]
653
723
transport_set_ptr.restype = None
655
725
bye = _library.gnutls_bye
656
726
bye.argtypes = [session_t, close_request_t]
657
727
bye.restype = _error_code
658
728
bye.errcheck = _retry_on_error
660
730
check_version = _library.gnutls_check_version
661
731
check_version.argtypes = [ctypes.c_char_p]
662
732
check_version.restype = ctypes.c_char_p
664
734
# All the function declarations below are from gnutls/openpgp.h
666
736
openpgp_crt_init = _library.gnutls_openpgp_crt_init
667
737
openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
668
738
openpgp_crt_init.restype = _error_code
670
740
openpgp_crt_import = _library.gnutls_openpgp_crt_import
671
741
openpgp_crt_import.argtypes = [openpgp_crt_t,
672
742
ctypes.POINTER(datum_t),
673
743
openpgp_crt_fmt_t]
674
744
openpgp_crt_import.restype = _error_code
676
746
openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
677
747
openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
678
748
ctypes.POINTER(ctypes.c_uint)]
679
749
openpgp_crt_verify_self.restype = _error_code
681
751
openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
682
752
openpgp_crt_deinit.argtypes = [openpgp_crt_t]
683
753
openpgp_crt_deinit.restype = None
685
755
openpgp_crt_get_fingerprint = (
686
756
_library.gnutls_openpgp_crt_get_fingerprint)
687
757
openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
715
787
checker: subprocess.Popen(); a running checker process used
716
788
to see if the client lives.
717
789
'None' if no process is running.
718
checker_callback_tag: a gobject event source tag, or None
790
checker_callback_tag: a GLib event source tag, or None
719
791
checker_command: string; External command which is run to check
720
792
if client lives. %() expansions are done at
721
793
runtime with vars(self) as dict, so that for
722
794
instance %(name)s can be used in the command.
723
checker_initiator_tag: a gobject event source tag, or None
795
checker_initiator_tag: a GLib event source tag, or None
724
796
created: datetime.datetime(); (UTC) object creation
725
797
client_structure: Object describing what attributes a client has
726
798
and is used for storing the client at exit
727
799
current_checker_command: string; current running checker_command
728
disable_initiator_tag: a gobject event source tag, or None
800
disable_initiator_tag: a GLib event source tag, or None
730
802
fingerprint: string (40 or 32 hexadecimal digits); used to
731
803
uniquely identify the client
780
852
for client_name in config.sections():
781
853
section = dict(config.items(client_name))
782
854
client = settings[client_name] = {}
784
856
client["host"] = section["host"]
785
857
# Reformat values from string types to Python types
786
858
client["approved_by_default"] = config.getboolean(
787
859
client_name, "approved_by_default")
788
860
client["enabled"] = config.getboolean(client_name,
791
863
# Uppercase and remove spaces from fingerprint for later
792
864
# comparison purposes with return value from the
793
865
# fingerprint() function
794
866
client["fingerprint"] = (section["fingerprint"].upper()
795
867
.replace(" ", ""))
796
868
if "secret" in section:
797
client["secret"] = section["secret"].decode("base64")
869
client["secret"] = codecs.decode(section["secret"]
798
872
elif "secfile" in section:
799
873
with open(os.path.expanduser(os.path.expandvars
800
874
(section["secfile"])),
853
927
self.changedstate = multiprocessing_manager.Condition(
854
928
multiprocessing_manager.Lock())
855
929
self.client_structure = [attr
856
for attr in self.__dict__.iterkeys()
930
for attr in self.__dict__.keys()
857
931
if not attr.startswith("_")]
858
932
self.client_structure.append("client_structure")
860
934
for name, t in inspect.getmembers(
861
935
type(self), lambda obj: isinstance(obj, property)):
862
936
if not name.startswith("_"):
863
937
self.client_structure.append(name)
865
939
# Send notice to process children that client state has changed
866
940
def send_changedstate(self):
867
941
with self.changedstate:
868
942
self.changedstate.notify_all()
870
944
def enable(self):
871
945
"""Start this client's checker and timeout hooks"""
872
946
if getattr(self, "enabled", False):
886
960
logger.info("Disabling client %s", self.name)
887
961
if getattr(self, "disable_initiator_tag", None) is not None:
888
gobject.source_remove(self.disable_initiator_tag)
962
GLib.source_remove(self.disable_initiator_tag)
889
963
self.disable_initiator_tag = None
890
964
self.expires = None
891
965
if getattr(self, "checker_initiator_tag", None) is not None:
892
gobject.source_remove(self.checker_initiator_tag)
966
GLib.source_remove(self.checker_initiator_tag)
893
967
self.checker_initiator_tag = None
894
968
self.stop_checker()
895
969
self.enabled = False
897
971
self.send_changedstate()
898
# 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
901
975
def __del__(self):
904
978
def init_checker(self):
905
979
# Schedule a new checker to be started an 'interval' from now,
906
980
# and every interval from then on.
907
981
if self.checker_initiator_tag is not None:
908
gobject.source_remove(self.checker_initiator_tag)
909
self.checker_initiator_tag = gobject.timeout_add(
982
GLib.source_remove(self.checker_initiator_tag)
983
self.checker_initiator_tag = GLib.timeout_add(
910
984
int(self.interval.total_seconds() * 1000),
911
985
self.start_checker)
912
986
# Schedule a disable() when 'timeout' has passed
913
987
if self.disable_initiator_tag is not None:
914
gobject.source_remove(self.disable_initiator_tag)
915
self.disable_initiator_tag = gobject.timeout_add(
988
GLib.source_remove(self.disable_initiator_tag)
989
self.disable_initiator_tag = GLib.timeout_add(
916
990
int(self.timeout.total_seconds() * 1000), self.disable)
917
991
# Also start a new checker *right now*.
918
992
self.start_checker()
920
994
def checker_callback(self, source, condition, connection,
922
996
"""The checker has completed, so take appropriate actions."""
941
1015
logger.warning("Checker for %(name)s crashed?",
945
1019
def checked_ok(self):
946
1020
"""Assert that the client has been seen, alive and well."""
947
1021
self.last_checked_ok = datetime.datetime.utcnow()
948
1022
self.last_checker_status = 0
949
1023
self.last_checker_signal = None
950
1024
self.bump_timeout()
952
1026
def bump_timeout(self, timeout=None):
953
1027
"""Bump up the timeout for this client."""
954
1028
if timeout is None:
955
1029
timeout = self.timeout
956
1030
if self.disable_initiator_tag is not None:
957
gobject.source_remove(self.disable_initiator_tag)
1031
GLib.source_remove(self.disable_initiator_tag)
958
1032
self.disable_initiator_tag = None
959
1033
if getattr(self, "enabled", False):
960
self.disable_initiator_tag = gobject.timeout_add(
1034
self.disable_initiator_tag = GLib.timeout_add(
961
1035
int(timeout.total_seconds() * 1000), self.disable)
962
1036
self.expires = datetime.datetime.utcnow() + timeout
964
1038
def need_approval(self):
965
1039
self.last_approval_request = datetime.datetime.utcnow()
967
1041
def start_checker(self):
968
1042
"""Start a new checker subprocess if one is not running.
970
1044
If a checker already exists, leave it running and do
972
1046
# The reason for not killing a running checker is that if we
1005
1079
# The exception is when not debugging but nevertheless
1006
1080
# running in the foreground; use the previously
1007
1081
# created wnull.
1008
popen_args = { "close_fds": True,
1082
popen_args = {"close_fds": True,
1011
1085
if (not self.server_settings["debug"]
1012
1086
and self.server_settings["foreground"]):
1013
1087
popen_args.update({"stdout": wnull,
1015
pipe = multiprocessing.Pipe(duplex = False)
1089
pipe = multiprocessing.Pipe(duplex=False)
1016
1090
self.checker = multiprocessing.Process(
1018
args = (pipe[1], subprocess.call, command),
1019
kwargs = popen_args)
1092
args=(pipe[1], subprocess.call, command),
1020
1094
self.checker.start()
1021
self.checker_callback_tag = gobject.io_add_watch(
1022
pipe[0].fileno(), gobject.IO_IN,
1095
self.checker_callback_tag = GLib.io_add_watch(
1096
pipe[0].fileno(), GLib.IO_IN,
1023
1097
self.checker_callback, pipe[0], command)
1024
# Re-run this periodically if run by gobject.timeout_add
1098
# Re-run this periodically if run by GLib.timeout_add
1027
1101
def stop_checker(self):
1028
1102
"""Force the checker process, if any, to stop."""
1029
1103
if self.checker_callback_tag:
1030
gobject.source_remove(self.checker_callback_tag)
1104
GLib.source_remove(self.checker_callback_tag)
1031
1105
self.checker_callback_tag = None
1032
1106
if getattr(self, "checker", None) is None:
1064
1138
func._dbus_name = func.__name__
1065
1139
if func._dbus_name.endswith("_dbus_property"):
1066
1140
func._dbus_name = func._dbus_name[:-14]
1067
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
1141
func._dbus_get_args_options = {'byte_arrays': byte_arrays}
1070
1144
return decorator
1073
1147
def dbus_interface_annotations(dbus_interface):
1074
1148
"""Decorator for marking functions returning interface annotations
1078
1152
@dbus_interface_annotations("org.example.Interface")
1079
1153
def _foo(self): # Function name does not matter
1080
1154
return {"org.freedesktop.DBus.Deprecated": "true",
1081
1155
"org.freedesktop.DBus.Property.EmitsChangedSignal":
1085
1159
def decorator(func):
1086
1160
func._dbus_is_interface = True
1087
1161
func._dbus_interface = dbus_interface
1088
1162
func._dbus_name = dbus_interface
1091
1165
return decorator
1094
1168
def dbus_annotations(annotations):
1095
1169
"""Decorator to annotate D-Bus methods, signals or properties
1098
1172
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
1099
1173
"org.freedesktop.DBus.Property."
1100
1174
"EmitsChangedSignal": "false"})
1156
1230
for cls in self.__class__.__mro__
1157
1231
for name, athing in
1158
1232
inspect.getmembers(cls, self._is_dbus_thing(thing)))
1160
1234
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1161
out_signature = "s",
1162
path_keyword = 'object_path',
1163
connection_keyword = 'connection')
1236
path_keyword='object_path',
1237
connection_keyword='connection')
1164
1238
def Introspect(self, object_path, connection):
1165
1239
"""Overloading of standard D-Bus method.
1167
1241
Inserts annotation tags on methods and signals.
1169
1243
xmlstring = dbus.service.Object.Introspect(self, object_path,
1172
1246
document = xml.dom.minidom.parseString(xmlstring)
1174
1248
for if_tag in document.getElementsByTagName("interface"):
1175
1249
# Add annotation tags
1176
1250
for typ in ("method", "signal"):
1392
1466
except AttributeError:
1393
1467
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1395
1470
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1396
1471
"""A D-Bus object with an ObjectManager.
1398
1473
Classes inheriting from this exposes the standard
1399
1474
GetManagedObjects call and the InterfacesAdded and
1400
1475
InterfacesRemoved signals on the standard
1401
1476
"org.freedesktop.DBus.ObjectManager" interface.
1403
1478
Note: No signals are sent automatically; they must be sent
1406
1481
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1407
out_signature = "a{oa{sa{sv}}}")
1482
out_signature="a{oa{sa{sv}}}")
1408
1483
def GetManagedObjects(self):
1409
1484
"""This function must be overridden"""
1410
1485
raise NotImplementedError()
1412
1487
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1413
signature = "oa{sa{sv}}")
1488
signature="oa{sa{sv}}")
1414
1489
def InterfacesAdded(self, object_path, interfaces_and_properties):
1417
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
1492
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature="oas")
1418
1493
def InterfacesRemoved(self, object_path, interfaces):
1421
1496
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1422
out_signature = "s",
1423
path_keyword = 'object_path',
1424
connection_keyword = 'connection')
1498
path_keyword='object_path',
1499
connection_keyword='connection')
1425
1500
def Introspect(self, object_path, connection):
1426
1501
"""Overloading of standard D-Bus method.
1428
1503
Override return argument name of GetManagedObjects to be
1429
1504
"objpath_interfaces_and_properties"
1468
1544
dbus.service.Object, it will add alternate D-Bus attributes with
1469
1545
interface names according to the "alt_interface_names" mapping.
1472
1548
@alternate_dbus_interfaces({"org.example.Interface":
1473
1549
"net.example.AlternateInterface"})
1474
1550
class SampleDBusObject(dbus.service.Object):
1475
1551
@dbus.service.method("org.example.Interface")
1476
1552
def SampleDBusMethod():
1479
1555
The above "SampleDBusMethod" on "SampleDBusObject" will be
1480
1556
reachable via two interfaces: "org.example.Interface" and
1481
1557
"net.example.AlternateInterface", the latter of which will have
1482
1558
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1483
1559
"true", unless "deprecate" is passed with a False value.
1485
1561
This works for methods and signals, and also for D-Bus properties
1486
1562
(from DBusObjectWithProperties) and interfaces (from the
1487
1563
dbus_interface_annotations decorator).
1490
1566
def wrapper(cls):
1491
1567
for orig_interface_name, alt_interface_name in (
1492
1568
alt_interface_names.items()):
1507
1583
interface_names.add(alt_interface)
1508
1584
# Is this a D-Bus signal?
1509
1585
if getattr(attribute, "_dbus_is_signal", False):
1586
# Extract the original non-method undecorated
1587
# function by black magic
1510
1588
if sys.version_info.major == 2:
1511
# Extract the original non-method undecorated
1512
# function by black magic
1513
1589
nonmethod_func = (dict(
1514
1590
zip(attribute.func_code.co_freevars,
1515
1591
attribute.__closure__))
1516
1592
["func"].cell_contents)
1518
nonmethod_func = attribute
1594
nonmethod_func = (dict(
1595
zip(attribute.__code__.co_freevars,
1596
attribute.__closure__))
1597
["func"].cell_contents)
1519
1598
# Create a new, but exactly alike, function
1520
1599
# object, and decorate it to be a new D-Bus signal
1521
1600
# with the alternate D-Bus interface name
1522
if sys.version_info.major == 2:
1523
new_function = types.FunctionType(
1524
nonmethod_func.func_code,
1525
nonmethod_func.func_globals,
1526
nonmethod_func.func_name,
1527
nonmethod_func.func_defaults,
1528
nonmethod_func.func_closure)
1530
new_function = types.FunctionType(
1531
nonmethod_func.__code__,
1532
nonmethod_func.__globals__,
1533
nonmethod_func.__name__,
1534
nonmethod_func.__defaults__,
1535
nonmethod_func.__closure__)
1601
new_function = copy_function(nonmethod_func)
1536
1602
new_function = (dbus.service.signal(
1538
1604
attribute._dbus_signature)(new_function))
1653
1712
"se.bsnet.fukt.Mandos"})
1654
1713
class ClientDBus(Client, DBusObjectWithProperties):
1655
1714
"""A Client class using D-Bus
1658
1717
dbus_object_path: dbus.ObjectPath
1659
1718
bus: dbus.SystemBus()
1662
1721
runtime_expansions = (Client.runtime_expansions
1663
1722
+ ("dbus_object_path", ))
1665
1724
_interface = "se.recompile.Mandos.Client"
1667
1726
# dbus.service.Object doesn't use super(), so we can't either.
1669
def __init__(self, bus = None, *args, **kwargs):
1728
def __init__(self, bus=None, *args, **kwargs):
1671
1730
Client.__init__(self, *args, **kwargs)
1672
1731
# Only now, when this client is initialized, can it show up on
1709
1768
dbus_value = transform_func(
1710
1769
type_func(value),
1711
variant_level = variant_level)
1770
variant_level=variant_level)
1712
1771
self.PropertyChanged(dbus.String(dbus_name),
1714
1773
self.PropertiesChanged(
1716
dbus.Dictionary({ dbus.String(dbus_name):
1775
dbus.Dictionary({dbus.String(dbus_name):
1719
1778
setattr(self, attrname, value)
1721
1780
return property(lambda self: getattr(self, attrname), setter)
1723
1782
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1724
1783
approvals_pending = notifychangeproperty(dbus.Boolean,
1725
1784
"ApprovalPending",
1727
1786
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1728
1787
last_enabled = notifychangeproperty(datetime_to_dbus,
1730
1789
checker = notifychangeproperty(
1731
1790
dbus.Boolean, "CheckerRunning",
1732
type_func = lambda checker: checker is not None)
1791
type_func=lambda checker: checker is not None)
1733
1792
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1734
1793
"LastCheckedOK")
1735
1794
last_checker_status = notifychangeproperty(dbus.Int16,
1740
1799
"ApprovedByDefault")
1741
1800
approval_delay = notifychangeproperty(
1742
1801
dbus.UInt64, "ApprovalDelay",
1743
type_func = lambda td: td.total_seconds() * 1000)
1802
type_func=lambda td: td.total_seconds() * 1000)
1744
1803
approval_duration = notifychangeproperty(
1745
1804
dbus.UInt64, "ApprovalDuration",
1746
type_func = lambda td: td.total_seconds() * 1000)
1805
type_func=lambda td: td.total_seconds() * 1000)
1747
1806
host = notifychangeproperty(dbus.String, "Host")
1748
1807
timeout = notifychangeproperty(
1749
1808
dbus.UInt64, "Timeout",
1750
type_func = lambda td: td.total_seconds() * 1000)
1809
type_func=lambda td: td.total_seconds() * 1000)
1751
1810
extended_timeout = notifychangeproperty(
1752
1811
dbus.UInt64, "ExtendedTimeout",
1753
type_func = lambda td: td.total_seconds() * 1000)
1812
type_func=lambda td: td.total_seconds() * 1000)
1754
1813
interval = notifychangeproperty(
1755
1814
dbus.UInt64, "Interval",
1756
type_func = lambda td: td.total_seconds() * 1000)
1815
type_func=lambda td: td.total_seconds() * 1000)
1757
1816
checker_command = notifychangeproperty(dbus.String, "Checker")
1758
1817
secret = notifychangeproperty(dbus.ByteArray, "Secret",
1759
1818
invalidate_only=True)
1761
1820
del notifychangeproperty
1763
1822
def __del__(self, *args, **kwargs):
1765
1824
self.remove_from_connection()
1800
1859
# Emit D-Bus signal
1801
1860
self.CheckerStarted(self.current_checker_command)
1804
1863
def _reset_approved(self):
1805
1864
self.approved = None
1808
1867
def approve(self, value=True):
1809
1868
self.approved = value
1810
gobject.timeout_add(int(self.approval_duration.total_seconds()
1811
* 1000), self._reset_approved)
1869
GLib.timeout_add(int(self.approval_duration.total_seconds()
1870
* 1000), self._reset_approved)
1812
1871
self.send_changedstate()
1814
## D-Bus methods, signals & properties
1873
# D-Bus methods, signals & properties
1820
1879
# CheckerCompleted - signal
1821
1880
@dbus.service.signal(_interface, signature="nxs")
1822
1881
def CheckerCompleted(self, exitcode, waitstatus, command):
1826
1885
# CheckerStarted - signal
1827
1886
@dbus.service.signal(_interface, signature="s")
1828
1887
def CheckerStarted(self, command):
1832
1891
# PropertyChanged - signal
1833
1892
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1834
1893
@dbus.service.signal(_interface, signature="sv")
1835
1894
def PropertyChanged(self, property, value):
1839
1898
# GotSecret - signal
1840
1899
@dbus.service.signal(_interface)
1841
1900
def GotSecret(self):
1844
1903
server to mandos-client
1848
1907
# Rejected - signal
1849
1908
@dbus.service.signal(_interface, signature="s")
1850
1909
def Rejected(self, reason):
1854
1913
# NeedApproval - signal
1855
1914
@dbus.service.signal(_interface, signature="tb")
1856
1915
def NeedApproval(self, timeout, default):
1858
1917
return self.need_approval()
1862
1921
# Approve - method
1863
1922
@dbus.service.method(_interface, in_signature="b")
1864
1923
def Approve(self, value):
1865
1924
self.approve(value)
1867
1926
# CheckedOK - method
1868
1927
@dbus.service.method(_interface)
1869
1928
def CheckedOK(self):
1870
1929
self.checked_ok()
1872
1931
# Enable - method
1873
1932
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1874
1933
@dbus.service.method(_interface)
1875
1934
def Enable(self):
1879
1938
# StartChecker - method
1880
1939
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1881
1940
@dbus.service.method(_interface)
1882
1941
def StartChecker(self):
1884
1943
self.start_checker()
1886
1945
# Disable - method
1887
1946
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1888
1947
@dbus.service.method(_interface)
1889
1948
def Disable(self):
1893
1952
# StopChecker - method
1894
1953
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1895
1954
@dbus.service.method(_interface)
1896
1955
def StopChecker(self):
1897
1956
self.stop_checker()
1901
1960
# ApprovalPending - property
1902
1961
@dbus_service_property(_interface, signature="b", access="read")
1903
1962
def ApprovalPending_dbus_property(self):
1904
1963
return dbus.Boolean(bool(self.approvals_pending))
1906
1965
# ApprovedByDefault - property
1907
1966
@dbus_service_property(_interface,
1988
2047
self.checked_ok()
1990
2049
return datetime_to_dbus(self.last_checked_ok)
1992
2051
# LastCheckerStatus - property
1993
2052
@dbus_service_property(_interface, signature="n", access="read")
1994
2053
def LastCheckerStatus_dbus_property(self):
1995
2054
return dbus.Int16(self.last_checker_status)
1997
2056
# Expires - property
1998
2057
@dbus_service_property(_interface, signature="s", access="read")
1999
2058
def Expires_dbus_property(self):
2000
2059
return datetime_to_dbus(self.expires)
2002
2061
# LastApprovalRequest - property
2003
2062
@dbus_service_property(_interface, signature="s", access="read")
2004
2063
def LastApprovalRequest_dbus_property(self):
2005
2064
return datetime_to_dbus(self.last_approval_request)
2007
2066
# Timeout - property
2008
2067
@dbus_service_property(_interface,
2130
2189
class ClientHandler(socketserver.BaseRequestHandler, object):
2131
2190
"""A class to handle client connections.
2133
2192
Instantiated once for each connection to handle it.
2134
2193
Note: This will run in its own forked process."""
2136
2195
def handle(self):
2137
2196
with contextlib.closing(self.server.child_pipe) as child_pipe:
2138
2197
logger.info("TCP connection from: %s",
2139
2198
str(self.client_address))
2140
2199
logger.debug("Pipe FD: %d",
2141
2200
self.server.child_pipe.fileno())
2143
2202
session = gnutls.ClientSession(self.request)
2145
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
2146
# "+AES-256-CBC", "+SHA1",
2147
# "+COMP-NULL", "+CTYPE-OPENPGP",
2204
# priority = ':'.join(("NONE", "+VERS-TLS1.1",
2205
# "+AES-256-CBC", "+SHA1",
2206
# "+COMP-NULL", "+CTYPE-OPENPGP",
2149
2208
# Use a fallback default, since this MUST be set.
2150
2209
priority = self.server.gnutls_priority
2151
2210
if priority is None:
2152
2211
priority = "NORMAL"
2153
gnutls.priority_set_direct(session._c_object, priority,
2212
gnutls.priority_set_direct(session._c_object,
2213
priority.encode("utf-8"),
2156
2216
# Start communication using the Mandos protocol
2157
2217
# Get protocol number
2158
2218
line = self.request.makefile().readline()
2323
2383
class MultiprocessingMixIn(object):
2324
2384
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
2326
2386
def sub_process_main(self, request, address):
2328
2388
self.finish_request(request, address)
2329
2389
except Exception:
2330
2390
self.handle_error(request, address)
2331
2391
self.close_request(request)
2333
2393
def process_request(self, request, address):
2334
2394
"""Start a new process to process the request."""
2335
proc = multiprocessing.Process(target = self.sub_process_main,
2336
args = (request, address))
2395
proc = multiprocessing.Process(target=self.sub_process_main,
2396
args=(request, address))
2341
2401
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
2342
2402
""" adds a pipe to the MixIn """
2344
2404
def process_request(self, request, client_address):
2345
2405
"""Overrides and wraps the original process_request().
2347
2407
This function creates a new pipe in self.pipe
2349
2409
parent_pipe, self.child_pipe = multiprocessing.Pipe()
2351
2411
proc = MultiprocessingMixIn.process_request(self, request,
2352
2412
client_address)
2353
2413
self.child_pipe.close()
2354
2414
self.add_pipe(parent_pipe, proc)
2356
2416
def add_pipe(self, parent_pipe, proc):
2357
2417
"""Dummy function; override as necessary"""
2358
2418
raise NotImplementedError()
2406
2467
# socket_wrapper(), if socketfd was set.
2407
2468
socketserver.TCPServer.__init__(self, server_address,
2408
2469
RequestHandlerClass)
2410
2471
def server_bind(self):
2411
2472
"""This overrides the normal server_bind() function
2412
2473
to bind to an interface if one was specified, and also NOT to
2413
2474
bind to an address or port if they were not specified."""
2475
global SO_BINDTODEVICE
2414
2476
if self.interface is not None:
2415
2477
if SO_BINDTODEVICE is None:
2416
logger.error("SO_BINDTODEVICE does not exist;"
2417
" cannot bind to interface %s",
2421
self.socket.setsockopt(
2422
socket.SOL_SOCKET, SO_BINDTODEVICE,
2423
(self.interface + "\0").encode("utf-8"))
2424
except socket.error as error:
2425
if error.errno == errno.EPERM:
2426
logger.error("No permission to bind to"
2427
" interface %s", self.interface)
2428
elif error.errno == errno.ENOPROTOOPT:
2429
logger.error("SO_BINDTODEVICE not available;"
2430
" cannot bind to interface %s",
2432
elif error.errno == errno.ENODEV:
2433
logger.error("Interface %s does not exist,"
2434
" cannot bind", self.interface)
2478
# Fall back to a hard-coded value which seems to be
2480
logger.warning("SO_BINDTODEVICE not found, trying 25")
2481
SO_BINDTODEVICE = 25
2483
self.socket.setsockopt(
2484
socket.SOL_SOCKET, SO_BINDTODEVICE,
2485
(self.interface + "\0").encode("utf-8"))
2486
except socket.error as error:
2487
if error.errno == errno.EPERM:
2488
logger.error("No permission to bind to"
2489
" interface %s", self.interface)
2490
elif error.errno == errno.ENOPROTOOPT:
2491
logger.error("SO_BINDTODEVICE not available;"
2492
" cannot bind to interface %s",
2494
elif error.errno == errno.ENODEV:
2495
logger.error("Interface %s does not exist,"
2496
" cannot bind", self.interface)
2437
2499
# Only bind(2) the socket if we really need to.
2438
2500
if self.server_address[0] or self.server_address[1]:
2439
2501
if not self.server_address[0]:
2440
2502
if self.address_family == socket.AF_INET6:
2441
any_address = "::" # in6addr_any
2503
any_address = "::" # in6addr_any
2443
any_address = "0.0.0.0" # INADDR_ANY
2505
any_address = "0.0.0.0" # INADDR_ANY
2444
2506
self.server_address = (any_address,
2445
2507
self.server_address[1])
2446
2508
elif not self.server_address[1]:
2480
2542
self.gnutls_priority = gnutls_priority
2481
2543
IPv6_TCPServer.__init__(self, server_address,
2482
2544
RequestHandlerClass,
2483
interface = interface,
2484
use_ipv6 = use_ipv6,
2485
socketfd = socketfd)
2545
interface=interface,
2487
2549
def server_activate(self):
2488
2550
if self.enabled:
2489
2551
return socketserver.TCPServer.server_activate(self)
2491
2553
def enable(self):
2492
2554
self.enabled = True
2494
2556
def add_pipe(self, parent_pipe, proc):
2495
2557
# Call "handle_ipc" for both data and EOF events
2496
gobject.io_add_watch(
2497
2559
parent_pipe.fileno(),
2498
gobject.IO_IN | gobject.IO_HUP,
2560
GLib.IO_IN | GLib.IO_HUP,
2499
2561
functools.partial(self.handle_ipc,
2500
parent_pipe = parent_pipe,
2562
parent_pipe=parent_pipe,
2503
2565
def handle_ipc(self, source, condition,
2504
2566
parent_pipe=None,
2506
2568
client_object=None):
2507
2569
# error, or the other end of multiprocessing.Pipe has closed
2508
if condition & (gobject.IO_ERR | gobject.IO_HUP):
2570
if condition & (GLib.IO_ERR | GLib.IO_HUP):
2509
2571
# Wait for other process to exit
2513
2575
# Read a request from the child
2514
2576
request = parent_pipe.recv()
2515
2577
command = request[0]
2517
2579
if command == 'init':
2518
2580
fpr = request[1]
2519
2581
address = request[2]
2521
for c in self.clients.itervalues():
2583
for c in self.clients.values():
2522
2584
if c.fingerprint == fpr:
2587
2649
>>> rfc3339_duration_to_delta("P1DT3M20S")
2588
2650
datetime.timedelta(1, 200)
2591
2653
# Parsing an RFC 3339 duration with regular expressions is not
2592
2654
# possible - there would have to be multiple places for the same
2593
2655
# values, like seconds. The current code, while more esoteric, is
2594
2656
# cleaner without depending on a parsing library. If Python had a
2595
2657
# built-in library for parsing we would use it, but we'd like to
2596
2658
# avoid excessive use of external libraries.
2598
2660
# New type for defining tokens, syntax, and semantics all-in-one
2599
2661
Token = collections.namedtuple("Token", (
2600
2662
"regexp", # To match token; if "value" is not None, must have
2784
2849
parser.add_argument("--no-zeroconf", action="store_false",
2785
2850
dest="zeroconf", help="Do not use Zeroconf",
2788
2853
options = parser.parse_args()
2790
2855
if options.check:
2792
2857
fail_count, test_count = doctest.testmod()
2793
2858
sys.exit(os.EX_OK if fail_count == 0 else 1)
2795
2860
# Default values for config file for server-global settings
2796
server_defaults = { "interface": "",
2801
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2802
":+SIGN-DSA-SHA256",
2803
"servicename": "Mandos",
2809
"statedir": "/var/lib/mandos",
2810
"foreground": "False",
2861
server_defaults = {"interface": "",
2866
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2867
":+SIGN-DSA-SHA256",
2868
"servicename": "Mandos",
2874
"statedir": "/var/lib/mandos",
2875
"foreground": "False",
2814
2879
# Parse config file for server-global settings
2815
2880
server_config = configparser.SafeConfigParser(server_defaults)
2816
2881
del server_defaults
3006
logger.debug("Did setuid/setgid to {}:{}".format(uid,
2938
3008
except OSError as error:
3009
logger.warning("Failed to setuid/setgid to {}:{}: {}"
3010
.format(uid, gid, os.strerror(error.errno)))
2939
3011
if error.errno != errno.EPERM:
2943
3015
# Enable all possible GnuTLS debugging
2945
3017
# "Use a log level over 10 to enable all debugging options."
2946
3018
# - GnuTLS manual
2947
3019
gnutls.global_set_log_level(11)
2949
3021
@gnutls.log_func
2950
3022
def debug_gnutls(level, string):
2951
3023
logger.debug("GnuTLS: %s", string[:-1])
2953
3025
gnutls.global_set_log_function(debug_gnutls)
2955
3027
# Redirect stdin so all checkers get /dev/null
2956
3028
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2957
3029
os.dup2(null, sys.stdin.fileno())
2961
3033
# Need to fork before connecting to D-Bus
2962
3034
if not foreground:
2963
3035
# Close all input and output, do double fork, etc.
2966
# multiprocessing will use threads, so before we use gobject we
2967
# need to inform gobject that threads will be used.
2968
gobject.threads_init()
3038
# multiprocessing will use threads, so before we use GLib we need
3039
# to inform GLib that threads will be used.
2970
3042
global main_loop
2971
3043
# From the Avahi example code
2972
3044
DBusGMainLoop(set_as_default=True)
2973
main_loop = gobject.MainLoop()
3045
main_loop = GLib.MainLoop()
2974
3046
bus = dbus.SystemBus()
2975
3047
# End of Avahi example code
2990
3062
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2991
3063
service = AvahiServiceToSyslog(
2992
name = server_settings["servicename"],
2993
servicetype = "_mandos._tcp",
2994
protocol = protocol,
3064
name=server_settings["servicename"],
3065
servicetype="_mandos._tcp",
2996
3068
if server_settings["interface"]:
2997
3069
service.interface = if_nametoindex(
2998
3070
server_settings["interface"].encode("utf-8"))
3000
3072
global multiprocessing_manager
3001
3073
multiprocessing_manager = multiprocessing.Manager()
3003
3075
client_class = Client
3005
client_class = functools.partial(ClientDBus, bus = bus)
3077
client_class = functools.partial(ClientDBus, bus=bus)
3007
3079
client_settings = Client.config_parser(client_config)
3008
3080
old_client_settings = {}
3009
3081
clients_data = {}
3011
3083
# This is used to redirect stdout and stderr for checker processes
3013
wnull = open(os.devnull, "w") # A writable /dev/null
3085
wnull = open(os.devnull, "w") # A writable /dev/null
3014
3086
# Only used if server is running in foreground but not in debug
3016
3088
if debug or not foreground:
3019
3091
# Get client data and settings from last running state.
3020
3092
if server_settings["restore"]:
3022
3094
with open(stored_state_path, "rb") as stored_state:
3023
clients_data, old_client_settings = pickle.load(
3095
if sys.version_info.major == 2:
3096
clients_data, old_client_settings = pickle.load(
3099
bytes_clients_data, bytes_old_client_settings = (
3100
pickle.load(stored_state, encoding="bytes"))
3101
# Fix bytes to strings
3104
clients_data = {(key.decode("utf-8")
3105
if isinstance(key, bytes)
3108
bytes_clients_data.items()}
3109
del bytes_clients_data
3110
for key in clients_data:
3111
value = {(k.decode("utf-8")
3112
if isinstance(k, bytes) else k): v
3114
clients_data[key].items()}
3115
clients_data[key] = value
3117
value["client_structure"] = [
3119
if isinstance(s, bytes)
3121
value["client_structure"]]
3123
for k in ("name", "host"):
3124
if isinstance(value[k], bytes):
3125
value[k] = value[k].decode("utf-8")
3126
# old_client_settings
3128
old_client_settings = {
3129
(key.decode("utf-8")
3130
if isinstance(key, bytes)
3133
bytes_old_client_settings.items()}
3134
del bytes_old_client_settings
3136
for value in old_client_settings.values():
3137
if isinstance(value["host"], bytes):
3138
value["host"] = (value["host"]
3025
3140
os.remove(stored_state_path)
3026
3141
except IOError as e:
3027
3142
if e.errno == errno.ENOENT:
3127
3242
pidfilename, pid)
3129
3244
del pidfilename
3131
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
3132
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
3246
for termsig in (signal.SIGHUP, signal.SIGTERM):
3247
GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig,
3248
lambda: main_loop.quit() and False)
3136
3252
@alternate_dbus_interfaces(
3137
{ "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
3253
{"se.recompile.Mandos": "se.bsnet.fukt.Mandos"})
3138
3254
class MandosDBusService(DBusObjectWithObjectManager):
3139
3255
"""A D-Bus proxy object"""
3141
3257
def __init__(self):
3142
3258
dbus.service.Object.__init__(self, bus, "/")
3144
3260
_interface = "se.recompile.Mandos"
3146
3262
@dbus.service.signal(_interface, signature="o")
3147
3263
def ClientAdded(self, objpath):
3151
3267
@dbus.service.signal(_interface, signature="ss")
3152
3268
def ClientNotFound(self, fingerprint, address):
3156
3272
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3158
3274
@dbus.service.signal(_interface, signature="os")
3159
3275
def ClientRemoved(self, objpath, name):
3163
3279
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3165
3281
@dbus.service.method(_interface, out_signature="ao")
3166
3282
def GetAllClients(self):
3168
3284
return dbus.Array(c.dbus_object_path for c in
3169
tcp_server.clients.itervalues())
3285
tcp_server.clients.values())
3171
3287
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3173
3289
@dbus.service.method(_interface,
3193
3309
self.client_removed_signal(c)
3195
3311
raise KeyError(object_path)
3199
3315
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
3200
out_signature = "a{oa{sa{sv}}}")
3316
out_signature="a{oa{sa{sv}}}")
3201
3317
def GetManagedObjects(self):
3202
3318
"""D-Bus method"""
3203
3319
return dbus.Dictionary(
3204
{ client.dbus_object_path:
3206
{ interface: client.GetAll(interface)
3208
client._get_all_interface_names()})
3209
for client in tcp_server.clients.values()})
3320
{client.dbus_object_path:
3322
{interface: client.GetAll(interface)
3324
client._get_all_interface_names()})
3325
for client in tcp_server.clients.values()})
3211
3327
def client_added_signal(self, client):
3212
3328
"""Send the new standard signal and the old signal"""
3232
3348
self.ClientRemoved(client.dbus_object_path,
3235
3351
mandos_dbus_service = MandosDBusService()
3353
# Save modules to variables to exempt the modules from being
3354
# unloaded before the function registered with atexit() is run.
3355
mp = multiprocessing
3238
3359
"Cleanup function; run on exit"
3240
3361
service.cleanup()
3242
multiprocessing.active_children()
3363
mp.active_children()
3244
3365
if not (tcp_server.clients or client_settings):
3247
3368
# Store client before exiting. Secrets are encrypted with key
3248
3369
# based on what config file has. If config file is
3249
3370
# removed/edited, old secret will thus be unrecovable.
3251
3372
with PGPEngine() as pgp:
3252
for client in tcp_server.clients.itervalues():
3373
for client in tcp_server.clients.values():
3253
3374
key = client_settings[client.name]["secret"]
3254
3375
client.encrypted_secret = pgp.encrypt(client.secret,
3256
3377
client_dict = {}
3258
3379
# A list of attributes that can not be pickled
3260
exclude = { "bus", "changedstate", "secret",
3261
"checker", "server_settings" }
3381
exclude = {"bus", "changedstate", "secret",
3382
"checker", "server_settings"}
3262
3383
for name, typ in inspect.getmembers(dbus.service
3264
3385
exclude.add(name)
3266
3387
client_dict["encrypted_secret"] = (client
3267
3388
.encrypted_secret)
3268
3389
for attr in client.client_structure:
3269
3390
if attr not in exclude:
3270
3391
client_dict[attr] = getattr(client, attr)
3272
3393
clients[client.name] = client_dict
3273
3394
del client_settings[client.name]["secret"]
3276
3397
with tempfile.NamedTemporaryFile(