81
78
import dbus.service
82
from gi.repository import GLib
82
from gi.repository import GObject as gobject
83
84
from dbus.mainloop.glib import DBusGMainLoop
86
87
import xml.dom.minidom
89
# Try to find the value of SO_BINDTODEVICE:
91
# This is where SO_BINDTODEVICE is in Python 3.3 (or 3.4?) and
92
# newer, and it is also the most natural place for it:
93
91
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
94
92
except AttributeError:
96
# This is where SO_BINDTODEVICE was up to and including Python
98
94
from IN import SO_BINDTODEVICE
99
95
except ImportError:
100
# In Python 2.7 it seems to have been removed entirely.
101
# Try running the C preprocessor:
103
cc = subprocess.Popen(["cc", "--language=c", "-E",
105
stdin=subprocess.PIPE,
106
stdout=subprocess.PIPE)
107
stdout = cc.communicate(
108
"#include <sys/socket.h>\nSO_BINDTODEVICE\n")[0]
109
SO_BINDTODEVICE = int(stdout.splitlines()[-1])
110
except (OSError, ValueError, IndexError):
112
SO_BINDTODEVICE = None
96
SO_BINDTODEVICE = None
114
98
if sys.version_info.major == 2:
118
102
stored_state_file = "clients.pickle"
120
104
logger = logging.getLogger()
181
149
class PGPEngine(object):
182
150
"""A simple class for OpenPGP symmetric encryption & decryption"""
184
152
def __init__(self):
185
153
self.tempdir = tempfile.mkdtemp(prefix="mandos-")
188
output = subprocess.check_output(["gpgconf"])
189
for line in output.splitlines():
190
name, text, path = line.split(b":")
195
if e.errno != errno.ENOENT:
197
154
self.gnupgargs = ['--batch',
198
'--homedir', self.tempdir,
155
'--home', self.tempdir,
201
# Only GPG version 1 has the --no-use-agent option.
202
if self.gpg == "gpg" or self.gpg.endswith("/gpg"):
203
self.gnupgargs.append("--no-use-agent")
205
160
def __enter__(self):
208
163
def __exit__(self, exc_type, exc_value, traceback):
212
167
def __del__(self):
215
170
def _cleanup(self):
216
171
if self.tempdir is not None:
217
172
# Delete contents of tempdir
218
173
for root, dirs, files in os.walk(self.tempdir,
220
175
for filename in files:
221
176
os.remove(os.path.join(root, filename))
222
177
for dirname in dirs:
235
190
.replace(b"\n", b"\\n")
236
191
.replace(b"\0", b"\\x00"))
239
194
def encrypt(self, data, password):
240
195
passphrase = self.password_encode(password)
241
196
with tempfile.NamedTemporaryFile(
242
197
dir=self.tempdir) as passfile:
243
198
passfile.write(passphrase)
245
proc = subprocess.Popen([self.gpg, '--symmetric',
200
proc = subprocess.Popen(['gpg', '--symmetric',
246
201
'--passphrase-file',
248
203
+ self.gnupgargs,
249
stdin=subprocess.PIPE,
250
stdout=subprocess.PIPE,
251
stderr=subprocess.PIPE)
252
ciphertext, err = proc.communicate(input=data)
204
stdin = subprocess.PIPE,
205
stdout = subprocess.PIPE,
206
stderr = subprocess.PIPE)
207
ciphertext, err = proc.communicate(input = data)
253
208
if proc.returncode != 0:
254
209
raise PGPError(err)
255
210
return ciphertext
257
212
def decrypt(self, data, password):
258
213
passphrase = self.password_encode(password)
259
214
with tempfile.NamedTemporaryFile(
260
dir=self.tempdir) as passfile:
215
dir = self.tempdir) as passfile:
261
216
passfile.write(passphrase)
263
proc = subprocess.Popen([self.gpg, '--decrypt',
218
proc = subprocess.Popen(['gpg', '--decrypt',
264
219
'--passphrase-file',
266
221
+ self.gnupgargs,
267
stdin=subprocess.PIPE,
268
stdout=subprocess.PIPE,
269
stderr=subprocess.PIPE)
270
decrypted_plaintext, err = proc.communicate(input=data)
222
stdin = subprocess.PIPE,
223
stdout = subprocess.PIPE,
224
stderr = subprocess.PIPE)
225
decrypted_plaintext, err = proc.communicate(input = data)
271
226
if proc.returncode != 0:
272
227
raise PGPError(err)
273
228
return decrypted_plaintext
276
# Pretend that we have an Avahi module
278
"""This isn't so much a class as it is a module-like namespace.
279
It is instantiated once, and simulates having an Avahi module."""
280
IF_UNSPEC = -1 # avahi-common/address.h
281
PROTO_UNSPEC = -1 # avahi-common/address.h
282
PROTO_INET = 0 # avahi-common/address.h
283
PROTO_INET6 = 1 # avahi-common/address.h
284
DBUS_NAME = "org.freedesktop.Avahi"
285
DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"
286
DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
287
DBUS_PATH_SERVER = "/"
289
def string_array_to_txt_array(self, t):
290
return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
291
for s in t), signature="ay")
292
ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h
293
ENTRY_GROUP_COLLISION = 3 # avahi-common/defs.h
294
ENTRY_GROUP_FAILURE = 4 # avahi-common/defs.h
295
SERVER_INVALID = 0 # avahi-common/defs.h
296
SERVER_REGISTERING = 1 # avahi-common/defs.h
297
SERVER_RUNNING = 2 # avahi-common/defs.h
298
SERVER_COLLISION = 3 # avahi-common/defs.h
299
SERVER_FAILURE = 4 # avahi-common/defs.h
303
231
class AvahiError(Exception):
304
232
def __init__(self, value, *args, **kwargs):
305
233
self.value = value
501
429
.format(self.name)))
505
432
# Pretend that we have a GnuTLS module
506
433
class GnuTLS(object):
507
434
"""This isn't so much a class as it is a module-like namespace.
508
435
It is instantiated once, and simulates having a GnuTLS module."""
510
library = ctypes.util.find_library("gnutls")
512
library = ctypes.util.find_library("gnutls-deb0")
513
_library = ctypes.cdll.LoadLibrary(library)
515
_need_version = b"3.3.0"
437
_library = ctypes.cdll.LoadLibrary(
438
ctypes.util.find_library("gnutls"))
439
_need_version = "3.3.0"
517
440
def __init__(self):
518
# Need to use "self" here, since this method is called before
519
# the assignment to the "gnutls" global variable happens.
520
if self.check_version(self._need_version) is None:
521
raise self.Error("Needs GnuTLS {} or later"
522
.format(self._need_version))
441
# Need to use class name "GnuTLS" here, since this method is
442
# called before the assignment to the "gnutls" global variable
444
if GnuTLS.check_version(self._need_version) is None:
445
raise GnuTLS.Error("Needs GnuTLS {} or later"
446
.format(self._need_version))
524
448
# Unless otherwise indicated, the constants and types below are
525
449
# all from the gnutls/gnutls.h C header file.
534
456
CRD_CERTIFICATE = 1
535
457
E_NO_CERTIFICATE_FOUND = -49
536
458
OPENPGP_FMT_RAW = 0 # gnutls/openpgp.h
539
461
class session_int(ctypes.Structure):
541
463
session_t = ctypes.POINTER(session_int)
543
464
class certificate_credentials_st(ctypes.Structure):
545
466
certificate_credentials_t = ctypes.POINTER(
546
467
certificate_credentials_st)
547
468
certificate_type_t = ctypes.c_int
549
469
class datum_t(ctypes.Structure):
550
470
_fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
551
471
('size', ctypes.c_uint)]
553
472
class openpgp_crt_int(ctypes.Structure):
555
474
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
556
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
475
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
557
476
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
558
credentials_type_t = ctypes.c_int
477
credentials_type_t = ctypes.c_int #
559
478
transport_ptr_t = ctypes.c_void_p
560
479
close_request_t = ctypes.c_int
563
482
class Error(Exception):
564
483
# We need to use the class name "GnuTLS" here, since this
565
484
# exception might be raised from within GnuTLS.__init__,
566
485
# which is called before the assignment to the "gnutls"
567
# global variable has happened.
568
def __init__(self, message=None, code=None, args=()):
486
# global variable happens.
487
def __init__(self, message = None, code = None, args=()):
569
488
# Default usage is by a message string, but if a return
570
489
# code is passed, convert it to a string with
571
490
# gnutls.strerror()
573
491
if message is None and code is not None:
574
492
message = GnuTLS.strerror(code)
575
493
return super(GnuTLS.Error, self).__init__(
578
496
class CertificateSecurityError(Error):
582
500
class Credentials(object):
583
501
def __init__(self):
604
522
ctypes.cast(credentials._c_object,
605
523
ctypes.c_void_p))
606
524
self.credentials = credentials
608
526
def __del__(self):
609
527
gnutls.deinit(self._c_object)
611
529
def handshake(self):
612
530
return gnutls.handshake(self._c_object)
614
532
def send(self, data):
615
533
data = bytes(data)
618
data_len -= gnutls.record_send(self._c_object,
536
return gnutls.record_send(self._c_object, data, len(data))
623
539
return gnutls.bye(self._c_object, gnutls.SHUT_RDWR)
625
# Error handling functions
541
# Error handling function
626
542
def _error_code(result):
627
543
"""A function to raise exceptions on errors, suitable
628
544
for the 'restype' attribute on ctypes functions"""
631
547
if result == gnutls.E_NO_CERTIFICATE_FOUND:
632
raise gnutls.CertificateSecurityError(code=result)
633
raise gnutls.Error(code=result)
635
def _retry_on_error(result, func, arguments):
636
"""A function to retry on some errors, suitable
637
for the 'errcheck' attribute on ctypes functions"""
639
if result not in (gnutls.E_INTERRUPTED, gnutls.E_AGAIN):
640
return _error_code(result)
641
result = func(*arguments)
548
raise gnutls.CertificateSecurityError(code = result)
549
raise gnutls.Error(code = result)
644
551
# Unless otherwise indicated, the function declarations below are
645
552
# all from the gnutls/gnutls.h C header file.
648
555
priority_set_direct = _library.gnutls_priority_set_direct
649
556
priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
650
557
ctypes.POINTER(ctypes.c_char_p)]
651
558
priority_set_direct.restype = _error_code
653
560
init = _library.gnutls_init
654
561
init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
655
562
init.restype = _error_code
657
564
set_default_priority = _library.gnutls_set_default_priority
658
565
set_default_priority.argtypes = [session_t]
659
566
set_default_priority.restype = _error_code
661
568
record_send = _library.gnutls_record_send
662
569
record_send.argtypes = [session_t, ctypes.c_void_p,
664
571
record_send.restype = ctypes.c_ssize_t
665
record_send.errcheck = _retry_on_error
667
573
certificate_allocate_credentials = (
668
574
_library.gnutls_certificate_allocate_credentials)
669
575
certificate_allocate_credentials.argtypes = [
670
576
ctypes.POINTER(certificate_credentials_t)]
671
577
certificate_allocate_credentials.restype = _error_code
673
579
certificate_free_credentials = (
674
580
_library.gnutls_certificate_free_credentials)
675
certificate_free_credentials.argtypes = [
676
certificate_credentials_t]
581
certificate_free_credentials.argtypes = [certificate_credentials_t]
677
582
certificate_free_credentials.restype = None
679
584
handshake_set_private_extensions = (
680
585
_library.gnutls_handshake_set_private_extensions)
681
586
handshake_set_private_extensions.argtypes = [session_t,
683
588
handshake_set_private_extensions.restype = None
685
590
credentials_set = _library.gnutls_credentials_set
686
591
credentials_set.argtypes = [session_t, credentials_type_t,
688
593
credentials_set.restype = _error_code
690
595
strerror = _library.gnutls_strerror
691
596
strerror.argtypes = [ctypes.c_int]
692
597
strerror.restype = ctypes.c_char_p
694
599
certificate_type_get = _library.gnutls_certificate_type_get
695
600
certificate_type_get.argtypes = [session_t]
696
601
certificate_type_get.restype = _error_code
698
603
certificate_get_peers = _library.gnutls_certificate_get_peers
699
604
certificate_get_peers.argtypes = [session_t,
700
605
ctypes.POINTER(ctypes.c_uint)]
701
606
certificate_get_peers.restype = ctypes.POINTER(datum_t)
703
608
global_set_log_level = _library.gnutls_global_set_log_level
704
609
global_set_log_level.argtypes = [ctypes.c_int]
705
610
global_set_log_level.restype = None
707
612
global_set_log_function = _library.gnutls_global_set_log_function
708
613
global_set_log_function.argtypes = [log_func]
709
614
global_set_log_function.restype = None
711
616
deinit = _library.gnutls_deinit
712
617
deinit.argtypes = [session_t]
713
618
deinit.restype = None
715
620
handshake = _library.gnutls_handshake
716
621
handshake.argtypes = [session_t]
717
622
handshake.restype = _error_code
718
handshake.errcheck = _retry_on_error
720
624
transport_set_ptr = _library.gnutls_transport_set_ptr
721
625
transport_set_ptr.argtypes = [session_t, transport_ptr_t]
722
626
transport_set_ptr.restype = None
724
628
bye = _library.gnutls_bye
725
629
bye.argtypes = [session_t, close_request_t]
726
630
bye.restype = _error_code
727
bye.errcheck = _retry_on_error
729
632
check_version = _library.gnutls_check_version
730
633
check_version.argtypes = [ctypes.c_char_p]
731
634
check_version.restype = ctypes.c_char_p
733
636
# All the function declarations below are from gnutls/openpgp.h
735
638
openpgp_crt_init = _library.gnutls_openpgp_crt_init
736
639
openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
737
640
openpgp_crt_init.restype = _error_code
739
642
openpgp_crt_import = _library.gnutls_openpgp_crt_import
740
643
openpgp_crt_import.argtypes = [openpgp_crt_t,
741
644
ctypes.POINTER(datum_t),
742
645
openpgp_crt_fmt_t]
743
646
openpgp_crt_import.restype = _error_code
745
648
openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
746
649
openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
747
650
ctypes.POINTER(ctypes.c_uint)]
748
651
openpgp_crt_verify_self.restype = _error_code
750
653
openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
751
654
openpgp_crt_deinit.argtypes = [openpgp_crt_t]
752
655
openpgp_crt_deinit.restype = None
754
657
openpgp_crt_get_fingerprint = (
755
658
_library.gnutls_openpgp_crt_get_fingerprint)
756
659
openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
786
687
checker: subprocess.Popen(); a running checker process used
787
688
to see if the client lives.
788
689
'None' if no process is running.
789
checker_callback_tag: a GLib event source tag, or None
690
checker_callback_tag: a gobject event source tag, or None
790
691
checker_command: string; External command which is run to check
791
692
if client lives. %() expansions are done at
792
693
runtime with vars(self) as dict, so that for
793
694
instance %(name)s can be used in the command.
794
checker_initiator_tag: a GLib event source tag, or None
695
checker_initiator_tag: a gobject event source tag, or None
795
696
created: datetime.datetime(); (UTC) object creation
796
697
client_structure: Object describing what attributes a client has
797
698
and is used for storing the client at exit
798
699
current_checker_command: string; current running checker_command
799
disable_initiator_tag: a GLib event source tag, or None
700
disable_initiator_tag: a gobject event source tag, or None
801
702
fingerprint: string (40 or 32 hexadecimal digits); used to
802
703
uniquely identify the client
959
858
logger.info("Disabling client %s", self.name)
960
859
if getattr(self, "disable_initiator_tag", None) is not None:
961
GLib.source_remove(self.disable_initiator_tag)
860
gobject.source_remove(self.disable_initiator_tag)
962
861
self.disable_initiator_tag = None
963
862
self.expires = None
964
863
if getattr(self, "checker_initiator_tag", None) is not None:
965
GLib.source_remove(self.checker_initiator_tag)
864
gobject.source_remove(self.checker_initiator_tag)
966
865
self.checker_initiator_tag = None
967
866
self.stop_checker()
968
867
self.enabled = False
970
869
self.send_changedstate()
971
# Do not run this again if called by a GLib.timeout_add
870
# Do not run this again if called by a gobject.timeout_add
974
873
def __del__(self):
977
876
def init_checker(self):
978
877
# Schedule a new checker to be started an 'interval' from now,
979
878
# and every interval from then on.
980
879
if self.checker_initiator_tag is not None:
981
GLib.source_remove(self.checker_initiator_tag)
982
self.checker_initiator_tag = GLib.timeout_add(
880
gobject.source_remove(self.checker_initiator_tag)
881
self.checker_initiator_tag = gobject.timeout_add(
983
882
int(self.interval.total_seconds() * 1000),
984
883
self.start_checker)
985
884
# Schedule a disable() when 'timeout' has passed
986
885
if self.disable_initiator_tag is not None:
987
GLib.source_remove(self.disable_initiator_tag)
988
self.disable_initiator_tag = GLib.timeout_add(
886
gobject.source_remove(self.disable_initiator_tag)
887
self.disable_initiator_tag = gobject.timeout_add(
989
888
int(self.timeout.total_seconds() * 1000), self.disable)
990
889
# Also start a new checker *right now*.
991
890
self.start_checker()
993
892
def checker_callback(self, source, condition, connection,
995
894
"""The checker has completed, so take appropriate actions."""
1014
913
logger.warning("Checker for %(name)s crashed?",
1018
917
def checked_ok(self):
1019
918
"""Assert that the client has been seen, alive and well."""
1020
919
self.last_checked_ok = datetime.datetime.utcnow()
1021
920
self.last_checker_status = 0
1022
921
self.last_checker_signal = None
1023
922
self.bump_timeout()
1025
924
def bump_timeout(self, timeout=None):
1026
925
"""Bump up the timeout for this client."""
1027
926
if timeout is None:
1028
927
timeout = self.timeout
1029
928
if self.disable_initiator_tag is not None:
1030
GLib.source_remove(self.disable_initiator_tag)
929
gobject.source_remove(self.disable_initiator_tag)
1031
930
self.disable_initiator_tag = None
1032
931
if getattr(self, "enabled", False):
1033
self.disable_initiator_tag = GLib.timeout_add(
932
self.disable_initiator_tag = gobject.timeout_add(
1034
933
int(timeout.total_seconds() * 1000), self.disable)
1035
934
self.expires = datetime.datetime.utcnow() + timeout
1037
936
def need_approval(self):
1038
937
self.last_approval_request = datetime.datetime.utcnow()
1040
939
def start_checker(self):
1041
940
"""Start a new checker subprocess if one is not running.
1043
942
If a checker already exists, leave it running and do
1045
944
# The reason for not killing a running checker is that if we
1078
977
# The exception is when not debugging but nevertheless
1079
978
# running in the foreground; use the previously
1080
979
# created wnull.
1081
popen_args = {"close_fds": True,
980
popen_args = { "close_fds": True,
1084
983
if (not self.server_settings["debug"]
1085
984
and self.server_settings["foreground"]):
1086
985
popen_args.update({"stdout": wnull,
1088
pipe = multiprocessing.Pipe(duplex=False)
987
pipe = multiprocessing.Pipe(duplex = False)
1089
988
self.checker = multiprocessing.Process(
1091
args=(pipe[1], subprocess.call, command),
990
args = (pipe[1], subprocess.call, command),
1093
992
self.checker.start()
1094
self.checker_callback_tag = GLib.io_add_watch(
1095
pipe[0].fileno(), GLib.IO_IN,
993
self.checker_callback_tag = gobject.io_add_watch(
994
pipe[0].fileno(), gobject.IO_IN,
1096
995
self.checker_callback, pipe[0], command)
1097
# Re-run this periodically if run by GLib.timeout_add
996
# Re-run this periodically if run by gobject.timeout_add
1100
999
def stop_checker(self):
1101
1000
"""Force the checker process, if any, to stop."""
1102
1001
if self.checker_callback_tag:
1103
GLib.source_remove(self.checker_callback_tag)
1002
gobject.source_remove(self.checker_callback_tag)
1104
1003
self.checker_callback_tag = None
1105
1004
if getattr(self, "checker", None) is None:
1137
1036
func._dbus_name = func.__name__
1138
1037
if func._dbus_name.endswith("_dbus_property"):
1139
1038
func._dbus_name = func._dbus_name[:-14]
1140
func._dbus_get_args_options = {'byte_arrays': byte_arrays}
1039
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
1143
1042
return decorator
1146
1045
def dbus_interface_annotations(dbus_interface):
1147
1046
"""Decorator for marking functions returning interface annotations
1151
1050
@dbus_interface_annotations("org.example.Interface")
1152
1051
def _foo(self): # Function name does not matter
1153
1052
return {"org.freedesktop.DBus.Deprecated": "true",
1154
1053
"org.freedesktop.DBus.Property.EmitsChangedSignal":
1158
1057
def decorator(func):
1159
1058
func._dbus_is_interface = True
1160
1059
func._dbus_interface = dbus_interface
1161
1060
func._dbus_name = dbus_interface
1164
1063
return decorator
1167
1066
def dbus_annotations(annotations):
1168
1067
"""Decorator to annotate D-Bus methods, signals or properties
1171
1070
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
1172
1071
"org.freedesktop.DBus.Property."
1173
1072
"EmitsChangedSignal": "false"})
1460
1359
exc_info=error)
1461
1360
return xmlstring
1465
1363
dbus.OBJECT_MANAGER_IFACE
1466
1364
except AttributeError:
1467
1365
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1470
1367
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1471
1368
"""A D-Bus object with an ObjectManager.
1473
1370
Classes inheriting from this exposes the standard
1474
1371
GetManagedObjects call and the InterfacesAdded and
1475
1372
InterfacesRemoved signals on the standard
1476
1373
"org.freedesktop.DBus.ObjectManager" interface.
1478
1375
Note: No signals are sent automatically; they must be sent
1481
1378
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1482
out_signature="a{oa{sa{sv}}}")
1379
out_signature = "a{oa{sa{sv}}}")
1483
1380
def GetManagedObjects(self):
1484
1381
"""This function must be overridden"""
1485
1382
raise NotImplementedError()
1487
1384
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1488
signature="oa{sa{sv}}")
1385
signature = "oa{sa{sv}}")
1489
1386
def InterfacesAdded(self, object_path, interfaces_and_properties):
1492
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature="oas")
1389
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
1493
1390
def InterfacesRemoved(self, object_path, interfaces):
1496
1393
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1498
path_keyword='object_path',
1499
connection_keyword='connection')
1394
out_signature = "s",
1395
path_keyword = 'object_path',
1396
connection_keyword = 'connection')
1500
1397
def Introspect(self, object_path, connection):
1501
1398
"""Overloading of standard D-Bus method.
1503
1400
Override return argument name of GetManagedObjects to be
1504
1401
"objpath_interfaces_and_properties"
1583
1479
interface_names.add(alt_interface)
1584
1480
# Is this a D-Bus signal?
1585
1481
if getattr(attribute, "_dbus_is_signal", False):
1586
# Extract the original non-method undecorated
1587
# function by black magic
1588
1482
if sys.version_info.major == 2:
1483
# Extract the original non-method undecorated
1484
# function by black magic
1589
1485
nonmethod_func = (dict(
1590
1486
zip(attribute.func_code.co_freevars,
1591
1487
attribute.__closure__))
1592
1488
["func"].cell_contents)
1594
nonmethod_func = (dict(
1595
zip(attribute.__code__.co_freevars,
1596
attribute.__closure__))
1597
["func"].cell_contents)
1490
nonmethod_func = attribute
1598
1491
# Create a new, but exactly alike, function
1599
1492
# object, and decorate it to be a new D-Bus signal
1600
1493
# with the alternate D-Bus interface name
1601
new_function = copy_function(nonmethod_func)
1494
if sys.version_info.major == 2:
1495
new_function = types.FunctionType(
1496
nonmethod_func.func_code,
1497
nonmethod_func.func_globals,
1498
nonmethod_func.func_name,
1499
nonmethod_func.func_defaults,
1500
nonmethod_func.func_closure)
1502
new_function = types.FunctionType(
1503
nonmethod_func.__code__,
1504
nonmethod_func.__globals__,
1505
nonmethod_func.__name__,
1506
nonmethod_func.__defaults__,
1507
nonmethod_func.__closure__)
1602
1508
new_function = (dbus.service.signal(
1604
1510
attribute._dbus_signature)(new_function))
1768
1681
dbus_value = transform_func(
1769
1682
type_func(value),
1770
variant_level=variant_level)
1683
variant_level = variant_level)
1771
1684
self.PropertyChanged(dbus.String(dbus_name),
1773
1686
self.PropertiesChanged(
1775
dbus.Dictionary({dbus.String(dbus_name):
1688
dbus.Dictionary({ dbus.String(dbus_name):
1778
1691
setattr(self, attrname, value)
1780
1693
return property(lambda self: getattr(self, attrname), setter)
1782
1695
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1783
1696
approvals_pending = notifychangeproperty(dbus.Boolean,
1784
1697
"ApprovalPending",
1786
1699
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1787
1700
last_enabled = notifychangeproperty(datetime_to_dbus,
1789
1702
checker = notifychangeproperty(
1790
1703
dbus.Boolean, "CheckerRunning",
1791
type_func=lambda checker: checker is not None)
1704
type_func = lambda checker: checker is not None)
1792
1705
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1793
1706
"LastCheckedOK")
1794
1707
last_checker_status = notifychangeproperty(dbus.Int16,
1799
1712
"ApprovedByDefault")
1800
1713
approval_delay = notifychangeproperty(
1801
1714
dbus.UInt64, "ApprovalDelay",
1802
type_func=lambda td: td.total_seconds() * 1000)
1715
type_func = lambda td: td.total_seconds() * 1000)
1803
1716
approval_duration = notifychangeproperty(
1804
1717
dbus.UInt64, "ApprovalDuration",
1805
type_func=lambda td: td.total_seconds() * 1000)
1718
type_func = lambda td: td.total_seconds() * 1000)
1806
1719
host = notifychangeproperty(dbus.String, "Host")
1807
1720
timeout = notifychangeproperty(
1808
1721
dbus.UInt64, "Timeout",
1809
type_func=lambda td: td.total_seconds() * 1000)
1722
type_func = lambda td: td.total_seconds() * 1000)
1810
1723
extended_timeout = notifychangeproperty(
1811
1724
dbus.UInt64, "ExtendedTimeout",
1812
type_func=lambda td: td.total_seconds() * 1000)
1725
type_func = lambda td: td.total_seconds() * 1000)
1813
1726
interval = notifychangeproperty(
1814
1727
dbus.UInt64, "Interval",
1815
type_func=lambda td: td.total_seconds() * 1000)
1728
type_func = lambda td: td.total_seconds() * 1000)
1816
1729
checker_command = notifychangeproperty(dbus.String, "Checker")
1817
1730
secret = notifychangeproperty(dbus.ByteArray, "Secret",
1818
1731
invalidate_only=True)
1820
1733
del notifychangeproperty
1822
1735
def __del__(self, *args, **kwargs):
1824
1737
self.remove_from_connection()
1859
1772
# Emit D-Bus signal
1860
1773
self.CheckerStarted(self.current_checker_command)
1863
1776
def _reset_approved(self):
1864
1777
self.approved = None
1867
1780
def approve(self, value=True):
1868
1781
self.approved = value
1869
GLib.timeout_add(int(self.approval_duration.total_seconds()
1870
* 1000), self._reset_approved)
1782
gobject.timeout_add(int(self.approval_duration.total_seconds()
1783
* 1000), self._reset_approved)
1871
1784
self.send_changedstate()
1873
# D-Bus methods, signals & properties
1786
## D-Bus methods, signals & properties
1879
1792
# CheckerCompleted - signal
1880
1793
@dbus.service.signal(_interface, signature="nxs")
1881
1794
def CheckerCompleted(self, exitcode, waitstatus, command):
1885
1798
# CheckerStarted - signal
1886
1799
@dbus.service.signal(_interface, signature="s")
1887
1800
def CheckerStarted(self, command):
1891
1804
# PropertyChanged - signal
1892
1805
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1893
1806
@dbus.service.signal(_interface, signature="sv")
1894
1807
def PropertyChanged(self, property, value):
1898
1811
# GotSecret - signal
1899
1812
@dbus.service.signal(_interface)
1900
1813
def GotSecret(self):
1903
1816
server to mandos-client
1907
1820
# Rejected - signal
1908
1821
@dbus.service.signal(_interface, signature="s")
1909
1822
def Rejected(self, reason):
1913
1826
# NeedApproval - signal
1914
1827
@dbus.service.signal(_interface, signature="tb")
1915
1828
def NeedApproval(self, timeout, default):
1917
1830
return self.need_approval()
1921
1834
# Approve - method
1922
1835
@dbus.service.method(_interface, in_signature="b")
1923
1836
def Approve(self, value):
1924
1837
self.approve(value)
1926
1839
# CheckedOK - method
1927
1840
@dbus.service.method(_interface)
1928
1841
def CheckedOK(self):
1929
1842
self.checked_ok()
1931
1844
# Enable - method
1932
1845
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1933
1846
@dbus.service.method(_interface)
1934
1847
def Enable(self):
1938
1851
# StartChecker - method
1939
1852
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1940
1853
@dbus.service.method(_interface)
1941
1854
def StartChecker(self):
1943
1856
self.start_checker()
1945
1858
# Disable - method
1946
1859
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1947
1860
@dbus.service.method(_interface)
1948
1861
def Disable(self):
1952
1865
# StopChecker - method
1953
1866
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1954
1867
@dbus.service.method(_interface)
1955
1868
def StopChecker(self):
1956
1869
self.stop_checker()
1960
1873
# ApprovalPending - property
1961
1874
@dbus_service_property(_interface, signature="b", access="read")
1962
1875
def ApprovalPending_dbus_property(self):
1963
1876
return dbus.Boolean(bool(self.approvals_pending))
1965
1878
# ApprovedByDefault - property
1966
1879
@dbus_service_property(_interface,
2189
2102
class ClientHandler(socketserver.BaseRequestHandler, object):
2190
2103
"""A class to handle client connections.
2192
2105
Instantiated once for each connection to handle it.
2193
2106
Note: This will run in its own forked process."""
2195
2108
def handle(self):
2196
2109
with contextlib.closing(self.server.child_pipe) as child_pipe:
2197
2110
logger.info("TCP connection from: %s",
2198
2111
str(self.client_address))
2199
2112
logger.debug("Pipe FD: %d",
2200
2113
self.server.child_pipe.fileno())
2202
2115
session = gnutls.ClientSession(self.request)
2204
# priority = ':'.join(("NONE", "+VERS-TLS1.1",
2205
# "+AES-256-CBC", "+SHA1",
2206
# "+COMP-NULL", "+CTYPE-OPENPGP",
2117
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
2118
# "+AES-256-CBC", "+SHA1",
2119
# "+COMP-NULL", "+CTYPE-OPENPGP",
2208
2121
# Use a fallback default, since this MUST be set.
2209
2122
priority = self.server.gnutls_priority
2210
2123
if priority is None:
2211
2124
priority = "NORMAL"
2212
gnutls.priority_set_direct(session._c_object,
2213
priority.encode("utf-8"),
2125
gnutls.priority_set_direct(session._c_object, priority,
2216
2128
# Start communication using the Mandos protocol
2217
2129
# Get protocol number
2218
2130
line = self.request.makefile().readline()
2383
2301
class MultiprocessingMixIn(object):
2384
2302
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
2386
2304
def sub_process_main(self, request, address):
2388
2306
self.finish_request(request, address)
2389
2307
except Exception:
2390
2308
self.handle_error(request, address)
2391
2309
self.close_request(request)
2393
2311
def process_request(self, request, address):
2394
2312
"""Start a new process to process the request."""
2395
proc = multiprocessing.Process(target=self.sub_process_main,
2396
args=(request, address))
2313
proc = multiprocessing.Process(target = self.sub_process_main,
2314
args = (request, address))
2401
2319
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
2402
2320
""" adds a pipe to the MixIn """
2404
2322
def process_request(self, request, client_address):
2405
2323
"""Overrides and wraps the original process_request().
2407
2325
This function creates a new pipe in self.pipe
2409
2327
parent_pipe, self.child_pipe = multiprocessing.Pipe()
2411
2329
proc = MultiprocessingMixIn.process_request(self, request,
2412
2330
client_address)
2413
2331
self.child_pipe.close()
2414
2332
self.add_pipe(parent_pipe, proc)
2416
2334
def add_pipe(self, parent_pipe, proc):
2417
2335
"""Dummy function; override as necessary"""
2418
2336
raise NotImplementedError()
2467
2384
# socket_wrapper(), if socketfd was set.
2468
2385
socketserver.TCPServer.__init__(self, server_address,
2469
2386
RequestHandlerClass)
2471
2388
def server_bind(self):
2472
2389
"""This overrides the normal server_bind() function
2473
2390
to bind to an interface if one was specified, and also NOT to
2474
2391
bind to an address or port if they were not specified."""
2475
global SO_BINDTODEVICE
2476
2392
if self.interface is not None:
2477
2393
if SO_BINDTODEVICE is None:
2478
# Fall back to a hard-coded value which seems to be
2480
logger.warning("SO_BINDTODEVICE not found, trying 25")
2481
SO_BINDTODEVICE = 25
2483
self.socket.setsockopt(
2484
socket.SOL_SOCKET, SO_BINDTODEVICE,
2485
(self.interface + "\0").encode("utf-8"))
2486
except socket.error as error:
2487
if error.errno == errno.EPERM:
2488
logger.error("No permission to bind to"
2489
" interface %s", self.interface)
2490
elif error.errno == errno.ENOPROTOOPT:
2491
logger.error("SO_BINDTODEVICE not available;"
2492
" cannot bind to interface %s",
2494
elif error.errno == errno.ENODEV:
2495
logger.error("Interface %s does not exist,"
2496
" cannot bind", self.interface)
2394
logger.error("SO_BINDTODEVICE does not exist;"
2395
" cannot bind to interface %s",
2399
self.socket.setsockopt(
2400
socket.SOL_SOCKET, SO_BINDTODEVICE,
2401
(self.interface + "\0").encode("utf-8"))
2402
except socket.error as error:
2403
if error.errno == errno.EPERM:
2404
logger.error("No permission to bind to"
2405
" interface %s", self.interface)
2406
elif error.errno == errno.ENOPROTOOPT:
2407
logger.error("SO_BINDTODEVICE not available;"
2408
" cannot bind to interface %s",
2410
elif error.errno == errno.ENODEV:
2411
logger.error("Interface %s does not exist,"
2412
" cannot bind", self.interface)
2499
2415
# Only bind(2) the socket if we really need to.
2500
2416
if self.server_address[0] or self.server_address[1]:
2501
2417
if not self.server_address[0]:
2502
2418
if self.address_family == socket.AF_INET6:
2503
any_address = "::" # in6addr_any
2419
any_address = "::" # in6addr_any
2505
any_address = "0.0.0.0" # INADDR_ANY
2421
any_address = "0.0.0.0" # INADDR_ANY
2506
2422
self.server_address = (any_address,
2507
2423
self.server_address[1])
2508
2424
elif not self.server_address[1]:
2542
2458
self.gnutls_priority = gnutls_priority
2543
2459
IPv6_TCPServer.__init__(self, server_address,
2544
2460
RequestHandlerClass,
2545
interface=interface,
2461
interface = interface,
2462
use_ipv6 = use_ipv6,
2463
socketfd = socketfd)
2549
2465
def server_activate(self):
2550
2466
if self.enabled:
2551
2467
return socketserver.TCPServer.server_activate(self)
2553
2469
def enable(self):
2554
2470
self.enabled = True
2556
2472
def add_pipe(self, parent_pipe, proc):
2557
2473
# Call "handle_ipc" for both data and EOF events
2474
gobject.io_add_watch(
2559
2475
parent_pipe.fileno(),
2560
GLib.IO_IN | GLib.IO_HUP,
2476
gobject.IO_IN | gobject.IO_HUP,
2561
2477
functools.partial(self.handle_ipc,
2562
parent_pipe=parent_pipe,
2478
parent_pipe = parent_pipe,
2565
2481
def handle_ipc(self, source, condition,
2566
2482
parent_pipe=None,
2568
2484
client_object=None):
2569
2485
# error, or the other end of multiprocessing.Pipe has closed
2570
if condition & (GLib.IO_ERR | GLib.IO_HUP):
2486
if condition & (gobject.IO_ERR | gobject.IO_HUP):
2571
2487
# Wait for other process to exit
2575
2491
# Read a request from the child
2576
2492
request = parent_pipe.recv()
2577
2493
command = request[0]
2579
2495
if command == 'init':
2580
2496
fpr = request[1]
2581
2497
address = request[2]
2583
for c in self.clients.values():
2499
for c in self.clients.itervalues():
2584
2500
if c.fingerprint == fpr:
3006
logger.debug("Did setuid/setgid to {}:{}".format(uid,
3008
2916
except OSError as error:
3009
logger.warning("Failed to setuid/setgid to {}:{}: {}"
3010
.format(uid, gid, os.strerror(error.errno)))
3011
2917
if error.errno != errno.EPERM:
3015
2921
# Enable all possible GnuTLS debugging
3017
2923
# "Use a log level over 10 to enable all debugging options."
3018
2924
# - GnuTLS manual
3019
2925
gnutls.global_set_log_level(11)
3021
2927
@gnutls.log_func
3022
2928
def debug_gnutls(level, string):
3023
2929
logger.debug("GnuTLS: %s", string[:-1])
3025
2931
gnutls.global_set_log_function(debug_gnutls)
3027
2933
# Redirect stdin so all checkers get /dev/null
3028
2934
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
3029
2935
os.dup2(null, sys.stdin.fileno())
3033
2939
# Need to fork before connecting to D-Bus
3034
2940
if not foreground:
3035
2941
# Close all input and output, do double fork, etc.
3038
# multiprocessing will use threads, so before we use GLib we need
3039
# to inform GLib that threads will be used.
2944
# multiprocessing will use threads, so before we use gobject we
2945
# need to inform gobject that threads will be used.
2946
gobject.threads_init()
3042
2948
global main_loop
3043
2949
# From the Avahi example code
3044
2950
DBusGMainLoop(set_as_default=True)
3045
main_loop = GLib.MainLoop()
2951
main_loop = gobject.MainLoop()
3046
2952
bus = dbus.SystemBus()
3047
2953
# End of Avahi example code
3062
2968
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
3063
2969
service = AvahiServiceToSyslog(
3064
name=server_settings["servicename"],
3065
servicetype="_mandos._tcp",
2970
name = server_settings["servicename"],
2971
servicetype = "_mandos._tcp",
2972
protocol = protocol,
3068
2974
if server_settings["interface"]:
3069
2975
service.interface = if_nametoindex(
3070
2976
server_settings["interface"].encode("utf-8"))
3072
2978
global multiprocessing_manager
3073
2979
multiprocessing_manager = multiprocessing.Manager()
3075
2981
client_class = Client
3077
client_class = functools.partial(ClientDBus, bus=bus)
2983
client_class = functools.partial(ClientDBus, bus = bus)
3079
2985
client_settings = Client.config_parser(client_config)
3080
2986
old_client_settings = {}
3081
2987
clients_data = {}
3083
2989
# This is used to redirect stdout and stderr for checker processes
3085
wnull = open(os.devnull, "w") # A writable /dev/null
2991
wnull = open(os.devnull, "w") # A writable /dev/null
3086
2992
# Only used if server is running in foreground but not in debug
3088
2994
if debug or not foreground:
3091
2997
# Get client data and settings from last running state.
3092
2998
if server_settings["restore"]:
3094
3000
with open(stored_state_path, "rb") as stored_state:
3095
if sys.version_info.major == 2:
3096
clients_data, old_client_settings = pickle.load(
3099
bytes_clients_data, bytes_old_client_settings = (
3100
pickle.load(stored_state, encoding="bytes"))
3101
# Fix bytes to strings
3104
clients_data = {(key.decode("utf-8")
3105
if isinstance(key, bytes)
3108
bytes_clients_data.items()}
3109
del bytes_clients_data
3110
for key in clients_data:
3111
value = {(k.decode("utf-8")
3112
if isinstance(k, bytes) else k): v
3114
clients_data[key].items()}
3115
clients_data[key] = value
3117
value["client_structure"] = [
3119
if isinstance(s, bytes)
3121
value["client_structure"]]
3123
for k in ("name", "host"):
3124
if isinstance(value[k], bytes):
3125
value[k] = value[k].decode("utf-8")
3126
# old_client_settings
3128
old_client_settings = {
3129
(key.decode("utf-8")
3130
if isinstance(key, bytes)
3133
bytes_old_client_settings.items()}
3134
del bytes_old_client_settings
3136
for value in old_client_settings.values():
3137
if isinstance(value["host"], bytes):
3138
value["host"] = (value["host"]
3001
clients_data, old_client_settings = pickle.load(
3140
3003
os.remove(stored_state_path)
3141
3004
except IOError as e:
3142
3005
if e.errno == errno.ENOENT:
3242
3105
pidfilename, pid)
3244
3107
del pidfilename
3246
for termsig in (signal.SIGHUP, signal.SIGTERM):
3247
GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig,
3248
lambda: main_loop.quit() and False)
3109
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
3110
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
3252
3114
@alternate_dbus_interfaces(
3253
{"se.recompile.Mandos": "se.bsnet.fukt.Mandos"})
3115
{ "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
3254
3116
class MandosDBusService(DBusObjectWithObjectManager):
3255
3117
"""A D-Bus proxy object"""
3257
3119
def __init__(self):
3258
3120
dbus.service.Object.__init__(self, bus, "/")
3260
3122
_interface = "se.recompile.Mandos"
3262
3124
@dbus.service.signal(_interface, signature="o")
3263
3125
def ClientAdded(self, objpath):
3267
3129
@dbus.service.signal(_interface, signature="ss")
3268
3130
def ClientNotFound(self, fingerprint, address):
3272
3134
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3274
3136
@dbus.service.signal(_interface, signature="os")
3275
3137
def ClientRemoved(self, objpath, name):
3279
3141
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3281
3143
@dbus.service.method(_interface, out_signature="ao")
3282
3144
def GetAllClients(self):
3284
3146
return dbus.Array(c.dbus_object_path for c in
3285
tcp_server.clients.values())
3147
tcp_server.clients.itervalues())
3287
3149
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3289
3151
@dbus.service.method(_interface,
3348
3210
self.ClientRemoved(client.dbus_object_path,
3351
3213
mandos_dbus_service = MandosDBusService()
3353
# Save modules to variables to exempt the modules from being
3354
# unloaded before the function registered with atexit() is run.
3355
mp = multiprocessing
3359
3216
"Cleanup function; run on exit"
3361
3218
service.cleanup()
3363
mp.active_children()
3220
multiprocessing.active_children()
3365
3222
if not (tcp_server.clients or client_settings):
3368
3225
# Store client before exiting. Secrets are encrypted with key
3369
3226
# based on what config file has. If config file is
3370
3227
# removed/edited, old secret will thus be unrecovable.
3372
3229
with PGPEngine() as pgp:
3373
for client in tcp_server.clients.values():
3230
for client in tcp_server.clients.itervalues():
3374
3231
key = client_settings[client.name]["secret"]
3375
3232
client.encrypted_secret = pgp.encrypt(client.secret,
3377
3234
client_dict = {}
3379
3236
# A list of attributes that can not be pickled
3381
exclude = {"bus", "changedstate", "secret",
3382
"checker", "server_settings"}
3238
exclude = { "bus", "changedstate", "secret",
3239
"checker", "server_settings" }
3383
3240
for name, typ in inspect.getmembers(dbus.service
3385
3242
exclude.add(name)
3387
3244
client_dict["encrypted_secret"] = (client
3388
3245
.encrypted_secret)
3389
3246
for attr in client.client_structure:
3390
3247
if attr not in exclude:
3391
3248
client_dict[attr] = getattr(client, attr)
3393
3250
clients[client.name] = client_dict
3394
3251
del client_settings[client.name]["secret"]
3397
3254
with tempfile.NamedTemporaryFile(