70
import cPickle as pickle
62
import cPickle as pickle
73
63
import multiprocessing
82
66
import dbus.service
83
from gi.repository import GLib
84
69
from dbus.mainloop.glib import DBusGMainLoop
87
72
import xml.dom.minidom
90
# Try to find the value of SO_BINDTODEVICE:
92
# This is where SO_BINDTODEVICE is in Python 3.3 (or 3.4?) and
93
# newer, and it is also the most natural place for it:
94
76
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
95
77
except AttributeError:
97
# This is where SO_BINDTODEVICE was up to and including Python
99
79
from IN import SO_BINDTODEVICE
100
80
except ImportError:
101
# In Python 2.7 it seems to have been removed entirely.
102
# Try running the C preprocessor:
104
cc = subprocess.Popen(["cc", "--language=c", "-E",
106
stdin=subprocess.PIPE,
107
stdout=subprocess.PIPE)
108
stdout = cc.communicate(
109
"#include <sys/socket.h>\nSO_BINDTODEVICE\n")[0]
110
SO_BINDTODEVICE = int(stdout.splitlines()[-1])
111
except (OSError, ValueError, IndexError):
113
SO_BINDTODEVICE = None
115
if sys.version_info.major == 2:
119
stored_state_file = "clients.pickle"
121
logger = logging.getLogger()
125
if_nametoindex = ctypes.cdll.LoadLibrary(
126
ctypes.util.find_library("c")).if_nametoindex
127
except (OSError, AttributeError):
129
def if_nametoindex(interface):
130
"Get an interface index the hard way, i.e. using fcntl()"
131
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
132
with contextlib.closing(socket.socket()) as s:
133
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
134
struct.pack(b"16s16x", interface))
135
interface_index = struct.unpack("I", ifreq[16:20])[0]
136
return interface_index
139
def copy_function(func):
140
"""Make a copy of a function"""
141
if sys.version_info.major == 2:
142
return types.FunctionType(func.func_code,
148
return types.FunctionType(func.__code__,
155
def initlogger(debug, level=logging.WARNING):
156
"""init logger and add loglevel"""
159
syslogger = (logging.handlers.SysLogHandler(
160
facility=logging.handlers.SysLogHandler.LOG_DAEMON,
162
syslogger.setFormatter(logging.Formatter
163
('Mandos [%(process)d]: %(levelname)s:'
165
logger.addHandler(syslogger)
168
console = logging.StreamHandler()
169
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
173
logger.addHandler(console)
174
logger.setLevel(level)
177
class PGPError(Exception):
178
"""Exception if encryption/decryption fails"""
182
class PGPEngine(object):
183
"""A simple class for OpenPGP symmetric encryption & decryption"""
186
self.tempdir = tempfile.mkdtemp(prefix="mandos-")
189
output = subprocess.check_output(["gpgconf"])
190
for line in output.splitlines():
191
name, text, path = line.split(b":")
196
if e.errno != errno.ENOENT:
198
self.gnupgargs = ['--batch',
199
'--homedir', self.tempdir,
202
# Only GPG version 1 has the --no-use-agent option.
203
if self.gpg == "gpg" or self.gpg.endswith("/gpg"):
204
self.gnupgargs.append("--no-use-agent")
209
def __exit__(self, exc_type, exc_value, traceback):
217
if self.tempdir is not None:
218
# Delete contents of tempdir
219
for root, dirs, files in os.walk(self.tempdir,
221
for filename in files:
222
os.remove(os.path.join(root, filename))
224
os.rmdir(os.path.join(root, dirname))
226
os.rmdir(self.tempdir)
229
def password_encode(self, password):
230
# Passphrase can not be empty and can not contain newlines or
231
# NUL bytes. So we prefix it and hex encode it.
232
encoded = b"mandos" + binascii.hexlify(password)
233
if len(encoded) > 2048:
234
# GnuPG can't handle long passwords, so encode differently
235
encoded = (b"mandos" + password.replace(b"\\", b"\\\\")
236
.replace(b"\n", b"\\n")
237
.replace(b"\0", b"\\x00"))
240
def encrypt(self, data, password):
241
passphrase = self.password_encode(password)
242
with tempfile.NamedTemporaryFile(
243
dir=self.tempdir) as passfile:
244
passfile.write(passphrase)
246
proc = subprocess.Popen([self.gpg, '--symmetric',
250
stdin=subprocess.PIPE,
251
stdout=subprocess.PIPE,
252
stderr=subprocess.PIPE)
253
ciphertext, err = proc.communicate(input=data)
254
if proc.returncode != 0:
258
def decrypt(self, data, password):
259
passphrase = self.password_encode(password)
260
with tempfile.NamedTemporaryFile(
261
dir=self.tempdir) as passfile:
262
passfile.write(passphrase)
264
proc = subprocess.Popen([self.gpg, '--decrypt',
268
stdin=subprocess.PIPE,
269
stdout=subprocess.PIPE,
270
stderr=subprocess.PIPE)
271
decrypted_plaintext, err = proc.communicate(input=data)
272
if proc.returncode != 0:
274
return decrypted_plaintext
277
# Pretend that we have an Avahi module
279
"""This isn't so much a class as it is a module-like namespace."""
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 = "/"
290
def string_array_to_txt_array(t):
291
return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
292
for s in t), signature="ay")
293
ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h
294
ENTRY_GROUP_COLLISION = 3 # avahi-common/defs.h
295
ENTRY_GROUP_FAILURE = 4 # avahi-common/defs.h
296
SERVER_INVALID = 0 # avahi-common/defs.h
297
SERVER_REGISTERING = 1 # avahi-common/defs.h
298
SERVER_RUNNING = 2 # avahi-common/defs.h
299
SERVER_COLLISION = 3 # avahi-common/defs.h
300
SERVER_FAILURE = 4 # avahi-common/defs.h
81
SO_BINDTODEVICE = None
86
#logger = logging.getLogger(u'mandos')
87
logger = logging.Logger(u'mandos')
88
syslogger = (logging.handlers.SysLogHandler
89
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
90
address = "/dev/log"))
91
syslogger.setFormatter(logging.Formatter
92
(u'Mandos [%(process)d]: %(levelname)s:'
94
logger.addHandler(syslogger)
96
console = logging.StreamHandler()
97
console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:'
100
logger.addHandler(console)
303
102
class AvahiError(Exception):
304
103
def __init__(self, value, *args, **kwargs):
305
104
self.value = value
306
return super(AvahiError, self).__init__(value, *args,
105
super(AvahiError, self).__init__(value, *args, **kwargs)
106
def __unicode__(self):
107
return unicode(repr(self.value))
310
109
class AvahiServiceError(AvahiError):
314
112
class AvahiGroupError(AvahiError):
318
116
class AvahiService(object):
319
117
"""An Avahi (Zeroconf) service.
322
120
interface: integer; avahi.IF_UNSPEC or an interface index.
323
121
Used to optionally bind to the specified interface.
324
name: string; Example: 'Mandos'
325
type: string; Example: '_mandos._tcp'.
326
See <https://www.iana.org/assignments/service-names-port-numbers>
122
name: string; Example: u'Mandos'
123
type: string; Example: u'_mandos._tcp'.
124
See <http://www.dns-sd.org/ServiceTypes.html>
327
125
port: integer; what port to announce
328
126
TXT: list of strings; TXT record for the service
329
127
domain: string; Domain to publish on, default to .local if empty.
418
197
dbus.UInt16(self.port),
419
198
avahi.string_array_to_txt_array(self.TXT))
420
199
self.group.Commit()
422
200
def entry_group_state_changed(self, state, error):
423
201
"""Derived from the Avahi example code"""
424
logger.debug("Avahi entry group state change: %i", state)
202
logger.debug(u"Avahi entry group state change: %i", state)
426
204
if state == avahi.ENTRY_GROUP_ESTABLISHED:
427
logger.debug("Zeroconf service established.")
205
logger.debug(u"Zeroconf service established.")
428
206
elif state == avahi.ENTRY_GROUP_COLLISION:
429
logger.info("Zeroconf service name collision.")
207
logger.warning(u"Zeroconf service name collision.")
431
209
elif state == avahi.ENTRY_GROUP_FAILURE:
432
logger.critical("Avahi: Error in group state changed %s",
434
raise AvahiGroupError("State changed: {!s}".format(error))
210
logger.critical(u"Avahi: Error in group state changed %s",
212
raise AvahiGroupError(u"State changed: %s"
436
214
def cleanup(self):
437
215
"""Derived from the Avahi example code"""
438
216
if self.group is not None:
441
except (dbus.exceptions.UnknownMethodException,
442
dbus.exceptions.DBusException):
444
218
self.group = None
447
def server_state_changed(self, state, error=None):
219
def server_state_changed(self, state):
448
220
"""Derived from the Avahi example code"""
449
logger.debug("Avahi server state change: %i", state)
451
avahi.SERVER_INVALID: "Zeroconf server invalid",
452
avahi.SERVER_REGISTERING: None,
453
avahi.SERVER_COLLISION: "Zeroconf server name collision",
454
avahi.SERVER_FAILURE: "Zeroconf server failure",
456
if state in bad_states:
457
if bad_states[state] is not None:
459
logger.error(bad_states[state])
461
logger.error(bad_states[state] + ": %r", error)
221
logger.debug(u"Avahi server state change: %i", state)
222
if state == avahi.SERVER_COLLISION:
223
logger.error(u"Zeroconf server name collision")
463
225
elif state == avahi.SERVER_RUNNING:
466
except dbus.exceptions.DBusException as error:
467
if (error.get_dbus_name()
468
== "org.freedesktop.Avahi.CollisionError"):
469
logger.info("Local Zeroconf service name"
471
return self.rename(remove=False)
473
logger.critical("D-Bus Exception", exc_info=error)
478
logger.debug("Unknown state: %r", state)
480
logger.debug("Unknown state: %r: %r", state, error)
482
227
def activate(self):
483
228
"""Derived from the Avahi example code"""
484
229
if self.server is None:
485
230
self.server = dbus.Interface(
486
231
self.bus.get_object(avahi.DBUS_NAME,
487
avahi.DBUS_PATH_SERVER,
488
follow_name_owner_changes=True),
232
avahi.DBUS_PATH_SERVER),
489
233
avahi.DBUS_INTERFACE_SERVER)
490
self.server.connect_to_signal("StateChanged",
491
self.server_state_changed)
234
self.server.connect_to_signal(u"StateChanged",
235
self.server_state_changed)
492
236
self.server_state_changed(self.server.GetState())
495
class AvahiServiceToSyslog(AvahiService):
496
def rename(self, *args, **kwargs):
497
"""Add the new name to the syslog messages"""
498
ret = super(AvahiServiceToSyslog, self).rename(*args, **kwargs)
499
syslogger.setFormatter(logging.Formatter(
500
'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
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."""
509
library = ctypes.util.find_library("gnutls")
511
library = ctypes.util.find_library("gnutls-deb0")
512
_library = ctypes.cdll.LoadLibrary(library)
515
# Unless otherwise indicated, the constants and types below are
516
# all from the gnutls/gnutls.h C header file.
527
E_NO_CERTIFICATE_FOUND = -49
532
KEYID_USE_SHA256 = 1 # gnutls/x509.h
533
OPENPGP_FMT_RAW = 0 # gnutls/openpgp.h
536
class session_int(ctypes.Structure):
538
session_t = ctypes.POINTER(session_int)
540
class certificate_credentials_st(ctypes.Structure):
542
certificate_credentials_t = ctypes.POINTER(
543
certificate_credentials_st)
544
certificate_type_t = ctypes.c_int
546
class datum_t(ctypes.Structure):
547
_fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
548
('size', ctypes.c_uint)]
550
class openpgp_crt_int(ctypes.Structure):
552
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
553
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
554
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
555
credentials_type_t = ctypes.c_int
556
transport_ptr_t = ctypes.c_void_p
557
close_request_t = ctypes.c_int
560
class Error(Exception):
561
def __init__(self, message=None, code=None, args=()):
562
# Default usage is by a message string, but if a return
563
# code is passed, convert it to a string with
566
if message is None and code is not None:
567
message = gnutls.strerror(code)
568
return super(gnutls.Error, self).__init__(
571
class CertificateSecurityError(Error):
575
class Credentials(object):
577
self._c_object = gnutls.certificate_credentials_t()
578
gnutls.certificate_allocate_credentials(
579
ctypes.byref(self._c_object))
580
self.type = gnutls.CRD_CERTIFICATE
583
gnutls.certificate_free_credentials(self._c_object)
585
class ClientSession(object):
586
def __init__(self, socket, credentials=None):
587
self._c_object = gnutls.session_t()
588
gnutls_flags = gnutls.CLIENT
589
if gnutls.check_version(b"3.5.6"):
590
gnutls_flags |= gnutls.NO_TICKETS
592
gnutls_flags |= gnutls.ENABLE_RAWPK
593
gnutls.init(ctypes.byref(self._c_object), gnutls_flags)
595
gnutls.set_default_priority(self._c_object)
596
gnutls.transport_set_ptr(self._c_object, socket.fileno())
597
gnutls.handshake_set_private_extensions(self._c_object,
600
if credentials is None:
601
credentials = gnutls.Credentials()
602
gnutls.credentials_set(self._c_object, credentials.type,
603
ctypes.cast(credentials._c_object,
605
self.credentials = credentials
608
gnutls.deinit(self._c_object)
611
return gnutls.handshake(self._c_object)
613
def send(self, data):
617
data_len -= gnutls.record_send(self._c_object,
622
return gnutls.bye(self._c_object, gnutls.SHUT_RDWR)
624
# Error handling functions
625
def _error_code(result):
626
"""A function to raise exceptions on errors, suitable
627
for the 'restype' attribute on ctypes functions"""
630
if result == gnutls.E_NO_CERTIFICATE_FOUND:
631
raise gnutls.CertificateSecurityError(code=result)
632
raise gnutls.Error(code=result)
634
def _retry_on_error(result, func, arguments):
635
"""A function to retry on some errors, suitable
636
for the 'errcheck' attribute on ctypes functions"""
638
if result not in (gnutls.E_INTERRUPTED, gnutls.E_AGAIN):
639
return _error_code(result)
640
result = func(*arguments)
643
# Unless otherwise indicated, the function declarations below are
644
# all from the gnutls/gnutls.h C header file.
647
priority_set_direct = _library.gnutls_priority_set_direct
648
priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
649
ctypes.POINTER(ctypes.c_char_p)]
650
priority_set_direct.restype = _error_code
652
init = _library.gnutls_init
653
init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
654
init.restype = _error_code
656
set_default_priority = _library.gnutls_set_default_priority
657
set_default_priority.argtypes = [session_t]
658
set_default_priority.restype = _error_code
660
record_send = _library.gnutls_record_send
661
record_send.argtypes = [session_t, ctypes.c_void_p,
663
record_send.restype = ctypes.c_ssize_t
664
record_send.errcheck = _retry_on_error
666
certificate_allocate_credentials = (
667
_library.gnutls_certificate_allocate_credentials)
668
certificate_allocate_credentials.argtypes = [
669
ctypes.POINTER(certificate_credentials_t)]
670
certificate_allocate_credentials.restype = _error_code
672
certificate_free_credentials = (
673
_library.gnutls_certificate_free_credentials)
674
certificate_free_credentials.argtypes = [
675
certificate_credentials_t]
676
certificate_free_credentials.restype = None
678
handshake_set_private_extensions = (
679
_library.gnutls_handshake_set_private_extensions)
680
handshake_set_private_extensions.argtypes = [session_t,
682
handshake_set_private_extensions.restype = None
684
credentials_set = _library.gnutls_credentials_set
685
credentials_set.argtypes = [session_t, credentials_type_t,
687
credentials_set.restype = _error_code
689
strerror = _library.gnutls_strerror
690
strerror.argtypes = [ctypes.c_int]
691
strerror.restype = ctypes.c_char_p
693
certificate_type_get = _library.gnutls_certificate_type_get
694
certificate_type_get.argtypes = [session_t]
695
certificate_type_get.restype = _error_code
697
certificate_get_peers = _library.gnutls_certificate_get_peers
698
certificate_get_peers.argtypes = [session_t,
699
ctypes.POINTER(ctypes.c_uint)]
700
certificate_get_peers.restype = ctypes.POINTER(datum_t)
702
global_set_log_level = _library.gnutls_global_set_log_level
703
global_set_log_level.argtypes = [ctypes.c_int]
704
global_set_log_level.restype = None
706
global_set_log_function = _library.gnutls_global_set_log_function
707
global_set_log_function.argtypes = [log_func]
708
global_set_log_function.restype = None
710
deinit = _library.gnutls_deinit
711
deinit.argtypes = [session_t]
712
deinit.restype = None
714
handshake = _library.gnutls_handshake
715
handshake.argtypes = [session_t]
716
handshake.restype = _error_code
717
handshake.errcheck = _retry_on_error
719
transport_set_ptr = _library.gnutls_transport_set_ptr
720
transport_set_ptr.argtypes = [session_t, transport_ptr_t]
721
transport_set_ptr.restype = None
723
bye = _library.gnutls_bye
724
bye.argtypes = [session_t, close_request_t]
725
bye.restype = _error_code
726
bye.errcheck = _retry_on_error
728
check_version = _library.gnutls_check_version
729
check_version.argtypes = [ctypes.c_char_p]
730
check_version.restype = ctypes.c_char_p
732
_need_version = b"3.3.0"
733
if check_version(_need_version) is None:
734
raise self.Error("Needs GnuTLS {} or later"
735
.format(_need_version))
737
_tls_rawpk_version = b"3.6.6"
738
has_rawpk = bool(check_version(_tls_rawpk_version))
742
class pubkey_st(ctypes.Structure):
744
pubkey_t = ctypes.POINTER(pubkey_st)
746
x509_crt_fmt_t = ctypes.c_int
748
# All the function declarations below are from gnutls/abstract.h
749
pubkey_init = _library.gnutls_pubkey_init
750
pubkey_init.argtypes = [ctypes.POINTER(pubkey_t)]
751
pubkey_init.restype = _error_code
753
pubkey_import = _library.gnutls_pubkey_import
754
pubkey_import.argtypes = [pubkey_t, ctypes.POINTER(datum_t),
756
pubkey_import.restype = _error_code
758
pubkey_get_key_id = _library.gnutls_pubkey_get_key_id
759
pubkey_get_key_id.argtypes = [pubkey_t, ctypes.c_int,
760
ctypes.POINTER(ctypes.c_ubyte),
761
ctypes.POINTER(ctypes.c_size_t)]
762
pubkey_get_key_id.restype = _error_code
764
pubkey_deinit = _library.gnutls_pubkey_deinit
765
pubkey_deinit.argtypes = [pubkey_t]
766
pubkey_deinit.restype = None
768
# All the function declarations below are from gnutls/openpgp.h
770
openpgp_crt_init = _library.gnutls_openpgp_crt_init
771
openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
772
openpgp_crt_init.restype = _error_code
774
openpgp_crt_import = _library.gnutls_openpgp_crt_import
775
openpgp_crt_import.argtypes = [openpgp_crt_t,
776
ctypes.POINTER(datum_t),
778
openpgp_crt_import.restype = _error_code
780
openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
781
openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
782
ctypes.POINTER(ctypes.c_uint)]
783
openpgp_crt_verify_self.restype = _error_code
785
openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
786
openpgp_crt_deinit.argtypes = [openpgp_crt_t]
787
openpgp_crt_deinit.restype = None
789
openpgp_crt_get_fingerprint = (
790
_library.gnutls_openpgp_crt_get_fingerprint)
791
openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
795
openpgp_crt_get_fingerprint.restype = _error_code
797
if check_version(b"3.6.4"):
798
certificate_type_get2 = _library.gnutls_certificate_type_get2
799
certificate_type_get2.argtypes = [session_t, ctypes.c_int]
800
certificate_type_get2.restype = _error_code
802
# Remove non-public functions
803
del _error_code, _retry_on_error
806
def call_pipe(connection, # : multiprocessing.Connection
807
func, *args, **kwargs):
808
"""This function is meant to be called by multiprocessing.Process
810
This function runs func(*args, **kwargs), and writes the resulting
811
return value on the provided multiprocessing.Connection.
813
connection.send(func(*args, **kwargs))
817
239
class Client(object):
818
240
"""A representation of a client host served by this server.
821
approved: bool(); 'None' if not yet approved/disapproved
243
_approved: bool(); 'None' if not yet approved/disapproved
822
244
approval_delay: datetime.timedelta(); Time to wait for approval
823
245
approval_duration: datetime.timedelta(); Duration of one approval
824
checker: multiprocessing.Process(); a running checker process used
825
to see if the client lives. 'None' if no process is
827
checker_callback_tag: a GLib event source tag, or None
246
checker: subprocess.Popen(); a running checker process used
247
to see if the client lives.
248
'None' if no process is running.
249
checker_callback_tag: - '' -
828
250
checker_command: string; External command which is run to check
829
251
if client lives. %() expansions are done at
830
252
runtime with vars(self) as dict, so that for
831
253
instance %(name)s can be used in the command.
832
checker_initiator_tag: a GLib event source tag, or None
254
checker_initiator_tag: a gobject event source tag, or None
833
255
created: datetime.datetime(); (UTC) object creation
834
client_structure: Object describing what attributes a client has
835
and is used for storing the client at exit
836
256
current_checker_command: string; current running checker_command
837
disable_initiator_tag: a GLib event source tag, or None
257
disable_hook: If set, called by disable() as disable_hook(self)
258
disable_initiator_tag: - '' -
839
260
fingerprint: string (40 or 32 hexadecimal digits); used to
840
uniquely identify an OpenPGP client
841
key_id: string (64 hexadecimal digits); used to uniquely identify
842
a client using raw public keys
261
uniquely identify the client
843
262
host: string; available for use by the checker command
844
263
interval: datetime.timedelta(); How often to start a new checker
845
last_approval_request: datetime.datetime(); (UTC) or None
846
264
last_checked_ok: datetime.datetime(); (UTC) or None
847
last_checker_status: integer between 0 and 255 reflecting exit
848
status of last checker. -1 reflects crashed
849
checker, -2 means no checker completed yet.
850
last_checker_signal: The signal which killed the last checker, if
851
last_checker_status is -1
852
last_enabled: datetime.datetime(); (UTC) or None
265
last_enabled: datetime.datetime(); (UTC)
853
266
name: string; from the config file, used in log messages and
854
267
D-Bus identifiers
855
268
secret: bytestring; sent verbatim (over TLS) to client
856
269
timeout: datetime.timedelta(); How long from last_checked_ok
857
270
until this client is disabled
858
extended_timeout: extra long timeout when secret has been sent
859
271
runtime_expansions: Allowed attributes for runtime expansion.
860
expires: datetime.datetime(); time (UTC) when a client will be
862
server_settings: The server_settings dict from main()
865
runtime_expansions = ("approval_delay", "approval_duration",
866
"created", "enabled", "expires", "key_id",
867
"fingerprint", "host", "interval",
868
"last_approval_request", "last_checked_ok",
869
"last_enabled", "name", "timeout")
872
"extended_timeout": "PT15M",
874
"checker": "fping -q -- %%(host)s",
876
"approval_delay": "PT0S",
877
"approval_duration": "PT1S",
878
"approved_by_default": "True",
274
runtime_expansions = (u"approval_delay", u"approval_duration",
275
u"created", u"enabled", u"fingerprint",
276
u"host", u"interval", u"last_checked_ok",
277
u"last_enabled", u"name", u"timeout")
883
def config_parser(config):
884
"""Construct a new dict of client settings of this form:
885
{ client_name: {setting_name: value, ...}, ...}
886
with exceptions for any special settings as defined above.
887
NOTE: Must be a pure function. Must return the same result
888
value given the same arguments.
891
for client_name in config.sections():
892
section = dict(config.items(client_name))
893
client = settings[client_name] = {}
895
client["host"] = section["host"]
896
# Reformat values from string types to Python types
897
client["approved_by_default"] = config.getboolean(
898
client_name, "approved_by_default")
899
client["enabled"] = config.getboolean(client_name,
902
# Uppercase and remove spaces from key_id and fingerprint
903
# for later comparison purposes with return value from the
904
# key_id() and fingerprint() functions
905
client["key_id"] = (section.get("key_id", "").upper()
907
client["fingerprint"] = (section["fingerprint"].upper()
909
if "secret" in section:
910
client["secret"] = codecs.decode(section["secret"]
913
elif "secfile" in section:
914
with open(os.path.expanduser(os.path.expandvars
915
(section["secfile"])),
917
client["secret"] = secfile.read()
919
raise TypeError("No secret or secfile for section {}"
921
client["timeout"] = string_to_delta(section["timeout"])
922
client["extended_timeout"] = string_to_delta(
923
section["extended_timeout"])
924
client["interval"] = string_to_delta(section["interval"])
925
client["approval_delay"] = string_to_delta(
926
section["approval_delay"])
927
client["approval_duration"] = string_to_delta(
928
section["approval_duration"])
929
client["checker_command"] = section["checker"]
930
client["last_approval_request"] = None
931
client["last_checked_ok"] = None
932
client["last_checker_status"] = -2
936
def __init__(self, settings, name=None, server_settings=None):
280
def _timedelta_to_milliseconds(td):
281
"Convert a datetime.timedelta() to milliseconds"
282
return ((td.days * 24 * 60 * 60 * 1000)
283
+ (td.seconds * 1000)
284
+ (td.microseconds // 1000))
286
def timeout_milliseconds(self):
287
"Return the 'timeout' attribute in milliseconds"
288
return self._timedelta_to_milliseconds(self.timeout)
290
def interval_milliseconds(self):
291
"Return the 'interval' attribute in milliseconds"
292
return self._timedelta_to_milliseconds(self.interval)
294
def approval_delay_milliseconds(self):
295
return self._timedelta_to_milliseconds(self.approval_delay)
297
def __init__(self, name = None, disable_hook=None, config=None):
298
"""Note: the 'checker' key in 'config' sets the
299
'checker_command' attribute and *not* the 'checker'
938
if server_settings is None:
940
self.server_settings = server_settings
941
# adding all client settings
942
for setting, value in settings.items():
943
setattr(self, setting, value)
946
if not hasattr(self, "last_enabled"):
947
self.last_enabled = datetime.datetime.utcnow()
948
if not hasattr(self, "expires"):
949
self.expires = (datetime.datetime.utcnow()
304
logger.debug(u"Creating client %r", self.name)
305
# Uppercase and remove spaces from fingerprint for later
306
# comparison purposes with return value from the fingerprint()
308
self.fingerprint = (config[u"fingerprint"].upper()
310
logger.debug(u" Fingerprint: %s", self.fingerprint)
311
if u"secret" in config:
312
self.secret = config[u"secret"].decode(u"base64")
313
elif u"secfile" in config:
314
with open(os.path.expanduser(os.path.expandvars
315
(config[u"secfile"])),
317
self.secret = secfile.read()
952
self.last_enabled = None
955
logger.debug("Creating client %r", self.name)
956
logger.debug(" Key ID: %s", self.key_id)
957
logger.debug(" Fingerprint: %s", self.fingerprint)
958
self.created = settings.get("created",
959
datetime.datetime.utcnow())
961
# attributes specific for this server instance
319
raise TypeError(u"No secret or secfile for client %s"
321
self.host = config.get(u"host", u"")
322
self.created = datetime.datetime.utcnow()
324
self.last_enabled = None
325
self.last_checked_ok = None
326
self.timeout = string_to_delta(config[u"timeout"])
327
self.interval = string_to_delta(config[u"interval"])
328
self.disable_hook = disable_hook
962
329
self.checker = None
963
330
self.checker_initiator_tag = None
964
331
self.disable_initiator_tag = None
965
332
self.checker_callback_tag = None
333
self.checker_command = config[u"checker"]
966
334
self.current_checker_command = None
335
self.last_connect = None
336
self._approved = None
337
self.approved_by_default = config.get(u"approved_by_default",
968
339
self.approvals_pending = 0
969
self.changedstate = multiprocessing_manager.Condition(
970
multiprocessing_manager.Lock())
971
self.client_structure = [attr
972
for attr in self.__dict__.keys()
973
if not attr.startswith("_")]
974
self.client_structure.append("client_structure")
976
for name, t in inspect.getmembers(
977
type(self), lambda obj: isinstance(obj, property)):
978
if not name.startswith("_"):
979
self.client_structure.append(name)
981
# Send notice to process children that client state has changed
340
self.approval_delay = string_to_delta(
341
config[u"approval_delay"])
342
self.approval_duration = string_to_delta(
343
config[u"approval_duration"])
344
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
982
346
def send_changedstate(self):
983
with self.changedstate:
984
self.changedstate.notify_all()
347
self.changedstate.acquire()
348
self.changedstate.notify_all()
349
self.changedstate.release()
986
351
def enable(self):
987
352
"""Start this client's checker and timeout hooks"""
988
if getattr(self, "enabled", False):
353
if getattr(self, u"enabled", False):
989
354
# Already enabled
991
self.expires = datetime.datetime.utcnow() + self.timeout
356
self.send_changedstate()
993
357
self.last_enabled = datetime.datetime.utcnow()
995
self.send_changedstate()
358
# Schedule a new checker to be started an 'interval' from now,
359
# and every interval from then on.
360
self.checker_initiator_tag = (gobject.timeout_add
361
(self.interval_milliseconds(),
363
# Schedule a disable() when 'timeout' has passed
364
self.disable_initiator_tag = (gobject.timeout_add
365
(self.timeout_milliseconds(),
368
# Also start a new checker *right now*.
997
371
def disable(self, quiet=True):
998
372
"""Disable this client."""
999
373
if not getattr(self, "enabled", False):
1002
logger.info("Disabling client %s", self.name)
1003
if getattr(self, "disable_initiator_tag", None) is not None:
1004
GLib.source_remove(self.disable_initiator_tag)
376
self.send_changedstate()
378
logger.info(u"Disabling client %s", self.name)
379
if getattr(self, u"disable_initiator_tag", False):
380
gobject.source_remove(self.disable_initiator_tag)
1005
381
self.disable_initiator_tag = None
1007
if getattr(self, "checker_initiator_tag", None) is not None:
1008
GLib.source_remove(self.checker_initiator_tag)
382
if getattr(self, u"checker_initiator_tag", False):
383
gobject.source_remove(self.checker_initiator_tag)
1009
384
self.checker_initiator_tag = None
1010
385
self.stop_checker()
386
if self.disable_hook:
387
self.disable_hook(self)
1011
388
self.enabled = False
1013
self.send_changedstate()
1014
# Do not run this again if called by a GLib.timeout_add
389
# Do not run this again if called by a gobject.timeout_add
1017
392
def __del__(self):
393
self.disable_hook = None
1020
def init_checker(self):
1021
# Schedule a new checker to be started an 'interval' from now,
1022
# and every interval from then on.
1023
if self.checker_initiator_tag is not None:
1024
GLib.source_remove(self.checker_initiator_tag)
1025
self.checker_initiator_tag = GLib.timeout_add(
1026
int(self.interval.total_seconds() * 1000),
1028
# Schedule a disable() when 'timeout' has passed
1029
if self.disable_initiator_tag is not None:
1030
GLib.source_remove(self.disable_initiator_tag)
1031
self.disable_initiator_tag = GLib.timeout_add(
1032
int(self.timeout.total_seconds() * 1000), self.disable)
1033
# Also start a new checker *right now*.
1034
self.start_checker()
1036
def checker_callback(self, source, condition, connection,
396
def checker_callback(self, pid, condition, command):
1038
397
"""The checker has completed, so take appropriate actions."""
1039
# Read return code from connection (see call_pipe)
1040
returncode = connection.recv()
1043
398
self.checker_callback_tag = None
1044
399
self.checker = None
1047
self.last_checker_status = returncode
1048
self.last_checker_signal = None
1049
if self.last_checker_status == 0:
1050
logger.info("Checker for %(name)s succeeded",
400
if os.WIFEXITED(condition):
401
exitstatus = os.WEXITSTATUS(condition)
403
logger.info(u"Checker for %(name)s succeeded",
1052
405
self.checked_ok()
1054
logger.info("Checker for %(name)s failed", vars(self))
407
logger.info(u"Checker for %(name)s failed",
1056
self.last_checker_status = -1
1057
self.last_checker_signal = -returncode
1058
logger.warning("Checker for %(name)s crashed?",
410
logger.warning(u"Checker for %(name)s crashed?",
1062
413
def checked_ok(self):
1063
"""Assert that the client has been seen, alive and well."""
414
"""Bump up the timeout for this client.
416
This should only be called when the client has been seen,
1064
419
self.last_checked_ok = datetime.datetime.utcnow()
1065
self.last_checker_status = 0
1066
self.last_checker_signal = None
1069
def bump_timeout(self, timeout=None):
1070
"""Bump up the timeout for this client."""
1072
timeout = self.timeout
1073
if self.disable_initiator_tag is not None:
1074
GLib.source_remove(self.disable_initiator_tag)
1075
self.disable_initiator_tag = None
1076
if getattr(self, "enabled", False):
1077
self.disable_initiator_tag = GLib.timeout_add(
1078
int(timeout.total_seconds() * 1000), self.disable)
1079
self.expires = datetime.datetime.utcnow() + timeout
1081
def need_approval(self):
1082
self.last_approval_request = datetime.datetime.utcnow()
420
gobject.source_remove(self.disable_initiator_tag)
421
self.disable_initiator_tag = (gobject.timeout_add
422
(self.timeout_milliseconds(),
1084
425
def start_checker(self):
1085
426
"""Start a new checker subprocess if one is not running.
1087
428
If a checker already exists, leave it running and do
1089
430
# The reason for not killing a running checker is that if we
1090
# did that, and if a checker (for some reason) started running
1091
# slowly and taking more than 'interval' time, then the client
1092
# would inevitably timeout, since no checker would get a
1093
# chance to run to completion. If we instead leave running
431
# did that, then if a checker (for some reason) started
432
# running slowly and taking more than 'interval' time, the
433
# client would inevitably timeout, since no checker would get
434
# a chance to run to completion. If we instead leave running
1094
435
# checkers alone, the checker would have to take more time
1095
436
# than 'timeout' for the client to be disabled, which is as it
1098
if self.checker is not None and not self.checker.is_alive():
1099
logger.warning("Checker was not alive; joining")
439
# If a checker exists, make sure it is not a zombie
441
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
442
except (AttributeError, OSError), error:
443
if (isinstance(error, OSError)
444
and error.errno != errno.ECHILD):
448
logger.warning(u"Checker was a zombie")
449
gobject.source_remove(self.checker_callback_tag)
450
self.checker_callback(pid, status,
451
self.current_checker_command)
1102
452
# Start a new checker if needed
1103
453
if self.checker is None:
1104
# Escape attributes for the shell
1106
attr: re.escape(str(getattr(self, attr)))
1107
for attr in self.runtime_expansions}
1109
command = self.checker_command % escaped_attrs
1110
except TypeError as error:
1111
logger.error('Could not format string "%s"',
1112
self.checker_command,
1114
return True # Try again later
455
# In case checker_command has exactly one % operator
456
command = self.checker_command % self.host
458
# Escape attributes for the shell
459
escaped_attrs = dict(
461
re.escape(unicode(str(getattr(self, attr, u"")),
465
self.runtime_expansions)
468
command = self.checker_command % escaped_attrs
469
except TypeError, error:
470
logger.error(u'Could not format string "%s":'
471
u' %s', self.checker_command, error)
472
return True # Try again later
1115
473
self.current_checker_command = command
1116
logger.info("Starting checker %r for %s", command,
1118
# We don't need to redirect stdout and stderr, since
1119
# in normal mode, that is already done by daemon(),
1120
# and in debug mode we don't want to. (Stdin is
1121
# always replaced by /dev/null.)
1122
# The exception is when not debugging but nevertheless
1123
# running in the foreground; use the previously
1125
popen_args = {"close_fds": True,
1128
if (not self.server_settings["debug"]
1129
and self.server_settings["foreground"]):
1130
popen_args.update({"stdout": wnull,
1132
pipe = multiprocessing.Pipe(duplex=False)
1133
self.checker = multiprocessing.Process(
1135
args=(pipe[1], subprocess.call, command),
1137
self.checker.start()
1138
self.checker_callback_tag = GLib.io_add_watch(
1139
pipe[0].fileno(), GLib.IO_IN,
1140
self.checker_callback, pipe[0], command)
1141
# Re-run this periodically if run by GLib.timeout_add
475
logger.info(u"Starting checker %r for %s",
477
# We don't need to redirect stdout and stderr, since
478
# in normal mode, that is already done by daemon(),
479
# and in debug mode we don't want to. (Stdin is
480
# always replaced by /dev/null.)
481
self.checker = subprocess.Popen(command,
483
shell=True, cwd=u"/")
484
self.checker_callback_tag = (gobject.child_watch_add
486
self.checker_callback,
488
# The checker may have completed before the gobject
489
# watch was added. Check for this.
490
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
492
gobject.source_remove(self.checker_callback_tag)
493
self.checker_callback(pid, status, command)
494
except OSError, error:
495
logger.error(u"Failed to start subprocess: %s",
497
# Re-run this periodically if run by gobject.timeout_add
1144
500
def stop_checker(self):
1145
501
"""Force the checker process, if any, to stop."""
1146
502
if self.checker_callback_tag:
1147
GLib.source_remove(self.checker_callback_tag)
503
gobject.source_remove(self.checker_callback_tag)
1148
504
self.checker_callback_tag = None
1149
if getattr(self, "checker", None) is None:
505
if getattr(self, u"checker", None) is None:
1151
logger.debug("Stopping checker for %(name)s", vars(self))
1152
self.checker.terminate()
507
logger.debug(u"Stopping checker for %(name)s", vars(self))
509
os.kill(self.checker.pid, signal.SIGTERM)
511
#if self.checker.poll() is None:
512
# os.kill(self.checker.pid, signal.SIGKILL)
513
except OSError, error:
514
if error.errno != errno.ESRCH: # No such process
1153
516
self.checker = None
1156
def dbus_service_property(dbus_interface,
518
def dbus_service_property(dbus_interface, signature=u"v",
519
access=u"readwrite", byte_arrays=False):
1160
520
"""Decorators for marking methods of a DBusObjectWithProperties to
1161
521
become properties on the D-Bus.
1163
523
The decorated method will be called with no arguments by "Get"
1164
524
and with one argument by "Set".
1166
526
The parameters, where they are supported, are the same as
1167
527
dbus.service.method, except there is only "signature", since the
1168
528
type from Get() and the type sent to Set() is the same.
1170
530
# Encoding deeply encoded byte arrays is not supported yet by the
1171
531
# "Set" method, so we fail early here:
1172
if byte_arrays and signature != "ay":
1173
raise ValueError("Byte arrays not supported for non-'ay'"
1174
" signature {!r}".format(signature))
532
if byte_arrays and signature != u"ay":
533
raise ValueError(u"Byte arrays not supported for non-'ay'"
534
u" signature %r" % signature)
1176
535
def decorator(func):
1177
536
func._dbus_is_property = True
1178
537
func._dbus_interface = dbus_interface
1179
538
func._dbus_signature = signature
1180
539
func._dbus_access = access
1181
540
func._dbus_name = func.__name__
1182
if func._dbus_name.endswith("_dbus_property"):
541
if func._dbus_name.endswith(u"_dbus_property"):
1183
542
func._dbus_name = func._dbus_name[:-14]
1184
func._dbus_get_args_options = {'byte_arrays': byte_arrays}
1190
def dbus_interface_annotations(dbus_interface):
1191
"""Decorator for marking functions returning interface annotations
1195
@dbus_interface_annotations("org.example.Interface")
1196
def _foo(self): # Function name does not matter
1197
return {"org.freedesktop.DBus.Deprecated": "true",
1198
"org.freedesktop.DBus.Property.EmitsChangedSignal":
1202
def decorator(func):
1203
func._dbus_is_interface = True
1204
func._dbus_interface = dbus_interface
1205
func._dbus_name = dbus_interface
1211
def dbus_annotations(annotations):
1212
"""Decorator to annotate D-Bus methods, signals or properties
1215
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
1216
"org.freedesktop.DBus.Property."
1217
"EmitsChangedSignal": "false"})
1218
@dbus_service_property("org.example.Interface", signature="b",
1220
def Property_dbus_property(self):
1221
return dbus.Boolean(False)
1223
See also the DBusObjectWithAnnotations class.
1226
def decorator(func):
1227
func._dbus_annotations = annotations
543
func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
1230
545
return decorator
1233
548
class DBusPropertyException(dbus.exceptions.DBusException):
1234
549
"""A base class for D-Bus property-related exceptions
551
def __unicode__(self):
552
return unicode(str(self))
1239
555
class DBusPropertyAccessException(DBusPropertyException):
1251
class DBusObjectWithAnnotations(dbus.service.Object):
1252
"""A D-Bus object with annotations.
1254
Classes inheriting from this can use the dbus_annotations
1255
decorator to add annotations to methods or signals.
1259
def _is_dbus_thing(thing):
1260
"""Returns a function testing if an attribute is a D-Bus thing
1262
If called like _is_dbus_thing("method") it returns a function
1263
suitable for use as predicate to inspect.getmembers().
1265
return lambda obj: getattr(obj, "_dbus_is_{}".format(thing),
1268
def _get_all_dbus_things(self, thing):
1269
"""Returns a generator of (name, attribute) pairs
1271
return ((getattr(athing.__get__(self), "_dbus_name", name),
1272
athing.__get__(self))
1273
for cls in self.__class__.__mro__
1275
inspect.getmembers(cls, self._is_dbus_thing(thing)))
1277
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1279
path_keyword='object_path',
1280
connection_keyword='connection')
1281
def Introspect(self, object_path, connection):
1282
"""Overloading of standard D-Bus method.
1284
Inserts annotation tags on methods and signals.
1286
xmlstring = dbus.service.Object.Introspect(self, object_path,
1289
document = xml.dom.minidom.parseString(xmlstring)
1291
for if_tag in document.getElementsByTagName("interface"):
1292
# Add annotation tags
1293
for typ in ("method", "signal"):
1294
for tag in if_tag.getElementsByTagName(typ):
1296
for name, prop in (self.
1297
_get_all_dbus_things(typ)):
1298
if (name == tag.getAttribute("name")
1299
and prop._dbus_interface
1300
== if_tag.getAttribute("name")):
1301
annots.update(getattr(
1302
prop, "_dbus_annotations", {}))
1303
for name, value in annots.items():
1304
ann_tag = document.createElement(
1306
ann_tag.setAttribute("name", name)
1307
ann_tag.setAttribute("value", value)
1308
tag.appendChild(ann_tag)
1309
# Add interface annotation tags
1310
for annotation, value in dict(
1311
itertools.chain.from_iterable(
1312
annotations().items()
1313
for name, annotations
1314
in self._get_all_dbus_things("interface")
1315
if name == if_tag.getAttribute("name")
1317
ann_tag = document.createElement("annotation")
1318
ann_tag.setAttribute("name", annotation)
1319
ann_tag.setAttribute("value", value)
1320
if_tag.appendChild(ann_tag)
1321
# Fix argument name for the Introspect method itself
1322
if (if_tag.getAttribute("name")
1323
== dbus.INTROSPECTABLE_IFACE):
1324
for cn in if_tag.getElementsByTagName("method"):
1325
if cn.getAttribute("name") == "Introspect":
1326
for arg in cn.getElementsByTagName("arg"):
1327
if (arg.getAttribute("direction")
1329
arg.setAttribute("name",
1331
xmlstring = document.toxml("utf-8")
1333
except (AttributeError, xml.dom.DOMException,
1334
xml.parsers.expat.ExpatError) as error:
1335
logger.error("Failed to override Introspection method",
1340
class DBusObjectWithProperties(DBusObjectWithAnnotations):
567
class DBusObjectWithProperties(dbus.service.Object):
1341
568
"""A D-Bus object with properties.
1343
570
Classes inheriting from this can use the dbus_service_property
1344
571
decorator to expose methods as D-Bus properties. It exposes the
1345
572
standard Get(), Set(), and GetAll() methods on the D-Bus.
576
def _is_dbus_property(obj):
577
return getattr(obj, u"_dbus_is_property", False)
579
def _get_all_dbus_properties(self):
580
"""Returns a generator of (name, attribute) pairs
582
return ((prop._dbus_name, prop)
584
inspect.getmembers(self, self._is_dbus_property))
1348
586
def _get_dbus_property(self, interface_name, property_name):
1349
587
"""Returns a bound method if one exists which is a D-Bus
1350
588
property with the specified name and interface.
1352
for cls in self.__class__.__mro__:
1353
for name, value in inspect.getmembers(
1354
cls, self._is_dbus_thing("property")):
1355
if (value._dbus_name == property_name
1356
and value._dbus_interface == interface_name):
1357
return value.__get__(self)
590
for name in (property_name,
591
property_name + u"_dbus_property"):
592
prop = getattr(self, name, None)
594
or not self._is_dbus_property(prop)
595
or prop._dbus_name != property_name
596
or (interface_name and prop._dbus_interface
597
and interface_name != prop._dbus_interface)):
1359
600
# No such property
1360
raise DBusPropertyNotFound("{}:{}.{}".format(
1361
self.dbus_object_path, interface_name, property_name))
1364
def _get_all_interface_names(cls):
1365
"""Get a sequence of all interfaces supported by an object"""
1366
return (name for name in set(getattr(getattr(x, attr),
1367
"_dbus_interface", None)
1368
for x in (inspect.getmro(cls))
1370
if name is not None)
1372
@dbus.service.method(dbus.PROPERTIES_IFACE,
601
raise DBusPropertyNotFound(self.dbus_object_path + u":"
602
+ interface_name + u"."
605
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
1375
607
def Get(self, interface_name, property_name):
1376
608
"""Standard D-Bus property Get() method, see D-Bus standard.
1378
610
prop = self._get_dbus_property(interface_name, property_name)
1379
if prop._dbus_access == "write":
611
if prop._dbus_access == u"write":
1380
612
raise DBusPropertyAccessException(property_name)
1382
if not hasattr(value, "variant_level"):
614
if not hasattr(value, u"variant_level"):
1384
616
return type(value)(value, variant_level=value.variant_level+1)
1386
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
618
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
1387
619
def Set(self, interface_name, property_name, value):
1388
620
"""Standard D-Bus property Set() method, see D-Bus standard.
1390
622
prop = self._get_dbus_property(interface_name, property_name)
1391
if prop._dbus_access == "read":
623
if prop._dbus_access == u"read":
1392
624
raise DBusPropertyAccessException(property_name)
1393
if prop._dbus_get_args_options["byte_arrays"]:
625
if prop._dbus_get_args_options[u"byte_arrays"]:
1394
626
# The byte_arrays option is not supported yet on
1395
627
# signatures other than "ay".
1396
if prop._dbus_signature != "ay":
1397
raise ValueError("Byte arrays not supported for non-"
1398
"'ay' signature {!r}"
1399
.format(prop._dbus_signature))
1400
value = dbus.ByteArray(b''.join(chr(byte)
628
if prop._dbus_signature != u"ay":
630
value = dbus.ByteArray(''.join(unichr(byte)
1404
@dbus.service.method(dbus.PROPERTIES_IFACE,
1406
out_signature="a{sv}")
634
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
635
out_signature=u"a{sv}")
1407
636
def GetAll(self, interface_name):
1408
637
"""Standard D-Bus property GetAll() method, see D-Bus
1411
640
Note: Will not include properties with access="write".
1414
for name, prop in self._get_all_dbus_things("property"):
643
for name, prop in self._get_all_dbus_properties():
1415
644
if (interface_name
1416
645
and interface_name != prop._dbus_interface):
1417
646
# Interface non-empty but did not match
1419
648
# Ignore write-only properties
1420
if prop._dbus_access == "write":
649
if prop._dbus_access == u"write":
1423
if not hasattr(value, "variant_level"):
1424
properties[name] = value
652
if not hasattr(value, u"variant_level"):
1426
properties[name] = type(value)(
1427
value, variant_level=value.variant_level + 1)
1428
return dbus.Dictionary(properties, signature="sv")
1430
@dbus.service.signal(dbus.PROPERTIES_IFACE, signature="sa{sv}as")
1431
def PropertiesChanged(self, interface_name, changed_properties,
1432
invalidated_properties):
1433
"""Standard D-Bus PropertiesChanged() signal, see D-Bus
655
all[name] = type(value)(value, variant_level=
656
value.variant_level+1)
657
return dbus.Dictionary(all, signature=u"sv")
1438
659
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1440
661
path_keyword='object_path',
1441
662
connection_keyword='connection')
1442
663
def Introspect(self, object_path, connection):
1443
"""Overloading of standard D-Bus method.
1445
Inserts property tags and interface annotation tags.
664
"""Standard D-Bus method, overloaded to insert property tags.
1447
xmlstring = DBusObjectWithAnnotations.Introspect(self,
666
xmlstring = dbus.service.Object.Introspect(self, object_path,
1451
669
document = xml.dom.minidom.parseString(xmlstring)
1453
670
def make_tag(document, name, prop):
1454
e = document.createElement("property")
1455
e.setAttribute("name", name)
1456
e.setAttribute("type", prop._dbus_signature)
1457
e.setAttribute("access", prop._dbus_access)
671
e = document.createElement(u"property")
672
e.setAttribute(u"name", name)
673
e.setAttribute(u"type", prop._dbus_signature)
674
e.setAttribute(u"access", prop._dbus_access)
1460
for if_tag in document.getElementsByTagName("interface"):
676
for if_tag in document.getElementsByTagName(u"interface"):
1462
677
for tag in (make_tag(document, name, prop)
1464
in self._get_all_dbus_things("property")
679
in self._get_all_dbus_properties()
1465
680
if prop._dbus_interface
1466
== if_tag.getAttribute("name")):
681
== if_tag.getAttribute(u"name")):
1467
682
if_tag.appendChild(tag)
1468
# Add annotation tags for properties
1469
for tag in if_tag.getElementsByTagName("property"):
1471
for name, prop in self._get_all_dbus_things(
1473
if (name == tag.getAttribute("name")
1474
and prop._dbus_interface
1475
== if_tag.getAttribute("name")):
1476
annots.update(getattr(
1477
prop, "_dbus_annotations", {}))
1478
for name, value in annots.items():
1479
ann_tag = document.createElement(
1481
ann_tag.setAttribute("name", name)
1482
ann_tag.setAttribute("value", value)
1483
tag.appendChild(ann_tag)
1484
683
# Add the names to the return values for the
1485
684
# "org.freedesktop.DBus.Properties" methods
1486
if (if_tag.getAttribute("name")
1487
== "org.freedesktop.DBus.Properties"):
1488
for cn in if_tag.getElementsByTagName("method"):
1489
if cn.getAttribute("name") == "Get":
1490
for arg in cn.getElementsByTagName("arg"):
1491
if (arg.getAttribute("direction")
1493
arg.setAttribute("name", "value")
1494
elif cn.getAttribute("name") == "GetAll":
1495
for arg in cn.getElementsByTagName("arg"):
1496
if (arg.getAttribute("direction")
1498
arg.setAttribute("name", "props")
1499
xmlstring = document.toxml("utf-8")
1501
except (AttributeError, xml.dom.DOMException,
1502
xml.parsers.expat.ExpatError) as error:
1503
logger.error("Failed to override Introspection method",
1509
dbus.OBJECT_MANAGER_IFACE
1510
except AttributeError:
1511
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1514
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1515
"""A D-Bus object with an ObjectManager.
1517
Classes inheriting from this exposes the standard
1518
GetManagedObjects call and the InterfacesAdded and
1519
InterfacesRemoved signals on the standard
1520
"org.freedesktop.DBus.ObjectManager" interface.
1522
Note: No signals are sent automatically; they must be sent
1525
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1526
out_signature="a{oa{sa{sv}}}")
1527
def GetManagedObjects(self):
1528
"""This function must be overridden"""
1529
raise NotImplementedError()
1531
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1532
signature="oa{sa{sv}}")
1533
def InterfacesAdded(self, object_path, interfaces_and_properties):
1536
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature="oas")
1537
def InterfacesRemoved(self, object_path, interfaces):
1540
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1542
path_keyword='object_path',
1543
connection_keyword='connection')
1544
def Introspect(self, object_path, connection):
1545
"""Overloading of standard D-Bus method.
1547
Override return argument name of GetManagedObjects to be
1548
"objpath_interfaces_and_properties"
1550
xmlstring = DBusObjectWithAnnotations.Introspect(self,
1554
document = xml.dom.minidom.parseString(xmlstring)
1556
for if_tag in document.getElementsByTagName("interface"):
1557
# Fix argument name for the GetManagedObjects method
1558
if (if_tag.getAttribute("name")
1559
== dbus.OBJECT_MANAGER_IFACE):
1560
for cn in if_tag.getElementsByTagName("method"):
1561
if (cn.getAttribute("name")
1562
== "GetManagedObjects"):
1563
for arg in cn.getElementsByTagName("arg"):
1564
if (arg.getAttribute("direction")
1568
"objpath_interfaces"
1570
xmlstring = document.toxml("utf-8")
1572
except (AttributeError, xml.dom.DOMException,
1573
xml.parsers.expat.ExpatError) as error:
1574
logger.error("Failed to override Introspection method",
1579
def datetime_to_dbus(dt, variant_level=0):
1580
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1582
return dbus.String("", variant_level=variant_level)
1583
return dbus.String(dt.isoformat(), variant_level=variant_level)
1586
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1587
"""A class decorator; applied to a subclass of
1588
dbus.service.Object, it will add alternate D-Bus attributes with
1589
interface names according to the "alt_interface_names" mapping.
1592
@alternate_dbus_interfaces({"org.example.Interface":
1593
"net.example.AlternateInterface"})
1594
class SampleDBusObject(dbus.service.Object):
1595
@dbus.service.method("org.example.Interface")
1596
def SampleDBusMethod():
1599
The above "SampleDBusMethod" on "SampleDBusObject" will be
1600
reachable via two interfaces: "org.example.Interface" and
1601
"net.example.AlternateInterface", the latter of which will have
1602
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1603
"true", unless "deprecate" is passed with a False value.
1605
This works for methods and signals, and also for D-Bus properties
1606
(from DBusObjectWithProperties) and interfaces (from the
1607
dbus_interface_annotations decorator).
1611
for orig_interface_name, alt_interface_name in (
1612
alt_interface_names.items()):
1614
interface_names = set()
1615
# Go though all attributes of the class
1616
for attrname, attribute in inspect.getmembers(cls):
1617
# Ignore non-D-Bus attributes, and D-Bus attributes
1618
# with the wrong interface name
1619
if (not hasattr(attribute, "_dbus_interface")
1620
or not attribute._dbus_interface.startswith(
1621
orig_interface_name)):
1623
# Create an alternate D-Bus interface name based on
1625
alt_interface = attribute._dbus_interface.replace(
1626
orig_interface_name, alt_interface_name)
1627
interface_names.add(alt_interface)
1628
# Is this a D-Bus signal?
1629
if getattr(attribute, "_dbus_is_signal", False):
1630
# Extract the original non-method undecorated
1631
# function by black magic
1632
if sys.version_info.major == 2:
1633
nonmethod_func = (dict(
1634
zip(attribute.func_code.co_freevars,
1635
attribute.__closure__))
1636
["func"].cell_contents)
1638
nonmethod_func = (dict(
1639
zip(attribute.__code__.co_freevars,
1640
attribute.__closure__))
1641
["func"].cell_contents)
1642
# Create a new, but exactly alike, function
1643
# object, and decorate it to be a new D-Bus signal
1644
# with the alternate D-Bus interface name
1645
new_function = copy_function(nonmethod_func)
1646
new_function = (dbus.service.signal(
1648
attribute._dbus_signature)(new_function))
1649
# Copy annotations, if any
1651
new_function._dbus_annotations = dict(
1652
attribute._dbus_annotations)
1653
except AttributeError:
1656
# Define a creator of a function to call both the
1657
# original and alternate functions, so both the
1658
# original and alternate signals gets sent when
1659
# the function is called
1660
def fixscope(func1, func2):
1661
"""This function is a scope container to pass
1662
func1 and func2 to the "call_both" function
1663
outside of its arguments"""
1665
@functools.wraps(func2)
1666
def call_both(*args, **kwargs):
1667
"""This function will emit two D-Bus
1668
signals by calling func1 and func2"""
1669
func1(*args, **kwargs)
1670
func2(*args, **kwargs)
1671
# Make wrapper function look like a D-Bus
1673
for name, attr in inspect.getmembers(func2):
1674
if name.startswith("_dbus_"):
1675
setattr(call_both, name, attr)
1678
# Create the "call_both" function and add it to
1680
attr[attrname] = fixscope(attribute, new_function)
1681
# Is this a D-Bus method?
1682
elif getattr(attribute, "_dbus_is_method", False):
1683
# Create a new, but exactly alike, function
1684
# object. Decorate it to be a new D-Bus method
1685
# with the alternate D-Bus interface name. Add it
1688
dbus.service.method(
1690
attribute._dbus_in_signature,
1691
attribute._dbus_out_signature)
1692
(copy_function(attribute)))
1693
# Copy annotations, if any
1695
attr[attrname]._dbus_annotations = dict(
1696
attribute._dbus_annotations)
1697
except AttributeError:
1699
# Is this a D-Bus property?
1700
elif getattr(attribute, "_dbus_is_property", False):
1701
# Create a new, but exactly alike, function
1702
# object, and decorate it to be a new D-Bus
1703
# property with the alternate D-Bus interface
1704
# name. Add it to the class.
1705
attr[attrname] = (dbus_service_property(
1706
alt_interface, attribute._dbus_signature,
1707
attribute._dbus_access,
1708
attribute._dbus_get_args_options
1710
(copy_function(attribute)))
1711
# Copy annotations, if any
1713
attr[attrname]._dbus_annotations = dict(
1714
attribute._dbus_annotations)
1715
except AttributeError:
1717
# Is this a D-Bus interface?
1718
elif getattr(attribute, "_dbus_is_interface", False):
1719
# Create a new, but exactly alike, function
1720
# object. Decorate it to be a new D-Bus interface
1721
# with the alternate D-Bus interface name. Add it
1724
dbus_interface_annotations(alt_interface)
1725
(copy_function(attribute)))
1727
# Deprecate all alternate interfaces
1728
iname = "_AlternateDBusNames_interface_annotation{}"
1729
for interface_name in interface_names:
1731
@dbus_interface_annotations(interface_name)
1733
return {"org.freedesktop.DBus.Deprecated":
1735
# Find an unused name
1736
for aname in (iname.format(i)
1737
for i in itertools.count()):
1738
if aname not in attr:
1742
# Replace the class with a new subclass of it with
1743
# methods, signals, etc. as created above.
1744
if sys.version_info.major == 2:
1745
cls = type(b"{}Alternate".format(cls.__name__),
1748
cls = type("{}Alternate".format(cls.__name__),
1755
@alternate_dbus_interfaces({"se.recompile.Mandos":
1756
"se.bsnet.fukt.Mandos"})
685
if (if_tag.getAttribute(u"name")
686
== u"org.freedesktop.DBus.Properties"):
687
for cn in if_tag.getElementsByTagName(u"method"):
688
if cn.getAttribute(u"name") == u"Get":
689
for arg in cn.getElementsByTagName(u"arg"):
690
if (arg.getAttribute(u"direction")
692
arg.setAttribute(u"name", u"value")
693
elif cn.getAttribute(u"name") == u"GetAll":
694
for arg in cn.getElementsByTagName(u"arg"):
695
if (arg.getAttribute(u"direction")
697
arg.setAttribute(u"name", u"props")
698
xmlstring = document.toxml(u"utf-8")
700
except (AttributeError, xml.dom.DOMException,
701
xml.parsers.expat.ExpatError), error:
702
logger.error(u"Failed to override Introspection method",
1757
707
class ClientDBus(Client, DBusObjectWithProperties):
1758
708
"""A Client class using D-Bus
1761
711
dbus_object_path: dbus.ObjectPath
1762
712
bus: dbus.SystemBus()
1765
715
runtime_expansions = (Client.runtime_expansions
1766
+ ("dbus_object_path", ))
1768
_interface = "se.recompile.Mandos.Client"
716
+ (u"dbus_object_path",))
1770
718
# dbus.service.Object doesn't use super(), so we can't either.
1772
def __init__(self, bus=None, *args, **kwargs):
720
def __init__(self, bus = None, *args, **kwargs):
721
self._approvals_pending = 0
1774
723
Client.__init__(self, *args, **kwargs)
1775
724
# Only now, when this client is initialized, can it show up on
1777
client_object_name = str(self.name).translate(
1778
{ord("."): ord("_"),
1779
ord("-"): ord("_")})
1780
self.dbus_object_path = dbus.ObjectPath(
1781
"/clients/" + client_object_name)
726
client_object_name = unicode(self.name).translate(
727
{ord(u"."): ord(u"_"),
728
ord(u"-"): ord(u"_")})
729
self.dbus_object_path = (dbus.ObjectPath
730
(u"/clients/" + client_object_name))
1782
731
DBusObjectWithProperties.__init__(self, self.bus,
1783
732
self.dbus_object_path)
1785
def notifychangeproperty(transform_func, dbus_name,
1786
type_func=lambda x: x,
1788
invalidate_only=False,
1789
_interface=_interface):
1790
""" Modify a variable so that it's a property which announces
1791
its changes to DBus.
1793
transform_fun: Function that takes a value and a variant_level
1794
and transforms it to a D-Bus type.
1795
dbus_name: D-Bus name of the variable
1796
type_func: Function that transform the value before sending it
1797
to the D-Bus. Default: no transform
1798
variant_level: D-Bus variant level. Default: 1
1800
attrname = "_{}".format(dbus_name)
1802
def setter(self, value):
1803
if hasattr(self, "dbus_object_path"):
1804
if (not hasattr(self, attrname) or
1805
type_func(getattr(self, attrname, None))
1806
!= type_func(value)):
1808
self.PropertiesChanged(
1809
_interface, dbus.Dictionary(),
1810
dbus.Array((dbus_name, )))
1812
dbus_value = transform_func(
1814
variant_level=variant_level)
1815
self.PropertyChanged(dbus.String(dbus_name),
1817
self.PropertiesChanged(
1819
dbus.Dictionary({dbus.String(dbus_name):
1822
setattr(self, attrname, value)
1824
return property(lambda self: getattr(self, attrname), setter)
1826
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1827
approvals_pending = notifychangeproperty(dbus.Boolean,
1830
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1831
last_enabled = notifychangeproperty(datetime_to_dbus,
1833
checker = notifychangeproperty(
1834
dbus.Boolean, "CheckerRunning",
1835
type_func=lambda checker: checker is not None)
1836
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1838
last_checker_status = notifychangeproperty(dbus.Int16,
1839
"LastCheckerStatus")
1840
last_approval_request = notifychangeproperty(
1841
datetime_to_dbus, "LastApprovalRequest")
1842
approved_by_default = notifychangeproperty(dbus.Boolean,
1843
"ApprovedByDefault")
1844
approval_delay = notifychangeproperty(
1845
dbus.UInt64, "ApprovalDelay",
1846
type_func=lambda td: td.total_seconds() * 1000)
1847
approval_duration = notifychangeproperty(
1848
dbus.UInt64, "ApprovalDuration",
1849
type_func=lambda td: td.total_seconds() * 1000)
1850
host = notifychangeproperty(dbus.String, "Host")
1851
timeout = notifychangeproperty(
1852
dbus.UInt64, "Timeout",
1853
type_func=lambda td: td.total_seconds() * 1000)
1854
extended_timeout = notifychangeproperty(
1855
dbus.UInt64, "ExtendedTimeout",
1856
type_func=lambda td: td.total_seconds() * 1000)
1857
interval = notifychangeproperty(
1858
dbus.UInt64, "Interval",
1859
type_func=lambda td: td.total_seconds() * 1000)
1860
checker_command = notifychangeproperty(dbus.String, "Checker")
1861
secret = notifychangeproperty(dbus.ByteArray, "Secret",
1862
invalidate_only=True)
1864
del notifychangeproperty
734
def _get_approvals_pending(self):
735
return self._approvals_pending
736
def _set_approvals_pending(self, value):
737
old_value = self._approvals_pending
738
self._approvals_pending = value
740
if (hasattr(self, "dbus_object_path")
741
and bval is not bool(old_value)):
742
dbus_bool = dbus.Boolean(bval, variant_level=1)
743
self.PropertyChanged(dbus.String(u"ApprovalPending"),
746
approvals_pending = property(_get_approvals_pending,
747
_set_approvals_pending)
748
del _get_approvals_pending, _set_approvals_pending
751
def _datetime_to_dbus(dt, variant_level=0):
752
"""Convert a UTC datetime.datetime() to a D-Bus type."""
753
return dbus.String(dt.isoformat(),
754
variant_level=variant_level)
757
oldstate = getattr(self, u"enabled", False)
758
r = Client.enable(self)
759
if oldstate != self.enabled:
761
self.PropertyChanged(dbus.String(u"Enabled"),
762
dbus.Boolean(True, variant_level=1))
763
self.PropertyChanged(
764
dbus.String(u"LastEnabled"),
765
self._datetime_to_dbus(self.last_enabled,
769
def disable(self, quiet = False):
770
oldstate = getattr(self, u"enabled", False)
771
r = Client.disable(self, quiet=quiet)
772
if not quiet and oldstate != self.enabled:
774
self.PropertyChanged(dbus.String(u"Enabled"),
775
dbus.Boolean(False, variant_level=1))
1866
778
def __del__(self, *args, **kwargs):
1868
780
self.remove_from_connection()
1869
781
except LookupError:
1871
if hasattr(DBusObjectWithProperties, "__del__"):
783
if hasattr(DBusObjectWithProperties, u"__del__"):
1872
784
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1873
785
Client.__del__(self, *args, **kwargs)
1875
def checker_callback(self, source, condition,
1876
connection, command, *args, **kwargs):
1877
ret = Client.checker_callback(self, source, condition,
1878
connection, command, *args,
1880
exitstatus = self.last_checker_status
787
def checker_callback(self, pid, condition, command,
789
self.checker_callback_tag = None
792
self.PropertyChanged(dbus.String(u"CheckerRunning"),
793
dbus.Boolean(False, variant_level=1))
794
if os.WIFEXITED(condition):
795
exitstatus = os.WEXITSTATUS(condition)
1882
796
# Emit D-Bus signal
1883
797
self.CheckerCompleted(dbus.Int16(exitstatus),
1884
# This is specific to GNU libC
1885
dbus.Int64(exitstatus << 8),
798
dbus.Int64(condition),
1886
799
dbus.String(command))
1888
801
# Emit D-Bus signal
1889
802
self.CheckerCompleted(dbus.Int16(-1),
1891
# This is specific to GNU libC
1893
| self.last_checker_signal),
803
dbus.Int64(condition),
1894
804
dbus.String(command))
806
return Client.checker_callback(self, pid, condition, command,
809
def checked_ok(self, *args, **kwargs):
810
r = Client.checked_ok(self, *args, **kwargs)
812
self.PropertyChanged(
813
dbus.String(u"LastCheckedOK"),
814
(self._datetime_to_dbus(self.last_checked_ok,
1897
818
def start_checker(self, *args, **kwargs):
1898
old_checker_pid = getattr(self.checker, "pid", None)
819
old_checker = self.checker
820
if self.checker is not None:
821
old_checker_pid = self.checker.pid
823
old_checker_pid = None
1899
824
r = Client.start_checker(self, *args, **kwargs)
1900
825
# Only if new checker process was started
1901
826
if (self.checker is not None
1902
827
and old_checker_pid != self.checker.pid):
1903
828
# Emit D-Bus signal
1904
829
self.CheckerStarted(self.current_checker_command)
830
self.PropertyChanged(
831
dbus.String(u"CheckerRunning"),
832
dbus.Boolean(True, variant_level=1))
835
def stop_checker(self, *args, **kwargs):
836
old_checker = getattr(self, u"checker", None)
837
r = Client.stop_checker(self, *args, **kwargs)
838
if (old_checker is not None
839
and getattr(self, u"checker", None) is None):
840
self.PropertyChanged(dbus.String(u"CheckerRunning"),
841
dbus.Boolean(False, variant_level=1))
1907
844
def _reset_approved(self):
1908
self.approved = None
845
self._approved = None
1911
848
def approve(self, value=True):
1912
self.approved = value
1913
GLib.timeout_add(int(self.approval_duration.total_seconds()
1914
* 1000), self._reset_approved)
1915
849
self.send_changedstate()
1917
# D-Bus methods, signals & properties
850
self._approved = value
851
gobject.timeout_add(self._timedelta_to_milliseconds
852
(self.approval_duration),
853
self._reset_approved)
856
## D-Bus methods, signals & properties
857
_interface = u"se.bsnet.fukt.Mandos.Client"
1923
861
# CheckerCompleted - signal
1924
@dbus.service.signal(_interface, signature="nxs")
862
@dbus.service.signal(_interface, signature=u"nxs")
1925
863
def CheckerCompleted(self, exitcode, waitstatus, command):
1929
867
# CheckerStarted - signal
1930
@dbus.service.signal(_interface, signature="s")
868
@dbus.service.signal(_interface, signature=u"s")
1931
869
def CheckerStarted(self, command):
1935
873
# PropertyChanged - signal
1936
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1937
@dbus.service.signal(_interface, signature="sv")
874
@dbus.service.signal(_interface, signature=u"sv")
1938
875
def PropertyChanged(self, property, value):
1942
879
# GotSecret - signal
1943
880
@dbus.service.signal(_interface)
1944
881
def GotSecret(self):
2186
1097
self.start_checker()
2188
1099
self.stop_checker()
2190
1101
# ObjectPath - property
2192
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const",
2193
"org.freedesktop.DBus.Deprecated": "true"})
2194
@dbus_service_property(_interface, signature="o", access="read")
1102
@dbus_service_property(_interface, signature=u"o", access=u"read")
2195
1103
def ObjectPath_dbus_property(self):
2196
return self.dbus_object_path # is already a dbus.ObjectPath
1104
return self.dbus_object_path # is already a dbus.ObjectPath
2198
1106
# Secret = property
2200
{"org.freedesktop.DBus.Property.EmitsChangedSignal":
2202
@dbus_service_property(_interface,
1107
@dbus_service_property(_interface, signature=u"ay",
1108
access=u"write", byte_arrays=True)
2206
1109
def Secret_dbus_property(self, value):
2207
self.secret = bytes(value)
1110
self.secret = str(value)
2212
1115
class ProxyClient(object):
2213
def __init__(self, child_pipe, key_id, fpr, address):
1116
def __init__(self, child_pipe, fpr, address):
2214
1117
self._pipe = child_pipe
2215
self._pipe.send(('init', key_id, fpr, address))
1118
self._pipe.send(('init', fpr, address))
2216
1119
if not self._pipe.recv():
2217
raise KeyError(key_id or fpr)
2219
1122
def __getattribute__(self, name):
1123
if(name == '_pipe'):
2221
1124
return super(ProxyClient, self).__getattribute__(name)
2222
1125
self._pipe.send(('getattr', name))
2223
1126
data = self._pipe.recv()
2224
1127
if data[0] == 'data':
2226
1129
if data[0] == 'function':
2228
1130
def func(*args, **kwargs):
2229
1131
self._pipe.send(('funcall', name, args, kwargs))
2230
1132
return self._pipe.recv()[1]
2234
1135
def __setattr__(self, name, value):
1136
if(name == '_pipe'):
2236
1137
return super(ProxyClient, self).__setattr__(name, value)
2237
1138
self._pipe.send(('setattr', name, value))
2240
1141
class ClientHandler(socketserver.BaseRequestHandler, object):
2241
1142
"""A class to handle client connections.
2243
1144
Instantiated once for each connection to handle it.
2244
1145
Note: This will run in its own forked process."""
2246
1147
def handle(self):
2247
1148
with contextlib.closing(self.server.child_pipe) as child_pipe:
2248
logger.info("TCP connection from: %s",
2249
str(self.client_address))
2250
logger.debug("Pipe FD: %d",
1149
logger.info(u"TCP connection from: %s",
1150
unicode(self.client_address))
1151
logger.debug(u"Pipe FD: %d",
2251
1152
self.server.child_pipe.fileno())
2253
session = gnutls.ClientSession(self.request)
2255
# priority = ':'.join(("NONE", "+VERS-TLS1.1",
2256
# "+AES-256-CBC", "+SHA1",
2257
# "+COMP-NULL", "+CTYPE-OPENPGP",
1154
session = (gnutls.connection
1155
.ClientSession(self.request,
1157
.X509Credentials()))
1159
# Note: gnutls.connection.X509Credentials is really a
1160
# generic GnuTLS certificate credentials object so long as
1161
# no X.509 keys are added to it. Therefore, we can use it
1162
# here despite using OpenPGP certificates.
1164
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1165
# u"+AES-256-CBC", u"+SHA1",
1166
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
2259
1168
# Use a fallback default, since this MUST be set.
2260
1169
priority = self.server.gnutls_priority
2261
1170
if priority is None:
2263
gnutls.priority_set_direct(session._c_object,
2264
priority.encode("utf-8"),
1171
priority = u"NORMAL"
1172
(gnutls.library.functions
1173
.gnutls_priority_set_direct(session._c_object,
2267
1176
# Start communication using the Mandos protocol
2268
1177
# Get protocol number
2269
1178
line = self.request.makefile().readline()
2270
logger.debug("Protocol version: %r", line)
1179
logger.debug(u"Protocol version: %r", line)
2272
1181
if int(line.strip().split()[0]) > 1:
2273
raise RuntimeError(line)
2274
except (ValueError, IndexError, RuntimeError) as error:
2275
logger.error("Unknown protocol version: %s", error)
1183
except (ValueError, IndexError, RuntimeError), error:
1184
logger.error(u"Unknown protocol version: %s", error)
2278
1187
# Start GnuTLS connection
2280
1189
session.handshake()
2281
except gnutls.Error as error:
2282
logger.warning("Handshake failed: %s", error)
1190
except gnutls.errors.GNUTLSError, error:
1191
logger.warning(u"Handshake failed: %s", error)
2283
1192
# Do not run session.bye() here: the session is not
2284
1193
# established. Just abandon the request.
2286
logger.debug("Handshake succeeded")
1195
logger.debug(u"Handshake succeeded")
2288
1197
approval_required = False
2290
if gnutls.has_rawpk:
2293
key_id = self.key_id(
2294
self.peer_certificate(session))
2295
except (TypeError, gnutls.Error) as error:
2296
logger.warning("Bad certificate: %s", error)
2298
logger.debug("Key ID: %s", key_id)
2303
fpr = self.fingerprint(
2304
self.peer_certificate(session))
2305
except (TypeError, gnutls.Error) as error:
2306
logger.warning("Bad certificate: %s", error)
2308
logger.debug("Fingerprint: %s", fpr)
2311
client = ProxyClient(child_pipe, key_id, fpr,
1200
fpr = self.fingerprint(self.peer_certificate
1202
except (TypeError, gnutls.errors.GNUTLSError), error:
1203
logger.warning(u"Bad certificate: %s", error)
1205
logger.debug(u"Fingerprint: %s", fpr)
1208
client = ProxyClient(child_pipe, fpr,
2312
1209
self.client_address)
2313
1210
except KeyError:
2316
1213
if client.approval_delay:
2317
1214
delay = client.approval_delay
2318
1215
client.approvals_pending += 1
2319
1216
approval_required = True
2322
1219
if not client.enabled:
2323
logger.info("Client %s is disabled",
1220
logger.warning(u"Client %s is disabled",
2325
1222
if self.server.use_dbus:
2326
1223
# Emit D-Bus signal
2327
client.Rejected("Disabled")
1224
client.Rejected("Disabled")
2330
if client.approved or not client.approval_delay:
2331
# We are approved or approval is disabled
1227
if client._approved or not client.approval_delay:
1228
#We are approved or approval is disabled
2333
elif client.approved is None:
2334
logger.info("Client %s needs approval",
1230
elif client._approved is None:
1231
logger.info(u"Client %s needs approval",
2336
1233
if self.server.use_dbus:
2337
1234
# Emit D-Bus signal
2338
1235
client.NeedApproval(
2339
client.approval_delay.total_seconds()
2340
* 1000, client.approved_by_default)
1236
client.approval_delay_milliseconds(),
1237
client.approved_by_default)
2342
logger.warning("Client %s was not approved",
1239
logger.warning(u"Client %s was not approved",
2344
1241
if self.server.use_dbus:
2345
1242
# Emit D-Bus signal
2346
1243
client.Rejected("Denied")
2349
# wait until timeout or approved
1246
#wait until timeout or approved
1247
#x = float(client._timedelta_to_milliseconds(delay))
2350
1248
time = datetime.datetime.now()
2351
1249
client.changedstate.acquire()
2352
client.changedstate.wait(delay.total_seconds())
1250
client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
2353
1251
client.changedstate.release()
2354
1252
time2 = datetime.datetime.now()
2355
1253
if (time2 - time) >= delay:
2367
1265
delay -= time2 - time
2370
session.send(client.secret)
2371
except gnutls.Error as error:
2372
logger.warning("gnutls send failed",
2376
logger.info("Sending secret to %s", client.name)
2377
# bump the timeout using extended_timeout
2378
client.bump_timeout(client.extended_timeout)
1268
while sent_size < len(client.secret):
1270
sent = session.send(client.secret[sent_size:])
1271
except (gnutls.errors.GNUTLSError), error:
1272
logger.warning("gnutls send failed")
1274
logger.debug(u"Sent: %d, remaining: %d",
1275
sent, len(client.secret)
1276
- (sent_size + sent))
1279
logger.info(u"Sending secret to %s", client.name)
1280
# bump the timeout as if seen
2379
1282
if self.server.use_dbus:
2380
1283
# Emit D-Bus signal
2381
1284
client.GotSecret()
2384
1287
if approval_required:
2385
1288
client.approvals_pending -= 1
2388
except gnutls.Error as error:
2389
logger.warning("GnuTLS bye failed",
1291
except (gnutls.errors.GNUTLSError), error:
1292
logger.warning("GnuTLS bye failed")
2393
1295
def peer_certificate(session):
2394
"Return the peer's certificate as a bytestring"
2396
cert_type = gnutls.certificate_type_get2(session._c_object,
2398
except AttributeError:
2399
cert_type = gnutls.certificate_type_get(session._c_object)
2400
if gnutls.has_rawpk:
2401
valid_cert_types = frozenset((gnutls.CRT_RAWPK,))
2403
valid_cert_types = frozenset((gnutls.CRT_OPENPGP,))
2404
# If not a valid certificate type...
2405
if cert_type not in valid_cert_types:
2406
logger.info("Cert type %r not in %r", cert_type,
2408
# ...return invalid data
1296
"Return the peer's OpenPGP certificate as a bytestring"
1297
# If not an OpenPGP certificate...
1298
if (gnutls.library.functions
1299
.gnutls_certificate_type_get(session._c_object)
1300
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1301
# ...do the normal thing
1302
return session.peer_certificate
2410
1303
list_size = ctypes.c_uint(1)
2411
cert_list = (gnutls.certificate_get_peers
1304
cert_list = (gnutls.library.functions
1305
.gnutls_certificate_get_peers
2412
1306
(session._c_object, ctypes.byref(list_size)))
2413
1307
if not bool(cert_list) and list_size.value != 0:
2414
raise gnutls.Error("error getting peer certificate")
1308
raise gnutls.errors.GNUTLSError(u"error getting peer"
2415
1310
if list_size.value == 0:
2417
1312
cert = cert_list[0]
2418
1313
return ctypes.string_at(cert.data, cert.size)
2421
def key_id(certificate):
2422
"Convert a certificate bytestring to a hexdigit key ID"
2423
# New GnuTLS "datum" with the public key
2424
datum = gnutls.datum_t(
2425
ctypes.cast(ctypes.c_char_p(certificate),
2426
ctypes.POINTER(ctypes.c_ubyte)),
2427
ctypes.c_uint(len(certificate)))
2428
# XXX all these need to be created in the gnutls "module"
2429
# New empty GnuTLS certificate
2430
pubkey = gnutls.pubkey_t()
2431
gnutls.pubkey_init(ctypes.byref(pubkey))
2432
# Import the raw public key into the certificate
2433
gnutls.pubkey_import(pubkey,
2434
ctypes.byref(datum),
2435
gnutls.X509_FMT_DER)
2436
# New buffer for the key ID
2437
buf = ctypes.create_string_buffer(32)
2438
buf_len = ctypes.c_size_t(len(buf))
2439
# Get the key ID from the raw public key into the buffer
2440
gnutls.pubkey_get_key_id(pubkey,
2441
gnutls.KEYID_USE_SHA256,
2442
ctypes.cast(ctypes.byref(buf),
2443
ctypes.POINTER(ctypes.c_ubyte)),
2444
ctypes.byref(buf_len))
2445
# Deinit the certificate
2446
gnutls.pubkey_deinit(pubkey)
2448
# Convert the buffer to a Python bytestring
2449
key_id = ctypes.string_at(buf, buf_len.value)
2450
# Convert the bytestring to hexadecimal notation
2451
hex_key_id = binascii.hexlify(key_id).upper()
2455
1316
def fingerprint(openpgp):
2456
1317
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
2457
1318
# New GnuTLS "datum" with the OpenPGP public key
2458
datum = gnutls.datum_t(
2459
ctypes.cast(ctypes.c_char_p(openpgp),
2460
ctypes.POINTER(ctypes.c_ubyte)),
2461
ctypes.c_uint(len(openpgp)))
1319
datum = (gnutls.library.types
1320
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1323
ctypes.c_uint(len(openpgp))))
2462
1324
# New empty GnuTLS certificate
2463
crt = gnutls.openpgp_crt_t()
2464
gnutls.openpgp_crt_init(ctypes.byref(crt))
1325
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1326
(gnutls.library.functions
1327
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
2465
1328
# Import the OpenPGP public key into the certificate
2466
gnutls.openpgp_crt_import(crt, ctypes.byref(datum),
2467
gnutls.OPENPGP_FMT_RAW)
1329
(gnutls.library.functions
1330
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1331
gnutls.library.constants
1332
.GNUTLS_OPENPGP_FMT_RAW))
2468
1333
# Verify the self signature in the key
2469
1334
crtverify = ctypes.c_uint()
2470
gnutls.openpgp_crt_verify_self(crt, 0,
2471
ctypes.byref(crtverify))
1335
(gnutls.library.functions
1336
.gnutls_openpgp_crt_verify_self(crt, 0,
1337
ctypes.byref(crtverify)))
2472
1338
if crtverify.value != 0:
2473
gnutls.openpgp_crt_deinit(crt)
2474
raise gnutls.CertificateSecurityError(code
1339
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1340
raise (gnutls.errors.CertificateSecurityError
2476
1342
# New buffer for the fingerprint
2477
1343
buf = ctypes.create_string_buffer(20)
2478
1344
buf_len = ctypes.c_size_t()
2479
1345
# Get the fingerprint from the certificate into the buffer
2480
gnutls.openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
2481
ctypes.byref(buf_len))
1346
(gnutls.library.functions
1347
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1348
ctypes.byref(buf_len)))
2482
1349
# Deinit the certificate
2483
gnutls.openpgp_crt_deinit(crt)
1350
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
2484
1351
# Convert the buffer to a Python bytestring
2485
1352
fpr = ctypes.string_at(buf, buf_len.value)
2486
1353
# Convert the bytestring to hexadecimal notation
2487
hex_fpr = binascii.hexlify(fpr).upper()
1354
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
2491
1358
class MultiprocessingMixIn(object):
2492
1359
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
2494
1360
def sub_process_main(self, request, address):
2496
1362
self.finish_request(request, address)
2498
1364
self.handle_error(request, address)
2499
1365
self.close_request(request)
2501
1367
def process_request(self, request, address):
2502
1368
"""Start a new process to process the request."""
2503
proc = multiprocessing.Process(target=self.sub_process_main,
2504
args=(request, address))
1369
multiprocessing.Process(target = self.sub_process_main,
1370
args = (request, address)).start()
2509
1372
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
2510
1373
""" adds a pipe to the MixIn """
2512
1374
def process_request(self, request, client_address):
2513
1375
"""Overrides and wraps the original process_request().
2515
1377
This function creates a new pipe in self.pipe
2517
1379
parent_pipe, self.child_pipe = multiprocessing.Pipe()
2519
proc = MultiprocessingMixIn.process_request(self, request,
1381
super(MultiprocessingMixInWithPipe,
1382
self).process_request(request, client_address)
2521
1383
self.child_pipe.close()
2522
self.add_pipe(parent_pipe, proc)
1384
self.add_pipe(parent_pipe)
2524
def add_pipe(self, parent_pipe, proc):
1386
def add_pipe(self, parent_pipe):
2525
1387
"""Dummy function; override as necessary"""
2526
raise NotImplementedError()
2529
1390
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
2530
1391
socketserver.TCPServer, object):
2531
1392
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
2534
1395
enabled: Boolean; whether this server is activated yet
2535
1396
interface: None or a network interface name (string)
2536
1397
use_ipv6: Boolean; to use IPv6 or not
2539
1399
def __init__(self, server_address, RequestHandlerClass,
2543
"""If socketfd is set, use that file descriptor instead of
2544
creating a new one with socket.socket().
1400
interface=None, use_ipv6=True):
2546
1401
self.interface = interface
2548
1403
self.address_family = socket.AF_INET6
2549
if socketfd is not None:
2550
# Save the file descriptor
2551
self.socketfd = socketfd
2552
# Save the original socket.socket() function
2553
self.socket_socket = socket.socket
2555
# To implement --socket, we monkey patch socket.socket.
2557
# (When socketserver.TCPServer is a new-style class, we
2558
# could make self.socket into a property instead of monkey
2559
# patching socket.socket.)
2561
# Create a one-time-only replacement for socket.socket()
2562
@functools.wraps(socket.socket)
2563
def socket_wrapper(*args, **kwargs):
2564
# Restore original function so subsequent calls are
2566
socket.socket = self.socket_socket
2567
del self.socket_socket
2568
# This time only, return a new socket object from the
2569
# saved file descriptor.
2570
return socket.fromfd(self.socketfd, *args, **kwargs)
2571
# Replace socket.socket() function with wrapper
2572
socket.socket = socket_wrapper
2573
# The socketserver.TCPServer.__init__ will call
2574
# socket.socket(), which might be our replacement,
2575
# socket_wrapper(), if socketfd was set.
2576
1404
socketserver.TCPServer.__init__(self, server_address,
2577
1405
RequestHandlerClass)
2579
1406
def server_bind(self):
2580
1407
"""This overrides the normal server_bind() function
2581
1408
to bind to an interface if one was specified, and also NOT to
2582
1409
bind to an address or port if they were not specified."""
2583
global SO_BINDTODEVICE
2584
1410
if self.interface is not None:
2585
1411
if SO_BINDTODEVICE is None:
2586
# Fall back to a hard-coded value which seems to be
2588
logger.warning("SO_BINDTODEVICE not found, trying 25")
2589
SO_BINDTODEVICE = 25
2591
self.socket.setsockopt(
2592
socket.SOL_SOCKET, SO_BINDTODEVICE,
2593
(self.interface + "\0").encode("utf-8"))
2594
except socket.error as error:
2595
if error.errno == errno.EPERM:
2596
logger.error("No permission to bind to"
2597
" interface %s", self.interface)
2598
elif error.errno == errno.ENOPROTOOPT:
2599
logger.error("SO_BINDTODEVICE not available;"
2600
" cannot bind to interface %s",
2602
elif error.errno == errno.ENODEV:
2603
logger.error("Interface %s does not exist,"
2604
" cannot bind", self.interface)
1412
logger.error(u"SO_BINDTODEVICE does not exist;"
1413
u" cannot bind to interface %s",
1417
self.socket.setsockopt(socket.SOL_SOCKET,
1421
except socket.error, error:
1422
if error[0] == errno.EPERM:
1423
logger.error(u"No permission to"
1424
u" bind to interface %s",
1426
elif error[0] == errno.ENOPROTOOPT:
1427
logger.error(u"SO_BINDTODEVICE not available;"
1428
u" cannot bind to interface %s",
2607
1432
# Only bind(2) the socket if we really need to.
2608
1433
if self.server_address[0] or self.server_address[1]:
2609
if self.server_address[1]:
2610
self.allow_reuse_address = True
2611
1434
if not self.server_address[0]:
2612
1435
if self.address_family == socket.AF_INET6:
2613
any_address = "::" # in6addr_any
1436
any_address = u"::" # in6addr_any
2615
any_address = "0.0.0.0" # INADDR_ANY
1438
any_address = socket.INADDR_ANY
2616
1439
self.server_address = (any_address,
2617
1440
self.server_address[1])
2618
1441
elif not self.server_address[1]:
2619
self.server_address = (self.server_address[0], 0)
1442
self.server_address = (self.server_address[0],
2620
1444
# if self.interface:
2621
1445
# self.server_address = (self.server_address[0],
2750
def rfc3339_duration_to_delta(duration):
2751
"""Parse an RFC 3339 "duration" and return a datetime.timedelta
2753
>>> rfc3339_duration_to_delta("P7D")
2754
datetime.timedelta(7)
2755
>>> rfc3339_duration_to_delta("PT60S")
2756
datetime.timedelta(0, 60)
2757
>>> rfc3339_duration_to_delta("PT60M")
2758
datetime.timedelta(0, 3600)
2759
>>> rfc3339_duration_to_delta("PT24H")
2760
datetime.timedelta(1)
2761
>>> rfc3339_duration_to_delta("P1W")
2762
datetime.timedelta(7)
2763
>>> rfc3339_duration_to_delta("PT5M30S")
2764
datetime.timedelta(0, 330)
2765
>>> rfc3339_duration_to_delta("P1DT3M20S")
2766
datetime.timedelta(1, 200)
2769
# Parsing an RFC 3339 duration with regular expressions is not
2770
# possible - there would have to be multiple places for the same
2771
# values, like seconds. The current code, while more esoteric, is
2772
# cleaner without depending on a parsing library. If Python had a
2773
# built-in library for parsing we would use it, but we'd like to
2774
# avoid excessive use of external libraries.
2776
# New type for defining tokens, syntax, and semantics all-in-one
2777
Token = collections.namedtuple("Token", (
2778
"regexp", # To match token; if "value" is not None, must have
2779
# a "group" containing digits
2780
"value", # datetime.timedelta or None
2781
"followers")) # Tokens valid after this token
2782
# RFC 3339 "duration" tokens, syntax, and semantics; taken from
2783
# the "duration" ABNF definition in RFC 3339, Appendix A.
2784
token_end = Token(re.compile(r"$"), None, frozenset())
2785
token_second = Token(re.compile(r"(\d+)S"),
2786
datetime.timedelta(seconds=1),
2787
frozenset((token_end, )))
2788
token_minute = Token(re.compile(r"(\d+)M"),
2789
datetime.timedelta(minutes=1),
2790
frozenset((token_second, token_end)))
2791
token_hour = Token(re.compile(r"(\d+)H"),
2792
datetime.timedelta(hours=1),
2793
frozenset((token_minute, token_end)))
2794
token_time = Token(re.compile(r"T"),
2796
frozenset((token_hour, token_minute,
2798
token_day = Token(re.compile(r"(\d+)D"),
2799
datetime.timedelta(days=1),
2800
frozenset((token_time, token_end)))
2801
token_month = Token(re.compile(r"(\d+)M"),
2802
datetime.timedelta(weeks=4),
2803
frozenset((token_day, token_end)))
2804
token_year = Token(re.compile(r"(\d+)Y"),
2805
datetime.timedelta(weeks=52),
2806
frozenset((token_month, token_end)))
2807
token_week = Token(re.compile(r"(\d+)W"),
2808
datetime.timedelta(weeks=1),
2809
frozenset((token_end, )))
2810
token_duration = Token(re.compile(r"P"), None,
2811
frozenset((token_year, token_month,
2812
token_day, token_time,
2814
# Define starting values:
2816
value = datetime.timedelta()
2818
# Following valid tokens
2819
followers = frozenset((token_duration, ))
2820
# String left to parse
2822
# Loop until end token is found
2823
while found_token is not token_end:
2824
# Search for any currently valid tokens
2825
for token in followers:
2826
match = token.regexp.match(s)
2827
if match is not None:
2829
if token.value is not None:
2830
# Value found, parse digits
2831
factor = int(match.group(1), 10)
2832
# Add to value so far
2833
value += factor * token.value
2834
# Strip token from string
2835
s = token.regexp.sub("", s, 1)
2838
# Set valid next tokens
2839
followers = found_token.followers
2842
# No currently valid tokens were found
2843
raise ValueError("Invalid RFC 3339 duration: {!r}"
2849
1559
def string_to_delta(interval):
2850
1560
"""Parse a string and return a datetime.timedelta
2852
>>> string_to_delta('7d')
1562
>>> string_to_delta(u'7d')
2853
1563
datetime.timedelta(7)
2854
>>> string_to_delta('60s')
1564
>>> string_to_delta(u'60s')
2855
1565
datetime.timedelta(0, 60)
2856
>>> string_to_delta('60m')
1566
>>> string_to_delta(u'60m')
2857
1567
datetime.timedelta(0, 3600)
2858
>>> string_to_delta('24h')
1568
>>> string_to_delta(u'24h')
2859
1569
datetime.timedelta(1)
2860
>>> string_to_delta('1w')
1570
>>> string_to_delta(u'1w')
2861
1571
datetime.timedelta(7)
2862
>>> string_to_delta('5m 30s')
1572
>>> string_to_delta(u'5m 30s')
2863
1573
datetime.timedelta(0, 330)
2867
return rfc3339_duration_to_delta(interval)
2871
1575
timevalue = datetime.timedelta(0)
2872
1576
for s in interval.split():
1578
suffix = unicode(s[-1])
2875
1579
value = int(s[:-1])
2877
1581
delta = datetime.timedelta(value)
1582
elif suffix == u"s":
2879
1583
delta = datetime.timedelta(0, value)
1584
elif suffix == u"m":
2881
1585
delta = datetime.timedelta(0, 0, 0, 0, value)
1586
elif suffix == u"h":
2883
1587
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1588
elif suffix == u"w":
2885
1589
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
2887
raise ValueError("Unknown suffix {!r}".format(suffix))
2888
except IndexError as e:
2889
raise ValueError(*(e.args))
1591
raise ValueError(u"Unknown suffix %r" % suffix)
1592
except (ValueError, IndexError), e:
1593
raise ValueError(e.message)
2890
1594
timevalue += delta
2891
1595
return timevalue
2894
def daemon(nochdir=False, noclose=False):
1598
def if_nametoindex(interface):
1599
"""Call the C function if_nametoindex(), or equivalent
1601
Note: This function cannot accept a unicode string."""
1602
global if_nametoindex
1604
if_nametoindex = (ctypes.cdll.LoadLibrary
1605
(ctypes.util.find_library(u"c"))
1607
except (OSError, AttributeError):
1608
logger.warning(u"Doing if_nametoindex the hard way")
1609
def if_nametoindex(interface):
1610
"Get an interface index the hard way, i.e. using fcntl()"
1611
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1612
with contextlib.closing(socket.socket()) as s:
1613
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1614
struct.pack(str(u"16s16x"),
1616
interface_index = struct.unpack(str(u"I"),
1618
return interface_index
1619
return if_nametoindex(interface)
1622
def daemon(nochdir = False, noclose = False):
2895
1623
"""See daemon(3). Standard BSD Unix function.
2897
1625
This should really exist as os.daemon, but it doesn't (yet)."""
2901
1629
if not nochdir:
2905
1633
if not noclose:
2906
1634
# Close all standard open file descriptors
2907
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
1635
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2908
1636
if not stat.S_ISCHR(os.fstat(null).st_mode):
2909
1637
raise OSError(errno.ENODEV,
2910
"{} not a character device"
2911
.format(os.devnull))
1638
u"%s not a character device"
2912
1640
os.dup2(null, sys.stdin.fileno())
2913
1641
os.dup2(null, sys.stdout.fileno())
2914
1642
os.dup2(null, sys.stderr.fileno())
2921
1649
##################################################################
2922
1650
# Parsing of options, both command line and config file
2924
parser = argparse.ArgumentParser()
2925
parser.add_argument("-v", "--version", action="version",
2926
version="%(prog)s {}".format(version),
2927
help="show version number and exit")
2928
parser.add_argument("-i", "--interface", metavar="IF",
2929
help="Bind to interface IF")
2930
parser.add_argument("-a", "--address",
2931
help="Address to listen for requests on")
2932
parser.add_argument("-p", "--port", type=int,
2933
help="Port number to receive requests on")
2934
parser.add_argument("--check", action="store_true",
2935
help="Run self-test")
2936
parser.add_argument("--debug", action="store_true",
2937
help="Debug mode; run in foreground and log"
2938
" to terminal", default=None)
2939
parser.add_argument("--debuglevel", metavar="LEVEL",
2940
help="Debug level for stdout output")
2941
parser.add_argument("--priority", help="GnuTLS"
2942
" priority string (see GnuTLS documentation)")
2943
parser.add_argument("--servicename",
2944
metavar="NAME", help="Zeroconf service name")
2945
parser.add_argument("--configdir",
2946
default="/etc/mandos", metavar="DIR",
2947
help="Directory to search for configuration"
2949
parser.add_argument("--no-dbus", action="store_false",
2950
dest="use_dbus", help="Do not provide D-Bus"
2951
" system bus interface", default=None)
2952
parser.add_argument("--no-ipv6", action="store_false",
2953
dest="use_ipv6", help="Do not use IPv6",
2955
parser.add_argument("--no-restore", action="store_false",
2956
dest="restore", help="Do not restore stored"
2957
" state", default=None)
2958
parser.add_argument("--socket", type=int,
2959
help="Specify a file descriptor to a network"
2960
" socket to use instead of creating one")
2961
parser.add_argument("--statedir", metavar="DIR",
2962
help="Directory to save/restore state in")
2963
parser.add_argument("--foreground", action="store_true",
2964
help="Run in foreground", default=None)
2965
parser.add_argument("--no-zeroconf", action="store_false",
2966
dest="zeroconf", help="Do not use Zeroconf",
2969
options = parser.parse_args()
1652
parser = optparse.OptionParser(version = "%%prog %s" % version)
1653
parser.add_option("-i", u"--interface", type=u"string",
1654
metavar="IF", help=u"Bind to interface IF")
1655
parser.add_option("-a", u"--address", type=u"string",
1656
help=u"Address to listen for requests on")
1657
parser.add_option("-p", u"--port", type=u"int",
1658
help=u"Port number to receive requests on")
1659
parser.add_option("--check", action=u"store_true",
1660
help=u"Run self-test")
1661
parser.add_option("--debug", action=u"store_true",
1662
help=u"Debug mode; run in foreground and log to"
1664
parser.add_option("--debuglevel", type=u"string", metavar="Level",
1665
help=u"Debug level for stdout output")
1666
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1667
u" priority string (see GnuTLS documentation)")
1668
parser.add_option("--servicename", type=u"string",
1669
metavar=u"NAME", help=u"Zeroconf service name")
1670
parser.add_option("--configdir", type=u"string",
1671
default=u"/etc/mandos", metavar=u"DIR",
1672
help=u"Directory to search for configuration"
1674
parser.add_option("--no-dbus", action=u"store_false",
1675
dest=u"use_dbus", help=u"Do not provide D-Bus"
1676
u" system bus interface")
1677
parser.add_option("--no-ipv6", action=u"store_false",
1678
dest=u"use_ipv6", help=u"Do not use IPv6")
1679
options = parser.parse_args()[0]
2971
1681
if options.check:
2973
fail_count, test_count = doctest.testmod()
2974
sys.exit(os.EX_OK if fail_count == 0 else 1)
2976
1686
# Default values for config file for server-global settings
2977
if gnutls.has_rawpk:
2978
priority = ("SECURE128:!CTYPE-X.509:+CTYPE-RAWPK:!RSA"
2979
":!VERS-ALL:+VERS-TLS1.3:%PROFILE_ULTRA")
2981
priority = ("SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2982
":+SIGN-DSA-SHA256")
2983
server_defaults = {"interface": "",
2987
"priority": priority,
2988
"servicename": "Mandos",
2994
"statedir": "/var/lib/mandos",
2995
"foreground": "False",
1687
server_defaults = { u"interface": u"",
1692
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1693
u"servicename": u"Mandos",
1694
u"use_dbus": u"True",
1695
u"use_ipv6": u"True",
3000
1699
# Parse config file for server-global settings
3001
1700
server_config = configparser.SafeConfigParser(server_defaults)
3002
1701
del server_defaults
3003
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1702
server_config.read(os.path.join(options.configdir,
3004
1704
# Convert the SafeConfigParser object to a dict
3005
1705
server_settings = server_config.defaults()
3006
1706
# Use the appropriate methods on the non-string config options
3007
for option in ("debug", "use_dbus", "use_ipv6", "restore",
3008
"foreground", "zeroconf"):
3009
server_settings[option] = server_config.getboolean("DEFAULT",
1707
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1708
server_settings[option] = server_config.getboolean(u"DEFAULT",
3011
1710
if server_settings["port"]:
3012
server_settings["port"] = server_config.getint("DEFAULT",
3014
if server_settings["socket"]:
3015
server_settings["socket"] = server_config.getint("DEFAULT",
3017
# Later, stdin will, and stdout and stderr might, be dup'ed
3018
# over with an opened os.devnull. But we don't want this to
3019
# happen with a supplied network socket.
3020
if 0 <= server_settings["socket"] <= 2:
3021
server_settings["socket"] = os.dup(server_settings
1711
server_settings["port"] = server_config.getint(u"DEFAULT",
3023
1713
del server_config
3025
1715
# Override the settings from the config file with command line
3026
1716
# options, if set.
3027
for option in ("interface", "address", "port", "debug",
3028
"priority", "servicename", "configdir", "use_dbus",
3029
"use_ipv6", "debuglevel", "restore", "statedir",
3030
"socket", "foreground", "zeroconf"):
1717
for option in (u"interface", u"address", u"port", u"debug",
1718
u"priority", u"servicename", u"configdir",
1719
u"use_dbus", u"use_ipv6", u"debuglevel"):
3031
1720
value = getattr(options, option)
3032
1721
if value is not None:
3033
1722
server_settings[option] = value
3035
1724
# Force all strings to be unicode
3036
1725
for option in server_settings.keys():
3037
if isinstance(server_settings[option], bytes):
3038
server_settings[option] = (server_settings[option]
3040
# Force all boolean options to be boolean
3041
for option in ("debug", "use_dbus", "use_ipv6", "restore",
3042
"foreground", "zeroconf"):
3043
server_settings[option] = bool(server_settings[option])
3044
# Debug implies foreground
3045
if server_settings["debug"]:
3046
server_settings["foreground"] = True
1726
if type(server_settings[option]) is str:
1727
server_settings[option] = unicode(server_settings[option])
3047
1728
# Now we have our good server settings in "server_settings"
3049
1730
##################################################################
3051
if (not server_settings["zeroconf"]
3052
and not (server_settings["port"]
3053
or server_settings["socket"] != "")):
3054
parser.error("Needs port or socket to work without Zeroconf")
3056
1732
# For convenience
3057
debug = server_settings["debug"]
3058
debuglevel = server_settings["debuglevel"]
3059
use_dbus = server_settings["use_dbus"]
3060
use_ipv6 = server_settings["use_ipv6"]
3061
stored_state_path = os.path.join(server_settings["statedir"],
3063
foreground = server_settings["foreground"]
3064
zeroconf = server_settings["zeroconf"]
3067
initlogger(debug, logging.DEBUG)
3072
level = getattr(logging, debuglevel.upper())
3073
initlogger(debug, level)
3075
if server_settings["servicename"] != "Mandos":
3076
syslogger.setFormatter(
3077
logging.Formatter('Mandos ({}) [%(process)d]:'
3078
' %(levelname)s: %(message)s'.format(
3079
server_settings["servicename"])))
1733
debug = server_settings[u"debug"]
1734
debuglevel = server_settings[u"debuglevel"]
1735
use_dbus = server_settings[u"use_dbus"]
1736
use_ipv6 = server_settings[u"use_ipv6"]
1738
if server_settings[u"servicename"] != u"Mandos":
1739
syslogger.setFormatter(logging.Formatter
1740
(u'Mandos (%s) [%%(process)d]:'
1741
u' %%(levelname)s: %%(message)s'
1742
% server_settings[u"servicename"]))
3081
1744
# Parse config file with clients
3082
client_config = configparser.SafeConfigParser(Client
3084
client_config.read(os.path.join(server_settings["configdir"],
1745
client_defaults = { u"timeout": u"1h",
1747
u"checker": u"fping -q -- %%(host)s",
1749
u"approval_delay": u"0s",
1750
u"approval_duration": u"1s",
1752
client_config = configparser.SafeConfigParser(client_defaults)
1753
client_config.read(os.path.join(server_settings[u"configdir"],
3087
1756
global mandos_dbus_service
3088
1757
mandos_dbus_service = None
3091
if server_settings["socket"] != "":
3092
socketfd = server_settings["socket"]
3093
tcp_server = MandosServer(
3094
(server_settings["address"], server_settings["port"]),
3096
interface=(server_settings["interface"] or None),
3098
gnutls_priority=server_settings["priority"],
3102
pidfilename = "/run/mandos.pid"
3103
if not os.path.isdir("/run/."):
3104
pidfilename = "/var/run/mandos.pid"
3107
pidfile = codecs.open(pidfilename, "w", encoding="utf-8")
3108
except IOError as e:
3109
logger.error("Could not open file %r", pidfilename,
3112
for name, group in (("_mandos", "_mandos"),
3113
("mandos", "mandos"),
3114
("nobody", "nogroup")):
3116
uid = pwd.getpwnam(name).pw_uid
3117
gid = pwd.getpwnam(group).pw_gid
1759
tcp_server = MandosServer((server_settings[u"address"],
1760
server_settings[u"port"]),
1762
interface=(server_settings[u"interface"]
1766
server_settings[u"priority"],
1769
pidfilename = u"/var/run/mandos.pid"
1771
pidfile = open(pidfilename, u"w")
1773
logger.error(u"Could not open file %r", pidfilename)
1776
uid = pwd.getpwnam(u"_mandos").pw_uid
1777
gid = pwd.getpwnam(u"_mandos").pw_gid
1780
uid = pwd.getpwnam(u"mandos").pw_uid
1781
gid = pwd.getpwnam(u"mandos").pw_gid
3119
1782
except KeyError:
1784
uid = pwd.getpwnam(u"nobody").pw_uid
1785
gid = pwd.getpwnam(u"nobody").pw_gid
3128
logger.debug("Did setuid/setgid to {}:{}".format(uid,
3130
except OSError as error:
3131
logger.warning("Failed to setuid/setgid to {}:{}: {}"
3132
.format(uid, gid, os.strerror(error.errno)))
3133
if error.errno != errno.EPERM:
1792
except OSError, error:
1793
if error[0] != errno.EPERM:
1796
if not debug and not debuglevel:
1797
syslogger.setLevel(logging.WARNING)
1798
console.setLevel(logging.WARNING)
1800
level = getattr(logging, debuglevel.upper())
1801
syslogger.setLevel(level)
1802
console.setLevel(level)
3137
1805
# Enable all possible GnuTLS debugging
3139
1807
# "Use a log level over 10 to enable all debugging options."
3140
1808
# - GnuTLS manual
3141
gnutls.global_set_log_level(11)
1809
gnutls.library.functions.gnutls_global_set_log_level(11)
1811
@gnutls.library.types.gnutls_log_func
3144
1812
def debug_gnutls(level, string):
3145
logger.debug("GnuTLS: %s", string[:-1])
3147
gnutls.global_set_log_function(debug_gnutls)
1813
logger.debug(u"GnuTLS: %s", string[:-1])
1815
(gnutls.library.functions
1816
.gnutls_global_set_log_function(debug_gnutls))
3149
1818
# Redirect stdin so all checkers get /dev/null
3150
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
1819
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
3151
1820
os.dup2(null, sys.stdin.fileno())
3155
# Need to fork before connecting to D-Bus
3157
# Close all input and output, do double fork, etc.
3160
# multiprocessing will use threads, so before we use GLib we need
3161
# to inform GLib that threads will be used.
1824
# No console logging
1825
logger.removeHandler(console)
3164
1828
global main_loop
3165
1829
# From the Avahi example code
3166
DBusGMainLoop(set_as_default=True)
3167
main_loop = GLib.MainLoop()
1830
DBusGMainLoop(set_as_default=True )
1831
main_loop = gobject.MainLoop()
3168
1832
bus = dbus.SystemBus()
3169
1833
# End of Avahi example code
3172
bus_name = dbus.service.BusName("se.recompile.Mandos",
3175
old_bus_name = dbus.service.BusName(
3176
"se.bsnet.fukt.Mandos", bus,
3178
except dbus.exceptions.DBusException as e:
3179
logger.error("Disabling D-Bus:", exc_info=e)
1836
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1837
bus, do_not_queue=True)
1838
except dbus.exceptions.NameExistsException, e:
1839
logger.error(unicode(e) + u", disabling D-Bus")
3180
1840
use_dbus = False
3181
server_settings["use_dbus"] = False
1841
server_settings[u"use_dbus"] = False
3182
1842
tcp_server.use_dbus = False
3184
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
3185
service = AvahiServiceToSyslog(
3186
name=server_settings["servicename"],
3187
servicetype="_mandos._tcp",
3190
if server_settings["interface"]:
3191
service.interface = if_nametoindex(
3192
server_settings["interface"].encode("utf-8"))
1843
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1844
service = AvahiService(name = server_settings[u"servicename"],
1845
servicetype = u"_mandos._tcp",
1846
protocol = protocol, bus = bus)
1847
if server_settings["interface"]:
1848
service.interface = (if_nametoindex
1849
(str(server_settings[u"interface"])))
1852
# Close all input and output, do double fork, etc.
3194
1855
global multiprocessing_manager
3195
1856
multiprocessing_manager = multiprocessing.Manager()
3197
1858
client_class = Client
3199
client_class = functools.partial(ClientDBus, bus=bus)
3201
client_settings = Client.config_parser(client_config)
3202
old_client_settings = {}
3205
# This is used to redirect stdout and stderr for checker processes
3207
wnull = open(os.devnull, "w") # A writable /dev/null
3208
# Only used if server is running in foreground but not in debug
3210
if debug or not foreground:
3213
# Get client data and settings from last running state.
3214
if server_settings["restore"]:
3216
with open(stored_state_path, "rb") as stored_state:
3217
if sys.version_info.major == 2:
3218
clients_data, old_client_settings = pickle.load(
3221
bytes_clients_data, bytes_old_client_settings = (
3222
pickle.load(stored_state, encoding="bytes"))
3223
# Fix bytes to strings
3226
clients_data = {(key.decode("utf-8")
3227
if isinstance(key, bytes)
3230
bytes_clients_data.items()}
3231
del bytes_clients_data
3232
for key in clients_data:
3233
value = {(k.decode("utf-8")
3234
if isinstance(k, bytes) else k): v
3236
clients_data[key].items()}
3237
clients_data[key] = value
3239
value["client_structure"] = [
3241
if isinstance(s, bytes)
3243
value["client_structure"]]
3245
for k in ("name", "host"):
3246
if isinstance(value[k], bytes):
3247
value[k] = value[k].decode("utf-8")
3248
if "key_id" not in value:
3249
value["key_id"] = ""
3250
elif "fingerprint" not in value:
3251
value["fingerprint"] = ""
3252
# old_client_settings
3254
old_client_settings = {
3255
(key.decode("utf-8")
3256
if isinstance(key, bytes)
3259
bytes_old_client_settings.items()}
3260
del bytes_old_client_settings
3262
for value in old_client_settings.values():
3263
if isinstance(value["host"], bytes):
3264
value["host"] = (value["host"]
3266
os.remove(stored_state_path)
3267
except IOError as e:
3268
if e.errno == errno.ENOENT:
3269
logger.warning("Could not load persistent state:"
3270
" {}".format(os.strerror(e.errno)))
3272
logger.critical("Could not load persistent state:",
3275
except EOFError as e:
3276
logger.warning("Could not load persistent state: "
3280
with PGPEngine() as pgp:
3281
for client_name, client in clients_data.items():
3282
# Skip removed clients
3283
if client_name not in client_settings:
3286
# Decide which value to use after restoring saved state.
3287
# We have three different values: Old config file,
3288
# new config file, and saved state.
3289
# New config value takes precedence if it differs from old
3290
# config value, otherwise use saved state.
3291
for name, value in client_settings[client_name].items():
3293
# For each value in new config, check if it
3294
# differs from the old config value (Except for
3295
# the "secret" attribute)
3296
if (name != "secret"
3298
old_client_settings[client_name][name])):
3299
client[name] = value
3303
# Clients who has passed its expire date can still be
3304
# enabled if its last checker was successful. A Client
3305
# whose checker succeeded before we stored its state is
3306
# assumed to have successfully run all checkers during
3308
if client["enabled"]:
3309
if datetime.datetime.utcnow() >= client["expires"]:
3310
if not client["last_checked_ok"]:
3312
"disabling client {} - Client never "
3313
"performed a successful checker".format(
3315
client["enabled"] = False
3316
elif client["last_checker_status"] != 0:
3318
"disabling client {} - Client last"
3319
" checker failed with error code"
3322
client["last_checker_status"]))
3323
client["enabled"] = False
3325
client["expires"] = (
3326
datetime.datetime.utcnow()
3327
+ client["timeout"])
3328
logger.debug("Last checker succeeded,"
3329
" keeping {} enabled".format(
1860
client_class = functools.partial(ClientDBus, bus = bus)
1861
def client_config_items(config, section):
1862
special_settings = {
1863
"approved_by_default":
1864
lambda: config.getboolean(section,
1865
"approved_by_default"),
1867
for name, value in config.items(section):
3332
client["secret"] = pgp.decrypt(
3333
client["encrypted_secret"],
3334
client_settings[client_name]["secret"])
3336
# If decryption fails, we use secret from new settings
3337
logger.debug("Failed to decrypt {} old secret".format(
3339
client["secret"] = (client_settings[client_name]
3342
# Add/remove clients based on new changes made to config
3343
for client_name in (set(old_client_settings)
3344
- set(client_settings)):
3345
del clients_data[client_name]
3346
for client_name in (set(client_settings)
3347
- set(old_client_settings)):
3348
clients_data[client_name] = client_settings[client_name]
3350
# Create all client objects
3351
for client_name, client in clients_data.items():
3352
tcp_server.clients[client_name] = client_class(
3355
server_settings=server_settings)
1869
yield (name, special_settings[name]())
1873
tcp_server.clients.update(set(
1874
client_class(name = section,
1875
config= dict(client_config_items(
1876
client_config, section)))
1877
for section in client_config.sections()))
3357
1878
if not tcp_server.clients:
3358
logger.warning("No clients defined")
3361
if pidfile is not None:
3365
print(pid, file=pidfile)
3367
logger.error("Could not write to file %r with PID %d",
1879
logger.warning(u"No clients defined")
1885
pidfile.write(str(pid) + "\n")
1888
logger.error(u"Could not write to file %r with PID %d",
1891
# "pidfile" was never created
3370
1893
del pidfilename
3372
for termsig in (signal.SIGHUP, signal.SIGTERM):
3373
GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig,
3374
lambda: main_loop.quit() and False)
1895
signal.signal(signal.SIGINT, signal.SIG_IGN)
1897
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1898
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
3378
@alternate_dbus_interfaces(
3379
{"se.recompile.Mandos": "se.bsnet.fukt.Mandos"})
3380
class MandosDBusService(DBusObjectWithObjectManager):
1901
class MandosDBusService(dbus.service.Object):
3381
1902
"""A D-Bus proxy object"""
3383
1903
def __init__(self):
3384
dbus.service.Object.__init__(self, bus, "/")
3386
_interface = "se.recompile.Mandos"
3388
@dbus.service.signal(_interface, signature="o")
1904
dbus.service.Object.__init__(self, bus, u"/")
1905
_interface = u"se.bsnet.fukt.Mandos"
1907
@dbus.service.signal(_interface, signature=u"o")
3389
1908
def ClientAdded(self, objpath):
3393
@dbus.service.signal(_interface, signature="ss")
3394
def ClientNotFound(self, key_id, address):
1912
@dbus.service.signal(_interface, signature=u"ss")
1913
def ClientNotFound(self, fingerprint, address):
3398
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3400
@dbus.service.signal(_interface, signature="os")
1917
@dbus.service.signal(_interface, signature=u"os")
3401
1918
def ClientRemoved(self, objpath, name):
3405
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3407
@dbus.service.method(_interface, out_signature="ao")
1922
@dbus.service.method(_interface, out_signature=u"ao")
3408
1923
def GetAllClients(self):
3410
return dbus.Array(c.dbus_object_path for c in
3411
tcp_server.clients.values())
3413
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
1925
return dbus.Array(c.dbus_object_path
1926
for c in tcp_server.clients)
3415
1928
@dbus.service.method(_interface,
3416
out_signature="a{oa{sv}}")
1929
out_signature=u"a{oa{sv}}")
3417
1930
def GetAllClientsWithProperties(self):
3419
1932
return dbus.Dictionary(
3420
{c.dbus_object_path: c.GetAll(
3421
"se.recompile.Mandos.Client")
3422
for c in tcp_server.clients.values()},
3425
@dbus.service.method(_interface, in_signature="o")
1933
((c.dbus_object_path, c.GetAll(u""))
1934
for c in tcp_server.clients),
1935
signature=u"oa{sv}")
1937
@dbus.service.method(_interface, in_signature=u"o")
3426
1938
def RemoveClient(self, object_path):
3428
for c in tcp_server.clients.values():
1940
for c in tcp_server.clients:
3429
1941
if c.dbus_object_path == object_path:
3430
del tcp_server.clients[c.name]
1942
tcp_server.clients.remove(c)
3431
1943
c.remove_from_connection()
3432
# Don't signal the disabling
1944
# Don't signal anything except ClientRemoved
3433
1945
c.disable(quiet=True)
3434
# Emit D-Bus signal for removal
3435
self.client_removed_signal(c)
1947
self.ClientRemoved(object_path, c.name)
3437
1949
raise KeyError(object_path)
3441
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
3442
out_signature="a{oa{sa{sv}}}")
3443
def GetManagedObjects(self):
3445
return dbus.Dictionary(
3446
{client.dbus_object_path:
3448
{interface: client.GetAll(interface)
3450
client._get_all_interface_names()})
3451
for client in tcp_server.clients.values()})
3453
def client_added_signal(self, client):
3454
"""Send the new standard signal and the old signal"""
3456
# New standard signal
3457
self.InterfacesAdded(
3458
client.dbus_object_path,
3460
{interface: client.GetAll(interface)
3462
client._get_all_interface_names()}))
3464
self.ClientAdded(client.dbus_object_path)
3466
def client_removed_signal(self, client):
3467
"""Send the new standard signal and the old signal"""
3469
# New standard signal
3470
self.InterfacesRemoved(
3471
client.dbus_object_path,
3472
client._get_all_interface_names())
3474
self.ClientRemoved(client.dbus_object_path,
3477
1953
mandos_dbus_service = MandosDBusService()
3479
# Save modules to variables to exempt the modules from being
3480
# unloaded before the function registered with atexit() is run.
3481
mp = multiprocessing
3485
1956
"Cleanup function; run on exit"
3489
mp.active_children()
3491
if not (tcp_server.clients or client_settings):
3494
# Store client before exiting. Secrets are encrypted with key
3495
# based on what config file has. If config file is
3496
# removed/edited, old secret will thus be unrecovable.
3498
with PGPEngine() as pgp:
3499
for client in tcp_server.clients.values():
3500
key = client_settings[client.name]["secret"]
3501
client.encrypted_secret = pgp.encrypt(client.secret,
3505
# A list of attributes that can not be pickled
3507
exclude = {"bus", "changedstate", "secret",
3508
"checker", "server_settings"}
3509
for name, typ in inspect.getmembers(dbus.service
3513
client_dict["encrypted_secret"] = (client
3515
for attr in client.client_structure:
3516
if attr not in exclude:
3517
client_dict[attr] = getattr(client, attr)
3519
clients[client.name] = client_dict
3520
del client_settings[client.name]["secret"]
3523
with tempfile.NamedTemporaryFile(
3527
dir=os.path.dirname(stored_state_path),
3528
delete=False) as stored_state:
3529
pickle.dump((clients, client_settings), stored_state,
3531
tempname = stored_state.name
3532
os.rename(tempname, stored_state_path)
3533
except (IOError, OSError) as e:
3539
if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
3540
logger.warning("Could not save persistent state: {}"
3541
.format(os.strerror(e.errno)))
3543
logger.warning("Could not save persistent state:",
3547
# Delete all clients, and settings from config
3548
1959
while tcp_server.clients:
3549
name, client = tcp_server.clients.popitem()
1960
client = tcp_server.clients.pop()
3551
1962
client.remove_from_connection()
3552
# Don't signal the disabling
1963
client.disable_hook = None
1964
# Don't signal anything except ClientRemoved
3553
1965
client.disable(quiet=True)
3554
# Emit D-Bus signal for removal
3556
mandos_dbus_service.client_removed_signal(client)
3557
client_settings.clear()
1968
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
3559
1971
atexit.register(cleanup)
3561
for client in tcp_server.clients.values():
1973
for client in tcp_server.clients:
3563
# Emit D-Bus signal for adding
3564
mandos_dbus_service.client_added_signal(client)
3565
# Need to initiate checking of clients
3567
client.init_checker()
1976
mandos_dbus_service.ClientAdded(client.dbus_object_path)
3569
1979
tcp_server.enable()
3570
1980
tcp_server.server_activate()
3572
1982
# Find out what port we got
3574
service.port = tcp_server.socket.getsockname()[1]
1983
service.port = tcp_server.socket.getsockname()[1]
3576
logger.info("Now listening on address %r, port %d,"
3577
" flowinfo %d, scope_id %d",
3578
*tcp_server.socket.getsockname())
1985
logger.info(u"Now listening on address %r, port %d,"
1986
" flowinfo %d, scope_id %d"
1987
% tcp_server.socket.getsockname())
3580
logger.info("Now listening on address %r, port %d",
3581
*tcp_server.socket.getsockname())
3583
# service.interface = tcp_server.socket.getsockname()[3]
1989
logger.info(u"Now listening on address %r, port %d"
1990
% tcp_server.socket.getsockname())
1992
#service.interface = tcp_server.socket.getsockname()[3]
3587
# From the Avahi example code
3590
except dbus.exceptions.DBusException as error:
3591
logger.critical("D-Bus Exception", exc_info=error)
3594
# End of Avahi example code
3596
GLib.io_add_watch(tcp_server.fileno(), GLib.IO_IN,
3597
lambda *args, **kwargs:
3598
(tcp_server.handle_request
3599
(*args[2:], **kwargs) or True))
3601
logger.debug("Starting main loop")
1995
# From the Avahi example code
1998
except dbus.exceptions.DBusException, error:
1999
logger.critical(u"DBusException: %s", error)
2002
# End of Avahi example code
2004
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2005
lambda *args, **kwargs:
2006
(tcp_server.handle_request
2007
(*args[2:], **kwargs) or True))
2009
logger.debug(u"Starting main loop")
3602
2010
main_loop.run()
3603
except AvahiError as error:
3604
logger.critical("Avahi Error", exc_info=error)
2011
except AvahiError, error:
2012
logger.critical(u"AvahiError: %s", error)
3607
2015
except KeyboardInterrupt:
3609
print("", file=sys.stderr)
3610
logger.debug("Server received KeyboardInterrupt")
3611
logger.debug("Server exiting")
2018
logger.debug(u"Server received KeyboardInterrupt")
2019
logger.debug(u"Server exiting")
3612
2020
# Must run before the D-Bus bus name gets deregistered
3616
2023
if __name__ == '__main__':