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 -*-
2
# -*- mode: python; coding: utf-8 -*-
4
4
# Mandos server - give out binary blobs to connecting clients.
6
6
# This program is partly derived from an example program for an Avahi
7
7
# service publisher, downloaded from
8
8
# <http://avahi.org/wiki/PythonPublishExample>. This includes the
9
9
# methods "add", "remove", "server_state_changed",
10
10
# "entry_group_state_changed", "cleanup", and "activate" in the
11
11
# "AvahiService" class, and some lines in "main".
13
13
# Everything else is
14
# 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
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
21
19
# the Free Software Foundation, either version 3 of the License, or
22
20
# (at your option) any later version.
24
# Mandos is distributed in the hope that it will be useful, but
25
# WITHOUT ANY WARRANTY; without even the implied warranty of
22
# This program is distributed in the hope that it will be useful,
23
# but WITHOUT ANY WARRANTY; without even the implied warranty of
26
24
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27
25
# GNU General Public License for more details.
29
27
# You should have received a copy of the GNU General Public License
30
# along with Mandos. If not, see <http://www.gnu.org/licenses/>.
28
# along with this program. If not, see
29
# <http://www.gnu.org/licenses/>.
32
31
# Contact the authors at <mandos@recompile.se>.
35
34
from __future__ import (division, absolute_import, print_function,
85
81
import dbus.service
87
from gi.repository import GLib
83
from gi.repository import GObject
85
import gobject as GObject
88
86
from dbus.mainloop.glib import DBusGMainLoop
91
89
import xml.dom.minidom
94
if sys.version_info.major == 2:
98
# Add collections.abc.Callable if it does not exist
100
collections.abc.Callable
101
except AttributeError:
103
Callable = collections.Callable
104
collections.abc = abc
107
# Add shlex.quote if it does not exist
110
except AttributeError:
111
shlex.quote = re.escape
113
# Show warnings by default
114
if not sys.warnoptions:
116
warnings.simplefilter("default")
118
# Try to find the value of SO_BINDTODEVICE:
120
# This is where SO_BINDTODEVICE is in Python 3.3 (or 3.4?) and
121
# newer, and it is also the most natural place for it:
122
93
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
123
94
except AttributeError:
125
# This is where SO_BINDTODEVICE was up to and including Python
127
96
from IN import SO_BINDTODEVICE
128
97
except ImportError:
129
# In Python 2.7 it seems to have been removed entirely.
130
# Try running the C preprocessor:
132
cc = subprocess.Popen(["cc", "--language=c", "-E",
134
stdin=subprocess.PIPE,
135
stdout=subprocess.PIPE)
136
stdout = cc.communicate(
137
"#include <sys/socket.h>\nSO_BINDTODEVICE\n")[0]
138
SO_BINDTODEVICE = int(stdout.splitlines()[-1])
139
except (OSError, ValueError, IndexError):
141
SO_BINDTODEVICE = None
143
if sys.version_info < (3, 2):
144
configparser.Configparser = configparser.SafeConfigParser
98
SO_BINDTODEVICE = None
100
if sys.version_info.major == 2:
147
104
stored_state_file = "clients.pickle"
149
106
logger = logging.getLogger()
150
logging.captureWarnings(True) # Show warnings via the logging system
154
110
if_nametoindex = ctypes.cdll.LoadLibrary(
155
111
ctypes.util.find_library("c")).if_nametoindex
156
112
except (OSError, AttributeError):
158
114
def if_nametoindex(interface):
159
115
"Get an interface index the hard way, i.e. using fcntl()"
160
116
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
276
230
'--passphrase-file',
278
232
+ self.gnupgargs,
279
stdin=subprocess.PIPE,
280
stdout=subprocess.PIPE,
281
stderr=subprocess.PIPE)
282
ciphertext, err = proc.communicate(input=data)
233
stdin = subprocess.PIPE,
234
stdout = subprocess.PIPE,
235
stderr = subprocess.PIPE)
236
ciphertext, err = proc.communicate(input = data)
283
237
if proc.returncode != 0:
284
238
raise PGPError(err)
285
239
return ciphertext
287
241
def decrypt(self, data, password):
288
242
passphrase = self.password_encode(password)
289
243
with tempfile.NamedTemporaryFile(
290
dir=self.tempdir) as passfile:
244
dir = self.tempdir) as passfile:
291
245
passfile.write(passphrase)
293
247
proc = subprocess.Popen([self.gpg, '--decrypt',
294
248
'--passphrase-file',
296
250
+ self.gnupgargs,
297
stdin=subprocess.PIPE,
298
stdout=subprocess.PIPE,
299
stderr=subprocess.PIPE)
300
decrypted_plaintext, err = proc.communicate(input=data)
251
stdin = subprocess.PIPE,
252
stdout = subprocess.PIPE,
253
stderr = subprocess.PIPE)
254
decrypted_plaintext, err = proc.communicate(input = data)
301
255
if proc.returncode != 0:
302
256
raise PGPError(err)
303
257
return decrypted_plaintext
306
259
# Pretend that we have an Avahi module
308
"""This isn't so much a class as it is a module-like namespace."""
309
IF_UNSPEC = -1 # avahi-common/address.h
310
PROTO_UNSPEC = -1 # avahi-common/address.h
311
PROTO_INET = 0 # avahi-common/address.h
312
PROTO_INET6 = 1 # avahi-common/address.h
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
313
267
DBUS_NAME = "org.freedesktop.Avahi"
314
268
DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"
315
269
DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
316
270
DBUS_PATH_SERVER = "/"
319
def string_array_to_txt_array(t):
271
def string_array_to_txt_array(self, t):
320
272
return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
321
273
for s in t), signature="ay")
322
ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h
323
ENTRY_GROUP_COLLISION = 3 # avahi-common/defs.h
324
ENTRY_GROUP_FAILURE = 4 # avahi-common/defs.h
325
SERVER_INVALID = 0 # avahi-common/defs.h
326
SERVER_REGISTERING = 1 # avahi-common/defs.h
327
SERVER_RUNNING = 2 # avahi-common/defs.h
328
SERVER_COLLISION = 3 # avahi-common/defs.h
329
SERVER_FAILURE = 4 # avahi-common/defs.h
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
332
284
class AvahiError(Exception):
333
285
def __init__(self, value, *args, **kwargs):
524
476
class AvahiServiceToSyslog(AvahiService):
525
477
def rename(self, *args, **kwargs):
526
478
"""Add the new name to the syslog messages"""
527
ret = super(AvahiServiceToSyslog, self).rename(*args, **kwargs)
479
ret = AvahiService.rename(self, *args, **kwargs)
528
480
syslogger.setFormatter(logging.Formatter(
529
481
'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
530
482
.format(self.name)))
534
485
# Pretend that we have a GnuTLS module
536
"""This isn't so much a class as it is a module-like namespace."""
538
library = ctypes.util.find_library("gnutls")
540
library = ctypes.util.find_library("gnutls-deb0")
541
_library = ctypes.cdll.LoadLibrary(library)
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))
544
501
# Unless otherwise indicated, the constants and types below are
545
502
# all from the gnutls/gnutls.h C header file.
549
506
E_INTERRUPTED = -52
555
511
CRD_CERTIFICATE = 1
556
512
E_NO_CERTIFICATE_FOUND = -49
561
KEYID_USE_SHA256 = 1 # gnutls/x509.h
562
513
OPENPGP_FMT_RAW = 0 # gnutls/openpgp.h
565
516
class session_int(ctypes.Structure):
567
518
session_t = ctypes.POINTER(session_int)
569
519
class certificate_credentials_st(ctypes.Structure):
571
521
certificate_credentials_t = ctypes.POINTER(
572
522
certificate_credentials_st)
573
523
certificate_type_t = ctypes.c_int
575
524
class datum_t(ctypes.Structure):
576
525
_fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
577
526
('size', ctypes.c_uint)]
579
527
class openpgp_crt_int(ctypes.Structure):
581
529
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
582
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
530
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
583
531
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
584
532
credentials_type_t = ctypes.c_int
585
533
transport_ptr_t = ctypes.c_void_p
586
534
close_request_t = ctypes.c_int
589
537
class Error(Exception):
590
def __init__(self, message=None, code=None, args=()):
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=()):
591
543
# Default usage is by a message string, but if a return
592
544
# code is passed, convert it to a string with
593
545
# gnutls.strerror()
595
547
if message is None and code is not None:
596
message = gnutls.strerror(code)
597
return super(gnutls.Error, self).__init__(
548
message = GnuTLS.strerror(code)
549
return super(GnuTLS.Error, self).__init__(
600
552
class CertificateSecurityError(Error):
556
class Credentials(object):
605
557
def __init__(self):
606
558
self._c_object = gnutls.certificate_credentials_t()
607
559
gnutls.certificate_allocate_credentials(
608
560
ctypes.byref(self._c_object))
609
561
self.type = gnutls.CRD_CERTIFICATE
611
563
def __del__(self):
612
564
gnutls.certificate_free_credentials(self._c_object)
615
def __init__(self, socket, credentials=None):
566
class ClientSession(object):
567
def __init__(self, socket, credentials = None):
616
568
self._c_object = gnutls.session_t()
617
gnutls_flags = gnutls.CLIENT
618
if gnutls.check_version(b"3.5.6"):
619
gnutls_flags |= gnutls.NO_TICKETS
621
gnutls_flags |= gnutls.ENABLE_RAWPK
622
gnutls.init(ctypes.byref(self._c_object), gnutls_flags)
569
gnutls.init(ctypes.byref(self._c_object), gnutls.CLIENT)
624
570
gnutls.set_default_priority(self._c_object)
625
571
gnutls.transport_set_ptr(self._c_object, socket.fileno())
626
572
gnutls.handshake_set_private_extensions(self._c_object,
668
614
return _error_code(result)
669
615
result = func(*arguments)
672
618
# Unless otherwise indicated, the function declarations below are
673
619
# all from the gnutls/gnutls.h C header file.
676
622
priority_set_direct = _library.gnutls_priority_set_direct
677
623
priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
678
624
ctypes.POINTER(ctypes.c_char_p)]
679
625
priority_set_direct.restype = _error_code
681
627
init = _library.gnutls_init
682
628
init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
683
629
init.restype = _error_code
685
631
set_default_priority = _library.gnutls_set_default_priority
686
632
set_default_priority.argtypes = [session_t]
687
633
set_default_priority.restype = _error_code
689
635
record_send = _library.gnutls_record_send
690
636
record_send.argtypes = [session_t, ctypes.c_void_p,
692
638
record_send.restype = ctypes.c_ssize_t
693
639
record_send.errcheck = _retry_on_error
695
641
certificate_allocate_credentials = (
696
642
_library.gnutls_certificate_allocate_credentials)
697
643
certificate_allocate_credentials.argtypes = [
698
644
ctypes.POINTER(certificate_credentials_t)]
699
645
certificate_allocate_credentials.restype = _error_code
701
647
certificate_free_credentials = (
702
648
_library.gnutls_certificate_free_credentials)
703
certificate_free_credentials.argtypes = [
704
certificate_credentials_t]
649
certificate_free_credentials.argtypes = [certificate_credentials_t]
705
650
certificate_free_credentials.restype = None
707
652
handshake_set_private_extensions = (
708
653
_library.gnutls_handshake_set_private_extensions)
709
654
handshake_set_private_extensions.argtypes = [session_t,
711
656
handshake_set_private_extensions.restype = None
713
658
credentials_set = _library.gnutls_credentials_set
714
659
credentials_set.argtypes = [session_t, credentials_type_t,
716
661
credentials_set.restype = _error_code
718
663
strerror = _library.gnutls_strerror
719
664
strerror.argtypes = [ctypes.c_int]
720
665
strerror.restype = ctypes.c_char_p
722
667
certificate_type_get = _library.gnutls_certificate_type_get
723
668
certificate_type_get.argtypes = [session_t]
724
669
certificate_type_get.restype = _error_code
726
671
certificate_get_peers = _library.gnutls_certificate_get_peers
727
672
certificate_get_peers.argtypes = [session_t,
728
673
ctypes.POINTER(ctypes.c_uint)]
729
674
certificate_get_peers.restype = ctypes.POINTER(datum_t)
731
676
global_set_log_level = _library.gnutls_global_set_log_level
732
677
global_set_log_level.argtypes = [ctypes.c_int]
733
678
global_set_log_level.restype = None
735
680
global_set_log_function = _library.gnutls_global_set_log_function
736
681
global_set_log_function.argtypes = [log_func]
737
682
global_set_log_function.restype = None
739
684
deinit = _library.gnutls_deinit
740
685
deinit.argtypes = [session_t]
741
686
deinit.restype = None
743
688
handshake = _library.gnutls_handshake
744
689
handshake.argtypes = [session_t]
745
690
handshake.restype = _error_code
746
691
handshake.errcheck = _retry_on_error
748
693
transport_set_ptr = _library.gnutls_transport_set_ptr
749
694
transport_set_ptr.argtypes = [session_t, transport_ptr_t]
750
695
transport_set_ptr.restype = None
752
697
bye = _library.gnutls_bye
753
698
bye.argtypes = [session_t, close_request_t]
754
699
bye.restype = _error_code
755
700
bye.errcheck = _retry_on_error
757
702
check_version = _library.gnutls_check_version
758
703
check_version.argtypes = [ctypes.c_char_p]
759
704
check_version.restype = ctypes.c_char_p
761
_need_version = b"3.3.0"
762
if check_version(_need_version) is None:
763
raise self.Error("Needs GnuTLS {} or later"
764
.format(_need_version))
766
_tls_rawpk_version = b"3.6.6"
767
has_rawpk = bool(check_version(_tls_rawpk_version))
771
class pubkey_st(ctypes.Structure):
773
pubkey_t = ctypes.POINTER(pubkey_st)
775
x509_crt_fmt_t = ctypes.c_int
777
# All the function declarations below are from gnutls/abstract.h
778
pubkey_init = _library.gnutls_pubkey_init
779
pubkey_init.argtypes = [ctypes.POINTER(pubkey_t)]
780
pubkey_init.restype = _error_code
782
pubkey_import = _library.gnutls_pubkey_import
783
pubkey_import.argtypes = [pubkey_t, ctypes.POINTER(datum_t),
785
pubkey_import.restype = _error_code
787
pubkey_get_key_id = _library.gnutls_pubkey_get_key_id
788
pubkey_get_key_id.argtypes = [pubkey_t, ctypes.c_int,
789
ctypes.POINTER(ctypes.c_ubyte),
790
ctypes.POINTER(ctypes.c_size_t)]
791
pubkey_get_key_id.restype = _error_code
793
pubkey_deinit = _library.gnutls_pubkey_deinit
794
pubkey_deinit.argtypes = [pubkey_t]
795
pubkey_deinit.restype = None
797
# All the function declarations below are from gnutls/openpgp.h
799
openpgp_crt_init = _library.gnutls_openpgp_crt_init
800
openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
801
openpgp_crt_init.restype = _error_code
803
openpgp_crt_import = _library.gnutls_openpgp_crt_import
804
openpgp_crt_import.argtypes = [openpgp_crt_t,
805
ctypes.POINTER(datum_t),
807
openpgp_crt_import.restype = _error_code
809
openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
810
openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
811
ctypes.POINTER(ctypes.c_uint)]
812
openpgp_crt_verify_self.restype = _error_code
814
openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
815
openpgp_crt_deinit.argtypes = [openpgp_crt_t]
816
openpgp_crt_deinit.restype = None
818
openpgp_crt_get_fingerprint = (
819
_library.gnutls_openpgp_crt_get_fingerprint)
820
openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
824
openpgp_crt_get_fingerprint.restype = _error_code
826
if check_version(b"3.6.4"):
827
certificate_type_get2 = _library.gnutls_certificate_type_get2
828
certificate_type_get2.argtypes = [session_t, ctypes.c_int]
829
certificate_type_get2.restype = _error_code
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
831
735
# Remove non-public functions
832
736
del _error_code, _retry_on_error
737
# Create the global "gnutls" object, simulating a module
835
740
def call_pipe(connection, # : multiprocessing.Connection
836
741
func, *args, **kwargs):
837
742
"""This function is meant to be called by multiprocessing.Process
839
744
This function runs func(*args, **kwargs), and writes the resulting
840
745
return value on the provided multiprocessing.Connection.
842
747
connection.send(func(*args, **kwargs))
843
748
connection.close()
750
class Client(object):
847
751
"""A representation of a client host served by this server.
850
754
approved: bool(); 'None' if not yet approved/disapproved
851
755
approval_delay: datetime.timedelta(); Time to wait for approval
852
756
approval_duration: datetime.timedelta(); Duration of one approval
853
checker: multiprocessing.Process(); a running checker process used
854
to see if the client lives. 'None' if no process is
856
checker_callback_tag: a GLib event source tag, or None
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
857
761
checker_command: string; External command which is run to check
858
762
if client lives. %() expansions are done at
859
763
runtime with vars(self) as dict, so that for
860
764
instance %(name)s can be used in the command.
861
checker_initiator_tag: a GLib event source tag, or None
765
checker_initiator_tag: a GObject event source tag, or None
862
766
created: datetime.datetime(); (UTC) object creation
863
767
client_structure: Object describing what attributes a client has
864
768
and is used for storing the client at exit
865
769
current_checker_command: string; current running checker_command
866
disable_initiator_tag: a GLib event source tag, or None
770
disable_initiator_tag: a GObject event source tag, or None
868
772
fingerprint: string (40 or 32 hexadecimal digits); used to
869
uniquely identify an OpenPGP client
870
key_id: string (64 hexadecimal digits); used to uniquely identify
871
a client using raw public keys
773
uniquely identify the client
872
774
host: string; available for use by the checker command
873
775
interval: datetime.timedelta(); How often to start a new checker
874
776
last_approval_request: datetime.datetime(); (UTC) or None
1031
930
logger.info("Disabling client %s", self.name)
1032
931
if getattr(self, "disable_initiator_tag", None) is not None:
1033
GLib.source_remove(self.disable_initiator_tag)
932
GObject.source_remove(self.disable_initiator_tag)
1034
933
self.disable_initiator_tag = None
1035
934
self.expires = None
1036
935
if getattr(self, "checker_initiator_tag", None) is not None:
1037
GLib.source_remove(self.checker_initiator_tag)
936
GObject.source_remove(self.checker_initiator_tag)
1038
937
self.checker_initiator_tag = None
1039
938
self.stop_checker()
1040
939
self.enabled = False
1042
941
self.send_changedstate()
1043
# Do not run this again if called by a GLib.timeout_add
942
# Do not run this again if called by a GObject.timeout_add
1046
945
def __del__(self):
1049
948
def init_checker(self):
1050
949
# Schedule a new checker to be started an 'interval' from now,
1051
950
# and every interval from then on.
1052
951
if self.checker_initiator_tag is not None:
1053
GLib.source_remove(self.checker_initiator_tag)
1054
self.checker_initiator_tag = GLib.timeout_add(
1055
random.randrange(int(self.interval.total_seconds() * 1000
952
GObject.source_remove(self.checker_initiator_tag)
953
self.checker_initiator_tag = GObject.timeout_add(
954
int(self.interval.total_seconds() * 1000),
1057
955
self.start_checker)
1058
956
# Schedule a disable() when 'timeout' has passed
1059
957
if self.disable_initiator_tag is not None:
1060
GLib.source_remove(self.disable_initiator_tag)
1061
self.disable_initiator_tag = GLib.timeout_add(
958
GObject.source_remove(self.disable_initiator_tag)
959
self.disable_initiator_tag = GObject.timeout_add(
1062
960
int(self.timeout.total_seconds() * 1000), self.disable)
1063
961
# Also start a new checker *right now*.
1064
962
self.start_checker()
1066
964
def checker_callback(self, source, condition, connection,
1068
966
"""The checker has completed, so take appropriate actions."""
967
self.checker_callback_tag = None
1069
969
# Read return code from connection (see call_pipe)
1070
970
returncode = connection.recv()
1071
971
connection.close()
1072
if self.checker is not None:
1074
self.checker_callback_tag = None
1077
973
if returncode >= 0:
1078
974
self.last_checker_status = returncode
1079
975
self.last_checker_signal = None
1535
1431
exc_info=error)
1536
1432
return xmlstring
1540
1435
dbus.OBJECT_MANAGER_IFACE
1541
1436
except AttributeError:
1542
1437
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1545
1439
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1546
1440
"""A D-Bus object with an ObjectManager.
1548
1442
Classes inheriting from this exposes the standard
1549
1443
GetManagedObjects call and the InterfacesAdded and
1550
1444
InterfacesRemoved signals on the standard
1551
1445
"org.freedesktop.DBus.ObjectManager" interface.
1553
1447
Note: No signals are sent automatically; they must be sent
1556
1450
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1557
out_signature="a{oa{sa{sv}}}")
1451
out_signature = "a{oa{sa{sv}}}")
1558
1452
def GetManagedObjects(self):
1559
1453
"""This function must be overridden"""
1560
1454
raise NotImplementedError()
1562
1456
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1563
signature="oa{sa{sv}}")
1457
signature = "oa{sa{sv}}")
1564
1458
def InterfacesAdded(self, object_path, interfaces_and_properties):
1567
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature="oas")
1461
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
1568
1462
def InterfacesRemoved(self, object_path, interfaces):
1571
1465
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1573
path_keyword='object_path',
1574
connection_keyword='connection')
1466
out_signature = "s",
1467
path_keyword = 'object_path',
1468
connection_keyword = 'connection')
1575
1469
def Introspect(self, object_path, connection):
1576
1470
"""Overloading of standard D-Bus method.
1578
1472
Override return argument name of GetManagedObjects to be
1579
1473
"objpath_interfaces_and_properties"
2522
class MultiprocessingMixIn:
2348
class MultiprocessingMixIn(object):
2523
2349
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
2525
2351
def sub_process_main(self, request, address):
2527
2353
self.finish_request(request, address)
2528
2354
except Exception:
2529
2355
self.handle_error(request, address)
2530
2356
self.close_request(request)
2532
2358
def process_request(self, request, address):
2533
2359
"""Start a new process to process the request."""
2534
proc = multiprocessing.Process(target=self.sub_process_main,
2535
args=(request, address))
2360
proc = multiprocessing.Process(target = self.sub_process_main,
2361
args = (request, address))
2540
class MultiprocessingMixInWithPipe(MultiprocessingMixIn):
2366
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
2541
2367
""" adds a pipe to the MixIn """
2543
2369
def process_request(self, request, client_address):
2544
2370
"""Overrides and wraps the original process_request().
2546
2372
This function creates a new pipe in self.pipe
2548
2374
parent_pipe, self.child_pipe = multiprocessing.Pipe()
2550
2376
proc = MultiprocessingMixIn.process_request(self, request,
2551
2377
client_address)
2552
2378
self.child_pipe.close()
2553
2379
self.add_pipe(parent_pipe, proc)
2555
2381
def add_pipe(self, parent_pipe, proc):
2556
2382
"""Dummy function; override as necessary"""
2557
2383
raise NotImplementedError()
2560
2386
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
2561
socketserver.TCPServer):
2387
socketserver.TCPServer, object):
2562
2388
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
2565
2391
enabled: Boolean; whether this server is activated yet
2566
2392
interface: None or a network interface name (string)
2567
2393
use_ipv6: Boolean; to use IPv6 or not
2570
2396
def __init__(self, server_address, RequestHandlerClass,
2571
2397
interface=None,
2606
2431
# socket_wrapper(), if socketfd was set.
2607
2432
socketserver.TCPServer.__init__(self, server_address,
2608
2433
RequestHandlerClass)
2610
2435
def server_bind(self):
2611
2436
"""This overrides the normal server_bind() function
2612
2437
to bind to an interface if one was specified, and also NOT to
2613
2438
bind to an address or port if they were not specified."""
2614
global SO_BINDTODEVICE
2615
2439
if self.interface is not None:
2616
2440
if SO_BINDTODEVICE is None:
2617
# Fall back to a hard-coded value which seems to be
2619
logger.warning("SO_BINDTODEVICE not found, trying 25")
2620
SO_BINDTODEVICE = 25
2622
self.socket.setsockopt(
2623
socket.SOL_SOCKET, SO_BINDTODEVICE,
2624
(self.interface + "\0").encode("utf-8"))
2625
except socket.error as error:
2626
if error.errno == errno.EPERM:
2627
logger.error("No permission to bind to"
2628
" interface %s", self.interface)
2629
elif error.errno == errno.ENOPROTOOPT:
2630
logger.error("SO_BINDTODEVICE not available;"
2631
" cannot bind to interface %s",
2633
elif error.errno == errno.ENODEV:
2634
logger.error("Interface %s does not exist,"
2635
" cannot bind", self.interface)
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)
2638
2462
# Only bind(2) the socket if we really need to.
2639
2463
if self.server_address[0] or self.server_address[1]:
2640
if self.server_address[1]:
2641
self.allow_reuse_address = True
2642
2464
if not self.server_address[0]:
2643
2465
if self.address_family == socket.AF_INET6:
2644
any_address = "::" # in6addr_any
2466
any_address = "::" # in6addr_any
2646
any_address = "0.0.0.0" # INADDR_ANY
2468
any_address = "0.0.0.0" # INADDR_ANY
2647
2469
self.server_address = (any_address,
2648
2470
self.server_address[1])
2649
2471
elif not self.server_address[1]:
2683
2505
self.gnutls_priority = gnutls_priority
2684
2506
IPv6_TCPServer.__init__(self, server_address,
2685
2507
RequestHandlerClass,
2686
interface=interface,
2508
interface = interface,
2509
use_ipv6 = use_ipv6,
2510
socketfd = socketfd)
2690
2512
def server_activate(self):
2691
2513
if self.enabled:
2692
2514
return socketserver.TCPServer.server_activate(self)
2694
2516
def enable(self):
2695
2517
self.enabled = True
2697
2519
def add_pipe(self, parent_pipe, proc):
2698
2520
# Call "handle_ipc" for both data and EOF events
2700
GLib.IOChannel.unix_new(parent_pipe.fileno()),
2701
GLib.PRIORITY_DEFAULT, GLib.IO_IN | GLib.IO_HUP,
2521
GObject.io_add_watch(
2522
parent_pipe.fileno(),
2523
GObject.IO_IN | GObject.IO_HUP,
2702
2524
functools.partial(self.handle_ipc,
2703
parent_pipe=parent_pipe,
2525
parent_pipe = parent_pipe,
2706
2528
def handle_ipc(self, source, condition,
2707
2529
parent_pipe=None,
2709
2531
client_object=None):
2710
2532
# error, or the other end of multiprocessing.Pipe has closed
2711
if condition & (GLib.IO_ERR | GLib.IO_HUP):
2533
if condition & (GObject.IO_ERR | GObject.IO_HUP):
2712
2534
# Wait for other process to exit
2716
2538
# Read a request from the child
2717
2539
request = parent_pipe.recv()
2718
2540
command = request[0]
2720
2542
if command == 'init':
2721
key_id = request[1].decode("ascii")
2722
fpr = request[2].decode("ascii")
2723
address = request[3]
2544
address = request[2]
2725
2546
for c in self.clients.values():
2726
if key_id == "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855":
2728
if key_id and c.key_id == key_id:
2731
if fpr and c.fingerprint == fpr:
2547
if c.fingerprint == fpr:
2735
logger.info("Client not found for key ID: %s, address"
2736
": %s", key_id or fpr, address)
2551
logger.info("Client not found for fingerprint: %s, ad"
2552
"dress: %s", fpr, address)
2737
2553
if self.use_dbus:
2738
2554
# Emit D-Bus signal
2739
mandos_dbus_service.ClientNotFound(key_id or fpr,
2555
mandos_dbus_service.ClientNotFound(fpr,
2741
2557
parent_pipe.send(False)
2745
GLib.IOChannel.unix_new(parent_pipe.fileno()),
2746
GLib.PRIORITY_DEFAULT, GLib.IO_IN | GLib.IO_HUP,
2560
GObject.io_add_watch(
2561
parent_pipe.fileno(),
2562
GObject.IO_IN | GObject.IO_HUP,
2747
2563
functools.partial(self.handle_ipc,
2748
parent_pipe=parent_pipe,
2750
client_object=client))
2564
parent_pipe = parent_pipe,
2566
client_object = client))
2751
2567
parent_pipe.send(True)
2752
2568
# remove the old hook in favor of the new above hook on
2756
2572
funcname = request[1]
2757
2573
args = request[2]
2758
2574
kwargs = request[3]
2760
2576
parent_pipe.send(('data', getattr(client_object,
2761
2577
funcname)(*args,
2764
2580
if command == 'getattr':
2765
2581
attrname = request[1]
2766
2582
if isinstance(client_object.__getattribute__(attrname),
2767
collections.abc.Callable):
2583
collections.Callable):
2768
2584
parent_pipe.send(('function', ))
2770
2586
parent_pipe.send((
2771
2587
'data', client_object.__getattribute__(attrname)))
2773
2589
if command == 'setattr':
2774
2590
attrname = request[1]
2775
2591
value = request[2]
2776
2592
setattr(client_object, attrname, value)
2781
2597
def rfc3339_duration_to_delta(duration):
2782
2598
"""Parse an RFC 3339 "duration" and return a datetime.timedelta
2784
>>> rfc3339_duration_to_delta("P7D") == datetime.timedelta(7)
2786
>>> rfc3339_duration_to_delta("PT60S") == datetime.timedelta(0, 60)
2788
>>> rfc3339_duration_to_delta("PT60M") == datetime.timedelta(0, 3600)
2790
>>> rfc3339_duration_to_delta("PT24H") == datetime.timedelta(1)
2792
>>> rfc3339_duration_to_delta("P1W") == datetime.timedelta(7)
2794
>>> rfc3339_duration_to_delta("PT5M30S") == datetime.timedelta(0, 330)
2796
>>> rfc3339_duration_to_delta("P1DT3M20S") == datetime.timedelta(1, 200)
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)
2800
2616
# Parsing an RFC 3339 duration with regular expressions is not
2801
2617
# possible - there would have to be multiple places for the same
2802
2618
# values, like seconds. The current code, while more esoteric, is
2803
2619
# cleaner without depending on a parsing library. If Python had a
2804
2620
# built-in library for parsing we would use it, but we'd like to
2805
2621
# avoid excessive use of external libraries.
2807
2623
# New type for defining tokens, syntax, and semantics all-in-one
2808
2624
Token = collections.namedtuple("Token", (
2809
2625
"regexp", # To match token; if "value" is not None, must have
2996
2809
parser.add_argument("--no-zeroconf", action="store_false",
2997
2810
dest="zeroconf", help="Do not use Zeroconf",
3000
2813
options = parser.parse_args()
2817
fail_count, test_count = doctest.testmod()
2818
sys.exit(os.EX_OK if fail_count == 0 else 1)
3002
2820
# Default values for config file for server-global settings
3003
if gnutls.has_rawpk:
3004
priority = ("SECURE128:!CTYPE-X.509:+CTYPE-RAWPK:!RSA"
3005
":!VERS-ALL:+VERS-TLS1.3:%PROFILE_ULTRA")
3007
priority = ("SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
3008
":+SIGN-DSA-SHA256")
3009
server_defaults = {"interface": "",
3013
"priority": priority,
3014
"servicename": "Mandos",
3020
"statedir": "/var/lib/mandos",
3021
"foreground": "False",
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",
3026
2839
# Parse config file for server-global settings
3027
server_config = configparser.ConfigParser(server_defaults)
2840
server_config = configparser.SafeConfigParser(server_defaults)
3028
2841
del server_defaults
3029
2842
server_config.read(os.path.join(options.configdir, "mandos.conf"))
3030
# Convert the ConfigParser object to a dict
2843
# Convert the SafeConfigParser object to a dict
3031
2844
server_settings = server_config.defaults()
3032
2845
# Use the appropriate methods on the non-string config options
3033
for option in ("debug", "use_dbus", "use_ipv6", "restore",
3034
"foreground", "zeroconf"):
2846
for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
3035
2847
server_settings[option] = server_config.getboolean("DEFAULT",
3037
2849
if server_settings["port"]:
3210
3022
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
3211
3023
service = AvahiServiceToSyslog(
3212
name=server_settings["servicename"],
3213
servicetype="_mandos._tcp",
3024
name = server_settings["servicename"],
3025
servicetype = "_mandos._tcp",
3026
protocol = protocol,
3216
3028
if server_settings["interface"]:
3217
3029
service.interface = if_nametoindex(
3218
3030
server_settings["interface"].encode("utf-8"))
3220
3032
global multiprocessing_manager
3221
3033
multiprocessing_manager = multiprocessing.Manager()
3223
3035
client_class = Client
3225
client_class = functools.partial(ClientDBus, bus=bus)
3037
client_class = functools.partial(ClientDBus, bus = bus)
3227
3039
client_settings = Client.config_parser(client_config)
3228
3040
old_client_settings = {}
3229
3041
clients_data = {}
3231
3043
# This is used to redirect stdout and stderr for checker processes
3233
wnull = open(os.devnull, "w") # A writable /dev/null
3045
wnull = open(os.devnull, "w") # A writable /dev/null
3234
3046
# Only used if server is running in foreground but not in debug
3236
3048
if debug or not foreground:
3239
3051
# Get client data and settings from last running state.
3240
3052
if server_settings["restore"]:
3242
3054
with open(stored_state_path, "rb") as stored_state:
3243
if sys.version_info.major == 2:
3055
if sys.version_info.major == 2:
3244
3056
clients_data, old_client_settings = pickle.load(
3247
3059
bytes_clients_data, bytes_old_client_settings = (
3248
pickle.load(stored_state, encoding="bytes"))
3249
# Fix bytes to strings
3060
pickle.load(stored_state, encoding = "bytes"))
3061
### Fix bytes to strings
3252
clients_data = {(key.decode("utf-8")
3253
if isinstance(key, bytes)
3256
bytes_clients_data.items()}
3257
del bytes_clients_data
3064
clients_data = { (key.decode("utf-8")
3065
if isinstance(key, bytes)
3068
bytes_clients_data.items() }
3258
3069
for key in clients_data:
3259
value = {(k.decode("utf-8")
3260
if isinstance(k, bytes) else k): v
3262
clients_data[key].items()}
3070
value = { (k.decode("utf-8")
3071
if isinstance(k, bytes) else k): v
3073
clients_data[key].items() }
3263
3074
clients_data[key] = value
3264
3075
# .client_structure
3265
3076
value["client_structure"] = [
3266
3077
(s.decode("utf-8")
3267
3078
if isinstance(s, bytes)
3268
3079
else s) for s in
3269
value["client_structure"]]
3270
# .name, .host, and .checker_command
3271
for k in ("name", "host", "checker_command"):
3080
value["client_structure"] ]
3082
for k in ("name", "host"):
3272
3083
if isinstance(value[k], bytes):
3273
3084
value[k] = value[k].decode("utf-8")
3274
if "key_id" not in value:
3275
value["key_id"] = ""
3276
elif "fingerprint" not in value:
3277
value["fingerprint"] = ""
3278
# old_client_settings
3085
## old_client_settings
3280
3087
old_client_settings = {
3281
3088
(key.decode("utf-8")
3282
3089
if isinstance(key, bytes)
3283
3090
else key): value
3284
3091
for key, value in
3285
bytes_old_client_settings.items()}
3286
del bytes_old_client_settings
3287
# .host and .checker_command
3092
bytes_old_client_settings.items() }
3288
3094
for value in old_client_settings.values():
3289
for attribute in ("host", "checker_command"):
3290
if isinstance(value[attribute], bytes):
3291
value[attribute] = (value[attribute]
3095
value["host"] = value["host"].decode("utf-8")
3293
3096
os.remove(stored_state_path)
3294
3097
except IOError as e:
3295
3098
if e.errno == errno.ENOENT: