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