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-2015 Teddy Hogeborn
15
# Copyright © 2008-2015 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,
37
from future_builtins import *
39
from future_builtins import *
40
44
import SocketServer as socketserver
190
236
.replace(b"\n", b"\\n")
191
237
.replace(b"\0", b"\\x00"))
194
240
def encrypt(self, data, password):
195
241
passphrase = self.password_encode(password)
196
242
with tempfile.NamedTemporaryFile(
197
243
dir=self.tempdir) as passfile:
198
244
passfile.write(passphrase)
200
proc = subprocess.Popen(['gpg', '--symmetric',
246
proc = subprocess.Popen([self.gpg, '--symmetric',
201
247
'--passphrase-file',
203
249
+ self.gnupgargs,
204
stdin = subprocess.PIPE,
205
stdout = subprocess.PIPE,
206
stderr = subprocess.PIPE)
207
ciphertext, err = proc.communicate(input = data)
250
stdin=subprocess.PIPE,
251
stdout=subprocess.PIPE,
252
stderr=subprocess.PIPE)
253
ciphertext, err = proc.communicate(input=data)
208
254
if proc.returncode != 0:
209
255
raise PGPError(err)
210
256
return ciphertext
212
258
def decrypt(self, data, password):
213
259
passphrase = self.password_encode(password)
214
260
with tempfile.NamedTemporaryFile(
215
dir = self.tempdir) as passfile:
261
dir=self.tempdir) as passfile:
216
262
passfile.write(passphrase)
218
proc = subprocess.Popen(['gpg', '--decrypt',
264
proc = subprocess.Popen([self.gpg, '--decrypt',
219
265
'--passphrase-file',
221
267
+ self.gnupgargs,
222
stdin = subprocess.PIPE,
223
stdout = subprocess.PIPE,
224
stderr = subprocess.PIPE)
225
decrypted_plaintext, err = proc.communicate(input = data)
268
stdin=subprocess.PIPE,
269
stdout=subprocess.PIPE,
270
stderr=subprocess.PIPE)
271
decrypted_plaintext, err = proc.communicate(input=data)
226
272
if proc.returncode != 0:
227
273
raise PGPError(err)
228
274
return decrypted_plaintext
277
# Pretend that we have an Avahi module
279
"""This isn't so much a class as it is a module-like namespace."""
280
IF_UNSPEC = -1 # avahi-common/address.h
281
PROTO_UNSPEC = -1 # avahi-common/address.h
282
PROTO_INET = 0 # avahi-common/address.h
283
PROTO_INET6 = 1 # avahi-common/address.h
284
DBUS_NAME = "org.freedesktop.Avahi"
285
DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"
286
DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
287
DBUS_PATH_SERVER = "/"
290
def string_array_to_txt_array(t):
291
return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
292
for s in t), signature="ay")
293
ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h
294
ENTRY_GROUP_COLLISION = 3 # avahi-common/defs.h
295
ENTRY_GROUP_FAILURE = 4 # avahi-common/defs.h
296
SERVER_INVALID = 0 # avahi-common/defs.h
297
SERVER_REGISTERING = 1 # avahi-common/defs.h
298
SERVER_RUNNING = 2 # avahi-common/defs.h
299
SERVER_COLLISION = 3 # avahi-common/defs.h
300
SERVER_FAILURE = 4 # avahi-common/defs.h
231
303
class AvahiError(Exception):
232
304
def __init__(self, value, *args, **kwargs):
233
305
self.value = value
423
495
class AvahiServiceToSyslog(AvahiService):
424
496
def rename(self, *args, **kwargs):
425
497
"""Add the new name to the syslog messages"""
426
ret = AvahiService.rename(self, *args, **kwargs)
498
ret = super(AvahiServiceToSyslog, self).rename(*args, **kwargs)
427
499
syslogger.setFormatter(logging.Formatter(
428
500
'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
429
501
.format(self.name)))
432
505
# Pretend that we have a GnuTLS module
433
class GnuTLS(object):
434
"""This isn't so much a class as it is a module-like namespace.
435
It is instantiated once, and simulates having a GnuTLS module."""
437
_library = ctypes.cdll.LoadLibrary(
438
ctypes.util.find_library("gnutls"))
439
_need_version = "3.3.0"
441
# Need to use class name "GnuTLS" here, since this method is
442
# called before the assignment to the "gnutls" global variable
444
if GnuTLS.check_version(self._need_version) is None:
445
raise GnuTLS.Error("Needs GnuTLS {} or later"
446
.format(self._need_version))
506
class gnutls(object):
507
"""This isn't so much a class as it is a module-like namespace."""
509
library = ctypes.util.find_library("gnutls")
511
library = ctypes.util.find_library("gnutls-deb0")
512
_library = ctypes.cdll.LoadLibrary(library)
448
515
# Unless otherwise indicated, the constants and types below are
449
516
# all from the gnutls/gnutls.h C header file.
453
520
E_INTERRUPTED = -52
458
526
CRD_CERTIFICATE = 1
459
527
E_NO_CERTIFICATE_FOUND = -49
532
KEYID_USE_SHA256 = 1 # gnutls/x509.h
460
533
OPENPGP_FMT_RAW = 0 # gnutls/openpgp.h
463
536
class session_int(ctypes.Structure):
465
538
session_t = ctypes.POINTER(session_int)
466
540
class certificate_credentials_st(ctypes.Structure):
468
542
certificate_credentials_t = ctypes.POINTER(
469
543
certificate_credentials_st)
470
544
certificate_type_t = ctypes.c_int
471
546
class datum_t(ctypes.Structure):
472
547
_fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
473
548
('size', ctypes.c_uint)]
474
550
class openpgp_crt_int(ctypes.Structure):
476
552
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
477
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
553
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
478
554
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
479
credentials_type_t = ctypes.c_int #
555
credentials_type_t = ctypes.c_int
480
556
transport_ptr_t = ctypes.c_void_p
481
557
close_request_t = ctypes.c_int
484
560
class Error(Exception):
485
# We need to use the class name "GnuTLS" here, since this
486
# exception might be raised from within GnuTLS.__init__,
487
# which is called before the assignment to the "gnutls"
488
# global variable has happened.
489
def __init__(self, message = None, code = None, args=()):
561
def __init__(self, message=None, code=None, args=()):
490
562
# Default usage is by a message string, but if a return
491
563
# code is passed, convert it to a string with
492
564
# gnutls.strerror()
494
566
if message is None and code is not None:
495
message = GnuTLS.strerror(code)
496
return super(GnuTLS.Error, self).__init__(
567
message = gnutls.strerror(code)
568
return super(gnutls.Error, self).__init__(
499
571
class CertificateSecurityError(Error):
503
575
class Credentials(object):
504
576
def __init__(self):
561
639
return _error_code(result)
562
640
result = func(*arguments)
565
643
# Unless otherwise indicated, the function declarations below are
566
644
# all from the gnutls/gnutls.h C header file.
569
647
priority_set_direct = _library.gnutls_priority_set_direct
570
648
priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
571
649
ctypes.POINTER(ctypes.c_char_p)]
572
650
priority_set_direct.restype = _error_code
574
652
init = _library.gnutls_init
575
653
init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
576
654
init.restype = _error_code
578
656
set_default_priority = _library.gnutls_set_default_priority
579
657
set_default_priority.argtypes = [session_t]
580
658
set_default_priority.restype = _error_code
582
660
record_send = _library.gnutls_record_send
583
661
record_send.argtypes = [session_t, ctypes.c_void_p,
585
663
record_send.restype = ctypes.c_ssize_t
586
664
record_send.errcheck = _retry_on_error
588
666
certificate_allocate_credentials = (
589
667
_library.gnutls_certificate_allocate_credentials)
590
668
certificate_allocate_credentials.argtypes = [
591
669
ctypes.POINTER(certificate_credentials_t)]
592
670
certificate_allocate_credentials.restype = _error_code
594
672
certificate_free_credentials = (
595
673
_library.gnutls_certificate_free_credentials)
596
certificate_free_credentials.argtypes = [certificate_credentials_t]
674
certificate_free_credentials.argtypes = [
675
certificate_credentials_t]
597
676
certificate_free_credentials.restype = None
599
678
handshake_set_private_extensions = (
600
679
_library.gnutls_handshake_set_private_extensions)
601
680
handshake_set_private_extensions.argtypes = [session_t,
603
682
handshake_set_private_extensions.restype = None
605
684
credentials_set = _library.gnutls_credentials_set
606
685
credentials_set.argtypes = [session_t, credentials_type_t,
608
687
credentials_set.restype = _error_code
610
689
strerror = _library.gnutls_strerror
611
690
strerror.argtypes = [ctypes.c_int]
612
691
strerror.restype = ctypes.c_char_p
614
693
certificate_type_get = _library.gnutls_certificate_type_get
615
694
certificate_type_get.argtypes = [session_t]
616
695
certificate_type_get.restype = _error_code
618
697
certificate_get_peers = _library.gnutls_certificate_get_peers
619
698
certificate_get_peers.argtypes = [session_t,
620
699
ctypes.POINTER(ctypes.c_uint)]
621
700
certificate_get_peers.restype = ctypes.POINTER(datum_t)
623
702
global_set_log_level = _library.gnutls_global_set_log_level
624
703
global_set_log_level.argtypes = [ctypes.c_int]
625
704
global_set_log_level.restype = None
627
706
global_set_log_function = _library.gnutls_global_set_log_function
628
707
global_set_log_function.argtypes = [log_func]
629
708
global_set_log_function.restype = None
631
710
deinit = _library.gnutls_deinit
632
711
deinit.argtypes = [session_t]
633
712
deinit.restype = None
635
714
handshake = _library.gnutls_handshake
636
715
handshake.argtypes = [session_t]
637
716
handshake.restype = _error_code
638
717
handshake.errcheck = _retry_on_error
640
719
transport_set_ptr = _library.gnutls_transport_set_ptr
641
720
transport_set_ptr.argtypes = [session_t, transport_ptr_t]
642
721
transport_set_ptr.restype = None
644
723
bye = _library.gnutls_bye
645
724
bye.argtypes = [session_t, close_request_t]
646
725
bye.restype = _error_code
647
726
bye.errcheck = _retry_on_error
649
728
check_version = _library.gnutls_check_version
650
729
check_version.argtypes = [ctypes.c_char_p]
651
730
check_version.restype = ctypes.c_char_p
653
# All the function declarations below are from gnutls/openpgp.h
655
openpgp_crt_init = _library.gnutls_openpgp_crt_init
656
openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
657
openpgp_crt_init.restype = _error_code
659
openpgp_crt_import = _library.gnutls_openpgp_crt_import
660
openpgp_crt_import.argtypes = [openpgp_crt_t,
661
ctypes.POINTER(datum_t),
663
openpgp_crt_import.restype = _error_code
665
openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
666
openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
667
ctypes.POINTER(ctypes.c_uint)]
668
openpgp_crt_verify_self.restype = _error_code
670
openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
671
openpgp_crt_deinit.argtypes = [openpgp_crt_t]
672
openpgp_crt_deinit.restype = None
674
openpgp_crt_get_fingerprint = (
675
_library.gnutls_openpgp_crt_get_fingerprint)
676
openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
680
openpgp_crt_get_fingerprint.restype = _error_code
732
_need_version = b"3.3.0"
733
if check_version(_need_version) is None:
734
raise self.Error("Needs GnuTLS {} or later"
735
.format(_need_version))
737
_tls_rawpk_version = b"3.6.6"
738
has_rawpk = bool(check_version(_tls_rawpk_version))
742
class pubkey_st(ctypes.Structure):
744
pubkey_t = ctypes.POINTER(pubkey_st)
746
x509_crt_fmt_t = ctypes.c_int
748
# All the function declarations below are from gnutls/abstract.h
749
pubkey_init = _library.gnutls_pubkey_init
750
pubkey_init.argtypes = [ctypes.POINTER(pubkey_t)]
751
pubkey_init.restype = _error_code
753
pubkey_import = _library.gnutls_pubkey_import
754
pubkey_import.argtypes = [pubkey_t, ctypes.POINTER(datum_t),
756
pubkey_import.restype = _error_code
758
pubkey_get_key_id = _library.gnutls_pubkey_get_key_id
759
pubkey_get_key_id.argtypes = [pubkey_t, ctypes.c_int,
760
ctypes.POINTER(ctypes.c_ubyte),
761
ctypes.POINTER(ctypes.c_size_t)]
762
pubkey_get_key_id.restype = _error_code
764
pubkey_deinit = _library.gnutls_pubkey_deinit
765
pubkey_deinit.argtypes = [pubkey_t]
766
pubkey_deinit.restype = None
768
# All the function declarations below are from gnutls/openpgp.h
770
openpgp_crt_init = _library.gnutls_openpgp_crt_init
771
openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
772
openpgp_crt_init.restype = _error_code
774
openpgp_crt_import = _library.gnutls_openpgp_crt_import
775
openpgp_crt_import.argtypes = [openpgp_crt_t,
776
ctypes.POINTER(datum_t),
778
openpgp_crt_import.restype = _error_code
780
openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
781
openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
782
ctypes.POINTER(ctypes.c_uint)]
783
openpgp_crt_verify_self.restype = _error_code
785
openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
786
openpgp_crt_deinit.argtypes = [openpgp_crt_t]
787
openpgp_crt_deinit.restype = None
789
openpgp_crt_get_fingerprint = (
790
_library.gnutls_openpgp_crt_get_fingerprint)
791
openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
795
openpgp_crt_get_fingerprint.restype = _error_code
797
if check_version("3.6.4"):
798
certificate_type_get2 = _library.gnutls_certificate_type_get2
799
certificate_type_get2.argtypes = [session_t, ctypes.c_int]
800
certificate_type_get2.restype = _error_code
682
802
# Remove non-public functions
683
803
del _error_code, _retry_on_error
684
# Create the global "gnutls" object, simulating a module
687
806
def call_pipe(connection, # : multiprocessing.Connection
688
807
func, *args, **kwargs):
689
808
"""This function is meant to be called by multiprocessing.Process
691
810
This function runs func(*args, **kwargs), and writes the resulting
692
811
return value on the provided multiprocessing.Connection.
694
813
connection.send(func(*args, **kwargs))
695
814
connection.close()
697
817
class Client(object):
698
818
"""A representation of a client host served by this server.
701
821
approved: bool(); 'None' if not yet approved/disapproved
702
822
approval_delay: datetime.timedelta(); Time to wait for approval
704
824
checker: subprocess.Popen(); a running checker process used
705
825
to see if the client lives.
706
826
'None' if no process is running.
707
checker_callback_tag: a gobject event source tag, or None
827
checker_callback_tag: a GLib event source tag, or None
708
828
checker_command: string; External command which is run to check
709
829
if client lives. %() expansions are done at
710
830
runtime with vars(self) as dict, so that for
711
831
instance %(name)s can be used in the command.
712
checker_initiator_tag: a gobject event source tag, or None
832
checker_initiator_tag: a GLib event source tag, or None
713
833
created: datetime.datetime(); (UTC) object creation
714
834
client_structure: Object describing what attributes a client has
715
835
and is used for storing the client at exit
716
836
current_checker_command: string; current running checker_command
717
disable_initiator_tag: a gobject event source tag, or None
837
disable_initiator_tag: a GLib event source tag, or None
719
839
fingerprint: string (40 or 32 hexadecimal digits); used to
720
uniquely identify the client
840
uniquely identify an OpenPGP client
841
key_id: string (64 hexadecimal digits); used to uniquely identify
842
a client using raw public keys
721
843
host: string; available for use by the checker command
722
844
interval: datetime.timedelta(); How often to start a new checker
723
845
last_approval_request: datetime.datetime(); (UTC) or None
875
1002
logger.info("Disabling client %s", self.name)
876
1003
if getattr(self, "disable_initiator_tag", None) is not None:
877
gobject.source_remove(self.disable_initiator_tag)
1004
GLib.source_remove(self.disable_initiator_tag)
878
1005
self.disable_initiator_tag = None
879
1006
self.expires = None
880
1007
if getattr(self, "checker_initiator_tag", None) is not None:
881
gobject.source_remove(self.checker_initiator_tag)
1008
GLib.source_remove(self.checker_initiator_tag)
882
1009
self.checker_initiator_tag = None
883
1010
self.stop_checker()
884
1011
self.enabled = False
886
1013
self.send_changedstate()
887
# Do not run this again if called by a gobject.timeout_add
1014
# Do not run this again if called by a GLib.timeout_add
890
1017
def __del__(self):
893
1020
def init_checker(self):
894
1021
# Schedule a new checker to be started an 'interval' from now,
895
1022
# and every interval from then on.
896
1023
if self.checker_initiator_tag is not None:
897
gobject.source_remove(self.checker_initiator_tag)
898
self.checker_initiator_tag = gobject.timeout_add(
1024
GLib.source_remove(self.checker_initiator_tag)
1025
self.checker_initiator_tag = GLib.timeout_add(
899
1026
int(self.interval.total_seconds() * 1000),
900
1027
self.start_checker)
901
1028
# Schedule a disable() when 'timeout' has passed
902
1029
if self.disable_initiator_tag is not None:
903
gobject.source_remove(self.disable_initiator_tag)
904
self.disable_initiator_tag = gobject.timeout_add(
1030
GLib.source_remove(self.disable_initiator_tag)
1031
self.disable_initiator_tag = GLib.timeout_add(
905
1032
int(self.timeout.total_seconds() * 1000), self.disable)
906
1033
# Also start a new checker *right now*.
907
1034
self.start_checker()
909
1036
def checker_callback(self, source, condition, connection,
911
1038
"""The checker has completed, so take appropriate actions."""
994
1121
# The exception is when not debugging but nevertheless
995
1122
# running in the foreground; use the previously
996
1123
# created wnull.
997
popen_args = { "close_fds": True,
1124
popen_args = {"close_fds": True,
1000
1127
if (not self.server_settings["debug"]
1001
1128
and self.server_settings["foreground"]):
1002
1129
popen_args.update({"stdout": wnull,
1004
pipe = multiprocessing.Pipe(duplex = False)
1131
pipe = multiprocessing.Pipe(duplex=False)
1005
1132
self.checker = multiprocessing.Process(
1007
args = (pipe[1], subprocess.call, command),
1008
kwargs = popen_args)
1134
args=(pipe[1], subprocess.call, command),
1009
1136
self.checker.start()
1010
self.checker_callback_tag = gobject.io_add_watch(
1011
pipe[0].fileno(), gobject.IO_IN,
1137
self.checker_callback_tag = GLib.io_add_watch(
1138
pipe[0].fileno(), GLib.IO_IN,
1012
1139
self.checker_callback, pipe[0], command)
1013
# Re-run this periodically if run by gobject.timeout_add
1140
# Re-run this periodically if run by GLib.timeout_add
1016
1143
def stop_checker(self):
1017
1144
"""Force the checker process, if any, to stop."""
1018
1145
if self.checker_callback_tag:
1019
gobject.source_remove(self.checker_callback_tag)
1146
GLib.source_remove(self.checker_callback_tag)
1020
1147
self.checker_callback_tag = None
1021
1148
if getattr(self, "checker", None) is None:
1376
1503
exc_info=error)
1377
1504
return xmlstring
1380
1508
dbus.OBJECT_MANAGER_IFACE
1381
1509
except AttributeError:
1382
1510
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1384
1513
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1385
1514
"""A D-Bus object with an ObjectManager.
1387
1516
Classes inheriting from this exposes the standard
1388
1517
GetManagedObjects call and the InterfacesAdded and
1389
1518
InterfacesRemoved signals on the standard
1390
1519
"org.freedesktop.DBus.ObjectManager" interface.
1392
1521
Note: No signals are sent automatically; they must be sent
1395
1524
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1396
out_signature = "a{oa{sa{sv}}}")
1525
out_signature="a{oa{sa{sv}}}")
1397
1526
def GetManagedObjects(self):
1398
1527
"""This function must be overridden"""
1399
1528
raise NotImplementedError()
1401
1530
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1402
signature = "oa{sa{sv}}")
1531
signature="oa{sa{sv}}")
1403
1532
def InterfacesAdded(self, object_path, interfaces_and_properties):
1406
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
1535
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature="oas")
1407
1536
def InterfacesRemoved(self, object_path, interfaces):
1410
1539
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1411
out_signature = "s",
1412
path_keyword = 'object_path',
1413
connection_keyword = 'connection')
1541
path_keyword='object_path',
1542
connection_keyword='connection')
1414
1543
def Introspect(self, object_path, connection):
1415
1544
"""Overloading of standard D-Bus method.
1417
1546
Override return argument name of GetManagedObjects to be
1418
1547
"objpath_interfaces_and_properties"
1496
1626
interface_names.add(alt_interface)
1497
1627
# Is this a D-Bus signal?
1498
1628
if getattr(attribute, "_dbus_is_signal", False):
1629
# Extract the original non-method undecorated
1630
# function by black magic
1499
1631
if sys.version_info.major == 2:
1500
# Extract the original non-method undecorated
1501
# function by black magic
1502
1632
nonmethod_func = (dict(
1503
1633
zip(attribute.func_code.co_freevars,
1504
1634
attribute.__closure__))
1505
1635
["func"].cell_contents)
1507
nonmethod_func = attribute
1637
nonmethod_func = (dict(
1638
zip(attribute.__code__.co_freevars,
1639
attribute.__closure__))
1640
["func"].cell_contents)
1508
1641
# Create a new, but exactly alike, function
1509
1642
# object, and decorate it to be a new D-Bus signal
1510
1643
# with the alternate D-Bus interface name
1511
if sys.version_info.major == 2:
1512
new_function = types.FunctionType(
1513
nonmethod_func.func_code,
1514
nonmethod_func.func_globals,
1515
nonmethod_func.func_name,
1516
nonmethod_func.func_defaults,
1517
nonmethod_func.func_closure)
1519
new_function = types.FunctionType(
1520
nonmethod_func.__code__,
1521
nonmethod_func.__globals__,
1522
nonmethod_func.__name__,
1523
nonmethod_func.__defaults__,
1524
nonmethod_func.__closure__)
1644
new_function = copy_function(nonmethod_func)
1525
1645
new_function = (dbus.service.signal(
1527
1647
attribute._dbus_signature)(new_function))
1729
1842
"ApprovedByDefault")
1730
1843
approval_delay = notifychangeproperty(
1731
1844
dbus.UInt64, "ApprovalDelay",
1732
type_func = lambda td: td.total_seconds() * 1000)
1845
type_func=lambda td: td.total_seconds() * 1000)
1733
1846
approval_duration = notifychangeproperty(
1734
1847
dbus.UInt64, "ApprovalDuration",
1735
type_func = lambda td: td.total_seconds() * 1000)
1848
type_func=lambda td: td.total_seconds() * 1000)
1736
1849
host = notifychangeproperty(dbus.String, "Host")
1737
1850
timeout = notifychangeproperty(
1738
1851
dbus.UInt64, "Timeout",
1739
type_func = lambda td: td.total_seconds() * 1000)
1852
type_func=lambda td: td.total_seconds() * 1000)
1740
1853
extended_timeout = notifychangeproperty(
1741
1854
dbus.UInt64, "ExtendedTimeout",
1742
type_func = lambda td: td.total_seconds() * 1000)
1855
type_func=lambda td: td.total_seconds() * 1000)
1743
1856
interval = notifychangeproperty(
1744
1857
dbus.UInt64, "Interval",
1745
type_func = lambda td: td.total_seconds() * 1000)
1858
type_func=lambda td: td.total_seconds() * 1000)
1746
1859
checker_command = notifychangeproperty(dbus.String, "Checker")
1747
1860
secret = notifychangeproperty(dbus.ByteArray, "Secret",
1748
1861
invalidate_only=True)
1750
1863
del notifychangeproperty
1752
1865
def __del__(self, *args, **kwargs):
1754
1867
self.remove_from_connection()
2119
2239
class ClientHandler(socketserver.BaseRequestHandler, object):
2120
2240
"""A class to handle client connections.
2122
2242
Instantiated once for each connection to handle it.
2123
2243
Note: This will run in its own forked process."""
2125
2245
def handle(self):
2126
2246
with contextlib.closing(self.server.child_pipe) as child_pipe:
2127
2247
logger.info("TCP connection from: %s",
2128
2248
str(self.client_address))
2129
2249
logger.debug("Pipe FD: %d",
2130
2250
self.server.child_pipe.fileno())
2132
2252
session = gnutls.ClientSession(self.request)
2134
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
2135
# "+AES-256-CBC", "+SHA1",
2136
# "+COMP-NULL", "+CTYPE-OPENPGP",
2254
# priority = ':'.join(("NONE", "+VERS-TLS1.1",
2255
# "+AES-256-CBC", "+SHA1",
2256
# "+COMP-NULL", "+CTYPE-OPENPGP",
2138
2258
# Use a fallback default, since this MUST be set.
2139
2259
priority = self.server.gnutls_priority
2140
2260
if priority is None:
2141
2261
priority = "NORMAL"
2142
gnutls.priority_set_direct(session._c_object, priority,
2262
gnutls.priority_set_direct(session._c_object,
2263
priority.encode("utf-8"),
2145
2266
# Start communication using the Mandos protocol
2146
2267
# Get protocol number
2147
2268
line = self.request.makefile().readline()
2273
2416
cert = cert_list[0]
2274
2417
return ctypes.string_at(cert.data, cert.size)
2420
def key_id(certificate):
2421
"Convert a certificate bytestring to a hexdigit key ID"
2422
# New GnuTLS "datum" with the public key
2423
datum = gnutls.datum_t(
2424
ctypes.cast(ctypes.c_char_p(certificate),
2425
ctypes.POINTER(ctypes.c_ubyte)),
2426
ctypes.c_uint(len(certificate)))
2427
# XXX all these need to be created in the gnutls "module"
2428
# New empty GnuTLS certificate
2429
pubkey = gnutls.pubkey_t()
2430
gnutls.pubkey_init(ctypes.byref(pubkey))
2431
# Import the raw public key into the certificate
2432
gnutls.pubkey_import(pubkey,
2433
ctypes.byref(datum),
2434
gnutls.X509_FMT_DER)
2435
# New buffer for the key ID
2436
buf = ctypes.create_string_buffer(32)
2437
buf_len = ctypes.c_size_t(len(buf))
2438
# Get the key ID from the raw public key into the buffer
2439
gnutls.pubkey_get_key_id(pubkey,
2440
gnutls.KEYID_USE_SHA256,
2441
ctypes.cast(ctypes.byref(buf),
2442
ctypes.POINTER(ctypes.c_ubyte)),
2443
ctypes.byref(buf_len))
2444
# Deinit the certificate
2445
gnutls.pubkey_deinit(pubkey)
2447
# Convert the buffer to a Python bytestring
2448
key_id = ctypes.string_at(buf, buf_len.value)
2449
# Convert the bytestring to hexadecimal notation
2450
hex_key_id = binascii.hexlify(key_id).upper()
2277
2454
def fingerprint(openpgp):
2278
2455
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
2312
2490
class MultiprocessingMixIn(object):
2313
2491
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
2315
2493
def sub_process_main(self, request, address):
2317
2495
self.finish_request(request, address)
2318
2496
except Exception:
2319
2497
self.handle_error(request, address)
2320
2498
self.close_request(request)
2322
2500
def process_request(self, request, address):
2323
2501
"""Start a new process to process the request."""
2324
proc = multiprocessing.Process(target = self.sub_process_main,
2325
args = (request, address))
2502
proc = multiprocessing.Process(target=self.sub_process_main,
2503
args=(request, address))
2330
2508
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
2331
2509
""" adds a pipe to the MixIn """
2333
2511
def process_request(self, request, client_address):
2334
2512
"""Overrides and wraps the original process_request().
2336
2514
This function creates a new pipe in self.pipe
2338
2516
parent_pipe, self.child_pipe = multiprocessing.Pipe()
2340
2518
proc = MultiprocessingMixIn.process_request(self, request,
2341
2519
client_address)
2342
2520
self.child_pipe.close()
2343
2521
self.add_pipe(parent_pipe, proc)
2345
2523
def add_pipe(self, parent_pipe, proc):
2346
2524
"""Dummy function; override as necessary"""
2347
2525
raise NotImplementedError()
2395
2574
# socket_wrapper(), if socketfd was set.
2396
2575
socketserver.TCPServer.__init__(self, server_address,
2397
2576
RequestHandlerClass)
2399
2578
def server_bind(self):
2400
2579
"""This overrides the normal server_bind() function
2401
2580
to bind to an interface if one was specified, and also NOT to
2402
2581
bind to an address or port if they were not specified."""
2582
global SO_BINDTODEVICE
2403
2583
if self.interface is not None:
2404
2584
if SO_BINDTODEVICE is None:
2405
logger.error("SO_BINDTODEVICE does not exist;"
2406
" cannot bind to interface %s",
2410
self.socket.setsockopt(
2411
socket.SOL_SOCKET, SO_BINDTODEVICE,
2412
(self.interface + "\0").encode("utf-8"))
2413
except socket.error as error:
2414
if error.errno == errno.EPERM:
2415
logger.error("No permission to bind to"
2416
" interface %s", self.interface)
2417
elif error.errno == errno.ENOPROTOOPT:
2418
logger.error("SO_BINDTODEVICE not available;"
2419
" cannot bind to interface %s",
2421
elif error.errno == errno.ENODEV:
2422
logger.error("Interface %s does not exist,"
2423
" cannot bind", self.interface)
2585
# Fall back to a hard-coded value which seems to be
2587
logger.warning("SO_BINDTODEVICE not found, trying 25")
2588
SO_BINDTODEVICE = 25
2590
self.socket.setsockopt(
2591
socket.SOL_SOCKET, SO_BINDTODEVICE,
2592
(self.interface + "\0").encode("utf-8"))
2593
except socket.error as error:
2594
if error.errno == errno.EPERM:
2595
logger.error("No permission to bind to"
2596
" interface %s", self.interface)
2597
elif error.errno == errno.ENOPROTOOPT:
2598
logger.error("SO_BINDTODEVICE not available;"
2599
" cannot bind to interface %s",
2601
elif error.errno == errno.ENODEV:
2602
logger.error("Interface %s does not exist,"
2603
" cannot bind", self.interface)
2426
2606
# Only bind(2) the socket if we really need to.
2427
2607
if self.server_address[0] or self.server_address[1]:
2608
if self.server_address[1]:
2609
self.allow_reuse_address = True
2428
2610
if not self.server_address[0]:
2429
2611
if self.address_family == socket.AF_INET6:
2430
any_address = "::" # in6addr_any
2612
any_address = "::" # in6addr_any
2432
any_address = "0.0.0.0" # INADDR_ANY
2614
any_address = "0.0.0.0" # INADDR_ANY
2433
2615
self.server_address = (any_address,
2434
2616
self.server_address[1])
2435
2617
elif not self.server_address[1]:
2469
2651
self.gnutls_priority = gnutls_priority
2470
2652
IPv6_TCPServer.__init__(self, server_address,
2471
2653
RequestHandlerClass,
2472
interface = interface,
2473
use_ipv6 = use_ipv6,
2474
socketfd = socketfd)
2654
interface=interface,
2476
2658
def server_activate(self):
2477
2659
if self.enabled:
2478
2660
return socketserver.TCPServer.server_activate(self)
2480
2662
def enable(self):
2481
2663
self.enabled = True
2483
2665
def add_pipe(self, parent_pipe, proc):
2484
2666
# Call "handle_ipc" for both data and EOF events
2485
gobject.io_add_watch(
2486
2668
parent_pipe.fileno(),
2487
gobject.IO_IN | gobject.IO_HUP,
2669
GLib.IO_IN | GLib.IO_HUP,
2488
2670
functools.partial(self.handle_ipc,
2489
parent_pipe = parent_pipe,
2671
parent_pipe=parent_pipe,
2492
2674
def handle_ipc(self, source, condition,
2493
2675
parent_pipe=None,
2495
2677
client_object=None):
2496
2678
# error, or the other end of multiprocessing.Pipe has closed
2497
if condition & (gobject.IO_ERR | gobject.IO_HUP):
2679
if condition & (GLib.IO_ERR | GLib.IO_HUP):
2498
2680
# Wait for other process to exit
2502
2684
# Read a request from the child
2503
2685
request = parent_pipe.recv()
2504
2686
command = request[0]
2506
2688
if command == 'init':
2508
address = request[2]
2510
for c in self.clients.itervalues():
2511
if c.fingerprint == fpr:
2689
key_id = request[1].decode("ascii")
2690
fpr = request[2].decode("ascii")
2691
address = request[3]
2693
for c in self.clients.values():
2694
if key_id == "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855":
2696
if key_id and c.key_id == key_id:
2699
if fpr and c.fingerprint == fpr:
2515
logger.info("Client not found for fingerprint: %s, ad"
2516
"dress: %s", fpr, address)
2703
logger.info("Client not found for key ID: %s, address"
2704
": %s", key_id or fpr, address)
2517
2705
if self.use_dbus:
2518
2706
# Emit D-Bus signal
2519
mandos_dbus_service.ClientNotFound(fpr,
2707
mandos_dbus_service.ClientNotFound(key_id or fpr,
2521
2709
parent_pipe.send(False)
2524
gobject.io_add_watch(
2525
2713
parent_pipe.fileno(),
2526
gobject.IO_IN | gobject.IO_HUP,
2714
GLib.IO_IN | GLib.IO_HUP,
2527
2715
functools.partial(self.handle_ipc,
2528
parent_pipe = parent_pipe,
2530
client_object = client))
2716
parent_pipe=parent_pipe,
2718
client_object=client))
2531
2719
parent_pipe.send(True)
2532
2720
# remove the old hook in favor of the new above hook on
3127
logger.debug("Did setuid/setgid to {}:{}".format(uid,
2927
3129
except OSError as error:
3130
logger.warning("Failed to setuid/setgid to {}:{}: {}"
3131
.format(uid, gid, os.strerror(error.errno)))
2928
3132
if error.errno != errno.EPERM:
2932
3136
# Enable all possible GnuTLS debugging
2934
3138
# "Use a log level over 10 to enable all debugging options."
2935
3139
# - GnuTLS manual
2936
3140
gnutls.global_set_log_level(11)
2938
3142
@gnutls.log_func
2939
3143
def debug_gnutls(level, string):
2940
3144
logger.debug("GnuTLS: %s", string[:-1])
2942
3146
gnutls.global_set_log_function(debug_gnutls)
2944
3148
# Redirect stdin so all checkers get /dev/null
2945
3149
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2946
3150
os.dup2(null, sys.stdin.fileno())
2950
3154
# Need to fork before connecting to D-Bus
2951
3155
if not foreground:
2952
3156
# Close all input and output, do double fork, etc.
2955
# multiprocessing will use threads, so before we use gobject we
2956
# need to inform gobject that threads will be used.
2957
gobject.threads_init()
3159
# multiprocessing will use threads, so before we use GLib we need
3160
# to inform GLib that threads will be used.
2959
3163
global main_loop
2960
3164
# From the Avahi example code
2961
3165
DBusGMainLoop(set_as_default=True)
2962
main_loop = gobject.MainLoop()
3166
main_loop = GLib.MainLoop()
2963
3167
bus = dbus.SystemBus()
2964
3168
# End of Avahi example code
2979
3183
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2980
3184
service = AvahiServiceToSyslog(
2981
name = server_settings["servicename"],
2982
servicetype = "_mandos._tcp",
2983
protocol = protocol,
3185
name=server_settings["servicename"],
3186
servicetype="_mandos._tcp",
2985
3189
if server_settings["interface"]:
2986
3190
service.interface = if_nametoindex(
2987
3191
server_settings["interface"].encode("utf-8"))
2989
3193
global multiprocessing_manager
2990
3194
multiprocessing_manager = multiprocessing.Manager()
2992
3196
client_class = Client
2994
client_class = functools.partial(ClientDBus, bus = bus)
3198
client_class = functools.partial(ClientDBus, bus=bus)
2996
3200
client_settings = Client.config_parser(client_config)
2997
3201
old_client_settings = {}
2998
3202
clients_data = {}
3000
3204
# This is used to redirect stdout and stderr for checker processes
3002
wnull = open(os.devnull, "w") # A writable /dev/null
3206
wnull = open(os.devnull, "w") # A writable /dev/null
3003
3207
# Only used if server is running in foreground but not in debug
3005
3209
if debug or not foreground:
3008
3212
# Get client data and settings from last running state.
3009
3213
if server_settings["restore"]:
3011
3215
with open(stored_state_path, "rb") as stored_state:
3012
clients_data, old_client_settings = pickle.load(
3216
if sys.version_info.major == 2:
3217
clients_data, old_client_settings = pickle.load(
3220
bytes_clients_data, bytes_old_client_settings = (
3221
pickle.load(stored_state, encoding="bytes"))
3222
# Fix bytes to strings
3225
clients_data = {(key.decode("utf-8")
3226
if isinstance(key, bytes)
3229
bytes_clients_data.items()}
3230
del bytes_clients_data
3231
for key in clients_data:
3232
value = {(k.decode("utf-8")
3233
if isinstance(k, bytes) else k): v
3235
clients_data[key].items()}
3236
clients_data[key] = value
3238
value["client_structure"] = [
3240
if isinstance(s, bytes)
3242
value["client_structure"]]
3244
for k in ("name", "host"):
3245
if isinstance(value[k], bytes):
3246
value[k] = value[k].decode("utf-8")
3247
if not value.has_key("key_id"):
3248
value["key_id"] = ""
3249
elif not value.has_key("fingerprint"):
3250
value["fingerprint"] = ""
3251
# old_client_settings
3253
old_client_settings = {
3254
(key.decode("utf-8")
3255
if isinstance(key, bytes)
3258
bytes_old_client_settings.items()}
3259
del bytes_old_client_settings
3261
for value in old_client_settings.values():
3262
if isinstance(value["host"], bytes):
3263
value["host"] = (value["host"]
3014
3265
os.remove(stored_state_path)
3015
3266
except IOError as e:
3016
3267
if e.errno == errno.ENOENT:
3116
3367
pidfilename, pid)
3118
3369
del pidfilename
3120
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
3121
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
3371
for termsig in (signal.SIGHUP, signal.SIGTERM):
3372
GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig,
3373
lambda: main_loop.quit() and False)
3125
3377
@alternate_dbus_interfaces(
3126
{ "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
3378
{"se.recompile.Mandos": "se.bsnet.fukt.Mandos"})
3127
3379
class MandosDBusService(DBusObjectWithObjectManager):
3128
3380
"""A D-Bus proxy object"""
3130
3382
def __init__(self):
3131
3383
dbus.service.Object.__init__(self, bus, "/")
3133
3385
_interface = "se.recompile.Mandos"
3135
3387
@dbus.service.signal(_interface, signature="o")
3136
3388
def ClientAdded(self, objpath):
3140
3392
@dbus.service.signal(_interface, signature="ss")
3141
def ClientNotFound(self, fingerprint, address):
3393
def ClientNotFound(self, key_id, address):
3145
3397
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3147
3399
@dbus.service.signal(_interface, signature="os")
3148
3400
def ClientRemoved(self, objpath, name):
3152
3404
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3154
3406
@dbus.service.method(_interface, out_signature="ao")
3155
3407
def GetAllClients(self):
3157
3409
return dbus.Array(c.dbus_object_path for c in
3158
tcp_server.clients.itervalues())
3410
tcp_server.clients.values())
3160
3412
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3162
3414
@dbus.service.method(_interface,
3221
3473
self.ClientRemoved(client.dbus_object_path,
3224
3476
mandos_dbus_service = MandosDBusService()
3478
# Save modules to variables to exempt the modules from being
3479
# unloaded before the function registered with atexit() is run.
3480
mp = multiprocessing
3227
3484
"Cleanup function; run on exit"
3229
3486
service.cleanup()
3231
multiprocessing.active_children()
3488
mp.active_children()
3233
3490
if not (tcp_server.clients or client_settings):
3236
3493
# Store client before exiting. Secrets are encrypted with key
3237
3494
# based on what config file has. If config file is
3238
3495
# removed/edited, old secret will thus be unrecovable.
3240
3497
with PGPEngine() as pgp:
3241
for client in tcp_server.clients.itervalues():
3498
for client in tcp_server.clients.values():
3242
3499
key = client_settings[client.name]["secret"]
3243
3500
client.encrypted_secret = pgp.encrypt(client.secret,
3245
3502
client_dict = {}
3247
3504
# A list of attributes that can not be pickled
3249
exclude = { "bus", "changedstate", "secret",
3250
"checker", "server_settings" }
3506
exclude = {"bus", "changedstate", "secret",
3507
"checker", "server_settings"}
3251
3508
for name, typ in inspect.getmembers(dbus.service
3253
3510
exclude.add(name)
3255
3512
client_dict["encrypted_secret"] = (client
3256
3513
.encrypted_secret)
3257
3514
for attr in client.client_structure:
3258
3515
if attr not in exclude:
3259
3516
client_dict[attr] = getattr(client, attr)
3261
3518
clients[client.name] = client_dict
3262
3519
del client_settings[client.name]["secret"]
3265
3522
with tempfile.NamedTemporaryFile(