65
import cPickle as pickle
69
import cPickle as pickle
66
72
import multiprocessing
74
81
import dbus.service
82
from gi.repository import GLib
77
83
from dbus.mainloop.glib import DBusGMainLoop
80
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:
84
93
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
85
94
except AttributeError:
96
# This is where SO_BINDTODEVICE was up to and including Python
87
98
from IN import SO_BINDTODEVICE
88
99
except ImportError:
89
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
114
if sys.version_info.major == 2:
92
118
stored_state_file = "clients.pickle"
94
120
logger = logging.getLogger()
95
syslogger = (logging.handlers.SysLogHandler
96
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
97
address = str("/dev/log")))
100
if_nametoindex = (ctypes.cdll.LoadLibrary
101
(ctypes.util.find_library("c"))
124
if_nametoindex = ctypes.cdll.LoadLibrary(
125
ctypes.util.find_library("c")).if_nametoindex
103
126
except (OSError, AttributeError):
104
128
def if_nametoindex(interface):
105
129
"Get an interface index the hard way, i.e. using fcntl()"
106
130
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
107
131
with contextlib.closing(socket.socket()) as s:
108
132
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
109
struct.pack(str("16s16x"),
111
interface_index = struct.unpack(str("I"),
133
struct.pack(b"16s16x", interface))
134
interface_index = struct.unpack("I", ifreq[16:20])[0]
113
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__,
116
154
def initlogger(debug, level=logging.WARNING):
117
155
"""init logger and add loglevel"""
158
syslogger = (logging.handlers.SysLogHandler(
159
facility=logging.handlers.SysLogHandler.LOG_DAEMON,
119
161
syslogger.setFormatter(logging.Formatter
120
162
('Mandos [%(process)d]: %(levelname)s:'
122
164
logger.addHandler(syslogger)
125
167
console = logging.StreamHandler()
126
168
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
179
235
.replace(b"\n", b"\\n")
180
236
.replace(b"\0", b"\\x00"))
183
239
def encrypt(self, data, password):
184
240
passphrase = self.password_encode(password)
185
with tempfile.NamedTemporaryFile(dir=self.tempdir
241
with tempfile.NamedTemporaryFile(
242
dir=self.tempdir) as passfile:
187
243
passfile.write(passphrase)
189
proc = subprocess.Popen(['gpg', '--symmetric',
245
proc = subprocess.Popen([self.gpg, '--symmetric',
190
246
'--passphrase-file',
192
248
+ self.gnupgargs,
193
stdin = subprocess.PIPE,
194
stdout = subprocess.PIPE,
195
stderr = subprocess.PIPE)
196
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)
197
253
if proc.returncode != 0:
198
254
raise PGPError(err)
199
255
return ciphertext
201
257
def decrypt(self, data, password):
202
258
passphrase = self.password_encode(password)
203
with tempfile.NamedTemporaryFile(dir = self.tempdir
259
with tempfile.NamedTemporaryFile(
260
dir=self.tempdir) as passfile:
205
261
passfile.write(passphrase)
207
proc = subprocess.Popen(['gpg', '--decrypt',
263
proc = subprocess.Popen([self.gpg, '--decrypt',
208
264
'--passphrase-file',
210
266
+ self.gnupgargs,
211
stdin = subprocess.PIPE,
212
stdout = subprocess.PIPE,
213
stderr = subprocess.PIPE)
214
decrypted_plaintext, err = proc.communicate(input
267
stdin=subprocess.PIPE,
268
stdout=subprocess.PIPE,
269
stderr=subprocess.PIPE)
270
decrypted_plaintext, err = proc.communicate(input=data)
216
271
if proc.returncode != 0:
217
272
raise PGPError(err)
218
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
221
303
class AvahiError(Exception):
222
304
def __init__(self, value, *args, **kwargs):
223
305
self.value = value
224
super(AvahiError, self).__init__(value, *args, **kwargs)
225
def __unicode__(self):
226
return unicode(repr(self.value))
306
return super(AvahiError, self).__init__(value, *args,
228
310
class AvahiServiceError(AvahiError):
231
314
class AvahiGroupError(AvahiError):
235
318
class AvahiService(object):
236
319
"""An Avahi (Zeroconf) service.
239
322
interface: integer; avahi.IF_UNSPEC or an interface index.
240
323
Used to optionally bind to the specified interface.
383
488
follow_name_owner_changes=True),
384
489
avahi.DBUS_INTERFACE_SERVER)
385
490
self.server.connect_to_signal("StateChanged",
386
self.server_state_changed)
491
self.server_state_changed)
387
492
self.server_state_changed(self.server.GetState())
390
495
class AvahiServiceToSyslog(AvahiService):
496
def rename(self, *args, **kwargs):
392
497
"""Add the new name to the syslog messages"""
393
ret = AvahiService.rename(self)
394
syslogger.setFormatter(logging.Formatter
395
('Mandos ({0}) [%(process)d]:'
396
' %(levelname)s: %(message)s'
498
ret = AvahiService.rename(self, *args, **kwargs)
499
syslogger.setFormatter(logging.Formatter(
500
'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
401
def timedelta_to_milliseconds(td):
402
"Convert a datetime.timedelta() to milliseconds"
403
return ((td.days * 24 * 60 * 60 * 1000)
404
+ (td.seconds * 1000)
405
+ (td.microseconds // 1000))
505
# Pretend that we have a GnuTLS module
506
class GnuTLS(object):
507
"""This isn't so much a class as it is a module-like namespace.
508
It is instantiated once, and simulates having a GnuTLS module."""
510
_library = ctypes.cdll.LoadLibrary(
511
ctypes.util.find_library("gnutls"))
512
_need_version = b"3.3.0"
515
# Need to use class name "GnuTLS" here, since this method is
516
# called before the assignment to the "gnutls" global variable
518
if GnuTLS.check_version(self._need_version) is None:
519
raise GnuTLS.Error("Needs GnuTLS {} or later"
520
.format(self._need_version))
522
# Unless otherwise indicated, the constants and types below are
523
# all from the gnutls/gnutls.h C header file.
533
E_NO_CERTIFICATE_FOUND = -49
534
OPENPGP_FMT_RAW = 0 # gnutls/openpgp.h
537
class session_int(ctypes.Structure):
539
session_t = ctypes.POINTER(session_int)
541
class certificate_credentials_st(ctypes.Structure):
543
certificate_credentials_t = ctypes.POINTER(
544
certificate_credentials_st)
545
certificate_type_t = ctypes.c_int
547
class datum_t(ctypes.Structure):
548
_fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
549
('size', ctypes.c_uint)]
551
class openpgp_crt_int(ctypes.Structure):
553
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
554
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
555
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
556
credentials_type_t = ctypes.c_int
557
transport_ptr_t = ctypes.c_void_p
558
close_request_t = ctypes.c_int
561
class Error(Exception):
562
# We need to use the class name "GnuTLS" here, since this
563
# exception might be raised from within GnuTLS.__init__,
564
# which is called before the assignment to the "gnutls"
565
# global variable has happened.
566
def __init__(self, message=None, code=None, args=()):
567
# Default usage is by a message string, but if a return
568
# code is passed, convert it to a string with
571
if message is None and code is not None:
572
message = GnuTLS.strerror(code)
573
return super(GnuTLS.Error, self).__init__(
576
class CertificateSecurityError(Error):
580
class Credentials(object):
582
self._c_object = gnutls.certificate_credentials_t()
583
gnutls.certificate_allocate_credentials(
584
ctypes.byref(self._c_object))
585
self.type = gnutls.CRD_CERTIFICATE
588
gnutls.certificate_free_credentials(self._c_object)
590
class ClientSession(object):
591
def __init__(self, socket, credentials=None):
592
self._c_object = gnutls.session_t()
593
gnutls.init(ctypes.byref(self._c_object), gnutls.CLIENT)
594
gnutls.set_default_priority(self._c_object)
595
gnutls.transport_set_ptr(self._c_object, socket.fileno())
596
gnutls.handshake_set_private_extensions(self._c_object,
599
if credentials is None:
600
credentials = gnutls.Credentials()
601
gnutls.credentials_set(self._c_object, credentials.type,
602
ctypes.cast(credentials._c_object,
604
self.credentials = credentials
607
gnutls.deinit(self._c_object)
610
return gnutls.handshake(self._c_object)
612
def send(self, data):
616
data_len -= gnutls.record_send(self._c_object,
621
return gnutls.bye(self._c_object, gnutls.SHUT_RDWR)
623
# Error handling functions
624
def _error_code(result):
625
"""A function to raise exceptions on errors, suitable
626
for the 'restype' attribute on ctypes functions"""
629
if result == gnutls.E_NO_CERTIFICATE_FOUND:
630
raise gnutls.CertificateSecurityError(code=result)
631
raise gnutls.Error(code=result)
633
def _retry_on_error(result, func, arguments):
634
"""A function to retry on some errors, suitable
635
for the 'errcheck' attribute on ctypes functions"""
637
if result not in (gnutls.E_INTERRUPTED, gnutls.E_AGAIN):
638
return _error_code(result)
639
result = func(*arguments)
642
# Unless otherwise indicated, the function declarations below are
643
# all from the gnutls/gnutls.h C header file.
646
priority_set_direct = _library.gnutls_priority_set_direct
647
priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
648
ctypes.POINTER(ctypes.c_char_p)]
649
priority_set_direct.restype = _error_code
651
init = _library.gnutls_init
652
init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
653
init.restype = _error_code
655
set_default_priority = _library.gnutls_set_default_priority
656
set_default_priority.argtypes = [session_t]
657
set_default_priority.restype = _error_code
659
record_send = _library.gnutls_record_send
660
record_send.argtypes = [session_t, ctypes.c_void_p,
662
record_send.restype = ctypes.c_ssize_t
663
record_send.errcheck = _retry_on_error
665
certificate_allocate_credentials = (
666
_library.gnutls_certificate_allocate_credentials)
667
certificate_allocate_credentials.argtypes = [
668
ctypes.POINTER(certificate_credentials_t)]
669
certificate_allocate_credentials.restype = _error_code
671
certificate_free_credentials = (
672
_library.gnutls_certificate_free_credentials)
673
certificate_free_credentials.argtypes = [
674
certificate_credentials_t]
675
certificate_free_credentials.restype = None
677
handshake_set_private_extensions = (
678
_library.gnutls_handshake_set_private_extensions)
679
handshake_set_private_extensions.argtypes = [session_t,
681
handshake_set_private_extensions.restype = None
683
credentials_set = _library.gnutls_credentials_set
684
credentials_set.argtypes = [session_t, credentials_type_t,
686
credentials_set.restype = _error_code
688
strerror = _library.gnutls_strerror
689
strerror.argtypes = [ctypes.c_int]
690
strerror.restype = ctypes.c_char_p
692
certificate_type_get = _library.gnutls_certificate_type_get
693
certificate_type_get.argtypes = [session_t]
694
certificate_type_get.restype = _error_code
696
certificate_get_peers = _library.gnutls_certificate_get_peers
697
certificate_get_peers.argtypes = [session_t,
698
ctypes.POINTER(ctypes.c_uint)]
699
certificate_get_peers.restype = ctypes.POINTER(datum_t)
701
global_set_log_level = _library.gnutls_global_set_log_level
702
global_set_log_level.argtypes = [ctypes.c_int]
703
global_set_log_level.restype = None
705
global_set_log_function = _library.gnutls_global_set_log_function
706
global_set_log_function.argtypes = [log_func]
707
global_set_log_function.restype = None
709
deinit = _library.gnutls_deinit
710
deinit.argtypes = [session_t]
711
deinit.restype = None
713
handshake = _library.gnutls_handshake
714
handshake.argtypes = [session_t]
715
handshake.restype = _error_code
716
handshake.errcheck = _retry_on_error
718
transport_set_ptr = _library.gnutls_transport_set_ptr
719
transport_set_ptr.argtypes = [session_t, transport_ptr_t]
720
transport_set_ptr.restype = None
722
bye = _library.gnutls_bye
723
bye.argtypes = [session_t, close_request_t]
724
bye.restype = _error_code
725
bye.errcheck = _retry_on_error
727
check_version = _library.gnutls_check_version
728
check_version.argtypes = [ctypes.c_char_p]
729
check_version.restype = ctypes.c_char_p
731
# All the function declarations below are from gnutls/openpgp.h
733
openpgp_crt_init = _library.gnutls_openpgp_crt_init
734
openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
735
openpgp_crt_init.restype = _error_code
737
openpgp_crt_import = _library.gnutls_openpgp_crt_import
738
openpgp_crt_import.argtypes = [openpgp_crt_t,
739
ctypes.POINTER(datum_t),
741
openpgp_crt_import.restype = _error_code
743
openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
744
openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
745
ctypes.POINTER(ctypes.c_uint)]
746
openpgp_crt_verify_self.restype = _error_code
748
openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
749
openpgp_crt_deinit.argtypes = [openpgp_crt_t]
750
openpgp_crt_deinit.restype = None
752
openpgp_crt_get_fingerprint = (
753
_library.gnutls_openpgp_crt_get_fingerprint)
754
openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
758
openpgp_crt_get_fingerprint.restype = _error_code
760
# Remove non-public functions
761
del _error_code, _retry_on_error
762
# Create the global "gnutls" object, simulating a module
766
def call_pipe(connection, # : multiprocessing.Connection
767
func, *args, **kwargs):
768
"""This function is meant to be called by multiprocessing.Process
770
This function runs func(*args, **kwargs), and writes the resulting
771
return value on the provided multiprocessing.Connection.
773
connection.send(func(*args, **kwargs))
408
777
class Client(object):
409
778
"""A representation of a client host served by this server.
412
781
approved: bool(); 'None' if not yet approved/disapproved
413
782
approval_delay: datetime.timedelta(); Time to wait for approval
448
819
disabled, or None
449
820
server_settings: The server_settings dict from main()
452
823
runtime_expansions = ("approval_delay", "approval_duration",
453
824
"created", "enabled", "expires",
454
825
"fingerprint", "host", "interval",
455
826
"last_approval_request", "last_checked_ok",
456
827
"last_enabled", "name", "timeout")
457
client_defaults = { "timeout": "PT5M",
458
"extended_timeout": "PT15M",
460
"checker": "fping -q -- %%(host)s",
462
"approval_delay": "PT0S",
463
"approval_duration": "PT1S",
464
"approved_by_default": "True",
468
def timeout_milliseconds(self):
469
"Return the 'timeout' attribute in milliseconds"
470
return timedelta_to_milliseconds(self.timeout)
472
def extended_timeout_milliseconds(self):
473
"Return the 'extended_timeout' attribute in milliseconds"
474
return timedelta_to_milliseconds(self.extended_timeout)
476
def interval_milliseconds(self):
477
"Return the 'interval' attribute in milliseconds"
478
return timedelta_to_milliseconds(self.interval)
480
def approval_delay_milliseconds(self):
481
return timedelta_to_milliseconds(self.approval_delay)
830
"extended_timeout": "PT15M",
832
"checker": "fping -q -- %%(host)s",
834
"approval_delay": "PT0S",
835
"approval_duration": "PT1S",
836
"approved_by_default": "True",
484
841
def config_parser(config):
485
842
"""Construct a new dict of client settings of this form:
601
957
logger.info("Disabling client %s", self.name)
602
958
if getattr(self, "disable_initiator_tag", None) is not None:
603
gobject.source_remove(self.disable_initiator_tag)
959
GLib.source_remove(self.disable_initiator_tag)
604
960
self.disable_initiator_tag = None
605
961
self.expires = None
606
962
if getattr(self, "checker_initiator_tag", None) is not None:
607
gobject.source_remove(self.checker_initiator_tag)
963
GLib.source_remove(self.checker_initiator_tag)
608
964
self.checker_initiator_tag = None
609
965
self.stop_checker()
610
966
self.enabled = False
612
968
self.send_changedstate()
613
# Do not run this again if called by a gobject.timeout_add
969
# Do not run this again if called by a GLib.timeout_add
616
972
def __del__(self):
619
975
def init_checker(self):
620
976
# Schedule a new checker to be started an 'interval' from now,
621
977
# and every interval from then on.
622
978
if self.checker_initiator_tag is not None:
623
gobject.source_remove(self.checker_initiator_tag)
624
self.checker_initiator_tag = (gobject.timeout_add
625
(self.interval_milliseconds(),
979
GLib.source_remove(self.checker_initiator_tag)
980
self.checker_initiator_tag = GLib.timeout_add(
981
int(self.interval.total_seconds() * 1000),
627
983
# Schedule a disable() when 'timeout' has passed
628
984
if self.disable_initiator_tag is not None:
629
gobject.source_remove(self.disable_initiator_tag)
630
self.disable_initiator_tag = (gobject.timeout_add
631
(self.timeout_milliseconds(),
985
GLib.source_remove(self.disable_initiator_tag)
986
self.disable_initiator_tag = GLib.timeout_add(
987
int(self.timeout.total_seconds() * 1000), self.disable)
633
988
# Also start a new checker *right now*.
634
989
self.start_checker()
636
def checker_callback(self, pid, condition, command):
991
def checker_callback(self, source, condition, connection,
637
993
"""The checker has completed, so take appropriate actions."""
638
994
self.checker_callback_tag = None
639
995
self.checker = None
640
if os.WIFEXITED(condition):
641
self.last_checker_status = os.WEXITSTATUS(condition)
996
# Read return code from connection (see call_pipe)
997
returncode = connection.recv()
1001
self.last_checker_status = returncode
1002
self.last_checker_signal = None
642
1003
if self.last_checker_status == 0:
643
1004
logger.info("Checker for %(name)s succeeded",
645
1006
self.checked_ok()
647
logger.info("Checker for %(name)s failed",
1008
logger.info("Checker for %(name)s failed", vars(self))
650
1010
self.last_checker_status = -1
1011
self.last_checker_signal = -returncode
651
1012
logger.warning("Checker for %(name)s crashed?",
654
1016
def checked_ok(self):
655
1017
"""Assert that the client has been seen, alive and well."""
656
1018
self.last_checked_ok = datetime.datetime.utcnow()
657
1019
self.last_checker_status = 0
1020
self.last_checker_signal = None
658
1021
self.bump_timeout()
660
1023
def bump_timeout(self, timeout=None):
661
1024
"""Bump up the timeout for this client."""
662
1025
if timeout is None:
663
1026
timeout = self.timeout
664
1027
if self.disable_initiator_tag is not None:
665
gobject.source_remove(self.disable_initiator_tag)
1028
GLib.source_remove(self.disable_initiator_tag)
666
1029
self.disable_initiator_tag = None
667
1030
if getattr(self, "enabled", False):
668
self.disable_initiator_tag = (gobject.timeout_add
669
(timedelta_to_milliseconds
670
(timeout), self.disable))
1031
self.disable_initiator_tag = GLib.timeout_add(
1032
int(timeout.total_seconds() * 1000), self.disable)
671
1033
self.expires = datetime.datetime.utcnow() + timeout
673
1035
def need_approval(self):
674
1036
self.last_approval_request = datetime.datetime.utcnow()
676
1038
def start_checker(self):
677
1039
"""Start a new checker subprocess if one is not running.
679
1041
If a checker already exists, leave it running and do
681
1043
# The reason for not killing a running checker is that if we
686
1048
# checkers alone, the checker would have to take more time
687
1049
# than 'timeout' for the client to be disabled, which is as it
690
# If a checker exists, make sure it is not a zombie
692
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
693
except AttributeError:
695
except OSError as error:
696
if error.errno != errno.ECHILD:
700
logger.warning("Checker was a zombie")
701
gobject.source_remove(self.checker_callback_tag)
702
self.checker_callback(pid, status,
703
self.current_checker_command)
1052
if self.checker is not None and not self.checker.is_alive():
1053
logger.warning("Checker was not alive; joining")
704
1056
# Start a new checker if needed
705
1057
if self.checker is None:
706
1058
# Escape attributes for the shell
707
escaped_attrs = dict(
708
(attr, re.escape(unicode(getattr(self, attr))))
710
self.runtime_expansions)
1060
attr: re.escape(str(getattr(self, attr)))
1061
for attr in self.runtime_expansions}
712
1063
command = self.checker_command % escaped_attrs
713
1064
except TypeError as error:
714
1065
logger.error('Could not format string "%s"',
715
self.checker_command, exc_info=error)
716
return True # Try again later
1066
self.checker_command,
1068
return True # Try again later
717
1069
self.current_checker_command = command
719
logger.info("Starting checker %r for %s",
721
# We don't need to redirect stdout and stderr, since
722
# in normal mode, that is already done by daemon(),
723
# and in debug mode we don't want to. (Stdin is
724
# always replaced by /dev/null.)
725
# The exception is when not debugging but nevertheless
726
# running in the foreground; use the previously
729
if (not self.server_settings["debug"]
730
and self.server_settings["foreground"]):
731
popen_args.update({"stdout": wnull,
733
self.checker = subprocess.Popen(command,
737
except OSError as error:
738
logger.error("Failed to start subprocess",
741
self.checker_callback_tag = (gobject.child_watch_add
743
self.checker_callback,
745
# The checker may have completed before the gobject
746
# watch was added. Check for this.
748
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
749
except OSError as error:
750
if error.errno == errno.ECHILD:
751
# This should never happen
752
logger.error("Child process vanished",
757
gobject.source_remove(self.checker_callback_tag)
758
self.checker_callback(pid, status, command)
759
# Re-run this periodically if run by gobject.timeout_add
1070
logger.info("Starting checker %r for %s", command,
1072
# We don't need to redirect stdout and stderr, since
1073
# in normal mode, that is already done by daemon(),
1074
# and in debug mode we don't want to. (Stdin is
1075
# always replaced by /dev/null.)
1076
# The exception is when not debugging but nevertheless
1077
# running in the foreground; use the previously
1079
popen_args = {"close_fds": True,
1082
if (not self.server_settings["debug"]
1083
and self.server_settings["foreground"]):
1084
popen_args.update({"stdout": wnull,
1086
pipe = multiprocessing.Pipe(duplex=False)
1087
self.checker = multiprocessing.Process(
1089
args=(pipe[1], subprocess.call, command),
1091
self.checker.start()
1092
self.checker_callback_tag = GLib.io_add_watch(
1093
pipe[0].fileno(), GLib.IO_IN,
1094
self.checker_callback, pipe[0], command)
1095
# Re-run this periodically if run by GLib.timeout_add
762
1098
def stop_checker(self):
763
1099
"""Force the checker process, if any, to stop."""
764
1100
if self.checker_callback_tag:
765
gobject.source_remove(self.checker_callback_tag)
1101
GLib.source_remove(self.checker_callback_tag)
766
1102
self.checker_callback_tag = None
767
1103
if getattr(self, "checker", None) is None:
769
1105
logger.debug("Stopping checker for %(name)s", vars(self))
771
self.checker.terminate()
773
#if self.checker.poll() is None:
774
# self.checker.kill()
775
except OSError as error:
776
if error.errno != errno.ESRCH: # No such process
1106
self.checker.terminate()
778
1107
self.checker = None
781
def dbus_service_property(dbus_interface, signature="v",
782
access="readwrite", byte_arrays=False):
1110
def dbus_service_property(dbus_interface,
783
1114
"""Decorators for marking methods of a DBusObjectWithProperties to
784
1115
become properties on the D-Bus.
786
1117
The decorated method will be called with no arguments by "Get"
787
1118
and with one argument by "Set".
789
1120
The parameters, where they are supported, are the same as
790
1121
dbus.service.method, except there is only "signature", since the
791
1122
type from Get() and the type sent to Set() is the same.
867
class DBusObjectWithProperties(dbus.service.Object):
868
"""A D-Bus object with properties.
870
Classes inheriting from this can use the dbus_service_property
871
decorator to expose methods as D-Bus properties. It exposes the
872
standard Get(), Set(), and GetAll() methods on the D-Bus.
1205
class DBusObjectWithAnnotations(dbus.service.Object):
1206
"""A D-Bus object with annotations.
1208
Classes inheriting from this can use the dbus_annotations
1209
decorator to add annotations to methods or signals.
876
1213
def _is_dbus_thing(thing):
877
1214
"""Returns a function testing if an attribute is a D-Bus thing
879
1216
If called like _is_dbus_thing("method") it returns a function
880
1217
suitable for use as predicate to inspect.getmembers().
882
return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
1219
return lambda obj: getattr(obj, "_dbus_is_{}".format(thing),
885
1222
def _get_all_dbus_things(self, thing):
886
1223
"""Returns a generator of (name, attribute) pairs
888
return ((getattr(athing.__get__(self), "_dbus_name",
1225
return ((getattr(athing.__get__(self), "_dbus_name", name),
890
1226
athing.__get__(self))
891
1227
for cls in self.__class__.__mro__
892
1228
for name, athing in
893
inspect.getmembers(cls,
894
self._is_dbus_thing(thing)))
1229
inspect.getmembers(cls, self._is_dbus_thing(thing)))
1231
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1233
path_keyword='object_path',
1234
connection_keyword='connection')
1235
def Introspect(self, object_path, connection):
1236
"""Overloading of standard D-Bus method.
1238
Inserts annotation tags on methods and signals.
1240
xmlstring = dbus.service.Object.Introspect(self, object_path,
1243
document = xml.dom.minidom.parseString(xmlstring)
1245
for if_tag in document.getElementsByTagName("interface"):
1246
# Add annotation tags
1247
for typ in ("method", "signal"):
1248
for tag in if_tag.getElementsByTagName(typ):
1250
for name, prop in (self.
1251
_get_all_dbus_things(typ)):
1252
if (name == tag.getAttribute("name")
1253
and prop._dbus_interface
1254
== if_tag.getAttribute("name")):
1255
annots.update(getattr(
1256
prop, "_dbus_annotations", {}))
1257
for name, value in annots.items():
1258
ann_tag = document.createElement(
1260
ann_tag.setAttribute("name", name)
1261
ann_tag.setAttribute("value", value)
1262
tag.appendChild(ann_tag)
1263
# Add interface annotation tags
1264
for annotation, value in dict(
1265
itertools.chain.from_iterable(
1266
annotations().items()
1267
for name, annotations
1268
in self._get_all_dbus_things("interface")
1269
if name == if_tag.getAttribute("name")
1271
ann_tag = document.createElement("annotation")
1272
ann_tag.setAttribute("name", annotation)
1273
ann_tag.setAttribute("value", value)
1274
if_tag.appendChild(ann_tag)
1275
# Fix argument name for the Introspect method itself
1276
if (if_tag.getAttribute("name")
1277
== dbus.INTROSPECTABLE_IFACE):
1278
for cn in if_tag.getElementsByTagName("method"):
1279
if cn.getAttribute("name") == "Introspect":
1280
for arg in cn.getElementsByTagName("arg"):
1281
if (arg.getAttribute("direction")
1283
arg.setAttribute("name",
1285
xmlstring = document.toxml("utf-8")
1287
except (AttributeError, xml.dom.DOMException,
1288
xml.parsers.expat.ExpatError) as error:
1289
logger.error("Failed to override Introspection method",
1294
class DBusObjectWithProperties(DBusObjectWithAnnotations):
1295
"""A D-Bus object with properties.
1297
Classes inheriting from this can use the dbus_service_property
1298
decorator to expose methods as D-Bus properties. It exposes the
1299
standard Get(), Set(), and GetAll() methods on the D-Bus.
896
1302
def _get_dbus_property(self, interface_name, property_name):
897
1303
"""Returns a bound method if one exists which is a D-Bus
898
1304
property with the specified name and interface.
900
for cls in self.__class__.__mro__:
901
for name, value in (inspect.getmembers
903
self._is_dbus_thing("property"))):
1306
for cls in self.__class__.__mro__:
1307
for name, value in inspect.getmembers(
1308
cls, self._is_dbus_thing("property")):
904
1309
if (value._dbus_name == property_name
905
1310
and value._dbus_interface == interface_name):
906
1311
return value.__get__(self)
908
1313
# No such property
909
raise DBusPropertyNotFound(self.dbus_object_path + ":"
910
+ interface_name + "."
913
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
1314
raise DBusPropertyNotFound("{}:{}.{}".format(
1315
self.dbus_object_path, interface_name, property_name))
1318
def _get_all_interface_names(cls):
1319
"""Get a sequence of all interfaces supported by an object"""
1320
return (name for name in set(getattr(getattr(x, attr),
1321
"_dbus_interface", None)
1322
for x in (inspect.getmro(cls))
1324
if name is not None)
1326
@dbus.service.method(dbus.PROPERTIES_IFACE,
914
1328
out_signature="v")
915
1329
def Get(self, interface_name, property_name):
916
1330
"""Standard D-Bus property Get() method, see D-Bus standard.
1045
1458
exc_info=error)
1046
1459
return xmlstring
1462
dbus.OBJECT_MANAGER_IFACE
1463
except AttributeError:
1464
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1467
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1468
"""A D-Bus object with an ObjectManager.
1470
Classes inheriting from this exposes the standard
1471
GetManagedObjects call and the InterfacesAdded and
1472
InterfacesRemoved signals on the standard
1473
"org.freedesktop.DBus.ObjectManager" interface.
1475
Note: No signals are sent automatically; they must be sent
1478
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1479
out_signature="a{oa{sa{sv}}}")
1480
def GetManagedObjects(self):
1481
"""This function must be overridden"""
1482
raise NotImplementedError()
1484
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1485
signature="oa{sa{sv}}")
1486
def InterfacesAdded(self, object_path, interfaces_and_properties):
1489
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature="oas")
1490
def InterfacesRemoved(self, object_path, interfaces):
1493
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1495
path_keyword='object_path',
1496
connection_keyword='connection')
1497
def Introspect(self, object_path, connection):
1498
"""Overloading of standard D-Bus method.
1500
Override return argument name of GetManagedObjects to be
1501
"objpath_interfaces_and_properties"
1503
xmlstring = DBusObjectWithAnnotations.Introspect(self,
1507
document = xml.dom.minidom.parseString(xmlstring)
1509
for if_tag in document.getElementsByTagName("interface"):
1510
# Fix argument name for the GetManagedObjects method
1511
if (if_tag.getAttribute("name")
1512
== dbus.OBJECT_MANAGER_IFACE):
1513
for cn in if_tag.getElementsByTagName("method"):
1514
if (cn.getAttribute("name")
1515
== "GetManagedObjects"):
1516
for arg in cn.getElementsByTagName("arg"):
1517
if (arg.getAttribute("direction")
1521
"objpath_interfaces"
1523
xmlstring = document.toxml("utf-8")
1525
except (AttributeError, xml.dom.DOMException,
1526
xml.parsers.expat.ExpatError) as error:
1527
logger.error("Failed to override Introspection method",
1049
1532
def datetime_to_dbus(dt, variant_level=0):
1050
1533
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1052
return dbus.String("", variant_level = variant_level)
1053
return dbus.String(dt.isoformat(),
1054
variant_level=variant_level)
1535
return dbus.String("", variant_level=variant_level)
1536
return dbus.String(dt.isoformat(), variant_level=variant_level)
1057
1539
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1087
1570
# Ignore non-D-Bus attributes, and D-Bus attributes
1088
1571
# with the wrong interface name
1089
1572
if (not hasattr(attribute, "_dbus_interface")
1090
or not attribute._dbus_interface
1091
.startswith(orig_interface_name)):
1573
or not attribute._dbus_interface.startswith(
1574
orig_interface_name)):
1093
1576
# Create an alternate D-Bus interface name based on
1094
1577
# the current name
1095
alt_interface = (attribute._dbus_interface
1096
.replace(orig_interface_name,
1097
alt_interface_name))
1578
alt_interface = attribute._dbus_interface.replace(
1579
orig_interface_name, alt_interface_name)
1098
1580
interface_names.add(alt_interface)
1099
1581
# Is this a D-Bus signal?
1100
1582
if getattr(attribute, "_dbus_is_signal", False):
1101
1583
# Extract the original non-method undecorated
1102
1584
# function by black magic
1103
nonmethod_func = (dict(
1585
if sys.version_info.major == 2:
1586
nonmethod_func = (dict(
1104
1587
zip(attribute.func_code.co_freevars,
1105
attribute.__closure__))["func"]
1588
attribute.__closure__))
1589
["func"].cell_contents)
1591
nonmethod_func = (dict(
1592
zip(attribute.__code__.co_freevars,
1593
attribute.__closure__))
1594
["func"].cell_contents)
1107
1595
# Create a new, but exactly alike, function
1108
1596
# object, and decorate it to be a new D-Bus signal
1109
1597
# with the alternate D-Bus interface name
1110
new_function = (dbus.service.signal
1112
attribute._dbus_signature)
1113
(types.FunctionType(
1114
nonmethod_func.func_code,
1115
nonmethod_func.func_globals,
1116
nonmethod_func.func_name,
1117
nonmethod_func.func_defaults,
1118
nonmethod_func.func_closure)))
1598
new_function = copy_function(nonmethod_func)
1599
new_function = (dbus.service.signal(
1601
attribute._dbus_signature)(new_function))
1119
1602
# Copy annotations, if any
1121
new_function._dbus_annotations = (
1122
dict(attribute._dbus_annotations))
1604
new_function._dbus_annotations = dict(
1605
attribute._dbus_annotations)
1123
1606
except AttributeError:
1125
1609
# Define a creator of a function to call both the
1126
1610
# original and alternate functions, so both the
1127
1611
# original and alternate signals gets sent when
1217
1694
if interface_names:
1218
1695
# Replace the class with a new subclass of it with
1219
1696
# methods, signals, etc. as created above.
1220
cls = type(b"{0}Alternate".format(cls.__name__),
1697
if sys.version_info.major == 2:
1698
cls = type(b"{}Alternate".format(cls.__name__),
1701
cls = type("{}Alternate".format(cls.__name__),
1226
1708
@alternate_dbus_interfaces({"se.recompile.Mandos":
1227
"se.bsnet.fukt.Mandos"})
1709
"se.bsnet.fukt.Mandos"})
1228
1710
class ClientDBus(Client, DBusObjectWithProperties):
1229
1711
"""A Client class using D-Bus
1232
1714
dbus_object_path: dbus.ObjectPath
1233
1715
bus: dbus.SystemBus()
1236
1718
runtime_expansions = (Client.runtime_expansions
1237
+ ("dbus_object_path",))
1719
+ ("dbus_object_path", ))
1721
_interface = "se.recompile.Mandos.Client"
1239
1723
# dbus.service.Object doesn't use super(), so we can't either.
1241
def __init__(self, bus = None, *args, **kwargs):
1725
def __init__(self, bus=None, *args, **kwargs):
1243
1727
Client.__init__(self, *args, **kwargs)
1244
1728
# Only now, when this client is initialized, can it show up on
1246
client_object_name = unicode(self.name).translate(
1730
client_object_name = str(self.name).translate(
1247
1731
{ord("."): ord("_"),
1248
1732
ord("-"): ord("_")})
1249
self.dbus_object_path = (dbus.ObjectPath
1250
("/clients/" + client_object_name))
1733
self.dbus_object_path = dbus.ObjectPath(
1734
"/clients/" + client_object_name)
1251
1735
DBusObjectWithProperties.__init__(self, self.bus,
1252
1736
self.dbus_object_path)
1254
def notifychangeproperty(transform_func,
1255
dbus_name, type_func=lambda x: x,
1738
def notifychangeproperty(transform_func, dbus_name,
1739
type_func=lambda x: x,
1741
invalidate_only=False,
1742
_interface=_interface):
1257
1743
""" Modify a variable so that it's a property which announces
1258
1744
its changes to DBus.
1260
1746
transform_fun: Function that takes a value and a variant_level
1261
1747
and transforms it to a D-Bus type.
1262
1748
dbus_name: D-Bus name of the variable
1327
1824
if hasattr(DBusObjectWithProperties, "__del__"):
1328
1825
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1329
1826
Client.__del__(self, *args, **kwargs)
1331
def checker_callback(self, pid, condition, command,
1333
self.checker_callback_tag = None
1335
if os.WIFEXITED(condition):
1336
exitstatus = os.WEXITSTATUS(condition)
1828
def checker_callback(self, source, condition,
1829
connection, command, *args, **kwargs):
1830
ret = Client.checker_callback(self, source, condition,
1831
connection, command, *args,
1833
exitstatus = self.last_checker_status
1337
1835
# Emit D-Bus signal
1338
1836
self.CheckerCompleted(dbus.Int16(exitstatus),
1339
dbus.Int64(condition),
1837
# This is specific to GNU libC
1838
dbus.Int64(exitstatus << 8),
1340
1839
dbus.String(command))
1342
1841
# Emit D-Bus signal
1343
1842
self.CheckerCompleted(dbus.Int16(-1),
1344
dbus.Int64(condition),
1844
# This is specific to GNU libC
1846
| self.last_checker_signal),
1345
1847
dbus.String(command))
1347
return Client.checker_callback(self, pid, condition, command,
1350
1850
def start_checker(self, *args, **kwargs):
1351
old_checker = self.checker
1352
if self.checker is not None:
1353
old_checker_pid = self.checker.pid
1355
old_checker_pid = None
1851
old_checker_pid = getattr(self.checker, "pid", None)
1356
1852
r = Client.start_checker(self, *args, **kwargs)
1357
1853
# Only if new checker process was started
1358
1854
if (self.checker is not None
1410
1900
server to mandos-client
1414
1904
# Rejected - signal
1415
1905
@dbus.service.signal(_interface, signature="s")
1416
1906
def Rejected(self, reason):
1420
1910
# NeedApproval - signal
1421
1911
@dbus.service.signal(_interface, signature="tb")
1422
1912
def NeedApproval(self, timeout, default):
1424
1914
return self.need_approval()
1428
1918
# Approve - method
1429
1919
@dbus.service.method(_interface, in_signature="b")
1430
1920
def Approve(self, value):
1431
1921
self.approve(value)
1433
1923
# CheckedOK - method
1434
1924
@dbus.service.method(_interface)
1435
1925
def CheckedOK(self):
1436
1926
self.checked_ok()
1438
1928
# Enable - method
1929
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1439
1930
@dbus.service.method(_interface)
1440
1931
def Enable(self):
1444
1935
# StartChecker - method
1936
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1445
1937
@dbus.service.method(_interface)
1446
1938
def StartChecker(self):
1448
1940
self.start_checker()
1450
1942
# Disable - method
1943
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1451
1944
@dbus.service.method(_interface)
1452
1945
def Disable(self):
1456
1949
# StopChecker - method
1950
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1457
1951
@dbus.service.method(_interface)
1458
1952
def StopChecker(self):
1459
1953
self.stop_checker()
1463
1957
# ApprovalPending - property
1464
1958
@dbus_service_property(_interface, signature="b", access="read")
1465
1959
def ApprovalPending_dbus_property(self):
1466
1960
return dbus.Boolean(bool(self.approvals_pending))
1468
1962
# ApprovedByDefault - property
1469
@dbus_service_property(_interface, signature="b",
1963
@dbus_service_property(_interface,
1470
1965
access="readwrite")
1471
1966
def ApprovedByDefault_dbus_property(self, value=None):
1472
1967
if value is None: # get
1473
1968
return dbus.Boolean(self.approved_by_default)
1474
1969
self.approved_by_default = bool(value)
1476
1971
# ApprovalDelay - property
1477
@dbus_service_property(_interface, signature="t",
1972
@dbus_service_property(_interface,
1478
1974
access="readwrite")
1479
1975
def ApprovalDelay_dbus_property(self, value=None):
1480
1976
if value is None: # get
1481
return dbus.UInt64(self.approval_delay_milliseconds())
1977
return dbus.UInt64(self.approval_delay.total_seconds()
1482
1979
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1484
1981
# ApprovalDuration - property
1485
@dbus_service_property(_interface, signature="t",
1982
@dbus_service_property(_interface,
1486
1984
access="readwrite")
1487
1985
def ApprovalDuration_dbus_property(self, value=None):
1488
1986
if value is None: # get
1489
return dbus.UInt64(timedelta_to_milliseconds(
1490
self.approval_duration))
1987
return dbus.UInt64(self.approval_duration.total_seconds()
1491
1989
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1493
1991
# Name - property
1993
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1494
1994
@dbus_service_property(_interface, signature="s", access="read")
1495
1995
def Name_dbus_property(self):
1496
1996
return dbus.String(self.name)
1498
1998
# Fingerprint - property
2000
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1499
2001
@dbus_service_property(_interface, signature="s", access="read")
1500
2002
def Fingerprint_dbus_property(self):
1501
2003
return dbus.String(self.fingerprint)
1503
2005
# Host - property
1504
@dbus_service_property(_interface, signature="s",
2006
@dbus_service_property(_interface,
1505
2008
access="readwrite")
1506
2009
def Host_dbus_property(self, value=None):
1507
2010
if value is None: # get
1508
2011
return dbus.String(self.host)
1509
self.host = unicode(value)
2012
self.host = str(value)
1511
2014
# Created - property
2016
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1512
2017
@dbus_service_property(_interface, signature="s", access="read")
1513
2018
def Created_dbus_property(self):
1514
2019
return datetime_to_dbus(self.created)
1516
2021
# LastEnabled - property
1517
2022
@dbus_service_property(_interface, signature="s", access="read")
1518
2023
def LastEnabled_dbus_property(self):
1519
2024
return datetime_to_dbus(self.last_enabled)
1521
2026
# Enabled - property
1522
@dbus_service_property(_interface, signature="b",
2027
@dbus_service_property(_interface,
1523
2029
access="readwrite")
1524
2030
def Enabled_dbus_property(self, value=None):
1525
2031
if value is None: # get
1573
2080
if (getattr(self, "disable_initiator_tag", None)
1576
gobject.source_remove(self.disable_initiator_tag)
1577
self.disable_initiator_tag = (
1578
gobject.timeout_add(
1579
timedelta_to_milliseconds(self.expires - now),
2083
GLib.source_remove(self.disable_initiator_tag)
2084
self.disable_initiator_tag = GLib.timeout_add(
2085
int((self.expires - now).total_seconds() * 1000),
1582
2088
# ExtendedTimeout - property
1583
@dbus_service_property(_interface, signature="t",
2089
@dbus_service_property(_interface,
1584
2091
access="readwrite")
1585
2092
def ExtendedTimeout_dbus_property(self, value=None):
1586
2093
if value is None: # get
1587
return dbus.UInt64(self.extended_timeout_milliseconds())
2094
return dbus.UInt64(self.extended_timeout.total_seconds()
1588
2096
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1590
2098
# Interval - property
1591
@dbus_service_property(_interface, signature="t",
2099
@dbus_service_property(_interface,
1592
2101
access="readwrite")
1593
2102
def Interval_dbus_property(self, value=None):
1594
2103
if value is None: # get
1595
return dbus.UInt64(self.interval_milliseconds())
2104
return dbus.UInt64(self.interval.total_seconds() * 1000)
1596
2105
self.interval = datetime.timedelta(0, 0, 0, value)
1597
2106
if getattr(self, "checker_initiator_tag", None) is None:
1599
2108
if self.enabled:
1600
2109
# Reschedule checker run
1601
gobject.source_remove(self.checker_initiator_tag)
1602
self.checker_initiator_tag = (gobject.timeout_add
1603
(value, self.start_checker))
1604
self.start_checker() # Start one now, too
2110
GLib.source_remove(self.checker_initiator_tag)
2111
self.checker_initiator_tag = GLib.timeout_add(
2112
value, self.start_checker)
2113
self.start_checker() # Start one now, too
1606
2115
# Checker - property
1607
@dbus_service_property(_interface, signature="s",
2116
@dbus_service_property(_interface,
1608
2118
access="readwrite")
1609
2119
def Checker_dbus_property(self, value=None):
1610
2120
if value is None: # get
1611
2121
return dbus.String(self.checker_command)
1612
self.checker_command = unicode(value)
2122
self.checker_command = str(value)
1614
2124
# CheckerRunning - property
1615
@dbus_service_property(_interface, signature="b",
2125
@dbus_service_property(_interface,
1616
2127
access="readwrite")
1617
2128
def CheckerRunning_dbus_property(self, value=None):
1618
2129
if value is None: # get
1665
2186
class ClientHandler(socketserver.BaseRequestHandler, object):
1666
2187
"""A class to handle client connections.
1668
2189
Instantiated once for each connection to handle it.
1669
2190
Note: This will run in its own forked process."""
1671
2192
def handle(self):
1672
2193
with contextlib.closing(self.server.child_pipe) as child_pipe:
1673
2194
logger.info("TCP connection from: %s",
1674
unicode(self.client_address))
2195
str(self.client_address))
1675
2196
logger.debug("Pipe FD: %d",
1676
2197
self.server.child_pipe.fileno())
1678
session = (gnutls.connection
1679
.ClientSession(self.request,
1681
.X509Credentials()))
1683
# Note: gnutls.connection.X509Credentials is really a
1684
# generic GnuTLS certificate credentials object so long as
1685
# no X.509 keys are added to it. Therefore, we can use it
1686
# here despite using OpenPGP certificates.
1688
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1689
# "+AES-256-CBC", "+SHA1",
1690
# "+COMP-NULL", "+CTYPE-OPENPGP",
2199
session = gnutls.ClientSession(self.request)
2201
# priority = ':'.join(("NONE", "+VERS-TLS1.1",
2202
# "+AES-256-CBC", "+SHA1",
2203
# "+COMP-NULL", "+CTYPE-OPENPGP",
1692
2205
# Use a fallback default, since this MUST be set.
1693
2206
priority = self.server.gnutls_priority
1694
2207
if priority is None:
1695
2208
priority = "NORMAL"
1696
(gnutls.library.functions
1697
.gnutls_priority_set_direct(session._c_object,
2209
gnutls.priority_set_direct(session._c_object,
2210
priority.encode("utf-8"),
1700
2213
# Start communication using the Mandos protocol
1701
2214
# Get protocol number
1702
2215
line = self.request.makefile().readline()
1791
2301
delay -= time2 - time
1794
while sent_size < len(client.secret):
1796
sent = session.send(client.secret[sent_size:])
1797
except gnutls.errors.GNUTLSError as error:
1798
logger.warning("gnutls send failed",
1801
logger.debug("Sent: %d, remaining: %d",
1802
sent, len(client.secret)
1803
- (sent_size + sent))
2304
session.send(client.secret)
2305
except gnutls.Error as error:
2306
logger.warning("gnutls send failed",
1806
2310
logger.info("Sending secret to %s", client.name)
1807
2311
# bump the timeout using extended_timeout
1808
2312
client.bump_timeout(client.extended_timeout)
1809
2313
if self.server.use_dbus:
1810
2314
# Emit D-Bus signal
1811
2315
client.GotSecret()
1814
2318
if approval_required:
1815
2319
client.approvals_pending -= 1
1818
except gnutls.errors.GNUTLSError as error:
2322
except gnutls.Error as error:
1819
2323
logger.warning("GnuTLS bye failed",
1820
2324
exc_info=error)
1823
2327
def peer_certificate(session):
1824
2328
"Return the peer's OpenPGP certificate as a bytestring"
1825
2329
# If not an OpenPGP certificate...
1826
if (gnutls.library.functions
1827
.gnutls_certificate_type_get(session._c_object)
1828
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1829
# ...do the normal thing
1830
return session.peer_certificate
2330
if (gnutls.certificate_type_get(session._c_object)
2331
!= gnutls.CRT_OPENPGP):
2332
# ...return invalid data
1831
2334
list_size = ctypes.c_uint(1)
1832
cert_list = (gnutls.library.functions
1833
.gnutls_certificate_get_peers
2335
cert_list = (gnutls.certificate_get_peers
1834
2336
(session._c_object, ctypes.byref(list_size)))
1835
2337
if not bool(cert_list) and list_size.value != 0:
1836
raise gnutls.errors.GNUTLSError("error getting peer"
2338
raise gnutls.Error("error getting peer certificate")
1838
2339
if list_size.value == 0:
1840
2341
cert = cert_list[0]
1841
2342
return ctypes.string_at(cert.data, cert.size)
1844
2345
def fingerprint(openpgp):
1845
2346
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1846
2347
# New GnuTLS "datum" with the OpenPGP public key
1847
datum = (gnutls.library.types
1848
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1851
ctypes.c_uint(len(openpgp))))
2348
datum = gnutls.datum_t(
2349
ctypes.cast(ctypes.c_char_p(openpgp),
2350
ctypes.POINTER(ctypes.c_ubyte)),
2351
ctypes.c_uint(len(openpgp)))
1852
2352
# New empty GnuTLS certificate
1853
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1854
(gnutls.library.functions
1855
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
2353
crt = gnutls.openpgp_crt_t()
2354
gnutls.openpgp_crt_init(ctypes.byref(crt))
1856
2355
# Import the OpenPGP public key into the certificate
1857
(gnutls.library.functions
1858
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1859
gnutls.library.constants
1860
.GNUTLS_OPENPGP_FMT_RAW))
2356
gnutls.openpgp_crt_import(crt, ctypes.byref(datum),
2357
gnutls.OPENPGP_FMT_RAW)
1861
2358
# Verify the self signature in the key
1862
2359
crtverify = ctypes.c_uint()
1863
(gnutls.library.functions
1864
.gnutls_openpgp_crt_verify_self(crt, 0,
1865
ctypes.byref(crtverify)))
2360
gnutls.openpgp_crt_verify_self(crt, 0,
2361
ctypes.byref(crtverify))
1866
2362
if crtverify.value != 0:
1867
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1868
raise (gnutls.errors.CertificateSecurityError
2363
gnutls.openpgp_crt_deinit(crt)
2364
raise gnutls.CertificateSecurityError("Verify failed")
1870
2365
# New buffer for the fingerprint
1871
2366
buf = ctypes.create_string_buffer(20)
1872
2367
buf_len = ctypes.c_size_t()
1873
2368
# Get the fingerprint from the certificate into the buffer
1874
(gnutls.library.functions
1875
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1876
ctypes.byref(buf_len)))
2369
gnutls.openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
2370
ctypes.byref(buf_len))
1877
2371
# Deinit the certificate
1878
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
2372
gnutls.openpgp_crt_deinit(crt)
1879
2373
# Convert the buffer to a Python bytestring
1880
2374
fpr = ctypes.string_at(buf, buf_len.value)
1881
2375
# Convert the bytestring to hexadecimal notation
1964
2464
# socket_wrapper(), if socketfd was set.
1965
2465
socketserver.TCPServer.__init__(self, server_address,
1966
2466
RequestHandlerClass)
1968
2468
def server_bind(self):
1969
2469
"""This overrides the normal server_bind() function
1970
2470
to bind to an interface if one was specified, and also NOT to
1971
2471
bind to an address or port if they were not specified."""
2472
global SO_BINDTODEVICE
1972
2473
if self.interface is not None:
1973
2474
if SO_BINDTODEVICE is None:
1974
logger.error("SO_BINDTODEVICE does not exist;"
1975
" cannot bind to interface %s",
1979
self.socket.setsockopt(socket.SOL_SOCKET,
1981
str(self.interface + '\0'))
1982
except socket.error as error:
1983
if error.errno == errno.EPERM:
1984
logger.error("No permission to bind to"
1985
" interface %s", self.interface)
1986
elif error.errno == errno.ENOPROTOOPT:
1987
logger.error("SO_BINDTODEVICE not available;"
1988
" cannot bind to interface %s",
1990
elif error.errno == errno.ENODEV:
1991
logger.error("Interface %s does not exist,"
1992
" cannot bind", self.interface)
2475
# Fall back to a hard-coded value which seems to be
2477
logger.warning("SO_BINDTODEVICE not found, trying 25")
2478
SO_BINDTODEVICE = 25
2480
self.socket.setsockopt(
2481
socket.SOL_SOCKET, SO_BINDTODEVICE,
2482
(self.interface + "\0").encode("utf-8"))
2483
except socket.error as error:
2484
if error.errno == errno.EPERM:
2485
logger.error("No permission to bind to"
2486
" interface %s", self.interface)
2487
elif error.errno == errno.ENOPROTOOPT:
2488
logger.error("SO_BINDTODEVICE not available;"
2489
" cannot bind to interface %s",
2491
elif error.errno == errno.ENODEV:
2492
logger.error("Interface %s does not exist,"
2493
" cannot bind", self.interface)
1995
2496
# Only bind(2) the socket if we really need to.
1996
2497
if self.server_address[0] or self.server_address[1]:
1997
2498
if not self.server_address[0]:
1998
2499
if self.address_family == socket.AF_INET6:
1999
any_address = "::" # in6addr_any
2500
any_address = "::" # in6addr_any
2001
any_address = "0.0.0.0" # INADDR_ANY
2502
any_address = "0.0.0.0" # INADDR_ANY
2002
2503
self.server_address = (any_address,
2003
2504
self.server_address[1])
2004
2505
elif not self.server_address[1]:
2005
self.server_address = (self.server_address[0],
2506
self.server_address = (self.server_address[0], 0)
2007
2507
# if self.interface:
2008
2508
# self.server_address = (self.server_address[0],
2385
2896
server_settings["socket"] = os.dup(server_settings
2387
2898
del server_config
2389
2900
# Override the settings from the config file with command line
2390
2901
# options, if set.
2391
2902
for option in ("interface", "address", "port", "debug",
2392
"priority", "servicename", "configdir",
2393
"use_dbus", "use_ipv6", "debuglevel", "restore",
2394
"statedir", "socket", "foreground"):
2903
"priority", "servicename", "configdir", "use_dbus",
2904
"use_ipv6", "debuglevel", "restore", "statedir",
2905
"socket", "foreground", "zeroconf"):
2395
2906
value = getattr(options, option)
2396
2907
if value is not None:
2397
2908
server_settings[option] = value
2399
2910
# Force all strings to be unicode
2400
2911
for option in server_settings.keys():
2401
if type(server_settings[option]) is str:
2402
server_settings[option] = unicode(server_settings[option])
2912
if isinstance(server_settings[option], bytes):
2913
server_settings[option] = (server_settings[option]
2403
2915
# Force all boolean options to be boolean
2404
2916
for option in ("debug", "use_dbus", "use_ipv6", "restore",
2917
"foreground", "zeroconf"):
2406
2918
server_settings[option] = bool(server_settings[option])
2407
2919
# Debug implies foreground
2408
2920
if server_settings["debug"]:
2409
2921
server_settings["foreground"] = True
2410
2922
# Now we have our good server settings in "server_settings"
2412
2924
##################################################################
2926
if (not server_settings["zeroconf"]
2927
and not (server_settings["port"]
2928
or server_settings["socket"] != "")):
2929
parser.error("Needs port or socket to work without Zeroconf")
2414
2931
# For convenience
2415
2932
debug = server_settings["debug"]
2416
2933
debuglevel = server_settings["debuglevel"]
2429
2947
level = getattr(logging, debuglevel.upper())
2430
2948
initlogger(debug, level)
2432
2950
if server_settings["servicename"] != "Mandos":
2433
syslogger.setFormatter(logging.Formatter
2434
('Mandos ({0}) [%(process)d]:'
2435
' %(levelname)s: %(message)s'
2436
.format(server_settings
2951
syslogger.setFormatter(
2952
logging.Formatter('Mandos ({}) [%(process)d]:'
2953
' %(levelname)s: %(message)s'.format(
2954
server_settings["servicename"])))
2439
2956
# Parse config file with clients
2440
2957
client_config = configparser.SafeConfigParser(Client
2441
2958
.client_defaults)
2442
2959
client_config.read(os.path.join(server_settings["configdir"],
2443
2960
"clients.conf"))
2445
2962
global mandos_dbus_service
2446
2963
mandos_dbus_service = None
2448
tcp_server = MandosServer((server_settings["address"],
2449
server_settings["port"]),
2451
interface=(server_settings["interface"]
2455
server_settings["priority"],
2457
socketfd=(server_settings["socket"]
2966
if server_settings["socket"] != "":
2967
socketfd = server_settings["socket"]
2968
tcp_server = MandosServer(
2969
(server_settings["address"], server_settings["port"]),
2971
interface=(server_settings["interface"] or None),
2973
gnutls_priority=server_settings["priority"],
2459
2976
if not foreground:
2460
2977
pidfilename = "/run/mandos.pid"
2461
2978
if not os.path.isdir("/run/."):
2462
2979
pidfilename = "/var/run/mandos.pid"
2465
pidfile = open(pidfilename, "w")
2982
pidfile = codecs.open(pidfilename, "w", encoding="utf-8")
2466
2983
except IOError as e:
2467
2984
logger.error("Could not open file %r", pidfilename,
2470
for name in ("_mandos", "mandos", "nobody"):
2987
for name, group in (("_mandos", "_mandos"),
2988
("mandos", "mandos"),
2989
("nobody", "nogroup")):
2472
2991
uid = pwd.getpwnam(name).pw_uid
2473
gid = pwd.getpwnam(name).pw_gid
2992
gid = pwd.getpwnam(group).pw_gid
2475
2994
except KeyError:
3003
logger.debug("Did setuid/setgid to {}:{}".format(uid,
2483
3005
except OSError as error:
3006
logger.warning("Failed to setuid/setgid to {}:{}: {}"
3007
.format(uid, gid, os.strerror(error.errno)))
2484
3008
if error.errno != errno.EPERM:
2488
3012
# Enable all possible GnuTLS debugging
2490
3014
# "Use a log level over 10 to enable all debugging options."
2491
3015
# - GnuTLS manual
2492
gnutls.library.functions.gnutls_global_set_log_level(11)
2494
@gnutls.library.types.gnutls_log_func
3016
gnutls.global_set_log_level(11)
2495
3019
def debug_gnutls(level, string):
2496
3020
logger.debug("GnuTLS: %s", string[:-1])
2498
(gnutls.library.functions
2499
.gnutls_global_set_log_function(debug_gnutls))
3022
gnutls.global_set_log_function(debug_gnutls)
2501
3024
# Redirect stdin so all checkers get /dev/null
2502
3025
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2503
3026
os.dup2(null, sys.stdin.fileno())
2507
3030
# Need to fork before connecting to D-Bus
2508
3031
if not foreground:
2509
3032
# Close all input and output, do double fork, etc.
2512
# multiprocessing will use threads, so before we use gobject we
2513
# need to inform gobject that threads will be used.
2514
gobject.threads_init()
3035
# multiprocessing will use threads, so before we use GLib we need
3036
# to inform GLib that threads will be used.
2516
3039
global main_loop
2517
3040
# From the Avahi example code
2518
3041
DBusGMainLoop(set_as_default=True)
2519
main_loop = gobject.MainLoop()
3042
main_loop = GLib.MainLoop()
2520
3043
bus = dbus.SystemBus()
2521
3044
# End of Avahi example code
2524
3047
bus_name = dbus.service.BusName("se.recompile.Mandos",
2525
bus, do_not_queue=True)
2526
old_bus_name = (dbus.service.BusName
2527
("se.bsnet.fukt.Mandos", bus,
2529
except dbus.exceptions.NameExistsException as e:
3050
old_bus_name = dbus.service.BusName(
3051
"se.bsnet.fukt.Mandos", bus,
3053
except dbus.exceptions.DBusException as e:
2530
3054
logger.error("Disabling D-Bus:", exc_info=e)
2531
3055
use_dbus = False
2532
3056
server_settings["use_dbus"] = False
2533
3057
tcp_server.use_dbus = False
2534
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2535
service = AvahiServiceToSyslog(name =
2536
server_settings["servicename"],
2537
servicetype = "_mandos._tcp",
2538
protocol = protocol, bus = bus)
2539
if server_settings["interface"]:
2540
service.interface = (if_nametoindex
2541
(str(server_settings["interface"])))
3059
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
3060
service = AvahiServiceToSyslog(
3061
name=server_settings["servicename"],
3062
servicetype="_mandos._tcp",
3065
if server_settings["interface"]:
3066
service.interface = if_nametoindex(
3067
server_settings["interface"].encode("utf-8"))
2543
3069
global multiprocessing_manager
2544
3070
multiprocessing_manager = multiprocessing.Manager()
2546
3072
client_class = Client
2548
client_class = functools.partial(ClientDBus, bus = bus)
3074
client_class = functools.partial(ClientDBus, bus=bus)
2550
3076
client_settings = Client.config_parser(client_config)
2551
3077
old_client_settings = {}
2552
3078
clients_data = {}
2554
3080
# This is used to redirect stdout and stderr for checker processes
2556
wnull = open(os.devnull, "w") # A writable /dev/null
3082
wnull = open(os.devnull, "w") # A writable /dev/null
2557
3083
# Only used if server is running in foreground but not in debug
2559
3085
if debug or not foreground:
2562
3088
# Get client data and settings from last running state.
2563
3089
if server_settings["restore"]:
2565
3091
with open(stored_state_path, "rb") as stored_state:
2566
clients_data, old_client_settings = (pickle.load
3092
if sys.version_info.major == 2:
3093
clients_data, old_client_settings = pickle.load(
3096
bytes_clients_data, bytes_old_client_settings = (
3097
pickle.load(stored_state, encoding="bytes"))
3098
# Fix bytes to strings
3101
clients_data = {(key.decode("utf-8")
3102
if isinstance(key, bytes)
3105
bytes_clients_data.items()}
3106
del bytes_clients_data
3107
for key in clients_data:
3108
value = {(k.decode("utf-8")
3109
if isinstance(k, bytes) else k): v
3111
clients_data[key].items()}
3112
clients_data[key] = value
3114
value["client_structure"] = [
3116
if isinstance(s, bytes)
3118
value["client_structure"]]
3120
for k in ("name", "host"):
3121
if isinstance(value[k], bytes):
3122
value[k] = value[k].decode("utf-8")
3123
# old_client_settings
3125
old_client_settings = {
3126
(key.decode("utf-8")
3127
if isinstance(key, bytes)
3130
bytes_old_client_settings.items()}
3131
del bytes_old_client_settings
3133
for value in old_client_settings.values():
3134
if isinstance(value["host"], bytes):
3135
value["host"] = (value["host"]
2568
3137
os.remove(stored_state_path)
2569
3138
except IOError as e:
2570
3139
if e.errno == errno.ENOENT:
2571
logger.warning("Could not load persistent state: {0}"
2572
.format(os.strerror(e.errno)))
3140
logger.warning("Could not load persistent state:"
3141
" {}".format(os.strerror(e.errno)))
2574
3143
logger.critical("Could not load persistent state:",
2577
3146
except EOFError as e:
2578
3147
logger.warning("Could not load persistent state: "
2579
"EOFError:", exc_info=e)
2581
3151
with PGPEngine() as pgp:
2582
for client_name, client in clients_data.iteritems():
3152
for client_name, client in clients_data.items():
2583
3153
# Skip removed clients
2584
3154
if client_name not in client_settings:
2587
3157
# Decide which value to use after restoring saved state.
2588
3158
# We have three different values: Old config file,
2589
3159
# new config file, and saved state.
2647
3217
for client_name in (set(client_settings)
2648
3218
- set(old_client_settings)):
2649
3219
clients_data[client_name] = client_settings[client_name]
2651
3221
# Create all client objects
2652
for client_name, client in clients_data.iteritems():
3222
for client_name, client in clients_data.items():
2653
3223
tcp_server.clients[client_name] = client_class(
2654
name = client_name, settings = client,
2655
server_settings = server_settings)
3226
server_settings=server_settings)
2657
3228
if not tcp_server.clients:
2658
3229
logger.warning("No clients defined")
2660
3231
if not foreground:
2661
3232
if pidfile is not None:
2665
pidfile.write(str(pid) + "\n".encode("utf-8"))
3236
print(pid, file=pidfile)
2666
3237
except IOError:
2667
3238
logger.error("Could not write to file %r with PID %d",
2668
3239
pidfilename, pid)
2670
3241
del pidfilename
2672
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2673
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
3243
for termsig in (signal.SIGHUP, signal.SIGTERM):
3244
GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig,
3245
lambda: main_loop.quit() and False)
2676
@alternate_dbus_interfaces({"se.recompile.Mandos":
2677
"se.bsnet.fukt.Mandos"})
2678
class MandosDBusService(DBusObjectWithProperties):
3249
@alternate_dbus_interfaces(
3250
{"se.recompile.Mandos": "se.bsnet.fukt.Mandos"})
3251
class MandosDBusService(DBusObjectWithObjectManager):
2679
3252
"""A D-Bus proxy object"""
2680
3254
def __init__(self):
2681
3255
dbus.service.Object.__init__(self, bus, "/")
2682
3257
_interface = "se.recompile.Mandos"
2684
@dbus_interface_annotations(_interface)
2686
return { "org.freedesktop.DBus.Property"
2687
".EmitsChangedSignal":
2690
3259
@dbus.service.signal(_interface, signature="o")
2691
3260
def ClientAdded(self, objpath):
2695
3264
@dbus.service.signal(_interface, signature="ss")
2696
3265
def ClientNotFound(self, fingerprint, address):
3269
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2700
3271
@dbus.service.signal(_interface, signature="os")
2701
3272
def ClientRemoved(self, objpath, name):
3276
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2705
3278
@dbus.service.method(_interface, out_signature="ao")
2706
3279
def GetAllClients(self):
2708
return dbus.Array(c.dbus_object_path
2710
tcp_server.clients.itervalues())
3281
return dbus.Array(c.dbus_object_path for c in
3282
tcp_server.clients.values())
3284
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2712
3286
@dbus.service.method(_interface,
2713
3287
out_signature="a{oa{sv}}")
2714
3288
def GetAllClientsWithProperties(self):
2716
3290
return dbus.Dictionary(
2717
((c.dbus_object_path, c.GetAll(""))
2718
for c in tcp_server.clients.itervalues()),
3291
{c.dbus_object_path: c.GetAll(
3292
"se.recompile.Mandos.Client")
3293
for c in tcp_server.clients.values()},
2719
3294
signature="oa{sv}")
2721
3296
@dbus.service.method(_interface, in_signature="o")
2722
3297
def RemoveClient(self, object_path):
2724
for c in tcp_server.clients.itervalues():
3299
for c in tcp_server.clients.values():
2725
3300
if c.dbus_object_path == object_path:
2726
3301
del tcp_server.clients[c.name]
2727
3302
c.remove_from_connection()
2728
# Don't signal anything except ClientRemoved
3303
# Don't signal the disabling
2729
3304
c.disable(quiet=True)
2731
self.ClientRemoved(object_path, c.name)
3305
# Emit D-Bus signal for removal
3306
self.client_removed_signal(c)
2733
3308
raise KeyError(object_path)
3312
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
3313
out_signature="a{oa{sa{sv}}}")
3314
def GetManagedObjects(self):
3316
return dbus.Dictionary(
3317
{client.dbus_object_path:
3319
{interface: client.GetAll(interface)
3321
client._get_all_interface_names()})
3322
for client in tcp_server.clients.values()})
3324
def client_added_signal(self, client):
3325
"""Send the new standard signal and the old signal"""
3327
# New standard signal
3328
self.InterfacesAdded(
3329
client.dbus_object_path,
3331
{interface: client.GetAll(interface)
3333
client._get_all_interface_names()}))
3335
self.ClientAdded(client.dbus_object_path)
3337
def client_removed_signal(self, client):
3338
"""Send the new standard signal and the old signal"""
3340
# New standard signal
3341
self.InterfacesRemoved(
3342
client.dbus_object_path,
3343
client._get_all_interface_names())
3345
self.ClientRemoved(client.dbus_object_path,
2737
3348
mandos_dbus_service = MandosDBusService()
3350
# Save modules to variables to exempt the modules from being
3351
# unloaded before the function registered with atexit() is run.
3352
mp = multiprocessing
2740
3356
"Cleanup function; run on exit"
2743
multiprocessing.active_children()
3360
mp.active_children()
2745
3362
if not (tcp_server.clients or client_settings):
2748
3365
# Store client before exiting. Secrets are encrypted with key
2749
3366
# based on what config file has. If config file is
2750
3367
# removed/edited, old secret will thus be unrecovable.
2752
3369
with PGPEngine() as pgp:
2753
for client in tcp_server.clients.itervalues():
3370
for client in tcp_server.clients.values():
2754
3371
key = client_settings[client.name]["secret"]
2755
3372
client.encrypted_secret = pgp.encrypt(client.secret,
2757
3374
client_dict = {}
2759
3376
# A list of attributes that can not be pickled
2761
exclude = set(("bus", "changedstate", "secret",
2762
"checker", "server_settings"))
2763
for name, typ in (inspect.getmembers
2764
(dbus.service.Object)):
3378
exclude = {"bus", "changedstate", "secret",
3379
"checker", "server_settings"}
3380
for name, typ in inspect.getmembers(dbus.service
2765
3382
exclude.add(name)
2767
3384
client_dict["encrypted_secret"] = (client
2768
3385
.encrypted_secret)
2769
3386
for attr in client.client_structure:
2770
3387
if attr not in exclude:
2771
3388
client_dict[attr] = getattr(client, attr)
2773
3390
clients[client.name] = client_dict
2774
3391
del client_settings[client.name]["secret"]
2777
with (tempfile.NamedTemporaryFile
2778
(mode='wb', suffix=".pickle", prefix='clients-',
2779
dir=os.path.dirname(stored_state_path),
2780
delete=False)) as stored_state:
2781
pickle.dump((clients, client_settings), stored_state)
2782
tempname=stored_state.name
3394
with tempfile.NamedTemporaryFile(
3398
dir=os.path.dirname(stored_state_path),
3399
delete=False) as stored_state:
3400
pickle.dump((clients, client_settings), stored_state,
3402
tempname = stored_state.name
2783
3403
os.rename(tempname, stored_state_path)
2784
3404
except (IOError, OSError) as e: