95
84
except ImportError:
96
85
SO_BINDTODEVICE = None
98
if sys.version_info.major == 2:
102
stored_state_file = "clients.pickle"
104
90
logger = logging.getLogger()
108
if_nametoindex = ctypes.cdll.LoadLibrary(
109
ctypes.util.find_library("c")).if_nametoindex
110
except (OSError, AttributeError):
112
def if_nametoindex(interface):
113
"Get an interface index the hard way, i.e. using fcntl()"
114
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
115
with contextlib.closing(socket.socket()) as s:
116
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
117
struct.pack(b"16s16x", interface))
118
interface_index = struct.unpack("I", ifreq[16:20])[0]
119
return interface_index
122
def initlogger(debug, level=logging.WARNING):
123
"""init logger and add loglevel"""
126
syslogger = (logging.handlers.SysLogHandler(
127
facility = logging.handlers.SysLogHandler.LOG_DAEMON,
128
address = "/dev/log"))
129
syslogger.setFormatter(logging.Formatter
130
('Mandos [%(process)d]: %(levelname)s:'
132
logger.addHandler(syslogger)
135
console = logging.StreamHandler()
136
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
140
logger.addHandler(console)
141
logger.setLevel(level)
144
class PGPError(Exception):
145
"""Exception if encryption/decryption fails"""
149
class PGPEngine(object):
150
"""A simple class for OpenPGP symmetric encryption & decryption"""
153
self.tempdir = tempfile.mkdtemp(prefix="mandos-")
156
output = subprocess.check_output(["gpgconf"])
157
for line in output.splitlines():
158
name, text, path = line.split(":")
163
if e.errno != errno.ENOENT:
165
self.gnupgargs = ['--batch',
166
'--homedir', self.tempdir,
174
def __exit__(self, exc_type, exc_value, traceback):
182
if self.tempdir is not None:
183
# Delete contents of tempdir
184
for root, dirs, files in os.walk(self.tempdir,
186
for filename in files:
187
os.remove(os.path.join(root, filename))
189
os.rmdir(os.path.join(root, dirname))
191
os.rmdir(self.tempdir)
194
def password_encode(self, password):
195
# Passphrase can not be empty and can not contain newlines or
196
# NUL bytes. So we prefix it and hex encode it.
197
encoded = b"mandos" + binascii.hexlify(password)
198
if len(encoded) > 2048:
199
# GnuPG can't handle long passwords, so encode differently
200
encoded = (b"mandos" + password.replace(b"\\", b"\\\\")
201
.replace(b"\n", b"\\n")
202
.replace(b"\0", b"\\x00"))
205
def encrypt(self, data, password):
206
passphrase = self.password_encode(password)
207
with tempfile.NamedTemporaryFile(
208
dir=self.tempdir) as passfile:
209
passfile.write(passphrase)
211
proc = subprocess.Popen([self.gpg, '--symmetric',
215
stdin = subprocess.PIPE,
216
stdout = subprocess.PIPE,
217
stderr = subprocess.PIPE)
218
ciphertext, err = proc.communicate(input = data)
219
if proc.returncode != 0:
223
def decrypt(self, data, password):
224
passphrase = self.password_encode(password)
225
with tempfile.NamedTemporaryFile(
226
dir = self.tempdir) as passfile:
227
passfile.write(passphrase)
229
proc = subprocess.Popen([self.gpg, '--decrypt',
233
stdin = subprocess.PIPE,
234
stdout = subprocess.PIPE,
235
stderr = subprocess.PIPE)
236
decrypted_plaintext, err = proc.communicate(input = data)
237
if proc.returncode != 0:
239
return decrypted_plaintext
91
stored_state_path = "/var/lib/mandos/clients.pickle"
93
syslogger = (logging.handlers.SysLogHandler
94
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
95
address = str("/dev/log")))
96
syslogger.setFormatter(logging.Formatter
97
('Mandos [%(process)d]: %(levelname)s:'
99
logger.addHandler(syslogger)
101
console = logging.StreamHandler()
102
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
106
logger.addHandler(console)
242
109
class AvahiError(Exception):
243
110
def __init__(self, value, *args, **kwargs):
244
111
self.value = value
245
return super(AvahiError, self).__init__(value, *args,
112
super(AvahiError, self).__init__(value, *args, **kwargs)
113
def __unicode__(self):
114
return unicode(repr(self.value))
249
116
class AvahiServiceError(AvahiError):
253
119
class AvahiGroupError(AvahiError):
427
263
follow_name_owner_changes=True),
428
264
avahi.DBUS_INTERFACE_SERVER)
429
265
self.server.connect_to_signal("StateChanged",
430
self.server_state_changed)
266
self.server_state_changed)
431
267
self.server_state_changed(self.server.GetState())
434
269
class AvahiServiceToSyslog(AvahiService):
435
def rename(self, *args, **kwargs):
436
271
"""Add the new name to the syslog messages"""
437
ret = AvahiService.rename(self, *args, **kwargs)
438
syslogger.setFormatter(logging.Formatter(
439
'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
272
ret = AvahiService.rename(self)
273
syslogger.setFormatter(logging.Formatter
274
('Mandos (%s) [%%(process)d]:'
275
' %%(levelname)s: %%(message)s'
443
# Pretend that we have a GnuTLS module
444
class GnuTLS(object):
445
"""This isn't so much a class as it is a module-like namespace.
446
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"
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))
459
# Unless otherwise indicated, the constants and types below are
460
# all from the gnutls/gnutls.h C header file.
470
E_NO_CERTIFICATE_FOUND = -49
471
OPENPGP_FMT_RAW = 0 # gnutls/openpgp.h
474
class session_int(ctypes.Structure):
476
session_t = ctypes.POINTER(session_int)
477
class certificate_credentials_st(ctypes.Structure):
479
certificate_credentials_t = ctypes.POINTER(
480
certificate_credentials_st)
481
certificate_type_t = ctypes.c_int
482
class datum_t(ctypes.Structure):
483
_fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
484
('size', ctypes.c_uint)]
485
class openpgp_crt_int(ctypes.Structure):
487
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
488
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
489
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
490
credentials_type_t = ctypes.c_int
491
transport_ptr_t = ctypes.c_void_p
492
close_request_t = ctypes.c_int
495
class Error(Exception):
496
# We need to use the class name "GnuTLS" here, since this
497
# exception might be raised from within GnuTLS.__init__,
498
# which is called before the assignment to the "gnutls"
499
# global variable has happened.
500
def __init__(self, message = None, code = None, args=()):
501
# Default usage is by a message string, but if a return
502
# code is passed, convert it to a string with
505
if message is None and code is not None:
506
message = GnuTLS.strerror(code)
507
return super(GnuTLS.Error, self).__init__(
510
class CertificateSecurityError(Error):
514
class Credentials(object):
516
self._c_object = gnutls.certificate_credentials_t()
517
gnutls.certificate_allocate_credentials(
518
ctypes.byref(self._c_object))
519
self.type = gnutls.CRD_CERTIFICATE
522
gnutls.certificate_free_credentials(self._c_object)
524
class ClientSession(object):
525
def __init__(self, socket, credentials = None):
526
self._c_object = gnutls.session_t()
527
gnutls.init(ctypes.byref(self._c_object), gnutls.CLIENT)
528
gnutls.set_default_priority(self._c_object)
529
gnutls.transport_set_ptr(self._c_object, socket.fileno())
530
gnutls.handshake_set_private_extensions(self._c_object,
533
if credentials is None:
534
credentials = gnutls.Credentials()
535
gnutls.credentials_set(self._c_object, credentials.type,
536
ctypes.cast(credentials._c_object,
538
self.credentials = credentials
541
gnutls.deinit(self._c_object)
544
return gnutls.handshake(self._c_object)
546
def send(self, data):
550
data_len -= gnutls.record_send(self._c_object,
555
return gnutls.bye(self._c_object, gnutls.SHUT_RDWR)
557
# Error handling functions
558
def _error_code(result):
559
"""A function to raise exceptions on errors, suitable
560
for the 'restype' attribute on ctypes functions"""
563
if result == gnutls.E_NO_CERTIFICATE_FOUND:
564
raise gnutls.CertificateSecurityError(code = result)
565
raise gnutls.Error(code = result)
567
def _retry_on_error(result, func, arguments):
568
"""A function to retry on some errors, suitable
569
for the 'errcheck' attribute on ctypes functions"""
571
if result not in (gnutls.E_INTERRUPTED, gnutls.E_AGAIN):
572
return _error_code(result)
573
result = func(*arguments)
576
# Unless otherwise indicated, the function declarations below are
577
# all from the gnutls/gnutls.h C header file.
580
priority_set_direct = _library.gnutls_priority_set_direct
581
priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
582
ctypes.POINTER(ctypes.c_char_p)]
583
priority_set_direct.restype = _error_code
585
init = _library.gnutls_init
586
init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
587
init.restype = _error_code
589
set_default_priority = _library.gnutls_set_default_priority
590
set_default_priority.argtypes = [session_t]
591
set_default_priority.restype = _error_code
593
record_send = _library.gnutls_record_send
594
record_send.argtypes = [session_t, ctypes.c_void_p,
596
record_send.restype = ctypes.c_ssize_t
597
record_send.errcheck = _retry_on_error
599
certificate_allocate_credentials = (
600
_library.gnutls_certificate_allocate_credentials)
601
certificate_allocate_credentials.argtypes = [
602
ctypes.POINTER(certificate_credentials_t)]
603
certificate_allocate_credentials.restype = _error_code
605
certificate_free_credentials = (
606
_library.gnutls_certificate_free_credentials)
607
certificate_free_credentials.argtypes = [certificate_credentials_t]
608
certificate_free_credentials.restype = None
610
handshake_set_private_extensions = (
611
_library.gnutls_handshake_set_private_extensions)
612
handshake_set_private_extensions.argtypes = [session_t,
614
handshake_set_private_extensions.restype = None
616
credentials_set = _library.gnutls_credentials_set
617
credentials_set.argtypes = [session_t, credentials_type_t,
619
credentials_set.restype = _error_code
621
strerror = _library.gnutls_strerror
622
strerror.argtypes = [ctypes.c_int]
623
strerror.restype = ctypes.c_char_p
625
certificate_type_get = _library.gnutls_certificate_type_get
626
certificate_type_get.argtypes = [session_t]
627
certificate_type_get.restype = _error_code
629
certificate_get_peers = _library.gnutls_certificate_get_peers
630
certificate_get_peers.argtypes = [session_t,
631
ctypes.POINTER(ctypes.c_uint)]
632
certificate_get_peers.restype = ctypes.POINTER(datum_t)
634
global_set_log_level = _library.gnutls_global_set_log_level
635
global_set_log_level.argtypes = [ctypes.c_int]
636
global_set_log_level.restype = None
638
global_set_log_function = _library.gnutls_global_set_log_function
639
global_set_log_function.argtypes = [log_func]
640
global_set_log_function.restype = None
642
deinit = _library.gnutls_deinit
643
deinit.argtypes = [session_t]
644
deinit.restype = None
646
handshake = _library.gnutls_handshake
647
handshake.argtypes = [session_t]
648
handshake.restype = _error_code
649
handshake.errcheck = _retry_on_error
651
transport_set_ptr = _library.gnutls_transport_set_ptr
652
transport_set_ptr.argtypes = [session_t, transport_ptr_t]
653
transport_set_ptr.restype = None
655
bye = _library.gnutls_bye
656
bye.argtypes = [session_t, close_request_t]
657
bye.restype = _error_code
658
bye.errcheck = _retry_on_error
660
check_version = _library.gnutls_check_version
661
check_version.argtypes = [ctypes.c_char_p]
662
check_version.restype = ctypes.c_char_p
664
# All the function declarations below are from gnutls/openpgp.h
666
openpgp_crt_init = _library.gnutls_openpgp_crt_init
667
openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
668
openpgp_crt_init.restype = _error_code
670
openpgp_crt_import = _library.gnutls_openpgp_crt_import
671
openpgp_crt_import.argtypes = [openpgp_crt_t,
672
ctypes.POINTER(datum_t),
674
openpgp_crt_import.restype = _error_code
676
openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
677
openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
678
ctypes.POINTER(ctypes.c_uint)]
679
openpgp_crt_verify_self.restype = _error_code
681
openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
682
openpgp_crt_deinit.argtypes = [openpgp_crt_t]
683
openpgp_crt_deinit.restype = None
685
openpgp_crt_get_fingerprint = (
686
_library.gnutls_openpgp_crt_get_fingerprint)
687
openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
691
openpgp_crt_get_fingerprint.restype = _error_code
693
# Remove non-public functions
694
del _error_code, _retry_on_error
695
# Create the global "gnutls" object, simulating a module
698
def call_pipe(connection, # : multiprocessing.Connection
699
func, *args, **kwargs):
700
"""This function is meant to be called by multiprocessing.Process
702
This function runs func(*args, **kwargs), and writes the resulting
703
return value on the provided multiprocessing.Connection.
705
connection.send(func(*args, **kwargs))
279
def _timedelta_to_milliseconds(td):
280
"Convert a datetime.timedelta() to milliseconds"
281
return ((td.days * 24 * 60 * 60 * 1000)
282
+ (td.seconds * 1000)
283
+ (td.microseconds // 1000))
708
285
class Client(object):
709
286
"""A representation of a client host served by this server.
712
approved: bool(); 'None' if not yet approved/disapproved
289
_approved: bool(); 'None' if not yet approved/disapproved
713
290
approval_delay: datetime.timedelta(); Time to wait for approval
714
291
approval_duration: datetime.timedelta(); Duration of one approval
715
292
checker: subprocess.Popen(); a running checker process used
716
293
to see if the client lives.
717
294
'None' if no process is running.
718
checker_callback_tag: a GObject event source tag, or None
295
checker_callback_tag: a gobject event source tag, or None
719
296
checker_command: string; External command which is run to check
720
297
if client lives. %() expansions are done at
721
298
runtime with vars(self) as dict, so that for
722
299
instance %(name)s can be used in the command.
723
checker_initiator_tag: a GObject event source tag, or None
300
checker_initiator_tag: a gobject event source tag, or None
724
301
created: datetime.datetime(); (UTC) object creation
725
302
client_structure: Object describing what attributes a client has
726
303
and is used for storing the client at exit
727
304
current_checker_command: string; current running checker_command
728
disable_initiator_tag: a GObject event source tag, or None
305
disable_initiator_tag: a gobject event source tag, or None
730
307
fingerprint: string (40 or 32 hexadecimal digits); used to
731
308
uniquely identify the client
733
310
interval: datetime.timedelta(); How often to start a new checker
734
311
last_approval_request: datetime.datetime(); (UTC) or None
735
312
last_checked_ok: datetime.datetime(); (UTC) or None
736
last_checker_status: integer between 0 and 255 reflecting exit
737
status of last checker. -1 reflects crashed
738
checker, -2 means no checker completed yet.
739
last_checker_signal: The signal which killed the last checker, if
740
last_checker_status is -1
741
last_enabled: datetime.datetime(); (UTC) or None
313
last_checker_status: integer between 0 and 255 reflecting exit status
314
of last checker. -1 reflect crashed checker,
316
last_enabled: datetime.datetime(); (UTC)
742
317
name: string; from the config file, used in log messages and
743
318
D-Bus identifiers
744
319
secret: bytestring; sent verbatim (over TLS) to client
745
320
timeout: datetime.timedelta(); How long from last_checked_ok
746
321
until this client is disabled
747
extended_timeout: extra long timeout when secret has been sent
322
extended_timeout: extra long timeout when password has been sent
748
323
runtime_expansions: Allowed attributes for runtime expansion.
749
324
expires: datetime.datetime(); time (UTC) when a client will be
750
325
disabled, or None
751
server_settings: The server_settings dict from main()
754
328
runtime_expansions = ("approval_delay", "approval_duration",
755
"created", "enabled", "expires",
756
"fingerprint", "host", "interval",
757
"last_approval_request", "last_checked_ok",
329
"created", "enabled", "fingerprint",
330
"host", "interval", "last_checked_ok",
758
331
"last_enabled", "name", "timeout")
761
"extended_timeout": "PT15M",
763
"checker": "fping -q -- %%(host)s",
765
"approval_delay": "PT0S",
766
"approval_duration": "PT1S",
767
"approved_by_default": "True",
772
def config_parser(config):
773
"""Construct a new dict of client settings of this form:
774
{ client_name: {setting_name: value, ...}, ...}
775
with exceptions for any special settings as defined above.
776
NOTE: Must be a pure function. Must return the same result
777
value given the same arguments.
780
for client_name in config.sections():
781
section = dict(config.items(client_name))
782
client = settings[client_name] = {}
784
client["host"] = section["host"]
785
# Reformat values from string types to Python types
786
client["approved_by_default"] = config.getboolean(
787
client_name, "approved_by_default")
788
client["enabled"] = config.getboolean(client_name,
791
# Uppercase and remove spaces from fingerprint for later
792
# comparison purposes with return value from the
793
# fingerprint() function
794
client["fingerprint"] = (section["fingerprint"].upper()
796
if "secret" in section:
797
client["secret"] = section["secret"].decode("base64")
798
elif "secfile" in section:
799
with open(os.path.expanduser(os.path.expandvars
800
(section["secfile"])),
802
client["secret"] = secfile.read()
804
raise TypeError("No secret or secfile for section {}"
806
client["timeout"] = string_to_delta(section["timeout"])
807
client["extended_timeout"] = string_to_delta(
808
section["extended_timeout"])
809
client["interval"] = string_to_delta(section["interval"])
810
client["approval_delay"] = string_to_delta(
811
section["approval_delay"])
812
client["approval_duration"] = string_to_delta(
813
section["approval_duration"])
814
client["checker_command"] = section["checker"]
815
client["last_approval_request"] = None
816
client["last_checked_ok"] = None
817
client["last_checker_status"] = -2
821
def __init__(self, settings, name = None, server_settings=None):
333
def timeout_milliseconds(self):
334
"Return the 'timeout' attribute in milliseconds"
335
return _timedelta_to_milliseconds(self.timeout)
337
def extended_timeout_milliseconds(self):
338
"Return the 'extended_timeout' attribute in milliseconds"
339
return _timedelta_to_milliseconds(self.extended_timeout)
341
def interval_milliseconds(self):
342
"Return the 'interval' attribute in milliseconds"
343
return _timedelta_to_milliseconds(self.interval)
345
def approval_delay_milliseconds(self):
346
return _timedelta_to_milliseconds(self.approval_delay)
348
def __init__(self, name = None, config=None):
349
"""Note: the 'checker' key in 'config' sets the
350
'checker_command' attribute and *not* the 'checker'
823
if server_settings is None:
825
self.server_settings = server_settings
826
# adding all client settings
827
for setting, value in settings.items():
828
setattr(self, setting, value)
831
if not hasattr(self, "last_enabled"):
832
self.last_enabled = datetime.datetime.utcnow()
833
if not hasattr(self, "expires"):
834
self.expires = (datetime.datetime.utcnow()
837
self.last_enabled = None
840
355
logger.debug("Creating client %r", self.name)
356
# Uppercase and remove spaces from fingerprint for later
357
# comparison purposes with return value from the fingerprint()
359
self.fingerprint = (config["fingerprint"].upper()
841
361
logger.debug(" Fingerprint: %s", self.fingerprint)
842
self.created = settings.get("created",
843
datetime.datetime.utcnow())
845
# attributes specific for this server instance
362
if "secret" in config:
363
self.secret = config["secret"].decode("base64")
364
elif "secfile" in config:
365
with open(os.path.expanduser(os.path.expandvars
366
(config["secfile"])),
368
self.secret = secfile.read()
370
raise TypeError("No secret or secfile for client %s"
372
self.host = config.get("host", "")
373
self.created = datetime.datetime.utcnow()
375
self.last_approval_request = None
376
self.last_enabled = datetime.datetime.utcnow()
377
self.last_checked_ok = None
378
self.last_checker_status = None
379
self.timeout = string_to_delta(config["timeout"])
380
self.extended_timeout = string_to_delta(config
381
["extended_timeout"])
382
self.interval = string_to_delta(config["interval"])
846
383
self.checker = None
847
384
self.checker_initiator_tag = None
848
385
self.disable_initiator_tag = None
386
self.expires = datetime.datetime.utcnow() + self.timeout
849
387
self.checker_callback_tag = None
388
self.checker_command = config["checker"]
850
389
self.current_checker_command = None
390
self._approved = None
391
self.approved_by_default = config.get("approved_by_default",
852
393
self.approvals_pending = 0
853
self.changedstate = multiprocessing_manager.Condition(
854
multiprocessing_manager.Lock())
855
self.client_structure = [attr
856
for attr in self.__dict__.iterkeys()
857
if not attr.startswith("_")]
394
self.approval_delay = string_to_delta(
395
config["approval_delay"])
396
self.approval_duration = string_to_delta(
397
config["approval_duration"])
398
self.changedstate = (multiprocessing_manager
399
.Condition(multiprocessing_manager
401
self.client_structure = [attr for attr in self.__dict__.iterkeys() if not attr.startswith("_")]
858
402
self.client_structure.append("client_structure")
860
for name, t in inspect.getmembers(
861
type(self), lambda obj: isinstance(obj, property)):
405
for name, t in inspect.getmembers(type(self),
406
lambda obj: isinstance(obj, property)):
862
407
if not name.startswith("_"):
863
408
self.client_structure.append(name)
872
417
if getattr(self, "enabled", False):
873
418
# Already enabled
420
self.send_changedstate()
875
421
self.expires = datetime.datetime.utcnow() + self.timeout
876
422
self.enabled = True
877
423
self.last_enabled = datetime.datetime.utcnow()
878
424
self.init_checker()
879
self.send_changedstate()
881
426
def disable(self, quiet=True):
882
427
"""Disable this client."""
883
428
if not getattr(self, "enabled", False):
431
self.send_changedstate()
886
433
logger.info("Disabling client %s", self.name)
887
if getattr(self, "disable_initiator_tag", None) is not None:
888
GObject.source_remove(self.disable_initiator_tag)
434
if getattr(self, "disable_initiator_tag", False):
435
gobject.source_remove(self.disable_initiator_tag)
889
436
self.disable_initiator_tag = None
890
437
self.expires = None
891
if getattr(self, "checker_initiator_tag", None) is not None:
892
GObject.source_remove(self.checker_initiator_tag)
438
if getattr(self, "checker_initiator_tag", False):
439
gobject.source_remove(self.checker_initiator_tag)
893
440
self.checker_initiator_tag = None
894
441
self.stop_checker()
895
442
self.enabled = False
897
self.send_changedstate()
898
# Do not run this again if called by a GObject.timeout_add
443
# Do not run this again if called by a gobject.timeout_add
901
446
def __del__(self):
904
449
def init_checker(self):
905
450
# Schedule a new checker to be started an 'interval' from now,
906
451
# and every interval from then on.
907
if self.checker_initiator_tag is not None:
908
GObject.source_remove(self.checker_initiator_tag)
909
self.checker_initiator_tag = GObject.timeout_add(
910
int(self.interval.total_seconds() * 1000),
452
self.checker_initiator_tag = (gobject.timeout_add
453
(self.interval_milliseconds(),
912
455
# Schedule a disable() when 'timeout' has passed
913
if self.disable_initiator_tag is not None:
914
GObject.source_remove(self.disable_initiator_tag)
915
self.disable_initiator_tag = GObject.timeout_add(
916
int(self.timeout.total_seconds() * 1000), self.disable)
456
self.disable_initiator_tag = (gobject.timeout_add
457
(self.timeout_milliseconds(),
917
459
# Also start a new checker *right now*.
918
460
self.start_checker()
920
def checker_callback(self, source, condition, connection,
463
def checker_callback(self, pid, condition, command):
922
464
"""The checker has completed, so take appropriate actions."""
923
465
self.checker_callback_tag = None
924
466
self.checker = None
925
# Read return code from connection (see call_pipe)
926
returncode = connection.recv()
930
self.last_checker_status = returncode
931
self.last_checker_signal = None
467
if os.WIFEXITED(condition):
468
self.last_checker_status = os.WEXITSTATUS(condition)
932
469
if self.last_checker_status == 0:
933
470
logger.info("Checker for %(name)s succeeded",
935
472
self.checked_ok()
937
logger.info("Checker for %(name)s failed", vars(self))
474
logger.info("Checker for %(name)s failed",
939
477
self.last_checker_status = -1
940
self.last_checker_signal = -returncode
941
478
logger.warning("Checker for %(name)s crashed?",
945
def checked_ok(self):
946
"""Assert that the client has been seen, alive and well."""
947
self.last_checked_ok = datetime.datetime.utcnow()
948
self.last_checker_status = 0
949
self.last_checker_signal = None
952
def bump_timeout(self, timeout=None):
953
"""Bump up the timeout for this client."""
481
def checked_ok(self, timeout=None):
482
"""Bump up the timeout for this client.
484
This should only be called when the client has been seen,
954
487
if timeout is None:
955
488
timeout = self.timeout
489
self.last_checked_ok = datetime.datetime.utcnow()
956
490
if self.disable_initiator_tag is not None:
957
GObject.source_remove(self.disable_initiator_tag)
958
self.disable_initiator_tag = None
491
gobject.source_remove(self.disable_initiator_tag)
959
492
if getattr(self, "enabled", False):
960
self.disable_initiator_tag = GObject.timeout_add(
961
int(timeout.total_seconds() * 1000), self.disable)
493
self.disable_initiator_tag = (gobject.timeout_add
494
(_timedelta_to_milliseconds
495
(timeout), self.disable))
962
496
self.expires = datetime.datetime.utcnow() + timeout
964
498
def need_approval(self):
970
504
If a checker already exists, leave it running and do
972
506
# The reason for not killing a running checker is that if we
973
# did that, and if a checker (for some reason) started running
974
# slowly and taking more than 'interval' time, then the client
975
# would inevitably timeout, since no checker would get a
976
# chance to run to completion. If we instead leave running
507
# did that, then if a checker (for some reason) started
508
# running slowly and taking more than 'interval' time, the
509
# client would inevitably timeout, since no checker would get
510
# a chance to run to completion. If we instead leave running
977
511
# checkers alone, the checker would have to take more time
978
512
# than 'timeout' for the client to be disabled, which is as it
981
if self.checker is not None and not self.checker.is_alive():
982
logger.warning("Checker was not alive; joining")
515
# If a checker exists, make sure it is not a zombie
517
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
518
except (AttributeError, OSError) as error:
519
if (isinstance(error, OSError)
520
and error.errno != errno.ECHILD):
524
logger.warning("Checker was a zombie")
525
gobject.source_remove(self.checker_callback_tag)
526
self.checker_callback(pid, status,
527
self.current_checker_command)
985
528
# Start a new checker if needed
986
529
if self.checker is None:
987
# Escape attributes for the shell
989
attr: re.escape(str(getattr(self, attr)))
990
for attr in self.runtime_expansions }
992
command = self.checker_command % escaped_attrs
993
except TypeError as error:
994
logger.error('Could not format string "%s"',
995
self.checker_command,
997
return True # Try again later
531
# In case checker_command has exactly one % operator
532
command = self.checker_command % self.host
534
# Escape attributes for the shell
535
escaped_attrs = dict(
537
re.escape(unicode(str(getattr(self, attr, "")),
541
self.runtime_expansions)
544
command = self.checker_command % escaped_attrs
545
except TypeError as error:
546
logger.error('Could not format string "%s":'
547
' %s', self.checker_command, error)
548
return True # Try again later
998
549
self.current_checker_command = command
999
logger.info("Starting checker %r for %s", command,
1001
# We don't need to redirect stdout and stderr, since
1002
# in normal mode, that is already done by daemon(),
1003
# and in debug mode we don't want to. (Stdin is
1004
# always replaced by /dev/null.)
1005
# The exception is when not debugging but nevertheless
1006
# running in the foreground; use the previously
1008
popen_args = { "close_fds": True,
1011
if (not self.server_settings["debug"]
1012
and self.server_settings["foreground"]):
1013
popen_args.update({"stdout": wnull,
1015
pipe = multiprocessing.Pipe(duplex = False)
1016
self.checker = multiprocessing.Process(
1018
args = (pipe[1], subprocess.call, command),
1019
kwargs = popen_args)
1020
self.checker.start()
1021
self.checker_callback_tag = GObject.io_add_watch(
1022
pipe[0].fileno(), GObject.IO_IN,
1023
self.checker_callback, pipe[0], command)
1024
# Re-run this periodically if run by GObject.timeout_add
551
logger.info("Starting checker %r for %s",
553
# We don't need to redirect stdout and stderr, since
554
# in normal mode, that is already done by daemon(),
555
# and in debug mode we don't want to. (Stdin is
556
# always replaced by /dev/null.)
557
self.checker = subprocess.Popen(command,
560
self.checker_callback_tag = (gobject.child_watch_add
562
self.checker_callback,
564
# The checker may have completed before the gobject
565
# watch was added. Check for this.
566
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
568
gobject.source_remove(self.checker_callback_tag)
569
self.checker_callback(pid, status, command)
570
except OSError as error:
571
logger.error("Failed to start subprocess: %s",
573
# Re-run this periodically if run by gobject.timeout_add
1027
576
def stop_checker(self):
1028
577
"""Force the checker process, if any, to stop."""
1029
578
if self.checker_callback_tag:
1030
GObject.source_remove(self.checker_callback_tag)
579
gobject.source_remove(self.checker_callback_tag)
1031
580
self.checker_callback_tag = None
1032
581
if getattr(self, "checker", None) is None:
1034
583
logger.debug("Stopping checker for %(name)s", vars(self))
1035
self.checker.terminate()
585
os.kill(self.checker.pid, signal.SIGTERM)
587
#if self.checker.poll() is None:
588
# os.kill(self.checker.pid, signal.SIGKILL)
589
except OSError as error:
590
if error.errno != errno.ESRCH: # No such process
1036
592
self.checker = None
1039
def dbus_service_property(dbus_interface,
594
# Encrypts a client secret and stores it in a varible encrypted_secret
595
def encrypt_secret(self, key):
596
# Encryption-key need to be of a specific size, so we hash inputed key
597
hasheng = hashlib.sha256()
599
encryptionkey = hasheng.digest()
601
# Create validation hash so we know at decryption if it was sucessful
602
hasheng = hashlib.sha256()
603
hasheng.update(self.secret)
604
validationhash = hasheng.digest()
607
iv = os.urandom(Crypto.Cipher.AES.block_size)
608
ciphereng = Crypto.Cipher.AES.new(encryptionkey,
609
Crypto.Cipher.AES.MODE_CFB, iv)
610
ciphertext = ciphereng.encrypt(validationhash+self.secret)
611
self.encrypted_secret = (ciphertext, iv)
613
# Decrypt a encrypted client secret
614
def decrypt_secret(self, key):
615
# Decryption-key need to be of a specific size, so we hash inputed key
616
hasheng = hashlib.sha256()
618
encryptionkey = hasheng.digest()
620
# Decrypt encrypted secret
621
ciphertext, iv = self.encrypted_secret
622
ciphereng = Crypto.Cipher.AES.new(encryptionkey,
623
Crypto.Cipher.AES.MODE_CFB, iv)
624
plain = ciphereng.decrypt(ciphertext)
626
# Validate decrypted secret to know if it was succesful
627
hasheng = hashlib.sha256()
628
validationhash = plain[:hasheng.digest_size]
629
secret = plain[hasheng.digest_size:]
630
hasheng.update(secret)
632
# if validation fails, we use key as new secret. Otherwhise, we use
633
# the decrypted secret
634
if hasheng.digest() == validationhash:
638
del self.encrypted_secret
641
def dbus_service_property(dbus_interface, signature="v",
642
access="readwrite", byte_arrays=False):
1043
643
"""Decorators for marking methods of a DBusObjectWithProperties to
1044
644
become properties on the D-Bus.
1384
821
except (AttributeError, xml.dom.DOMException,
1385
822
xml.parsers.expat.ExpatError) as error:
1386
823
logger.error("Failed to override Introspection method",
1391
dbus.OBJECT_MANAGER_IFACE
1392
except AttributeError:
1393
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1395
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1396
"""A D-Bus object with an ObjectManager.
1398
Classes inheriting from this exposes the standard
1399
GetManagedObjects call and the InterfacesAdded and
1400
InterfacesRemoved signals on the standard
1401
"org.freedesktop.DBus.ObjectManager" interface.
1403
Note: No signals are sent automatically; they must be sent
1406
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1407
out_signature = "a{oa{sa{sv}}}")
1408
def GetManagedObjects(self):
1409
"""This function must be overridden"""
1410
raise NotImplementedError()
1412
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1413
signature = "oa{sa{sv}}")
1414
def InterfacesAdded(self, object_path, interfaces_and_properties):
1417
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
1418
def InterfacesRemoved(self, object_path, interfaces):
1421
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1422
out_signature = "s",
1423
path_keyword = 'object_path',
1424
connection_keyword = 'connection')
1425
def Introspect(self, object_path, connection):
1426
"""Overloading of standard D-Bus method.
1428
Override return argument name of GetManagedObjects to be
1429
"objpath_interfaces_and_properties"
1431
xmlstring = DBusObjectWithAnnotations.Introspect(self,
1435
document = xml.dom.minidom.parseString(xmlstring)
1437
for if_tag in document.getElementsByTagName("interface"):
1438
# Fix argument name for the GetManagedObjects method
1439
if (if_tag.getAttribute("name")
1440
== dbus.OBJECT_MANAGER_IFACE):
1441
for cn in if_tag.getElementsByTagName("method"):
1442
if (cn.getAttribute("name")
1443
== "GetManagedObjects"):
1444
for arg in cn.getElementsByTagName("arg"):
1445
if (arg.getAttribute("direction")
1449
"objpath_interfaces"
1451
xmlstring = document.toxml("utf-8")
1453
except (AttributeError, xml.dom.DOMException,
1454
xml.parsers.expat.ExpatError) as error:
1455
logger.error("Failed to override Introspection method",
1459
def datetime_to_dbus(dt, variant_level=0):
828
def datetime_to_dbus (dt, variant_level=0):
1460
829
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1462
831
return dbus.String("", variant_level = variant_level)
1463
return dbus.String(dt.isoformat(), variant_level=variant_level)
1466
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1467
"""A class decorator; applied to a subclass of
1468
dbus.service.Object, it will add alternate D-Bus attributes with
1469
interface names according to the "alt_interface_names" mapping.
1472
@alternate_dbus_interfaces({"org.example.Interface":
1473
"net.example.AlternateInterface"})
1474
class SampleDBusObject(dbus.service.Object):
1475
@dbus.service.method("org.example.Interface")
1476
def SampleDBusMethod():
1479
The above "SampleDBusMethod" on "SampleDBusObject" will be
1480
reachable via two interfaces: "org.example.Interface" and
1481
"net.example.AlternateInterface", the latter of which will have
1482
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1483
"true", unless "deprecate" is passed with a False value.
1485
This works for methods and signals, and also for D-Bus properties
1486
(from DBusObjectWithProperties) and interfaces (from the
1487
dbus_interface_annotations decorator).
832
return dbus.String(dt.isoformat(),
833
variant_level=variant_level)
835
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
837
"""Applied to an empty subclass of a D-Bus object, this metaclass
838
will add additional D-Bus attributes matching a certain pattern.
1491
for orig_interface_name, alt_interface_name in (
1492
alt_interface_names.items()):
1494
interface_names = set()
1495
# Go though all attributes of the class
1496
for attrname, attribute in inspect.getmembers(cls):
840
def __new__(mcs, name, bases, attr):
841
# Go through all the base classes which could have D-Bus
842
# methods, signals, or properties in them
843
for base in (b for b in bases
844
if issubclass(b, dbus.service.Object)):
845
# Go though all attributes of the base class
846
for attrname, attribute in inspect.getmembers(base):
1497
847
# Ignore non-D-Bus attributes, and D-Bus attributes
1498
848
# with the wrong interface name
1499
849
if (not hasattr(attribute, "_dbus_interface")
1500
or not attribute._dbus_interface.startswith(
1501
orig_interface_name)):
850
or not attribute._dbus_interface
851
.startswith("se.recompile.Mandos")):
1503
853
# Create an alternate D-Bus interface name based on
1504
854
# the current name
1505
alt_interface = attribute._dbus_interface.replace(
1506
orig_interface_name, alt_interface_name)
1507
interface_names.add(alt_interface)
855
alt_interface = (attribute._dbus_interface
856
.replace("se.recompile.Mandos",
857
"se.bsnet.fukt.Mandos"))
1508
858
# Is this a D-Bus signal?
1509
859
if getattr(attribute, "_dbus_is_signal", False):
1510
if sys.version_info.major == 2:
1511
# Extract the original non-method undecorated
1512
# function by black magic
1513
nonmethod_func = (dict(
860
# Extract the original non-method function by
862
nonmethod_func = (dict(
1514
863
zip(attribute.func_code.co_freevars,
1515
attribute.__closure__))
1516
["func"].cell_contents)
1518
nonmethod_func = attribute
864
attribute.__closure__))["func"]
1519
866
# Create a new, but exactly alike, function
1520
867
# object, and decorate it to be a new D-Bus signal
1521
868
# 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__)
1536
new_function = (dbus.service.signal(
1538
attribute._dbus_signature)(new_function))
1539
# Copy annotations, if any
1541
new_function._dbus_annotations = dict(
1542
attribute._dbus_annotations)
1543
except AttributeError:
869
new_function = (dbus.service.signal
871
attribute._dbus_signature)
873
nonmethod_func.func_code,
874
nonmethod_func.func_globals,
875
nonmethod_func.func_name,
876
nonmethod_func.func_defaults,
877
nonmethod_func.func_closure)))
1545
878
# Define a creator of a function to call both the
1546
# original and alternate functions, so both the
1547
# original and alternate signals gets sent when
1548
# the function is called
879
# old and new functions, so both the old and new
880
# signals gets sent when the function is called
1549
881
def fixscope(func1, func2):
1550
882
"""This function is a scope container to pass
1551
883
func1 and func2 to the "call_both" function
1552
884
outside of its arguments"""
1554
@functools.wraps(func2)
1555
885
def call_both(*args, **kwargs):
1556
886
"""This function will emit two D-Bus
1557
887
signals by calling func1 and func2"""
1558
888
func1(*args, **kwargs)
1559
889
func2(*args, **kwargs)
1560
# Make wrapper function look like a D-Bus signal
1561
for name, attr in inspect.getmembers(func2):
1562
if name.startswith("_dbus_"):
1563
setattr(call_both, name, attr)
1565
890
return call_both
1566
891
# Create the "call_both" function and add it to
1568
attr[attrname] = fixscope(attribute, new_function)
893
attr[attrname] = fixscope(attribute,
1569
895
# Is this a D-Bus method?
1570
896
elif getattr(attribute, "_dbus_is_method", False):
1571
897
# Create a new, but exactly alike, function
1572
898
# object. Decorate it to be a new D-Bus method
1573
899
# with the alternate D-Bus interface name. Add it
1576
dbus.service.method(
1578
attribute._dbus_in_signature,
1579
attribute._dbus_out_signature)
1580
(types.FunctionType(attribute.func_code,
1581
attribute.func_globals,
1582
attribute.func_name,
1583
attribute.func_defaults,
1584
attribute.func_closure)))
1585
# Copy annotations, if any
1587
attr[attrname]._dbus_annotations = dict(
1588
attribute._dbus_annotations)
1589
except AttributeError:
901
attr[attrname] = (dbus.service.method
903
attribute._dbus_in_signature,
904
attribute._dbus_out_signature)
906
(attribute.func_code,
907
attribute.func_globals,
909
attribute.func_defaults,
910
attribute.func_closure)))
1591
911
# Is this a D-Bus property?
1592
912
elif getattr(attribute, "_dbus_is_property", False):
1593
913
# Create a new, but exactly alike, function
1594
914
# object, and decorate it to be a new D-Bus
1595
915
# property with the alternate D-Bus interface
1596
916
# name. Add it to the class.
1597
attr[attrname] = (dbus_service_property(
1598
alt_interface, attribute._dbus_signature,
1599
attribute._dbus_access,
1600
attribute._dbus_get_args_options
1602
(types.FunctionType(
1603
attribute.func_code,
1604
attribute.func_globals,
1605
attribute.func_name,
1606
attribute.func_defaults,
1607
attribute.func_closure)))
1608
# Copy annotations, if any
1610
attr[attrname]._dbus_annotations = dict(
1611
attribute._dbus_annotations)
1612
except AttributeError:
1614
# Is this a D-Bus interface?
1615
elif getattr(attribute, "_dbus_is_interface", False):
1616
# Create a new, but exactly alike, function
1617
# object. Decorate it to be a new D-Bus interface
1618
# with the alternate D-Bus interface name. Add it
1621
dbus_interface_annotations(alt_interface)
1622
(types.FunctionType(attribute.func_code,
1623
attribute.func_globals,
1624
attribute.func_name,
1625
attribute.func_defaults,
1626
attribute.func_closure)))
1628
# Deprecate all alternate interfaces
1629
iname="_AlternateDBusNames_interface_annotation{}"
1630
for interface_name in interface_names:
1632
@dbus_interface_annotations(interface_name)
1634
return { "org.freedesktop.DBus.Deprecated":
1636
# Find an unused name
1637
for aname in (iname.format(i)
1638
for i in itertools.count()):
1639
if aname not in attr:
1643
# Replace the class with a new subclass of it with
1644
# methods, signals, etc. as created above.
1645
cls = type(b"{}Alternate".format(cls.__name__),
1652
@alternate_dbus_interfaces({"se.recompile.Mandos":
1653
"se.bsnet.fukt.Mandos"})
917
attr[attrname] = (dbus_service_property
919
attribute._dbus_signature,
920
attribute._dbus_access,
922
._dbus_get_args_options
925
(attribute.func_code,
926
attribute.func_globals,
928
attribute.func_defaults,
929
attribute.func_closure)))
930
return type.__new__(mcs, name, bases, attr)
1654
932
class ClientDBus(Client, DBusObjectWithProperties):
1655
933
"""A Client class using D-Bus
2005
1248
return datetime_to_dbus(self.last_approval_request)
2007
1250
# Timeout - property
2008
@dbus_service_property(_interface,
1251
@dbus_service_property(_interface, signature="t",
2010
1252
access="readwrite")
2011
1253
def Timeout_dbus_property(self, value=None):
2012
1254
if value is None: # get
2013
return dbus.UInt64(self.timeout.total_seconds() * 1000)
2014
old_timeout = self.timeout
1255
return dbus.UInt64(self.timeout_milliseconds())
2015
1256
self.timeout = datetime.timedelta(0, 0, 0, value)
2016
# Reschedule disabling
2018
now = datetime.datetime.utcnow()
2019
self.expires += self.timeout - old_timeout
2020
if self.expires <= now:
2021
# The timeout has passed
2024
if (getattr(self, "disable_initiator_tag", None)
2027
GObject.source_remove(self.disable_initiator_tag)
2028
self.disable_initiator_tag = GObject.timeout_add(
2029
int((self.expires - now).total_seconds() * 1000),
1257
if getattr(self, "disable_initiator_tag", None) is None:
1259
# Reschedule timeout
1260
gobject.source_remove(self.disable_initiator_tag)
1261
self.disable_initiator_tag = None
1263
time_to_die = _timedelta_to_milliseconds((self
1268
if time_to_die <= 0:
1269
# The timeout has passed
1272
self.expires = (datetime.datetime.utcnow()
1273
+ datetime.timedelta(milliseconds =
1275
self.disable_initiator_tag = (gobject.timeout_add
1276
(time_to_die, self.disable))
2032
1278
# ExtendedTimeout - property
2033
@dbus_service_property(_interface,
1279
@dbus_service_property(_interface, signature="t",
2035
1280
access="readwrite")
2036
1281
def ExtendedTimeout_dbus_property(self, value=None):
2037
1282
if value is None: # get
2038
return dbus.UInt64(self.extended_timeout.total_seconds()
1283
return dbus.UInt64(self.extended_timeout_milliseconds())
2040
1284
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
2042
1286
# Interval - property
2043
@dbus_service_property(_interface,
1287
@dbus_service_property(_interface, signature="t",
2045
1288
access="readwrite")
2046
1289
def Interval_dbus_property(self, value=None):
2047
1290
if value is None: # get
2048
return dbus.UInt64(self.interval.total_seconds() * 1000)
1291
return dbus.UInt64(self.interval_milliseconds())
2049
1292
self.interval = datetime.timedelta(0, 0, 0, value)
2050
1293
if getattr(self, "checker_initiator_tag", None) is None:
2053
# Reschedule checker run
2054
GObject.source_remove(self.checker_initiator_tag)
2055
self.checker_initiator_tag = GObject.timeout_add(
2056
value, self.start_checker)
2057
self.start_checker() # Start one now, too
1295
# Reschedule checker run
1296
gobject.source_remove(self.checker_initiator_tag)
1297
self.checker_initiator_tag = (gobject.timeout_add
1298
(value, self.start_checker))
1299
self.start_checker() # Start one now, too
2059
1301
# Checker - property
2060
@dbus_service_property(_interface,
1302
@dbus_service_property(_interface, signature="s",
2062
1303
access="readwrite")
2063
1304
def Checker_dbus_property(self, value=None):
2064
1305
if value is None: # get
2065
1306
return dbus.String(self.checker_command)
2066
self.checker_command = str(value)
1307
self.checker_command = value
2068
1309
# CheckerRunning - property
2069
@dbus_service_property(_interface,
1310
@dbus_service_property(_interface, signature="b",
2071
1311
access="readwrite")
2072
1312
def CheckerRunning_dbus_property(self, value=None):
2073
1313
if value is None: # get
2288
1539
def fingerprint(openpgp):
2289
1540
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
2290
1541
# New GnuTLS "datum" with the OpenPGP public key
2291
datum = gnutls.datum_t(
2292
ctypes.cast(ctypes.c_char_p(openpgp),
2293
ctypes.POINTER(ctypes.c_ubyte)),
2294
ctypes.c_uint(len(openpgp)))
1542
datum = (gnutls.library.types
1543
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1546
ctypes.c_uint(len(openpgp))))
2295
1547
# New empty GnuTLS certificate
2296
crt = gnutls.openpgp_crt_t()
2297
gnutls.openpgp_crt_init(ctypes.byref(crt))
1548
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1549
(gnutls.library.functions
1550
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
2298
1551
# Import the OpenPGP public key into the certificate
2299
gnutls.openpgp_crt_import(crt, ctypes.byref(datum),
2300
gnutls.OPENPGP_FMT_RAW)
1552
(gnutls.library.functions
1553
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1554
gnutls.library.constants
1555
.GNUTLS_OPENPGP_FMT_RAW))
2301
1556
# Verify the self signature in the key
2302
1557
crtverify = ctypes.c_uint()
2303
gnutls.openpgp_crt_verify_self(crt, 0,
2304
ctypes.byref(crtverify))
1558
(gnutls.library.functions
1559
.gnutls_openpgp_crt_verify_self(crt, 0,
1560
ctypes.byref(crtverify)))
2305
1561
if crtverify.value != 0:
2306
gnutls.openpgp_crt_deinit(crt)
2307
raise gnutls.CertificateSecurityError("Verify failed")
1562
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1563
raise (gnutls.errors.CertificateSecurityError
2308
1565
# New buffer for the fingerprint
2309
1566
buf = ctypes.create_string_buffer(20)
2310
1567
buf_len = ctypes.c_size_t()
2311
1568
# Get the fingerprint from the certificate into the buffer
2312
gnutls.openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
2313
ctypes.byref(buf_len))
1569
(gnutls.library.functions
1570
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1571
ctypes.byref(buf_len)))
2314
1572
# Deinit the certificate
2315
gnutls.openpgp_crt_deinit(crt)
1573
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
2316
1574
# Convert the buffer to a Python bytestring
2317
1575
fpr = ctypes.string_at(buf, buf_len.value)
2318
1576
# Convert the bytestring to hexadecimal notation
2319
hex_fpr = binascii.hexlify(fpr).upper()
1577
hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
2323
1581
class MultiprocessingMixIn(object):
2324
1582
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
2326
1583
def sub_process_main(self, request, address):
2328
1585
self.finish_request(request, address)
2330
1587
self.handle_error(request, address)
2331
1588
self.close_request(request)
2333
1590
def process_request(self, request, address):
2334
1591
"""Start a new process to process the request."""
2335
1592
proc = multiprocessing.Process(target = self.sub_process_main,
2336
args = (request, address))
2341
1599
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
2342
1600
""" adds a pipe to the MixIn """
2344
1601
def process_request(self, request, client_address):
2345
1602
"""Overrides and wraps the original process_request().
2572
def rfc3339_duration_to_delta(duration):
2573
"""Parse an RFC 3339 "duration" and return a datetime.timedelta
2575
>>> rfc3339_duration_to_delta("P7D")
2576
datetime.timedelta(7)
2577
>>> rfc3339_duration_to_delta("PT60S")
2578
datetime.timedelta(0, 60)
2579
>>> rfc3339_duration_to_delta("PT60M")
2580
datetime.timedelta(0, 3600)
2581
>>> rfc3339_duration_to_delta("PT24H")
2582
datetime.timedelta(1)
2583
>>> rfc3339_duration_to_delta("P1W")
2584
datetime.timedelta(7)
2585
>>> rfc3339_duration_to_delta("PT5M30S")
2586
datetime.timedelta(0, 330)
2587
>>> rfc3339_duration_to_delta("P1DT3M20S")
2588
datetime.timedelta(1, 200)
2591
# Parsing an RFC 3339 duration with regular expressions is not
2592
# possible - there would have to be multiple places for the same
2593
# values, like seconds. The current code, while more esoteric, is
2594
# cleaner without depending on a parsing library. If Python had a
2595
# built-in library for parsing we would use it, but we'd like to
2596
# avoid excessive use of external libraries.
2598
# New type for defining tokens, syntax, and semantics all-in-one
2599
Token = collections.namedtuple("Token", (
2600
"regexp", # To match token; if "value" is not None, must have
2601
# a "group" containing digits
2602
"value", # datetime.timedelta or None
2603
"followers")) # Tokens valid after this token
2604
# RFC 3339 "duration" tokens, syntax, and semantics; taken from
2605
# the "duration" ABNF definition in RFC 3339, Appendix A.
2606
token_end = Token(re.compile(r"$"), None, frozenset())
2607
token_second = Token(re.compile(r"(\d+)S"),
2608
datetime.timedelta(seconds=1),
2609
frozenset((token_end, )))
2610
token_minute = Token(re.compile(r"(\d+)M"),
2611
datetime.timedelta(minutes=1),
2612
frozenset((token_second, token_end)))
2613
token_hour = Token(re.compile(r"(\d+)H"),
2614
datetime.timedelta(hours=1),
2615
frozenset((token_minute, token_end)))
2616
token_time = Token(re.compile(r"T"),
2618
frozenset((token_hour, token_minute,
2620
token_day = Token(re.compile(r"(\d+)D"),
2621
datetime.timedelta(days=1),
2622
frozenset((token_time, token_end)))
2623
token_month = Token(re.compile(r"(\d+)M"),
2624
datetime.timedelta(weeks=4),
2625
frozenset((token_day, token_end)))
2626
token_year = Token(re.compile(r"(\d+)Y"),
2627
datetime.timedelta(weeks=52),
2628
frozenset((token_month, token_end)))
2629
token_week = Token(re.compile(r"(\d+)W"),
2630
datetime.timedelta(weeks=1),
2631
frozenset((token_end, )))
2632
token_duration = Token(re.compile(r"P"), None,
2633
frozenset((token_year, token_month,
2634
token_day, token_time,
2636
# Define starting values
2637
value = datetime.timedelta() # Value so far
2639
followers = frozenset((token_duration, )) # Following valid tokens
2640
s = duration # String left to parse
2641
# Loop until end token is found
2642
while found_token is not token_end:
2643
# Search for any currently valid tokens
2644
for token in followers:
2645
match = token.regexp.match(s)
2646
if match is not None:
2648
if token.value is not None:
2649
# Value found, parse digits
2650
factor = int(match.group(1), 10)
2651
# Add to value so far
2652
value += factor * token.value
2653
# Strip token from string
2654
s = token.regexp.sub("", s, 1)
2657
# Set valid next tokens
2658
followers = found_token.followers
2661
# No currently valid tokens were found
2662
raise ValueError("Invalid RFC 3339 duration: {!r}"
2668
1801
def string_to_delta(interval):
2669
1802
"""Parse a string and return a datetime.timedelta
2799
1939
"debug": "False",
2801
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2802
":+SIGN-DSA-SHA256",
1941
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
2803
1942
"servicename": "Mandos",
2804
1943
"use_dbus": "True",
2805
1944
"use_ipv6": "True",
2806
1945
"debuglevel": "",
2809
"statedir": "/var/lib/mandos",
2810
"foreground": "False",
2814
1948
# Parse config file for server-global settings
2815
1949
server_config = configparser.SafeConfigParser(server_defaults)
2816
1950
del server_defaults
2817
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1951
server_config.read(os.path.join(options.configdir,
2818
1953
# Convert the SafeConfigParser object to a dict
2819
1954
server_settings = server_config.defaults()
2820
1955
# Use the appropriate methods on the non-string config options
2821
for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
1956
for option in ("debug", "use_dbus", "use_ipv6"):
2822
1957
server_settings[option] = server_config.getboolean("DEFAULT",
2824
1959
if server_settings["port"]:
2825
1960
server_settings["port"] = server_config.getint("DEFAULT",
2827
if server_settings["socket"]:
2828
server_settings["socket"] = server_config.getint("DEFAULT",
2830
# Later, stdin will, and stdout and stderr might, be dup'ed
2831
# over with an opened os.devnull. But we don't want this to
2832
# happen with a supplied network socket.
2833
if 0 <= server_settings["socket"] <= 2:
2834
server_settings["socket"] = os.dup(server_settings
2836
1962
del server_config
2838
1964
# Override the settings from the config file with command line
2839
1965
# options, if set.
2840
1966
for option in ("interface", "address", "port", "debug",
2841
"priority", "servicename", "configdir", "use_dbus",
2842
"use_ipv6", "debuglevel", "restore", "statedir",
2843
"socket", "foreground", "zeroconf"):
1967
"priority", "servicename", "configdir",
1968
"use_dbus", "use_ipv6", "debuglevel", "restore"):
2844
1969
value = getattr(options, option)
2845
1970
if value is not None:
2846
1971
server_settings[option] = value
2848
1973
# Force all strings to be unicode
2849
1974
for option in server_settings.keys():
2850
if isinstance(server_settings[option], bytes):
2851
server_settings[option] = (server_settings[option]
2853
# Force all boolean options to be boolean
2854
for option in ("debug", "use_dbus", "use_ipv6", "restore",
2855
"foreground", "zeroconf"):
2856
server_settings[option] = bool(server_settings[option])
2857
# Debug implies foreground
2858
if server_settings["debug"]:
2859
server_settings["foreground"] = True
1975
if type(server_settings[option]) is str:
1976
server_settings[option] = unicode(server_settings[option])
2860
1977
# Now we have our good server settings in "server_settings"
2862
1979
##################################################################
2864
if (not server_settings["zeroconf"]
2865
and not (server_settings["port"]
2866
or server_settings["socket"] != "")):
2867
parser.error("Needs port or socket to work without Zeroconf")
2869
1981
# For convenience
2870
1982
debug = server_settings["debug"]
2871
1983
debuglevel = server_settings["debuglevel"]
2872
1984
use_dbus = server_settings["use_dbus"]
2873
1985
use_ipv6 = server_settings["use_ipv6"]
2874
stored_state_path = os.path.join(server_settings["statedir"],
2876
foreground = server_settings["foreground"]
2877
zeroconf = server_settings["zeroconf"]
2880
initlogger(debug, logging.DEBUG)
2885
level = getattr(logging, debuglevel.upper())
2886
initlogger(debug, level)
2888
1987
if server_settings["servicename"] != "Mandos":
2889
syslogger.setFormatter(
2890
logging.Formatter('Mandos ({}) [%(process)d]:'
2891
' %(levelname)s: %(message)s'.format(
2892
server_settings["servicename"])))
1988
syslogger.setFormatter(logging.Formatter
1989
('Mandos (%s) [%%(process)d]:'
1990
' %%(levelname)s: %%(message)s'
1991
% server_settings["servicename"]))
2894
1993
# Parse config file with clients
2895
client_config = configparser.SafeConfigParser(Client
1994
client_defaults = { "timeout": "5m",
1995
"extended_timeout": "15m",
1997
"checker": "fping -q -- %%(host)s",
1999
"approval_delay": "0s",
2000
"approval_duration": "1s",
2002
client_config = configparser.SafeConfigParser(client_defaults)
2897
2003
client_config.read(os.path.join(server_settings["configdir"],
2898
2004
"clients.conf"))
2900
2006
global mandos_dbus_service
2901
2007
mandos_dbus_service = None
2904
if server_settings["socket"] != "":
2905
socketfd = server_settings["socket"]
2906
tcp_server = MandosServer(
2907
(server_settings["address"], server_settings["port"]),
2909
interface=(server_settings["interface"] or None),
2911
gnutls_priority=server_settings["priority"],
2915
pidfilename = "/run/mandos.pid"
2916
if not os.path.isdir("/run/."):
2917
pidfilename = "/var/run/mandos.pid"
2009
tcp_server = MandosServer((server_settings["address"],
2010
server_settings["port"]),
2012
interface=(server_settings["interface"]
2016
server_settings["priority"],
2019
pidfilename = "/var/run/mandos.pid"
2920
pidfile = codecs.open(pidfilename, "w", encoding="utf-8")
2921
except IOError as e:
2922
logger.error("Could not open file %r", pidfilename,
2021
pidfile = open(pidfilename, "w")
2023
logger.error("Could not open file %r", pidfilename)
2925
for name, group in (("_mandos", "_mandos"),
2926
("mandos", "mandos"),
2927
("nobody", "nogroup")):
2026
uid = pwd.getpwnam("_mandos").pw_uid
2027
gid = pwd.getpwnam("_mandos").pw_gid
2929
uid = pwd.getpwnam(name).pw_uid
2930
gid = pwd.getpwnam(group).pw_gid
2030
uid = pwd.getpwnam("mandos").pw_uid
2031
gid = pwd.getpwnam("mandos").pw_gid
2932
2032
except KeyError:
2034
uid = pwd.getpwnam("nobody").pw_uid
2035
gid = pwd.getpwnam("nobody").pw_gid
2941
logger.debug("Did setuid/setgid to {}:{}".format(uid,
2943
2042
except OSError as error:
2944
logger.warning("Failed to setuid/setgid to {}:{}: {}"
2945
.format(uid, gid, os.strerror(error.errno)))
2946
if error.errno != errno.EPERM:
2043
if error[0] != errno.EPERM:
2046
if not debug and not debuglevel:
2047
logger.setLevel(logging.WARNING)
2049
level = getattr(logging, debuglevel.upper())
2050
logger.setLevel(level)
2053
logger.setLevel(logging.DEBUG)
2950
2054
# Enable all possible GnuTLS debugging
2952
2056
# "Use a log level over 10 to enable all debugging options."
2953
2057
# - GnuTLS manual
2954
gnutls.global_set_log_level(11)
2058
gnutls.library.functions.gnutls_global_set_log_level(11)
2060
@gnutls.library.types.gnutls_log_func
2957
2061
def debug_gnutls(level, string):
2958
2062
logger.debug("GnuTLS: %s", string[:-1])
2960
gnutls.global_set_log_function(debug_gnutls)
2064
(gnutls.library.functions
2065
.gnutls_global_set_log_function(debug_gnutls))
2962
2067
# Redirect stdin so all checkers get /dev/null
2963
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2068
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2964
2069
os.dup2(null, sys.stdin.fileno())
2073
# No console logging
2074
logger.removeHandler(console)
2968
2076
# Need to fork before connecting to D-Bus
2970
2078
# Close all input and output, do double fork, etc.
2973
# multiprocessing will use threads, so before we use GObject we
2974
# need to inform GObject that threads will be used.
2975
GObject.threads_init()
2977
2081
global main_loop
2978
2082
# From the Avahi example code
2979
DBusGMainLoop(set_as_default=True)
2980
main_loop = GObject.MainLoop()
2083
DBusGMainLoop(set_as_default=True )
2084
main_loop = gobject.MainLoop()
2981
2085
bus = dbus.SystemBus()
2982
2086
# End of Avahi example code
2985
2089
bus_name = dbus.service.BusName("se.recompile.Mandos",
2988
old_bus_name = dbus.service.BusName(
2989
"se.bsnet.fukt.Mandos", bus,
2991
except dbus.exceptions.DBusException as e:
2992
logger.error("Disabling D-Bus:", exc_info=e)
2090
bus, do_not_queue=True)
2091
old_bus_name = (dbus.service.BusName
2092
("se.bsnet.fukt.Mandos", bus,
2094
except dbus.exceptions.NameExistsException as e:
2095
logger.error(unicode(e) + ", disabling D-Bus")
2993
2096
use_dbus = False
2994
2097
server_settings["use_dbus"] = False
2995
2098
tcp_server.use_dbus = False
2997
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2998
service = AvahiServiceToSyslog(
2999
name = server_settings["servicename"],
3000
servicetype = "_mandos._tcp",
3001
protocol = protocol,
3003
if server_settings["interface"]:
3004
service.interface = if_nametoindex(
3005
server_settings["interface"].encode("utf-8"))
2099
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2100
service = AvahiServiceToSyslog(name =
2101
server_settings["servicename"],
2102
servicetype = "_mandos._tcp",
2103
protocol = protocol, bus = bus)
2104
if server_settings["interface"]:
2105
service.interface = (if_nametoindex
2106
(str(server_settings["interface"])))
3007
2108
global multiprocessing_manager
3008
2109
multiprocessing_manager = multiprocessing.Manager()
3010
2111
client_class = Client
3012
client_class = functools.partial(ClientDBus, bus = bus)
3014
client_settings = Client.config_parser(client_config)
2113
client_class = functools.partial(ClientDBusTransitional,
2116
special_settings = {
2117
# Some settings need to be accessd by special methods;
2118
# booleans need .getboolean(), etc. Here is a list of them:
2119
"approved_by_default":
2121
client_config.getboolean(section, "approved_by_default"),
2123
# Construct a new dict of client settings of this form:
2124
# { client_name: {setting_name: value, ...}, ...}
2125
# with exceptions for any special settings as defined above
2126
client_settings = dict((clientname,
2128
(value if setting not in special_settings
2129
else special_settings[setting](clientname)))
2130
for setting, value in client_config.items(clientname)))
2131
for clientname in client_config.sections())
3015
2133
old_client_settings = {}
3018
# This is used to redirect stdout and stderr for checker processes
3020
wnull = open(os.devnull, "w") # A writable /dev/null
3021
# Only used if server is running in foreground but not in debug
3023
if debug or not foreground:
3026
# Get client data and settings from last running state.
2136
# Get client data and settings from last running state.
3027
2137
if server_settings["restore"]:
3029
2139
with open(stored_state_path, "rb") as stored_state:
3030
clients_data, old_client_settings = pickle.load(
2140
clients_data, old_client_settings = pickle.load(stored_state)
3032
2141
os.remove(stored_state_path)
3033
2142
except IOError as e:
3034
if e.errno == errno.ENOENT:
3035
logger.warning("Could not load persistent state:"
3036
" {}".format(os.strerror(e.errno)))
3038
logger.critical("Could not load persistent state:",
2143
logger.warning("Could not load persistant state: {0}".format(e))
2144
if e.errno != errno.ENOENT:
3041
except EOFError as e:
3042
logger.warning("Could not load persistent state: "
3046
with PGPEngine() as pgp:
3047
for client_name, client in clients_data.items():
3048
# Skip removed clients
3049
if client_name not in client_settings:
3052
# Decide which value to use after restoring saved state.
3053
# We have three different values: Old config file,
3054
# new config file, and saved state.
3055
# New config value takes precedence if it differs from old
3056
# config value, otherwise use saved state.
3057
for name, value in client_settings[client_name].items():
3059
# For each value in new config, check if it
3060
# differs from the old config value (Except for
3061
# the "secret" attribute)
3062
if (name != "secret"
3064
old_client_settings[client_name][name])):
3065
client[name] = value
3069
# Clients who has passed its expire date can still be
3070
# enabled if its last checker was successful. A Client
3071
# whose checker succeeded before we stored its state is
3072
# assumed to have successfully run all checkers during
3074
if client["enabled"]:
3075
if datetime.datetime.utcnow() >= client["expires"]:
3076
if not client["last_checked_ok"]:
3078
"disabling client {} - Client never "
3079
"performed a successful checker".format(
3081
client["enabled"] = False
3082
elif client["last_checker_status"] != 0:
3084
"disabling client {} - Client last"
3085
" checker failed with error code"
3088
client["last_checker_status"]))
3089
client["enabled"] = False
3091
client["expires"] = (
3092
datetime.datetime.utcnow()
3093
+ client["timeout"])
3094
logger.debug("Last checker succeeded,"
3095
" keeping {} enabled".format(
2147
for client in clients_data:
2148
client_name = client["name"]
2150
# Decide which value to use after restoring saved state.
2151
# We have three different values: Old config file,
2152
# new config file, and saved state.
2153
# New config value takes precedence if it differs from old
2154
# config value, otherwise use saved state.
2155
for name, value in client_settings[client_name].items():
3098
client["secret"] = pgp.decrypt(
3099
client["encrypted_secret"],
3100
client_settings[client_name]["secret"])
3102
# If decryption fails, we use secret from new settings
3103
logger.debug("Failed to decrypt {} old secret".format(
3105
client["secret"] = (client_settings[client_name]
3108
# Add/remove clients based on new changes made to config
3109
for client_name in (set(old_client_settings)
3110
- set(client_settings)):
3111
del clients_data[client_name]
3112
for client_name in (set(client_settings)
3113
- set(old_client_settings)):
3114
clients_data[client_name] = client_settings[client_name]
3116
# Create all client objects
3117
for client_name, client in clients_data.items():
3118
tcp_server.clients[client_name] = client_class(
3121
server_settings = server_settings)
2157
# For each value in new config, check if it differs
2158
# from the old config value (Except for the "secret"
2160
if name != "secret" and value != old_client_settings[client_name][name]:
2161
setattr(client, name, value)
2165
# Clients who has passed its expire date, can still be enabled if its
2166
# last checker was sucessful. Clients who checkers failed before we
2167
# stored it state is asumed to had failed checker during downtime.
2168
if client["enabled"] and client["last_checked_ok"]:
2169
if ((datetime.datetime.utcnow() - client["last_checked_ok"])
2170
> client["interval"]):
2171
if client["last_checker_status"] != 0:
2172
client["enabled"] = False
2174
client["expires"] = datetime.datetime.utcnow() + client["timeout"]
2176
client["changedstate"] = (multiprocessing_manager
2177
.Condition(multiprocessing_manager
2180
new_client = ClientDBusTransitional.__new__(ClientDBusTransitional)
2181
tcp_server.clients[client_name] = new_client
2182
new_client.bus = bus
2183
for name, value in client.iteritems():
2184
setattr(new_client, name, value)
2185
client_object_name = unicode(client_name).translate(
2186
{ord("."): ord("_"),
2187
ord("-"): ord("_")})
2188
new_client.dbus_object_path = (dbus.ObjectPath
2189
("/clients/" + client_object_name))
2190
DBusObjectWithProperties.__init__(new_client,
2192
new_client.dbus_object_path)
2194
tcp_server.clients[client_name] = Client.__new__(Client)
2195
for name, value in client.iteritems():
2196
setattr(tcp_server.clients[client_name], name, value)
2198
tcp_server.clients[client_name].decrypt_secret(
2199
client_settings[client_name]["secret"])
2201
# Create/remove clients based on new changes made to config
2202
for clientname in set(old_client_settings) - set(client_settings):
2203
del tcp_server.clients[clientname]
2204
for clientname in set(client_settings) - set(old_client_settings):
2205
tcp_server.clients[clientname] = (client_class(name = clientname,
3123
2211
if not tcp_server.clients:
3124
2212
logger.warning("No clients defined")
3127
if pidfile is not None:
3131
print(pid, file=pidfile)
3133
logger.error("Could not write to file %r with PID %d",
2218
pidfile.write(str(pid) + "\n".encode("utf-8"))
2221
logger.error("Could not write to file %r with PID %d",
2224
# "pidfile" was never created
3136
2226
del pidfilename
2228
signal.signal(signal.SIGINT, signal.SIG_IGN)
3138
2230
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
3139
2231
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
3143
@alternate_dbus_interfaces(
3144
{ "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
3145
class MandosDBusService(DBusObjectWithObjectManager):
2234
class MandosDBusService(dbus.service.Object):
3146
2235
"""A D-Bus proxy object"""
3148
2236
def __init__(self):
3149
2237
dbus.service.Object.__init__(self, bus, "/")
3151
2238
_interface = "se.recompile.Mandos"
3153
2240
@dbus.service.signal(_interface, signature="o")
3194
2275
if c.dbus_object_path == object_path:
3195
2276
del tcp_server.clients[c.name]
3196
2277
c.remove_from_connection()
3197
# Don't signal the disabling
2278
# Don't signal anything except ClientRemoved
3198
2279
c.disable(quiet=True)
3199
# Emit D-Bus signal for removal
3200
self.client_removed_signal(c)
2281
self.ClientRemoved(object_path, c.name)
3202
2283
raise KeyError(object_path)
3206
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
3207
out_signature = "a{oa{sa{sv}}}")
3208
def GetManagedObjects(self):
3210
return dbus.Dictionary(
3211
{ client.dbus_object_path:
3213
{ interface: client.GetAll(interface)
3215
client._get_all_interface_names()})
3216
for client in tcp_server.clients.values()})
3218
def client_added_signal(self, client):
3219
"""Send the new standard signal and the old signal"""
3221
# New standard signal
3222
self.InterfacesAdded(
3223
client.dbus_object_path,
3225
{ interface: client.GetAll(interface)
3227
client._get_all_interface_names()}))
3229
self.ClientAdded(client.dbus_object_path)
3231
def client_removed_signal(self, client):
3232
"""Send the new standard signal and the old signal"""
3234
# New standard signal
3235
self.InterfacesRemoved(
3236
client.dbus_object_path,
3237
client._get_all_interface_names())
3239
self.ClientRemoved(client.dbus_object_path,
3242
mandos_dbus_service = MandosDBusService()
2287
class MandosDBusServiceTransitional(MandosDBusService):
2288
__metaclass__ = AlternateDBusNamesMetaclass
2289
mandos_dbus_service = MandosDBusServiceTransitional()
3245
2292
"Cleanup function; run on exit"
3249
2295
multiprocessing.active_children()
3251
2296
if not (tcp_server.clients or client_settings):
3254
# Store client before exiting. Secrets are encrypted with key
3255
# based on what config file has. If config file is
3256
# removed/edited, old secret will thus be unrecovable.
3258
with PGPEngine() as pgp:
3259
for client in tcp_server.clients.itervalues():
3260
key = client_settings[client.name]["secret"]
3261
client.encrypted_secret = pgp.encrypt(client.secret,
3265
# A list of attributes that can not be pickled
3267
exclude = { "bus", "changedstate", "secret",
3268
"checker", "server_settings" }
3269
for name, typ in inspect.getmembers(dbus.service
3273
client_dict["encrypted_secret"] = (client
3275
for attr in client.client_structure:
3276
if attr not in exclude:
3277
client_dict[attr] = getattr(client, attr)
3279
clients[client.name] = client_dict
3280
del client_settings[client.name]["secret"]
2299
# Store client before exiting. Secrets are encrypted with key based
2300
# on what config file has. If config file is removed/edited, old
2301
# secret will thus be unrecovable.
2303
for client in tcp_server.clients.itervalues():
2304
client.encrypt_secret(client_settings[client.name]["secret"])
2308
# A list of attributes that will not be stored when shuting down.
2309
exclude = set(("bus", "changedstate", "secret"))
2310
for name, typ in inspect.getmembers(dbus.service.Object):
2313
client_dict["encrypted_secret"] = client.encrypted_secret
2314
for attr in client.client_structure:
2315
if attr not in exclude:
2316
client_dict[attr] = getattr(client, attr)
2318
clients.append(client_dict)
2319
del client_settings[client.name]["secret"]
3283
with tempfile.NamedTemporaryFile(
3287
dir=os.path.dirname(stored_state_path),
3288
delete=False) as stored_state:
2322
with os.fdopen(os.open(stored_state_path, os.O_CREAT|os.O_WRONLY|os.O_TRUNC, 0600), "wb") as stored_state:
3289
2323
pickle.dump((clients, client_settings), stored_state)
3290
tempname = stored_state.name
3291
os.rename(tempname, stored_state_path)
3292
except (IOError, OSError) as e:
3298
if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
3299
logger.warning("Could not save persistent state: {}"
3300
.format(os.strerror(e.errno)))
3302
logger.warning("Could not save persistent state:",
2324
except IOError as e:
2325
logger.warning("Could not save persistant state: {0}".format(e))
2326
if e.errno != errno.ENOENT:
3306
2329
# Delete all clients, and settings from config
3307
2330
while tcp_server.clients:
3308
2331
name, client = tcp_server.clients.popitem()
3310
2333
client.remove_from_connection()
3311
# Don't signal the disabling
2334
# Don't signal anything except ClientRemoved
3312
2335
client.disable(quiet=True)
3313
# Emit D-Bus signal for removal
3315
mandos_dbus_service.client_removed_signal(client)
2338
mandos_dbus_service.ClientRemoved(client
3316
2341
client_settings.clear()
3318
2343
atexit.register(cleanup)
3320
2345
for client in tcp_server.clients.itervalues():
3322
# Emit D-Bus signal for adding
3323
mandos_dbus_service.client_added_signal(client)
2348
mandos_dbus_service.ClientAdded(client.dbus_object_path)
3324
2349
# Need to initiate checking of clients
3325
2350
if client.enabled:
3326
2351
client.init_checker()
3328
2354
tcp_server.enable()
3329
2355
tcp_server.server_activate()
3331
2357
# Find out what port we got
3333
service.port = tcp_server.socket.getsockname()[1]
2358
service.port = tcp_server.socket.getsockname()[1]
3335
2360
logger.info("Now listening on address %r, port %d,"
3336
" flowinfo %d, scope_id %d",
3337
*tcp_server.socket.getsockname())
2361
" flowinfo %d, scope_id %d"
2362
% tcp_server.socket.getsockname())
3339
logger.info("Now listening on address %r, port %d",
3340
*tcp_server.socket.getsockname())
2364
logger.info("Now listening on address %r, port %d"
2365
% tcp_server.socket.getsockname())
3342
2367
#service.interface = tcp_server.socket.getsockname()[3]
3346
# From the Avahi example code
3349
except dbus.exceptions.DBusException as error:
3350
logger.critical("D-Bus Exception", exc_info=error)
3353
# End of Avahi example code
2370
# From the Avahi example code
2373
except dbus.exceptions.DBusException as error:
2374
logger.critical("DBusException: %s", error)
2377
# End of Avahi example code
3355
GObject.io_add_watch(tcp_server.fileno(), GObject.IO_IN,
2379
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
3356
2380
lambda *args, **kwargs:
3357
2381
(tcp_server.handle_request
3358
2382
(*args[2:], **kwargs) or True))