2
2
# -*- mode: python; coding: utf-8 -*-
4
4
# Mandos server - give out binary blobs to connecting clients.
6
6
# This program is partly derived from an example program for an Avahi
7
7
# service publisher, downloaded from
8
8
# <http://avahi.org/wiki/PythonPublishExample>. This includes the
9
9
# methods "add", "remove", "server_state_changed",
10
10
# "entry_group_state_changed", "cleanup", and "activate" in the
11
11
# "AvahiService" class, and some lines in "main".
13
13
# Everything else is
14
# 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,
254
227
'--passphrase-file',
256
229
+ self.gnupgargs,
257
stdin=subprocess.PIPE,
258
stdout=subprocess.PIPE,
259
stderr=subprocess.PIPE)
260
ciphertext, err = proc.communicate(input=data)
230
stdin = subprocess.PIPE,
231
stdout = subprocess.PIPE,
232
stderr = subprocess.PIPE)
233
ciphertext, err = proc.communicate(input = data)
261
234
if proc.returncode != 0:
262
235
raise PGPError(err)
263
236
return ciphertext
265
238
def decrypt(self, data, password):
266
239
passphrase = self.password_encode(password)
267
240
with tempfile.NamedTemporaryFile(
268
dir=self.tempdir) as passfile:
241
dir = self.tempdir) as passfile:
269
242
passfile.write(passphrase)
271
244
proc = subprocess.Popen([self.gpg, '--decrypt',
272
245
'--passphrase-file',
274
247
+ self.gnupgargs,
275
stdin=subprocess.PIPE,
276
stdout=subprocess.PIPE,
277
stderr=subprocess.PIPE)
278
decrypted_plaintext, err = proc.communicate(input=data)
248
stdin = subprocess.PIPE,
249
stdout = subprocess.PIPE,
250
stderr = subprocess.PIPE)
251
decrypted_plaintext, err = proc.communicate(input = data)
279
252
if proc.returncode != 0:
280
253
raise PGPError(err)
281
254
return decrypted_plaintext
284
256
# Pretend that we have an Avahi module
286
"""This isn't so much a class as it is a module-like namespace."""
287
IF_UNSPEC = -1 # avahi-common/address.h
288
PROTO_UNSPEC = -1 # avahi-common/address.h
289
PROTO_INET = 0 # avahi-common/address.h
290
PROTO_INET6 = 1 # avahi-common/address.h
258
"""This isn't so much a class as it is a module-like namespace.
259
It is instantiated once, and simulates having an Avahi module."""
260
IF_UNSPEC = -1 # avahi-common/address.h
261
PROTO_UNSPEC = -1 # avahi-common/address.h
262
PROTO_INET = 0 # avahi-common/address.h
263
PROTO_INET6 = 1 # avahi-common/address.h
291
264
DBUS_NAME = "org.freedesktop.Avahi"
292
265
DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"
293
266
DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
294
267
DBUS_PATH_SERVER = "/"
297
def string_array_to_txt_array(t):
268
def string_array_to_txt_array(self, t):
298
269
return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
299
270
for s in t), signature="ay")
300
ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h
301
ENTRY_GROUP_COLLISION = 3 # avahi-common/defs.h
302
ENTRY_GROUP_FAILURE = 4 # avahi-common/defs.h
303
SERVER_INVALID = 0 # avahi-common/defs.h
304
SERVER_REGISTERING = 1 # avahi-common/defs.h
305
SERVER_RUNNING = 2 # avahi-common/defs.h
306
SERVER_COLLISION = 3 # avahi-common/defs.h
307
SERVER_FAILURE = 4 # avahi-common/defs.h
271
ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h
272
ENTRY_GROUP_COLLISION = 3 # avahi-common/defs.h
273
ENTRY_GROUP_FAILURE = 4 # avahi-common/defs.h
274
SERVER_INVALID = 0 # avahi-common/defs.h
275
SERVER_REGISTERING = 1 # avahi-common/defs.h
276
SERVER_RUNNING = 2 # avahi-common/defs.h
277
SERVER_COLLISION = 3 # avahi-common/defs.h
278
SERVER_FAILURE = 4 # avahi-common/defs.h
310
281
class AvahiError(Exception):
311
282
def __init__(self, value, *args, **kwargs):
502
473
class AvahiServiceToSyslog(AvahiService):
503
474
def rename(self, *args, **kwargs):
504
475
"""Add the new name to the syslog messages"""
505
ret = super(AvahiServiceToSyslog, self).rename(*args, **kwargs)
476
ret = AvahiService.rename(self, *args, **kwargs)
506
477
syslogger.setFormatter(logging.Formatter(
507
478
'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
508
479
.format(self.name)))
512
482
# Pretend that we have a GnuTLS module
514
"""This isn't so much a class as it is a module-like namespace."""
516
library = ctypes.util.find_library("gnutls")
518
library = ctypes.util.find_library("gnutls-deb0")
519
_library = ctypes.cdll.LoadLibrary(library)
483
class GnuTLS(object):
484
"""This isn't so much a class as it is a module-like namespace.
485
It is instantiated once, and simulates having a GnuTLS module."""
487
_library = ctypes.cdll.LoadLibrary(
488
ctypes.util.find_library("gnutls"))
489
_need_version = b"3.3.0"
491
# Need to use class name "GnuTLS" here, since this method is
492
# called before the assignment to the "gnutls" global variable
494
if GnuTLS.check_version(self._need_version) is None:
495
raise GnuTLS.Error("Needs GnuTLS {} or later"
496
.format(self._need_version))
522
498
# Unless otherwise indicated, the constants and types below are
523
499
# all from the gnutls/gnutls.h C header file.
527
503
E_INTERRUPTED = -52
533
508
CRD_CERTIFICATE = 1
534
509
E_NO_CERTIFICATE_FOUND = -49
539
KEYID_USE_SHA256 = 1 # gnutls/x509.h
540
510
OPENPGP_FMT_RAW = 0 # gnutls/openpgp.h
543
513
class session_int(ctypes.Structure):
545
515
session_t = ctypes.POINTER(session_int)
547
516
class certificate_credentials_st(ctypes.Structure):
549
518
certificate_credentials_t = ctypes.POINTER(
550
519
certificate_credentials_st)
551
520
certificate_type_t = ctypes.c_int
553
521
class datum_t(ctypes.Structure):
554
522
_fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
555
523
('size', ctypes.c_uint)]
557
524
class openpgp_crt_int(ctypes.Structure):
559
526
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
560
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
527
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
561
528
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
562
529
credentials_type_t = ctypes.c_int
563
530
transport_ptr_t = ctypes.c_void_p
564
531
close_request_t = ctypes.c_int
567
534
class Error(Exception):
568
def __init__(self, message=None, code=None, args=()):
535
# We need to use the class name "GnuTLS" here, since this
536
# exception might be raised from within GnuTLS.__init__,
537
# which is called before the assignment to the "gnutls"
538
# global variable has happened.
539
def __init__(self, message = None, code = None, args=()):
569
540
# Default usage is by a message string, but if a return
570
541
# code is passed, convert it to a string with
571
542
# gnutls.strerror()
573
544
if message is None and code is not None:
574
message = gnutls.strerror(code)
575
return super(gnutls.Error, self).__init__(
545
message = GnuTLS.strerror(code)
546
return super(GnuTLS.Error, self).__init__(
578
549
class CertificateSecurityError(Error):
553
class Credentials(object):
583
554
def __init__(self):
584
555
self._c_object = gnutls.certificate_credentials_t()
585
556
gnutls.certificate_allocate_credentials(
586
557
ctypes.byref(self._c_object))
587
558
self.type = gnutls.CRD_CERTIFICATE
589
560
def __del__(self):
590
561
gnutls.certificate_free_credentials(self._c_object)
593
def __init__(self, socket, credentials=None):
563
class ClientSession(object):
564
def __init__(self, socket, credentials = None):
594
565
self._c_object = gnutls.session_t()
595
gnutls_flags = gnutls.CLIENT
596
if gnutls.check_version(b"3.5.6"):
597
gnutls_flags |= gnutls.NO_TICKETS
599
gnutls_flags |= gnutls.ENABLE_RAWPK
600
gnutls.init(ctypes.byref(self._c_object), gnutls_flags)
566
gnutls.init(ctypes.byref(self._c_object), gnutls.CLIENT)
602
567
gnutls.set_default_priority(self._c_object)
603
568
gnutls.transport_set_ptr(self._c_object, socket.fileno())
604
569
gnutls.handshake_set_private_extensions(self._c_object,
646
611
return _error_code(result)
647
612
result = func(*arguments)
650
615
# Unless otherwise indicated, the function declarations below are
651
616
# all from the gnutls/gnutls.h C header file.
654
619
priority_set_direct = _library.gnutls_priority_set_direct
655
620
priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
656
621
ctypes.POINTER(ctypes.c_char_p)]
657
622
priority_set_direct.restype = _error_code
659
624
init = _library.gnutls_init
660
625
init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
661
626
init.restype = _error_code
663
628
set_default_priority = _library.gnutls_set_default_priority
664
629
set_default_priority.argtypes = [session_t]
665
630
set_default_priority.restype = _error_code
667
632
record_send = _library.gnutls_record_send
668
633
record_send.argtypes = [session_t, ctypes.c_void_p,
670
635
record_send.restype = ctypes.c_ssize_t
671
636
record_send.errcheck = _retry_on_error
673
638
certificate_allocate_credentials = (
674
639
_library.gnutls_certificate_allocate_credentials)
675
640
certificate_allocate_credentials.argtypes = [
676
641
ctypes.POINTER(certificate_credentials_t)]
677
642
certificate_allocate_credentials.restype = _error_code
679
644
certificate_free_credentials = (
680
645
_library.gnutls_certificate_free_credentials)
681
certificate_free_credentials.argtypes = [
682
certificate_credentials_t]
646
certificate_free_credentials.argtypes = [certificate_credentials_t]
683
647
certificate_free_credentials.restype = None
685
649
handshake_set_private_extensions = (
686
650
_library.gnutls_handshake_set_private_extensions)
687
651
handshake_set_private_extensions.argtypes = [session_t,
689
653
handshake_set_private_extensions.restype = None
691
655
credentials_set = _library.gnutls_credentials_set
692
656
credentials_set.argtypes = [session_t, credentials_type_t,
694
658
credentials_set.restype = _error_code
696
660
strerror = _library.gnutls_strerror
697
661
strerror.argtypes = [ctypes.c_int]
698
662
strerror.restype = ctypes.c_char_p
700
664
certificate_type_get = _library.gnutls_certificate_type_get
701
665
certificate_type_get.argtypes = [session_t]
702
666
certificate_type_get.restype = _error_code
704
668
certificate_get_peers = _library.gnutls_certificate_get_peers
705
669
certificate_get_peers.argtypes = [session_t,
706
670
ctypes.POINTER(ctypes.c_uint)]
707
671
certificate_get_peers.restype = ctypes.POINTER(datum_t)
709
673
global_set_log_level = _library.gnutls_global_set_log_level
710
674
global_set_log_level.argtypes = [ctypes.c_int]
711
675
global_set_log_level.restype = None
713
677
global_set_log_function = _library.gnutls_global_set_log_function
714
678
global_set_log_function.argtypes = [log_func]
715
679
global_set_log_function.restype = None
717
681
deinit = _library.gnutls_deinit
718
682
deinit.argtypes = [session_t]
719
683
deinit.restype = None
721
685
handshake = _library.gnutls_handshake
722
686
handshake.argtypes = [session_t]
723
687
handshake.restype = _error_code
724
688
handshake.errcheck = _retry_on_error
726
690
transport_set_ptr = _library.gnutls_transport_set_ptr
727
691
transport_set_ptr.argtypes = [session_t, transport_ptr_t]
728
692
transport_set_ptr.restype = None
730
694
bye = _library.gnutls_bye
731
695
bye.argtypes = [session_t, close_request_t]
732
696
bye.restype = _error_code
733
697
bye.errcheck = _retry_on_error
735
699
check_version = _library.gnutls_check_version
736
700
check_version.argtypes = [ctypes.c_char_p]
737
701
check_version.restype = ctypes.c_char_p
739
_need_version = b"3.3.0"
740
if check_version(_need_version) is None:
741
raise self.Error("Needs GnuTLS {} or later"
742
.format(_need_version))
744
_tls_rawpk_version = b"3.6.6"
745
has_rawpk = bool(check_version(_tls_rawpk_version))
749
class pubkey_st(ctypes.Structure):
751
pubkey_t = ctypes.POINTER(pubkey_st)
753
x509_crt_fmt_t = ctypes.c_int
755
# All the function declarations below are from gnutls/abstract.h
756
pubkey_init = _library.gnutls_pubkey_init
757
pubkey_init.argtypes = [ctypes.POINTER(pubkey_t)]
758
pubkey_init.restype = _error_code
760
pubkey_import = _library.gnutls_pubkey_import
761
pubkey_import.argtypes = [pubkey_t, ctypes.POINTER(datum_t),
763
pubkey_import.restype = _error_code
765
pubkey_get_key_id = _library.gnutls_pubkey_get_key_id
766
pubkey_get_key_id.argtypes = [pubkey_t, ctypes.c_int,
767
ctypes.POINTER(ctypes.c_ubyte),
768
ctypes.POINTER(ctypes.c_size_t)]
769
pubkey_get_key_id.restype = _error_code
771
pubkey_deinit = _library.gnutls_pubkey_deinit
772
pubkey_deinit.argtypes = [pubkey_t]
773
pubkey_deinit.restype = None
775
# All the function declarations below are from gnutls/openpgp.h
777
openpgp_crt_init = _library.gnutls_openpgp_crt_init
778
openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
779
openpgp_crt_init.restype = _error_code
781
openpgp_crt_import = _library.gnutls_openpgp_crt_import
782
openpgp_crt_import.argtypes = [openpgp_crt_t,
783
ctypes.POINTER(datum_t),
785
openpgp_crt_import.restype = _error_code
787
openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
788
openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
789
ctypes.POINTER(ctypes.c_uint)]
790
openpgp_crt_verify_self.restype = _error_code
792
openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
793
openpgp_crt_deinit.argtypes = [openpgp_crt_t]
794
openpgp_crt_deinit.restype = None
796
openpgp_crt_get_fingerprint = (
797
_library.gnutls_openpgp_crt_get_fingerprint)
798
openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
802
openpgp_crt_get_fingerprint.restype = _error_code
804
if check_version(b"3.6.4"):
805
certificate_type_get2 = _library.gnutls_certificate_type_get2
806
certificate_type_get2.argtypes = [session_t, ctypes.c_int]
807
certificate_type_get2.restype = _error_code
703
# All the function declarations below are from gnutls/openpgp.h
705
openpgp_crt_init = _library.gnutls_openpgp_crt_init
706
openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
707
openpgp_crt_init.restype = _error_code
709
openpgp_crt_import = _library.gnutls_openpgp_crt_import
710
openpgp_crt_import.argtypes = [openpgp_crt_t,
711
ctypes.POINTER(datum_t),
713
openpgp_crt_import.restype = _error_code
715
openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
716
openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
717
ctypes.POINTER(ctypes.c_uint)]
718
openpgp_crt_verify_self.restype = _error_code
720
openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
721
openpgp_crt_deinit.argtypes = [openpgp_crt_t]
722
openpgp_crt_deinit.restype = None
724
openpgp_crt_get_fingerprint = (
725
_library.gnutls_openpgp_crt_get_fingerprint)
726
openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
730
openpgp_crt_get_fingerprint.restype = _error_code
809
732
# Remove non-public functions
810
733
del _error_code, _retry_on_error
734
# Create the global "gnutls" object, simulating a module
813
737
def call_pipe(connection, # : multiprocessing.Connection
814
738
func, *args, **kwargs):
815
739
"""This function is meant to be called by multiprocessing.Process
817
741
This function runs func(*args, **kwargs), and writes the resulting
818
742
return value on the provided multiprocessing.Connection.
820
744
connection.send(func(*args, **kwargs))
821
745
connection.close()
747
class Client(object):
825
748
"""A representation of a client host served by this server.
828
751
approved: bool(); 'None' if not yet approved/disapproved
829
752
approval_delay: datetime.timedelta(); Time to wait for approval
830
753
approval_duration: datetime.timedelta(); Duration of one approval
831
checker: multiprocessing.Process(); a running checker process used
832
to see if the client lives. 'None' if no process is
754
checker: subprocess.Popen(); a running checker process used
755
to see if the client lives.
756
'None' if no process is running.
834
757
checker_callback_tag: a GLib event source tag, or None
835
758
checker_command: string; External command which is run to check
836
759
if client lives. %() expansions are done at
1511
1428
exc_info=error)
1512
1429
return xmlstring
1516
1432
dbus.OBJECT_MANAGER_IFACE
1517
1433
except AttributeError:
1518
1434
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1521
1436
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1522
1437
"""A D-Bus object with an ObjectManager.
1524
1439
Classes inheriting from this exposes the standard
1525
1440
GetManagedObjects call and the InterfacesAdded and
1526
1441
InterfacesRemoved signals on the standard
1527
1442
"org.freedesktop.DBus.ObjectManager" interface.
1529
1444
Note: No signals are sent automatically; they must be sent
1532
1447
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1533
out_signature="a{oa{sa{sv}}}")
1448
out_signature = "a{oa{sa{sv}}}")
1534
1449
def GetManagedObjects(self):
1535
1450
"""This function must be overridden"""
1536
1451
raise NotImplementedError()
1538
1453
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1539
signature="oa{sa{sv}}")
1454
signature = "oa{sa{sv}}")
1540
1455
def InterfacesAdded(self, object_path, interfaces_and_properties):
1543
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature="oas")
1458
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
1544
1459
def InterfacesRemoved(self, object_path, interfaces):
1547
1462
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1549
path_keyword='object_path',
1550
connection_keyword='connection')
1463
out_signature = "s",
1464
path_keyword = 'object_path',
1465
connection_keyword = 'connection')
1551
1466
def Introspect(self, object_path, connection):
1552
1467
"""Overloading of standard D-Bus method.
1554
1469
Override return argument name of GetManagedObjects to be
1555
1470
"objpath_interfaces_and_properties"
2247
2152
class ClientHandler(socketserver.BaseRequestHandler, object):
2248
2153
"""A class to handle client connections.
2250
2155
Instantiated once for each connection to handle it.
2251
2156
Note: This will run in its own forked process."""
2253
2158
def handle(self):
2254
2159
with contextlib.closing(self.server.child_pipe) as child_pipe:
2255
2160
logger.info("TCP connection from: %s",
2256
2161
str(self.client_address))
2257
2162
logger.debug("Pipe FD: %d",
2258
2163
self.server.child_pipe.fileno())
2260
2165
session = gnutls.ClientSession(self.request)
2262
# priority = ':'.join(("NONE", "+VERS-TLS1.1",
2263
# "+AES-256-CBC", "+SHA1",
2264
# "+COMP-NULL", "+CTYPE-OPENPGP",
2167
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
2168
# "+AES-256-CBC", "+SHA1",
2169
# "+COMP-NULL", "+CTYPE-OPENPGP",
2266
2171
# Use a fallback default, since this MUST be set.
2267
2172
priority = self.server.gnutls_priority
2268
2173
if priority is None:
2269
2174
priority = "NORMAL"
2270
gnutls.priority_set_direct(session._c_object,
2271
priority.encode("utf-8"),
2175
gnutls.priority_set_direct(session._c_object, priority,
2274
2178
# Start communication using the Mandos protocol
2275
2179
# Get protocol number
2276
2180
line = self.request.makefile().readline()
2498
class MultiprocessingMixIn:
2345
class MultiprocessingMixIn(object):
2499
2346
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
2501
2348
def sub_process_main(self, request, address):
2503
2350
self.finish_request(request, address)
2504
2351
except Exception:
2505
2352
self.handle_error(request, address)
2506
2353
self.close_request(request)
2508
2355
def process_request(self, request, address):
2509
2356
"""Start a new process to process the request."""
2510
proc = multiprocessing.Process(target=self.sub_process_main,
2511
args=(request, address))
2357
proc = multiprocessing.Process(target = self.sub_process_main,
2358
args = (request, address))
2516
class MultiprocessingMixInWithPipe(MultiprocessingMixIn):
2363
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
2517
2364
""" adds a pipe to the MixIn """
2519
2366
def process_request(self, request, client_address):
2520
2367
"""Overrides and wraps the original process_request().
2522
2369
This function creates a new pipe in self.pipe
2524
2371
parent_pipe, self.child_pipe = multiprocessing.Pipe()
2526
2373
proc = MultiprocessingMixIn.process_request(self, request,
2527
2374
client_address)
2528
2375
self.child_pipe.close()
2529
2376
self.add_pipe(parent_pipe, proc)
2531
2378
def add_pipe(self, parent_pipe, proc):
2532
2379
"""Dummy function; override as necessary"""
2533
2380
raise NotImplementedError()
2536
2383
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
2537
socketserver.TCPServer):
2384
socketserver.TCPServer, object):
2538
2385
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
2541
2388
enabled: Boolean; whether this server is activated yet
2542
2389
interface: None or a network interface name (string)
2543
2390
use_ipv6: Boolean; to use IPv6 or not
2546
2393
def __init__(self, server_address, RequestHandlerClass,
2547
2394
interface=None,
2582
2428
# socket_wrapper(), if socketfd was set.
2583
2429
socketserver.TCPServer.__init__(self, server_address,
2584
2430
RequestHandlerClass)
2586
2432
def server_bind(self):
2587
2433
"""This overrides the normal server_bind() function
2588
2434
to bind to an interface if one was specified, and also NOT to
2589
2435
bind to an address or port if they were not specified."""
2590
global SO_BINDTODEVICE
2591
2436
if self.interface is not None:
2592
2437
if SO_BINDTODEVICE is None:
2593
# Fall back to a hard-coded value which seems to be
2595
logger.warning("SO_BINDTODEVICE not found, trying 25")
2596
SO_BINDTODEVICE = 25
2598
self.socket.setsockopt(
2599
socket.SOL_SOCKET, SO_BINDTODEVICE,
2600
(self.interface + "\0").encode("utf-8"))
2601
except socket.error as error:
2602
if error.errno == errno.EPERM:
2603
logger.error("No permission to bind to"
2604
" interface %s", self.interface)
2605
elif error.errno == errno.ENOPROTOOPT:
2606
logger.error("SO_BINDTODEVICE not available;"
2607
" cannot bind to interface %s",
2609
elif error.errno == errno.ENODEV:
2610
logger.error("Interface %s does not exist,"
2611
" cannot bind", self.interface)
2438
logger.error("SO_BINDTODEVICE does not exist;"
2439
" cannot bind to interface %s",
2443
self.socket.setsockopt(
2444
socket.SOL_SOCKET, SO_BINDTODEVICE,
2445
(self.interface + "\0").encode("utf-8"))
2446
except socket.error as error:
2447
if error.errno == errno.EPERM:
2448
logger.error("No permission to bind to"
2449
" interface %s", self.interface)
2450
elif error.errno == errno.ENOPROTOOPT:
2451
logger.error("SO_BINDTODEVICE not available;"
2452
" cannot bind to interface %s",
2454
elif error.errno == errno.ENODEV:
2455
logger.error("Interface %s does not exist,"
2456
" cannot bind", self.interface)
2614
2459
# Only bind(2) the socket if we really need to.
2615
2460
if self.server_address[0] or self.server_address[1]:
2616
if self.server_address[1]:
2617
self.allow_reuse_address = True
2618
2461
if not self.server_address[0]:
2619
2462
if self.address_family == socket.AF_INET6:
2620
any_address = "::" # in6addr_any
2463
any_address = "::" # in6addr_any
2622
any_address = "0.0.0.0" # INADDR_ANY
2465
any_address = "0.0.0.0" # INADDR_ANY
2623
2466
self.server_address = (any_address,
2624
2467
self.server_address[1])
2625
2468
elif not self.server_address[1]:
2659
2502
self.gnutls_priority = gnutls_priority
2660
2503
IPv6_TCPServer.__init__(self, server_address,
2661
2504
RequestHandlerClass,
2662
interface=interface,
2505
interface = interface,
2506
use_ipv6 = use_ipv6,
2507
socketfd = socketfd)
2666
2509
def server_activate(self):
2667
2510
if self.enabled:
2668
2511
return socketserver.TCPServer.server_activate(self)
2670
2513
def enable(self):
2671
2514
self.enabled = True
2673
2516
def add_pipe(self, parent_pipe, proc):
2674
2517
# Call "handle_ipc" for both data and EOF events
2675
2518
GLib.io_add_watch(
2676
2519
parent_pipe.fileno(),
2677
2520
GLib.IO_IN | GLib.IO_HUP,
2678
2521
functools.partial(self.handle_ipc,
2679
parent_pipe=parent_pipe,
2522
parent_pipe = parent_pipe,
2682
2525
def handle_ipc(self, source, condition,
2683
2526
parent_pipe=None,
2685
2528
client_object=None):
2686
2529
# error, or the other end of multiprocessing.Pipe has closed
2687
2530
if condition & (GLib.IO_ERR | GLib.IO_HUP):
2688
2531
# Wait for other process to exit
2692
2535
# Read a request from the child
2693
2536
request = parent_pipe.recv()
2694
2537
command = request[0]
2696
2539
if command == 'init':
2697
key_id = request[1].decode("ascii")
2698
fpr = request[2].decode("ascii")
2699
address = request[3]
2541
address = request[2]
2701
2543
for c in self.clients.values():
2702
if key_id == "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855":
2704
if key_id and c.key_id == key_id:
2707
if fpr and c.fingerprint == fpr:
2544
if c.fingerprint == fpr:
2711
logger.info("Client not found for key ID: %s, address"
2712
": %s", key_id or fpr, address)
2548
logger.info("Client not found for fingerprint: %s, ad"
2549
"dress: %s", fpr, address)
2713
2550
if self.use_dbus:
2714
2551
# Emit D-Bus signal
2715
mandos_dbus_service.ClientNotFound(key_id or fpr,
2552
mandos_dbus_service.ClientNotFound(fpr,
2717
2554
parent_pipe.send(False)
2720
2557
GLib.io_add_watch(
2721
2558
parent_pipe.fileno(),
2722
2559
GLib.IO_IN | GLib.IO_HUP,
2723
2560
functools.partial(self.handle_ipc,
2724
parent_pipe=parent_pipe,
2726
client_object=client))
2561
parent_pipe = parent_pipe,
2563
client_object = client))
2727
2564
parent_pipe.send(True)
2728
2565
# remove the old hook in favor of the new above hook on
2972
2806
parser.add_argument("--no-zeroconf", action="store_false",
2973
2807
dest="zeroconf", help="Do not use Zeroconf",
2976
2810
options = parser.parse_args()
2978
2812
if options.check:
2980
2814
fail_count, test_count = doctest.testmod()
2981
2815
sys.exit(os.EX_OK if fail_count == 0 else 1)
2983
2817
# Default values for config file for server-global settings
2984
if gnutls.has_rawpk:
2985
priority = ("SECURE128:!CTYPE-X.509:+CTYPE-RAWPK:!RSA"
2986
":!VERS-ALL:+VERS-TLS1.3:%PROFILE_ULTRA")
2988
priority = ("SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2989
":+SIGN-DSA-SHA256")
2990
server_defaults = {"interface": "",
2994
"priority": priority,
2995
"servicename": "Mandos",
3001
"statedir": "/var/lib/mandos",
3002
"foreground": "False",
2818
server_defaults = { "interface": "",
2823
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2824
":+SIGN-DSA-SHA256",
2825
"servicename": "Mandos",
2831
"statedir": "/var/lib/mandos",
2832
"foreground": "False",
3007
2836
# Parse config file for server-global settings
3008
server_config = configparser.ConfigParser(server_defaults)
2837
server_config = configparser.SafeConfigParser(server_defaults)
3009
2838
del server_defaults
3010
2839
server_config.read(os.path.join(options.configdir, "mandos.conf"))
3011
# Convert the ConfigParser object to a dict
2840
# Convert the SafeConfigParser object to a dict
3012
2841
server_settings = server_config.defaults()
3013
2842
# Use the appropriate methods on the non-string config options
3014
for option in ("debug", "use_dbus", "use_ipv6", "restore",
3015
"foreground", "zeroconf"):
2843
for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
3016
2844
server_settings[option] = server_config.getboolean("DEFAULT",
3018
2846
if server_settings["port"]:
3191
3019
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
3192
3020
service = AvahiServiceToSyslog(
3193
name=server_settings["servicename"],
3194
servicetype="_mandos._tcp",
3021
name = server_settings["servicename"],
3022
servicetype = "_mandos._tcp",
3023
protocol = protocol,
3197
3025
if server_settings["interface"]:
3198
3026
service.interface = if_nametoindex(
3199
3027
server_settings["interface"].encode("utf-8"))
3201
3029
global multiprocessing_manager
3202
3030
multiprocessing_manager = multiprocessing.Manager()
3204
3032
client_class = Client
3206
client_class = functools.partial(ClientDBus, bus=bus)
3034
client_class = functools.partial(ClientDBus, bus = bus)
3208
3036
client_settings = Client.config_parser(client_config)
3209
3037
old_client_settings = {}
3210
3038
clients_data = {}
3212
3040
# This is used to redirect stdout and stderr for checker processes
3214
wnull = open(os.devnull, "w") # A writable /dev/null
3042
wnull = open(os.devnull, "w") # A writable /dev/null
3215
3043
# Only used if server is running in foreground but not in debug
3217
3045
if debug or not foreground:
3220
3048
# Get client data and settings from last running state.
3221
3049
if server_settings["restore"]:
3223
3051
with open(stored_state_path, "rb") as stored_state:
3224
if sys.version_info.major == 2:
3052
if sys.version_info.major == 2:
3225
3053
clients_data, old_client_settings = pickle.load(
3228
3056
bytes_clients_data, bytes_old_client_settings = (
3229
pickle.load(stored_state, encoding="bytes"))
3230
# Fix bytes to strings
3057
pickle.load(stored_state, encoding = "bytes"))
3058
### Fix bytes to strings
3233
clients_data = {(key.decode("utf-8")
3234
if isinstance(key, bytes)
3237
bytes_clients_data.items()}
3061
clients_data = { (key.decode("utf-8")
3062
if isinstance(key, bytes)
3065
bytes_clients_data.items() }
3238
3066
del bytes_clients_data
3239
3067
for key in clients_data:
3240
value = {(k.decode("utf-8")
3241
if isinstance(k, bytes) else k): v
3243
clients_data[key].items()}
3068
value = { (k.decode("utf-8")
3069
if isinstance(k, bytes) else k): v
3071
clients_data[key].items() }
3244
3072
clients_data[key] = value
3245
3073
# .client_structure
3246
3074
value["client_structure"] = [
3247
3075
(s.decode("utf-8")
3248
3076
if isinstance(s, bytes)
3249
3077
else s) for s in
3250
value["client_structure"]]
3078
value["client_structure"] ]
3251
3079
# .name & .host
3252
3080
for k in ("name", "host"):
3253
3081
if isinstance(value[k], bytes):
3254
3082
value[k] = value[k].decode("utf-8")
3255
if "key_id" not in value:
3256
value["key_id"] = ""
3257
elif "fingerprint" not in value:
3258
value["fingerprint"] = ""
3259
# old_client_settings
3083
## old_client_settings
3261
3085
old_client_settings = {
3262
3086
(key.decode("utf-8")
3263
3087
if isinstance(key, bytes)
3264
3088
else key): value
3265
3089
for key, value in
3266
bytes_old_client_settings.items()}
3090
bytes_old_client_settings.items() }
3267
3091
del bytes_old_client_settings
3269
3093
for value in old_client_settings.values():