2
# -*- mode: python; coding: utf-8 -*-
2
# -*- mode: python; after-save-hook: (lambda () (let ((command (if (fboundp 'file-local-name) (file-local-name (buffer-file-name)) (or (file-remote-p (buffer-file-name) 'localname) (buffer-file-name))))) (if (= (progn (if (get-buffer "*Test*") (kill-buffer "*Test*")) (process-file-shell-command (format "%s --check" (shell-quote-argument command)) nil "*Test*")) 0) (let ((w (get-buffer-window "*Test*"))) (if w (delete-window w))) (progn (with-current-buffer "*Test*" (compilation-mode)) (display-buffer "*Test*" '(display-buffer-in-side-window)))))); 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
# Copyright © 2008-2016 Teddy Hogeborn
15
# Copyright © 2008-2016 Björn Påhlsson
17
# This program is free software: you can redistribute it and/or modify
18
# it under the terms of the GNU General Public License as published by
14
# Copyright © 2008-2019 Teddy Hogeborn
15
# Copyright © 2008-2019 Björn Påhlsson
17
# This file is part of Mandos.
19
# Mandos is free software: you can redistribute it and/or modify it
20
# under the terms of the GNU General Public License as published by
19
21
# the Free Software Foundation, either version 3 of the License, or
20
22
# (at your option) any later version.
22
# This program is distributed in the hope that it will be useful,
23
# but WITHOUT ANY WARRANTY; without even the implied warranty of
24
# Mandos is distributed in the hope that it will be useful, but
25
# WITHOUT ANY WARRANTY; without even the implied warranty of
24
26
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
27
# GNU General Public License for more details.
27
29
# You should have received a copy of the GNU General Public License
28
# along with this program. If not, see
29
# <http://www.gnu.org/licenses/>.
30
# along with Mandos. If not, see <http://www.gnu.org/licenses/>.
31
32
# Contact the authors at <mandos@recompile.se>.
34
35
from __future__ import (division, absolute_import, print_function,
81
84
import dbus.service
83
from gi.repository import GObject
85
import gobject as GObject
86
from gi.repository import GLib
86
87
from dbus.mainloop.glib import DBusGMainLoop
89
90
import xml.dom.minidom
93
if sys.version_info.major == 2:
97
# Add collections.abc.Callable if it does not exist
99
collections.abc.Callable
100
except AttributeError:
102
Callable = collections.Callable
103
collections.abc = abc
106
# Show warnings by default
107
if not sys.warnoptions:
109
warnings.simplefilter("default")
111
# Try to find the value of SO_BINDTODEVICE:
113
# This is where SO_BINDTODEVICE is in Python 3.3 (or 3.4?) and
114
# newer, and it is also the most natural place for it:
93
115
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
94
116
except AttributeError:
118
# This is where SO_BINDTODEVICE was up to and including Python
96
120
from IN import SO_BINDTODEVICE
97
121
except ImportError:
98
SO_BINDTODEVICE = None
100
if sys.version_info.major == 2:
122
# In Python 2.7 it seems to have been removed entirely.
123
# Try running the C preprocessor:
125
cc = subprocess.Popen(["cc", "--language=c", "-E",
127
stdin=subprocess.PIPE,
128
stdout=subprocess.PIPE)
129
stdout = cc.communicate(
130
"#include <sys/socket.h>\nSO_BINDTODEVICE\n")[0]
131
SO_BINDTODEVICE = int(stdout.splitlines()[-1])
132
except (OSError, ValueError, IndexError):
134
SO_BINDTODEVICE = None
136
if sys.version_info < (3, 2):
137
configparser.Configparser = configparser.SafeConfigParser
104
140
stored_state_file = "clients.pickle"
106
142
logger = logging.getLogger()
143
logging.captureWarnings(True) # Show warnings via the logging system
110
147
if_nametoindex = ctypes.cdll.LoadLibrary(
111
148
ctypes.util.find_library("c")).if_nametoindex
112
149
except (OSError, AttributeError):
114
151
def if_nametoindex(interface):
115
152
"Get an interface index the hard way, i.e. using fcntl()"
116
153
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
230
269
'--passphrase-file',
232
271
+ self.gnupgargs,
233
stdin = subprocess.PIPE,
234
stdout = subprocess.PIPE,
235
stderr = subprocess.PIPE)
236
ciphertext, err = proc.communicate(input = data)
272
stdin=subprocess.PIPE,
273
stdout=subprocess.PIPE,
274
stderr=subprocess.PIPE)
275
ciphertext, err = proc.communicate(input=data)
237
276
if proc.returncode != 0:
238
277
raise PGPError(err)
239
278
return ciphertext
241
280
def decrypt(self, data, password):
242
281
passphrase = self.password_encode(password)
243
282
with tempfile.NamedTemporaryFile(
244
dir = self.tempdir) as passfile:
283
dir=self.tempdir) as passfile:
245
284
passfile.write(passphrase)
247
286
proc = subprocess.Popen([self.gpg, '--decrypt',
248
287
'--passphrase-file',
250
289
+ self.gnupgargs,
251
stdin = subprocess.PIPE,
252
stdout = subprocess.PIPE,
253
stderr = subprocess.PIPE)
254
decrypted_plaintext, err = proc.communicate(input = data)
290
stdin=subprocess.PIPE,
291
stdout=subprocess.PIPE,
292
stderr=subprocess.PIPE)
293
decrypted_plaintext, err = proc.communicate(input=data)
255
294
if proc.returncode != 0:
256
295
raise PGPError(err)
257
296
return decrypted_plaintext
259
299
# Pretend that we have an Avahi module
261
"""This isn't so much a class as it is a module-like namespace.
262
It is instantiated once, and simulates having an Avahi module."""
263
IF_UNSPEC = -1 # avahi-common/address.h
264
PROTO_UNSPEC = -1 # avahi-common/address.h
265
PROTO_INET = 0 # avahi-common/address.h
266
PROTO_INET6 = 1 # avahi-common/address.h
301
"""This isn't so much a class as it is a module-like namespace."""
302
IF_UNSPEC = -1 # avahi-common/address.h
303
PROTO_UNSPEC = -1 # avahi-common/address.h
304
PROTO_INET = 0 # avahi-common/address.h
305
PROTO_INET6 = 1 # avahi-common/address.h
267
306
DBUS_NAME = "org.freedesktop.Avahi"
268
307
DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"
269
308
DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
270
309
DBUS_PATH_SERVER = "/"
271
def string_array_to_txt_array(self, t):
312
def string_array_to_txt_array(t):
272
313
return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
273
314
for s in t), signature="ay")
274
ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h
275
ENTRY_GROUP_COLLISION = 3 # avahi-common/defs.h
276
ENTRY_GROUP_FAILURE = 4 # avahi-common/defs.h
277
SERVER_INVALID = 0 # avahi-common/defs.h
278
SERVER_REGISTERING = 1 # avahi-common/defs.h
279
SERVER_RUNNING = 2 # avahi-common/defs.h
280
SERVER_COLLISION = 3 # avahi-common/defs.h
281
SERVER_FAILURE = 4 # avahi-common/defs.h
315
ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h
316
ENTRY_GROUP_COLLISION = 3 # avahi-common/defs.h
317
ENTRY_GROUP_FAILURE = 4 # avahi-common/defs.h
318
SERVER_INVALID = 0 # avahi-common/defs.h
319
SERVER_REGISTERING = 1 # avahi-common/defs.h
320
SERVER_RUNNING = 2 # avahi-common/defs.h
321
SERVER_COLLISION = 3 # avahi-common/defs.h
322
SERVER_FAILURE = 4 # avahi-common/defs.h
284
325
class AvahiError(Exception):
285
326
def __init__(self, value, *args, **kwargs):
476
517
class AvahiServiceToSyslog(AvahiService):
477
518
def rename(self, *args, **kwargs):
478
519
"""Add the new name to the syslog messages"""
479
ret = AvahiService.rename(self, *args, **kwargs)
520
ret = super(AvahiServiceToSyslog, self).rename(*args, **kwargs)
480
521
syslogger.setFormatter(logging.Formatter(
481
522
'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
482
523
.format(self.name)))
485
527
# Pretend that we have a GnuTLS module
486
class GnuTLS(object):
487
"""This isn't so much a class as it is a module-like namespace.
488
It is instantiated once, and simulates having a GnuTLS module."""
490
_library = ctypes.cdll.LoadLibrary(
491
ctypes.util.find_library("gnutls"))
492
_need_version = b"3.3.0"
494
# Need to use class name "GnuTLS" here, since this method is
495
# called before the assignment to the "gnutls" global variable
497
if GnuTLS.check_version(self._need_version) is None:
498
raise GnuTLS.Error("Needs GnuTLS {} or later"
499
.format(self._need_version))
529
"""This isn't so much a class as it is a module-like namespace."""
531
library = ctypes.util.find_library("gnutls")
533
library = ctypes.util.find_library("gnutls-deb0")
534
_library = ctypes.cdll.LoadLibrary(library)
501
537
# Unless otherwise indicated, the constants and types below are
502
538
# all from the gnutls/gnutls.h C header file.
506
542
E_INTERRUPTED = -52
511
548
CRD_CERTIFICATE = 1
512
549
E_NO_CERTIFICATE_FOUND = -49
554
KEYID_USE_SHA256 = 1 # gnutls/x509.h
513
555
OPENPGP_FMT_RAW = 0 # gnutls/openpgp.h
516
558
class session_int(ctypes.Structure):
518
560
session_t = ctypes.POINTER(session_int)
519
562
class certificate_credentials_st(ctypes.Structure):
521
564
certificate_credentials_t = ctypes.POINTER(
522
565
certificate_credentials_st)
523
566
certificate_type_t = ctypes.c_int
524
568
class datum_t(ctypes.Structure):
525
569
_fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
526
570
('size', ctypes.c_uint)]
527
572
class openpgp_crt_int(ctypes.Structure):
529
574
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
530
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
575
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
531
576
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
532
577
credentials_type_t = ctypes.c_int
533
578
transport_ptr_t = ctypes.c_void_p
534
579
close_request_t = ctypes.c_int
537
582
class Error(Exception):
538
# We need to use the class name "GnuTLS" here, since this
539
# exception might be raised from within GnuTLS.__init__,
540
# which is called before the assignment to the "gnutls"
541
# global variable has happened.
542
def __init__(self, message = None, code = None, args=()):
583
def __init__(self, message=None, code=None, args=()):
543
584
# Default usage is by a message string, but if a return
544
585
# code is passed, convert it to a string with
545
586
# gnutls.strerror()
547
588
if message is None and code is not None:
548
message = GnuTLS.strerror(code)
549
return super(GnuTLS.Error, self).__init__(
589
message = gnutls.strerror(code)
590
return super(gnutls.Error, self).__init__(
552
593
class CertificateSecurityError(Error):
556
class Credentials(object):
557
598
def __init__(self):
558
599
self._c_object = gnutls.certificate_credentials_t()
559
600
gnutls.certificate_allocate_credentials(
560
601
ctypes.byref(self._c_object))
561
602
self.type = gnutls.CRD_CERTIFICATE
563
604
def __del__(self):
564
605
gnutls.certificate_free_credentials(self._c_object)
566
class ClientSession(object):
567
def __init__(self, socket, credentials = None):
608
def __init__(self, socket, credentials=None):
568
609
self._c_object = gnutls.session_t()
569
gnutls.init(ctypes.byref(self._c_object), gnutls.CLIENT)
610
gnutls_flags = gnutls.CLIENT
611
if gnutls.check_version(b"3.5.6"):
612
gnutls_flags |= gnutls.NO_TICKETS
614
gnutls_flags |= gnutls.ENABLE_RAWPK
615
gnutls.init(ctypes.byref(self._c_object), gnutls_flags)
570
617
gnutls.set_default_priority(self._c_object)
571
618
gnutls.transport_set_ptr(self._c_object, socket.fileno())
572
619
gnutls.handshake_set_private_extensions(self._c_object,
614
661
return _error_code(result)
615
662
result = func(*arguments)
618
665
# Unless otherwise indicated, the function declarations below are
619
666
# all from the gnutls/gnutls.h C header file.
622
669
priority_set_direct = _library.gnutls_priority_set_direct
623
670
priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
624
671
ctypes.POINTER(ctypes.c_char_p)]
625
672
priority_set_direct.restype = _error_code
627
674
init = _library.gnutls_init
628
675
init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
629
676
init.restype = _error_code
631
678
set_default_priority = _library.gnutls_set_default_priority
632
679
set_default_priority.argtypes = [session_t]
633
680
set_default_priority.restype = _error_code
635
682
record_send = _library.gnutls_record_send
636
683
record_send.argtypes = [session_t, ctypes.c_void_p,
638
685
record_send.restype = ctypes.c_ssize_t
639
686
record_send.errcheck = _retry_on_error
641
688
certificate_allocate_credentials = (
642
689
_library.gnutls_certificate_allocate_credentials)
643
690
certificate_allocate_credentials.argtypes = [
644
691
ctypes.POINTER(certificate_credentials_t)]
645
692
certificate_allocate_credentials.restype = _error_code
647
694
certificate_free_credentials = (
648
695
_library.gnutls_certificate_free_credentials)
649
certificate_free_credentials.argtypes = [certificate_credentials_t]
696
certificate_free_credentials.argtypes = [
697
certificate_credentials_t]
650
698
certificate_free_credentials.restype = None
652
700
handshake_set_private_extensions = (
653
701
_library.gnutls_handshake_set_private_extensions)
654
702
handshake_set_private_extensions.argtypes = [session_t,
656
704
handshake_set_private_extensions.restype = None
658
706
credentials_set = _library.gnutls_credentials_set
659
707
credentials_set.argtypes = [session_t, credentials_type_t,
661
709
credentials_set.restype = _error_code
663
711
strerror = _library.gnutls_strerror
664
712
strerror.argtypes = [ctypes.c_int]
665
713
strerror.restype = ctypes.c_char_p
667
715
certificate_type_get = _library.gnutls_certificate_type_get
668
716
certificate_type_get.argtypes = [session_t]
669
717
certificate_type_get.restype = _error_code
671
719
certificate_get_peers = _library.gnutls_certificate_get_peers
672
720
certificate_get_peers.argtypes = [session_t,
673
721
ctypes.POINTER(ctypes.c_uint)]
674
722
certificate_get_peers.restype = ctypes.POINTER(datum_t)
676
724
global_set_log_level = _library.gnutls_global_set_log_level
677
725
global_set_log_level.argtypes = [ctypes.c_int]
678
726
global_set_log_level.restype = None
680
728
global_set_log_function = _library.gnutls_global_set_log_function
681
729
global_set_log_function.argtypes = [log_func]
682
730
global_set_log_function.restype = None
684
732
deinit = _library.gnutls_deinit
685
733
deinit.argtypes = [session_t]
686
734
deinit.restype = None
688
736
handshake = _library.gnutls_handshake
689
737
handshake.argtypes = [session_t]
690
738
handshake.restype = _error_code
691
739
handshake.errcheck = _retry_on_error
693
741
transport_set_ptr = _library.gnutls_transport_set_ptr
694
742
transport_set_ptr.argtypes = [session_t, transport_ptr_t]
695
743
transport_set_ptr.restype = None
697
745
bye = _library.gnutls_bye
698
746
bye.argtypes = [session_t, close_request_t]
699
747
bye.restype = _error_code
700
748
bye.errcheck = _retry_on_error
702
750
check_version = _library.gnutls_check_version
703
751
check_version.argtypes = [ctypes.c_char_p]
704
752
check_version.restype = ctypes.c_char_p
706
# All the function declarations below are from gnutls/openpgp.h
708
openpgp_crt_init = _library.gnutls_openpgp_crt_init
709
openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
710
openpgp_crt_init.restype = _error_code
712
openpgp_crt_import = _library.gnutls_openpgp_crt_import
713
openpgp_crt_import.argtypes = [openpgp_crt_t,
714
ctypes.POINTER(datum_t),
716
openpgp_crt_import.restype = _error_code
718
openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
719
openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
720
ctypes.POINTER(ctypes.c_uint)]
721
openpgp_crt_verify_self.restype = _error_code
723
openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
724
openpgp_crt_deinit.argtypes = [openpgp_crt_t]
725
openpgp_crt_deinit.restype = None
727
openpgp_crt_get_fingerprint = (
728
_library.gnutls_openpgp_crt_get_fingerprint)
729
openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
733
openpgp_crt_get_fingerprint.restype = _error_code
754
_need_version = b"3.3.0"
755
if check_version(_need_version) is None:
756
raise self.Error("Needs GnuTLS {} or later"
757
.format(_need_version))
759
_tls_rawpk_version = b"3.6.6"
760
has_rawpk = bool(check_version(_tls_rawpk_version))
764
class pubkey_st(ctypes.Structure):
766
pubkey_t = ctypes.POINTER(pubkey_st)
768
x509_crt_fmt_t = ctypes.c_int
770
# All the function declarations below are from gnutls/abstract.h
771
pubkey_init = _library.gnutls_pubkey_init
772
pubkey_init.argtypes = [ctypes.POINTER(pubkey_t)]
773
pubkey_init.restype = _error_code
775
pubkey_import = _library.gnutls_pubkey_import
776
pubkey_import.argtypes = [pubkey_t, ctypes.POINTER(datum_t),
778
pubkey_import.restype = _error_code
780
pubkey_get_key_id = _library.gnutls_pubkey_get_key_id
781
pubkey_get_key_id.argtypes = [pubkey_t, ctypes.c_int,
782
ctypes.POINTER(ctypes.c_ubyte),
783
ctypes.POINTER(ctypes.c_size_t)]
784
pubkey_get_key_id.restype = _error_code
786
pubkey_deinit = _library.gnutls_pubkey_deinit
787
pubkey_deinit.argtypes = [pubkey_t]
788
pubkey_deinit.restype = None
790
# All the function declarations below are from gnutls/openpgp.h
792
openpgp_crt_init = _library.gnutls_openpgp_crt_init
793
openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
794
openpgp_crt_init.restype = _error_code
796
openpgp_crt_import = _library.gnutls_openpgp_crt_import
797
openpgp_crt_import.argtypes = [openpgp_crt_t,
798
ctypes.POINTER(datum_t),
800
openpgp_crt_import.restype = _error_code
802
openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
803
openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
804
ctypes.POINTER(ctypes.c_uint)]
805
openpgp_crt_verify_self.restype = _error_code
807
openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
808
openpgp_crt_deinit.argtypes = [openpgp_crt_t]
809
openpgp_crt_deinit.restype = None
811
openpgp_crt_get_fingerprint = (
812
_library.gnutls_openpgp_crt_get_fingerprint)
813
openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
817
openpgp_crt_get_fingerprint.restype = _error_code
819
if check_version(b"3.6.4"):
820
certificate_type_get2 = _library.gnutls_certificate_type_get2
821
certificate_type_get2.argtypes = [session_t, ctypes.c_int]
822
certificate_type_get2.restype = _error_code
735
824
# Remove non-public functions
736
825
del _error_code, _retry_on_error
737
# Create the global "gnutls" object, simulating a module
740
828
def call_pipe(connection, # : multiprocessing.Connection
741
829
func, *args, **kwargs):
742
830
"""This function is meant to be called by multiprocessing.Process
744
832
This function runs func(*args, **kwargs), and writes the resulting
745
833
return value on the provided multiprocessing.Connection.
747
835
connection.send(func(*args, **kwargs))
748
836
connection.close()
750
class Client(object):
751
840
"""A representation of a client host served by this server.
754
843
approved: bool(); 'None' if not yet approved/disapproved
755
844
approval_delay: datetime.timedelta(); Time to wait for approval
756
845
approval_duration: datetime.timedelta(); Duration of one approval
757
checker: subprocess.Popen(); a running checker process used
758
to see if the client lives.
759
'None' if no process is running.
760
checker_callback_tag: a GObject event source tag, or None
846
checker: multiprocessing.Process(); a running checker process used
847
to see if the client lives. 'None' if no process is
849
checker_callback_tag: a GLib event source tag, or None
761
850
checker_command: string; External command which is run to check
762
851
if client lives. %() expansions are done at
763
852
runtime with vars(self) as dict, so that for
764
853
instance %(name)s can be used in the command.
765
checker_initiator_tag: a GObject event source tag, or None
854
checker_initiator_tag: a GLib event source tag, or None
766
855
created: datetime.datetime(); (UTC) object creation
767
856
client_structure: Object describing what attributes a client has
768
857
and is used for storing the client at exit
769
858
current_checker_command: string; current running checker_command
770
disable_initiator_tag: a GObject event source tag, or None
859
disable_initiator_tag: a GLib event source tag, or None
772
861
fingerprint: string (40 or 32 hexadecimal digits); used to
773
uniquely identify the client
862
uniquely identify an OpenPGP client
863
key_id: string (64 hexadecimal digits); used to uniquely identify
864
a client using raw public keys
774
865
host: string; available for use by the checker command
775
866
interval: datetime.timedelta(); How often to start a new checker
776
867
last_approval_request: datetime.datetime(); (UTC) or None
930
1024
logger.info("Disabling client %s", self.name)
931
1025
if getattr(self, "disable_initiator_tag", None) is not None:
932
GObject.source_remove(self.disable_initiator_tag)
1026
GLib.source_remove(self.disable_initiator_tag)
933
1027
self.disable_initiator_tag = None
934
1028
self.expires = None
935
1029
if getattr(self, "checker_initiator_tag", None) is not None:
936
GObject.source_remove(self.checker_initiator_tag)
1030
GLib.source_remove(self.checker_initiator_tag)
937
1031
self.checker_initiator_tag = None
938
1032
self.stop_checker()
939
1033
self.enabled = False
941
1035
self.send_changedstate()
942
# Do not run this again if called by a GObject.timeout_add
1036
# Do not run this again if called by a GLib.timeout_add
945
1039
def __del__(self):
948
1042
def init_checker(self):
949
1043
# Schedule a new checker to be started an 'interval' from now,
950
1044
# and every interval from then on.
951
1045
if self.checker_initiator_tag is not None:
952
GObject.source_remove(self.checker_initiator_tag)
953
self.checker_initiator_tag = GObject.timeout_add(
954
int(self.interval.total_seconds() * 1000),
1046
GLib.source_remove(self.checker_initiator_tag)
1047
self.checker_initiator_tag = GLib.timeout_add(
1048
random.randrange(int(self.interval.total_seconds() * 1000
955
1050
self.start_checker)
956
1051
# Schedule a disable() when 'timeout' has passed
957
1052
if self.disable_initiator_tag is not None:
958
GObject.source_remove(self.disable_initiator_tag)
959
self.disable_initiator_tag = GObject.timeout_add(
1053
GLib.source_remove(self.disable_initiator_tag)
1054
self.disable_initiator_tag = GLib.timeout_add(
960
1055
int(self.timeout.total_seconds() * 1000), self.disable)
961
1056
# Also start a new checker *right now*.
962
1057
self.start_checker()
964
1059
def checker_callback(self, source, condition, connection,
966
1061
"""The checker has completed, so take appropriate actions."""
967
self.checker_callback_tag = None
969
1062
# Read return code from connection (see call_pipe)
970
1063
returncode = connection.recv()
971
1064
connection.close()
1065
if self.checker is not None:
1067
self.checker_callback_tag = None
973
1070
if returncode >= 0:
974
1071
self.last_checker_status = returncode
975
1072
self.last_checker_signal = None
1431
1528
exc_info=error)
1432
1529
return xmlstring
1435
1533
dbus.OBJECT_MANAGER_IFACE
1436
1534
except AttributeError:
1437
1535
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1439
1538
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1440
1539
"""A D-Bus object with an ObjectManager.
1442
1541
Classes inheriting from this exposes the standard
1443
1542
GetManagedObjects call and the InterfacesAdded and
1444
1543
InterfacesRemoved signals on the standard
1445
1544
"org.freedesktop.DBus.ObjectManager" interface.
1447
1546
Note: No signals are sent automatically; they must be sent
1450
1549
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1451
out_signature = "a{oa{sa{sv}}}")
1550
out_signature="a{oa{sa{sv}}}")
1452
1551
def GetManagedObjects(self):
1453
1552
"""This function must be overridden"""
1454
1553
raise NotImplementedError()
1456
1555
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1457
signature = "oa{sa{sv}}")
1556
signature="oa{sa{sv}}")
1458
1557
def InterfacesAdded(self, object_path, interfaces_and_properties):
1461
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
1560
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature="oas")
1462
1561
def InterfacesRemoved(self, object_path, interfaces):
1465
1564
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1466
out_signature = "s",
1467
path_keyword = 'object_path',
1468
connection_keyword = 'connection')
1566
path_keyword='object_path',
1567
connection_keyword='connection')
1469
1568
def Introspect(self, object_path, connection):
1470
1569
"""Overloading of standard D-Bus method.
1472
1571
Override return argument name of GetManagedObjects to be
1473
1572
"objpath_interfaces_and_properties"
2348
class MultiprocessingMixIn(object):
2515
class MultiprocessingMixIn:
2349
2516
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
2351
2518
def sub_process_main(self, request, address):
2353
2520
self.finish_request(request, address)
2354
2521
except Exception:
2355
2522
self.handle_error(request, address)
2356
2523
self.close_request(request)
2358
2525
def process_request(self, request, address):
2359
2526
"""Start a new process to process the request."""
2360
proc = multiprocessing.Process(target = self.sub_process_main,
2361
args = (request, address))
2527
proc = multiprocessing.Process(target=self.sub_process_main,
2528
args=(request, address))
2366
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
2533
class MultiprocessingMixInWithPipe(MultiprocessingMixIn):
2367
2534
""" adds a pipe to the MixIn """
2369
2536
def process_request(self, request, client_address):
2370
2537
"""Overrides and wraps the original process_request().
2372
2539
This function creates a new pipe in self.pipe
2374
2541
parent_pipe, self.child_pipe = multiprocessing.Pipe()
2376
2543
proc = MultiprocessingMixIn.process_request(self, request,
2377
2544
client_address)
2378
2545
self.child_pipe.close()
2379
2546
self.add_pipe(parent_pipe, proc)
2381
2548
def add_pipe(self, parent_pipe, proc):
2382
2549
"""Dummy function; override as necessary"""
2383
2550
raise NotImplementedError()
2386
2553
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
2387
socketserver.TCPServer, object):
2554
socketserver.TCPServer):
2388
2555
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
2391
2558
enabled: Boolean; whether this server is activated yet
2392
2559
interface: None or a network interface name (string)
2393
2560
use_ipv6: Boolean; to use IPv6 or not
2396
2563
def __init__(self, server_address, RequestHandlerClass,
2397
2564
interface=None,
2431
2599
# socket_wrapper(), if socketfd was set.
2432
2600
socketserver.TCPServer.__init__(self, server_address,
2433
2601
RequestHandlerClass)
2435
2603
def server_bind(self):
2436
2604
"""This overrides the normal server_bind() function
2437
2605
to bind to an interface if one was specified, and also NOT to
2438
2606
bind to an address or port if they were not specified."""
2607
global SO_BINDTODEVICE
2439
2608
if self.interface is not None:
2440
2609
if SO_BINDTODEVICE is None:
2441
logger.error("SO_BINDTODEVICE does not exist;"
2442
" cannot bind to interface %s",
2446
self.socket.setsockopt(
2447
socket.SOL_SOCKET, SO_BINDTODEVICE,
2448
(self.interface + "\0").encode("utf-8"))
2449
except socket.error as error:
2450
if error.errno == errno.EPERM:
2451
logger.error("No permission to bind to"
2452
" interface %s", self.interface)
2453
elif error.errno == errno.ENOPROTOOPT:
2454
logger.error("SO_BINDTODEVICE not available;"
2455
" cannot bind to interface %s",
2457
elif error.errno == errno.ENODEV:
2458
logger.error("Interface %s does not exist,"
2459
" cannot bind", self.interface)
2610
# Fall back to a hard-coded value which seems to be
2612
logger.warning("SO_BINDTODEVICE not found, trying 25")
2613
SO_BINDTODEVICE = 25
2615
self.socket.setsockopt(
2616
socket.SOL_SOCKET, SO_BINDTODEVICE,
2617
(self.interface + "\0").encode("utf-8"))
2618
except socket.error as error:
2619
if error.errno == errno.EPERM:
2620
logger.error("No permission to bind to"
2621
" interface %s", self.interface)
2622
elif error.errno == errno.ENOPROTOOPT:
2623
logger.error("SO_BINDTODEVICE not available;"
2624
" cannot bind to interface %s",
2626
elif error.errno == errno.ENODEV:
2627
logger.error("Interface %s does not exist,"
2628
" cannot bind", self.interface)
2462
2631
# Only bind(2) the socket if we really need to.
2463
2632
if self.server_address[0] or self.server_address[1]:
2633
if self.server_address[1]:
2634
self.allow_reuse_address = True
2464
2635
if not self.server_address[0]:
2465
2636
if self.address_family == socket.AF_INET6:
2466
any_address = "::" # in6addr_any
2637
any_address = "::" # in6addr_any
2468
any_address = "0.0.0.0" # INADDR_ANY
2639
any_address = "0.0.0.0" # INADDR_ANY
2469
2640
self.server_address = (any_address,
2470
2641
self.server_address[1])
2471
2642
elif not self.server_address[1]:
2505
2676
self.gnutls_priority = gnutls_priority
2506
2677
IPv6_TCPServer.__init__(self, server_address,
2507
2678
RequestHandlerClass,
2508
interface = interface,
2509
use_ipv6 = use_ipv6,
2510
socketfd = socketfd)
2679
interface=interface,
2512
2683
def server_activate(self):
2513
2684
if self.enabled:
2514
2685
return socketserver.TCPServer.server_activate(self)
2516
2687
def enable(self):
2517
2688
self.enabled = True
2519
2690
def add_pipe(self, parent_pipe, proc):
2520
2691
# Call "handle_ipc" for both data and EOF events
2521
GObject.io_add_watch(
2522
parent_pipe.fileno(),
2523
GObject.IO_IN | GObject.IO_HUP,
2693
GLib.IOChannel.unix_new(parent_pipe.fileno()),
2694
GLib.PRIORITY_DEFAULT, GLib.IO_IN | GLib.IO_HUP,
2524
2695
functools.partial(self.handle_ipc,
2525
parent_pipe = parent_pipe,
2696
parent_pipe=parent_pipe,
2528
2699
def handle_ipc(self, source, condition,
2529
2700
parent_pipe=None,
2531
2702
client_object=None):
2532
2703
# error, or the other end of multiprocessing.Pipe has closed
2533
if condition & (GObject.IO_ERR | GObject.IO_HUP):
2704
if condition & (GLib.IO_ERR | GLib.IO_HUP):
2534
2705
# Wait for other process to exit
2538
2709
# Read a request from the child
2539
2710
request = parent_pipe.recv()
2540
2711
command = request[0]
2542
2713
if command == 'init':
2544
address = request[2]
2714
key_id = request[1].decode("ascii")
2715
fpr = request[2].decode("ascii")
2716
address = request[3]
2546
2718
for c in self.clients.values():
2547
if c.fingerprint == fpr:
2719
if key_id == "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855":
2721
if key_id and c.key_id == key_id:
2724
if fpr and c.fingerprint == fpr:
2551
logger.info("Client not found for fingerprint: %s, ad"
2552
"dress: %s", fpr, address)
2728
logger.info("Client not found for key ID: %s, address"
2729
": %s", key_id or fpr, address)
2553
2730
if self.use_dbus:
2554
2731
# Emit D-Bus signal
2555
mandos_dbus_service.ClientNotFound(fpr,
2732
mandos_dbus_service.ClientNotFound(key_id or fpr,
2557
2734
parent_pipe.send(False)
2560
GObject.io_add_watch(
2561
parent_pipe.fileno(),
2562
GObject.IO_IN | GObject.IO_HUP,
2738
GLib.IOChannel.unix_new(parent_pipe.fileno()),
2739
GLib.PRIORITY_DEFAULT, GLib.IO_IN | GLib.IO_HUP,
2563
2740
functools.partial(self.handle_ipc,
2564
parent_pipe = parent_pipe,
2566
client_object = client))
2741
parent_pipe=parent_pipe,
2743
client_object=client))
2567
2744
parent_pipe.send(True)
2568
2745
# remove the old hook in favor of the new above hook on
2572
2749
funcname = request[1]
2573
2750
args = request[2]
2574
2751
kwargs = request[3]
2576
2753
parent_pipe.send(('data', getattr(client_object,
2577
2754
funcname)(*args,
2580
2757
if command == 'getattr':
2581
2758
attrname = request[1]
2582
2759
if isinstance(client_object.__getattribute__(attrname),
2583
collections.Callable):
2760
collections.abc.Callable):
2584
2761
parent_pipe.send(('function', ))
2586
2763
parent_pipe.send((
2587
2764
'data', client_object.__getattribute__(attrname)))
2589
2766
if command == 'setattr':
2590
2767
attrname = request[1]
2591
2768
value = request[2]
2592
2769
setattr(client_object, attrname, value)
2597
2774
def rfc3339_duration_to_delta(duration):
2598
2775
"""Parse an RFC 3339 "duration" and return a datetime.timedelta
2600
>>> rfc3339_duration_to_delta("P7D")
2601
datetime.timedelta(7)
2602
>>> rfc3339_duration_to_delta("PT60S")
2603
datetime.timedelta(0, 60)
2604
>>> rfc3339_duration_to_delta("PT60M")
2605
datetime.timedelta(0, 3600)
2606
>>> rfc3339_duration_to_delta("PT24H")
2607
datetime.timedelta(1)
2608
>>> rfc3339_duration_to_delta("P1W")
2609
datetime.timedelta(7)
2610
>>> rfc3339_duration_to_delta("PT5M30S")
2611
datetime.timedelta(0, 330)
2612
>>> rfc3339_duration_to_delta("P1DT3M20S")
2613
datetime.timedelta(1, 200)
2777
>>> rfc3339_duration_to_delta("P7D") == datetime.timedelta(7)
2779
>>> rfc3339_duration_to_delta("PT60S") == datetime.timedelta(0, 60)
2781
>>> rfc3339_duration_to_delta("PT60M") == datetime.timedelta(0, 3600)
2783
>>> rfc3339_duration_to_delta("PT24H") == datetime.timedelta(1)
2785
>>> rfc3339_duration_to_delta("P1W") == datetime.timedelta(7)
2787
>>> rfc3339_duration_to_delta("PT5M30S") == datetime.timedelta(0, 330)
2789
>>> rfc3339_duration_to_delta("P1DT3M20S") == datetime.timedelta(1, 200)
2616
2793
# Parsing an RFC 3339 duration with regular expressions is not
2617
2794
# possible - there would have to be multiple places for the same
2618
2795
# values, like seconds. The current code, while more esoteric, is
2619
2796
# cleaner without depending on a parsing library. If Python had a
2620
2797
# built-in library for parsing we would use it, but we'd like to
2621
2798
# avoid excessive use of external libraries.
2623
2800
# New type for defining tokens, syntax, and semantics all-in-one
2624
2801
Token = collections.namedtuple("Token", (
2625
2802
"regexp", # To match token; if "value" is not None, must have
2809
2989
parser.add_argument("--no-zeroconf", action="store_false",
2810
2990
dest="zeroconf", help="Do not use Zeroconf",
2813
2993
options = parser.parse_args()
2817
fail_count, test_count = doctest.testmod()
2818
sys.exit(os.EX_OK if fail_count == 0 else 1)
2820
2995
# Default values for config file for server-global settings
2821
server_defaults = { "interface": "",
2826
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2827
":+SIGN-DSA-SHA256",
2828
"servicename": "Mandos",
2834
"statedir": "/var/lib/mandos",
2835
"foreground": "False",
2996
if gnutls.has_rawpk:
2997
priority = ("SECURE128:!CTYPE-X.509:+CTYPE-RAWPK:!RSA"
2998
":!VERS-ALL:+VERS-TLS1.3:%PROFILE_ULTRA")
3000
priority = ("SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
3001
":+SIGN-DSA-SHA256")
3002
server_defaults = {"interface": "",
3006
"priority": priority,
3007
"servicename": "Mandos",
3013
"statedir": "/var/lib/mandos",
3014
"foreground": "False",
2839
3019
# Parse config file for server-global settings
2840
server_config = configparser.SafeConfigParser(server_defaults)
3020
server_config = configparser.ConfigParser(server_defaults)
2841
3021
del server_defaults
2842
3022
server_config.read(os.path.join(options.configdir, "mandos.conf"))
2843
# Convert the SafeConfigParser object to a dict
3023
# Convert the ConfigParser object to a dict
2844
3024
server_settings = server_config.defaults()
2845
3025
# Use the appropriate methods on the non-string config options
2846
for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
3026
for option in ("debug", "use_dbus", "use_ipv6", "restore",
3027
"foreground", "zeroconf"):
2847
3028
server_settings[option] = server_config.getboolean("DEFAULT",
2849
3030
if server_settings["port"]:
3022
3203
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
3023
3204
service = AvahiServiceToSyslog(
3024
name = server_settings["servicename"],
3025
servicetype = "_mandos._tcp",
3026
protocol = protocol,
3205
name=server_settings["servicename"],
3206
servicetype="_mandos._tcp",
3028
3209
if server_settings["interface"]:
3029
3210
service.interface = if_nametoindex(
3030
3211
server_settings["interface"].encode("utf-8"))
3032
3213
global multiprocessing_manager
3033
3214
multiprocessing_manager = multiprocessing.Manager()
3035
3216
client_class = Client
3037
client_class = functools.partial(ClientDBus, bus = bus)
3218
client_class = functools.partial(ClientDBus, bus=bus)
3039
3220
client_settings = Client.config_parser(client_config)
3040
3221
old_client_settings = {}
3041
3222
clients_data = {}
3043
3224
# This is used to redirect stdout and stderr for checker processes
3045
wnull = open(os.devnull, "w") # A writable /dev/null
3226
wnull = open(os.devnull, "w") # A writable /dev/null
3046
3227
# Only used if server is running in foreground but not in debug
3048
3229
if debug or not foreground:
3051
3232
# Get client data and settings from last running state.
3052
3233
if server_settings["restore"]:
3054
3235
with open(stored_state_path, "rb") as stored_state:
3055
if sys.version_info.major == 2:
3236
if sys.version_info.major == 2:
3056
3237
clients_data, old_client_settings = pickle.load(
3059
3240
bytes_clients_data, bytes_old_client_settings = (
3060
pickle.load(stored_state, encoding = "bytes"))
3061
### Fix bytes to strings
3241
pickle.load(stored_state, encoding="bytes"))
3242
# Fix bytes to strings
3064
clients_data = { (key.decode("utf-8")
3065
if isinstance(key, bytes)
3068
bytes_clients_data.items() }
3245
clients_data = {(key.decode("utf-8")
3246
if isinstance(key, bytes)
3249
bytes_clients_data.items()}
3069
3250
del bytes_clients_data
3070
3251
for key in clients_data:
3071
value = { (k.decode("utf-8")
3072
if isinstance(k, bytes) else k): v
3074
clients_data[key].items() }
3252
value = {(k.decode("utf-8")
3253
if isinstance(k, bytes) else k): v
3255
clients_data[key].items()}
3075
3256
clients_data[key] = value
3076
3257
# .client_structure
3077
3258
value["client_structure"] = [
3078
3259
(s.decode("utf-8")
3079
3260
if isinstance(s, bytes)
3080
3261
else s) for s in
3081
value["client_structure"] ]
3083
for k in ("name", "host"):
3262
value["client_structure"]]
3263
# .name, .host, and .checker_command
3264
for k in ("name", "host", "checker_command"):
3084
3265
if isinstance(value[k], bytes):
3085
3266
value[k] = value[k].decode("utf-8")
3086
## old_client_settings
3267
if "key_id" not in value:
3268
value["key_id"] = ""
3269
elif "fingerprint" not in value:
3270
value["fingerprint"] = ""
3271
# old_client_settings
3088
3273
old_client_settings = {
3089
3274
(key.decode("utf-8")
3090
3275
if isinstance(key, bytes)
3091
3276
else key): value
3092
3277
for key, value in
3093
bytes_old_client_settings.items() }
3278
bytes_old_client_settings.items()}
3094
3279
del bytes_old_client_settings
3280
# .host and .checker_command
3096
3281
for value in old_client_settings.values():
3097
if isinstance(value["host"], bytes):
3098
value["host"] = (value["host"]
3282
for attribute in ("host", "checker_command"):
3283
if isinstance(value[attribute], bytes):
3284
value[attribute] = (value[attribute]
3100
3286
os.remove(stored_state_path)
3101
3287
except IOError as e:
3102
3288
if e.errno == errno.ENOENT: