235
196
.replace(b"\n", b"\\n")
236
197
.replace(b"\0", b"\\x00"))
239
200
def encrypt(self, data, password):
240
201
passphrase = self.password_encode(password)
241
202
with tempfile.NamedTemporaryFile(
242
203
dir=self.tempdir) as passfile:
243
204
passfile.write(passphrase)
245
proc = subprocess.Popen([self.gpg, '--symmetric',
206
proc = subprocess.Popen(['gpg', '--symmetric',
246
207
'--passphrase-file',
248
209
+ self.gnupgargs,
249
stdin=subprocess.PIPE,
250
stdout=subprocess.PIPE,
251
stderr=subprocess.PIPE)
252
ciphertext, err = proc.communicate(input=data)
210
stdin = subprocess.PIPE,
211
stdout = subprocess.PIPE,
212
stderr = subprocess.PIPE)
213
ciphertext, err = proc.communicate(input = data)
253
214
if proc.returncode != 0:
254
215
raise PGPError(err)
255
216
return ciphertext
257
218
def decrypt(self, data, password):
258
219
passphrase = self.password_encode(password)
259
220
with tempfile.NamedTemporaryFile(
260
dir=self.tempdir) as passfile:
221
dir = self.tempdir) as passfile:
261
222
passfile.write(passphrase)
263
proc = subprocess.Popen([self.gpg, '--decrypt',
224
proc = subprocess.Popen(['gpg', '--decrypt',
264
225
'--passphrase-file',
266
227
+ self.gnupgargs,
267
stdin=subprocess.PIPE,
268
stdout=subprocess.PIPE,
269
stderr=subprocess.PIPE)
270
decrypted_plaintext, err = proc.communicate(input=data)
228
stdin = subprocess.PIPE,
229
stdout = subprocess.PIPE,
230
stderr = subprocess.PIPE)
231
decrypted_plaintext, err = proc.communicate(input = data)
271
232
if proc.returncode != 0:
272
233
raise PGPError(err)
273
234
return decrypted_plaintext
276
# Pretend that we have an Avahi module
278
"""This isn't so much a class as it is a module-like namespace.
279
It is instantiated once, and simulates having an Avahi module."""
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 = "/"
289
def string_array_to_txt_array(self, t):
290
return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
291
for s in t), signature="ay")
292
ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h
293
ENTRY_GROUP_COLLISION = 3 # avahi-common/defs.h
294
ENTRY_GROUP_FAILURE = 4 # avahi-common/defs.h
295
SERVER_INVALID = 0 # avahi-common/defs.h
296
SERVER_REGISTERING = 1 # avahi-common/defs.h
297
SERVER_RUNNING = 2 # avahi-common/defs.h
298
SERVER_COLLISION = 3 # avahi-common/defs.h
299
SERVER_FAILURE = 4 # avahi-common/defs.h
303
237
class AvahiError(Exception):
304
238
def __init__(self, value, *args, **kwargs):
305
239
self.value = value
501
435
.format(self.name)))
505
# Pretend that we have a GnuTLS module
506
class GnuTLS(object):
507
"""This isn't so much a class as it is a module-like namespace.
508
It is instantiated once, and simulates having a GnuTLS module."""
510
_library = ctypes.cdll.LoadLibrary(
511
ctypes.util.find_library("gnutls"))
512
_need_version = b"3.3.0"
515
# Need to use class name "GnuTLS" here, since this method is
516
# called before the assignment to the "gnutls" global variable
518
if GnuTLS.check_version(self._need_version) is None:
519
raise GnuTLS.Error("Needs GnuTLS {} or later"
520
.format(self._need_version))
522
# Unless otherwise indicated, the constants and types below are
523
# all from the gnutls/gnutls.h C header file.
533
E_NO_CERTIFICATE_FOUND = -49
534
OPENPGP_FMT_RAW = 0 # gnutls/openpgp.h
537
class session_int(ctypes.Structure):
539
session_t = ctypes.POINTER(session_int)
541
class certificate_credentials_st(ctypes.Structure):
543
certificate_credentials_t = ctypes.POINTER(
544
certificate_credentials_st)
545
certificate_type_t = ctypes.c_int
547
class datum_t(ctypes.Structure):
548
_fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
549
('size', ctypes.c_uint)]
551
class openpgp_crt_int(ctypes.Structure):
553
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
554
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
555
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
556
credentials_type_t = ctypes.c_int
557
transport_ptr_t = ctypes.c_void_p
558
close_request_t = ctypes.c_int
561
class Error(Exception):
562
# We need to use the class name "GnuTLS" here, since this
563
# exception might be raised from within GnuTLS.__init__,
564
# which is called before the assignment to the "gnutls"
565
# global variable has happened.
566
def __init__(self, message=None, code=None, args=()):
567
# Default usage is by a message string, but if a return
568
# code is passed, convert it to a string with
571
if message is None and code is not None:
572
message = GnuTLS.strerror(code)
573
return super(GnuTLS.Error, self).__init__(
576
class CertificateSecurityError(Error):
580
class Credentials(object):
582
self._c_object = gnutls.certificate_credentials_t()
583
gnutls.certificate_allocate_credentials(
584
ctypes.byref(self._c_object))
585
self.type = gnutls.CRD_CERTIFICATE
588
gnutls.certificate_free_credentials(self._c_object)
590
class ClientSession(object):
591
def __init__(self, socket, credentials=None):
592
self._c_object = gnutls.session_t()
593
gnutls.init(ctypes.byref(self._c_object), gnutls.CLIENT)
594
gnutls.set_default_priority(self._c_object)
595
gnutls.transport_set_ptr(self._c_object, socket.fileno())
596
gnutls.handshake_set_private_extensions(self._c_object,
599
if credentials is None:
600
credentials = gnutls.Credentials()
601
gnutls.credentials_set(self._c_object, credentials.type,
602
ctypes.cast(credentials._c_object,
604
self.credentials = credentials
607
gnutls.deinit(self._c_object)
610
return gnutls.handshake(self._c_object)
612
def send(self, data):
616
data_len -= gnutls.record_send(self._c_object,
621
return gnutls.bye(self._c_object, gnutls.SHUT_RDWR)
623
# Error handling functions
624
def _error_code(result):
625
"""A function to raise exceptions on errors, suitable
626
for the 'restype' attribute on ctypes functions"""
629
if result == gnutls.E_NO_CERTIFICATE_FOUND:
630
raise gnutls.CertificateSecurityError(code=result)
631
raise gnutls.Error(code=result)
633
def _retry_on_error(result, func, arguments):
634
"""A function to retry on some errors, suitable
635
for the 'errcheck' attribute on ctypes functions"""
637
if result not in (gnutls.E_INTERRUPTED, gnutls.E_AGAIN):
638
return _error_code(result)
639
result = func(*arguments)
642
# Unless otherwise indicated, the function declarations below are
643
# all from the gnutls/gnutls.h C header file.
646
priority_set_direct = _library.gnutls_priority_set_direct
647
priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
648
ctypes.POINTER(ctypes.c_char_p)]
649
priority_set_direct.restype = _error_code
651
init = _library.gnutls_init
652
init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
653
init.restype = _error_code
655
set_default_priority = _library.gnutls_set_default_priority
656
set_default_priority.argtypes = [session_t]
657
set_default_priority.restype = _error_code
659
record_send = _library.gnutls_record_send
660
record_send.argtypes = [session_t, ctypes.c_void_p,
662
record_send.restype = ctypes.c_ssize_t
663
record_send.errcheck = _retry_on_error
665
certificate_allocate_credentials = (
666
_library.gnutls_certificate_allocate_credentials)
667
certificate_allocate_credentials.argtypes = [
668
ctypes.POINTER(certificate_credentials_t)]
669
certificate_allocate_credentials.restype = _error_code
671
certificate_free_credentials = (
672
_library.gnutls_certificate_free_credentials)
673
certificate_free_credentials.argtypes = [
674
certificate_credentials_t]
675
certificate_free_credentials.restype = None
677
handshake_set_private_extensions = (
678
_library.gnutls_handshake_set_private_extensions)
679
handshake_set_private_extensions.argtypes = [session_t,
681
handshake_set_private_extensions.restype = None
683
credentials_set = _library.gnutls_credentials_set
684
credentials_set.argtypes = [session_t, credentials_type_t,
686
credentials_set.restype = _error_code
688
strerror = _library.gnutls_strerror
689
strerror.argtypes = [ctypes.c_int]
690
strerror.restype = ctypes.c_char_p
692
certificate_type_get = _library.gnutls_certificate_type_get
693
certificate_type_get.argtypes = [session_t]
694
certificate_type_get.restype = _error_code
696
certificate_get_peers = _library.gnutls_certificate_get_peers
697
certificate_get_peers.argtypes = [session_t,
698
ctypes.POINTER(ctypes.c_uint)]
699
certificate_get_peers.restype = ctypes.POINTER(datum_t)
701
global_set_log_level = _library.gnutls_global_set_log_level
702
global_set_log_level.argtypes = [ctypes.c_int]
703
global_set_log_level.restype = None
705
global_set_log_function = _library.gnutls_global_set_log_function
706
global_set_log_function.argtypes = [log_func]
707
global_set_log_function.restype = None
709
deinit = _library.gnutls_deinit
710
deinit.argtypes = [session_t]
711
deinit.restype = None
713
handshake = _library.gnutls_handshake
714
handshake.argtypes = [session_t]
715
handshake.restype = _error_code
716
handshake.errcheck = _retry_on_error
718
transport_set_ptr = _library.gnutls_transport_set_ptr
719
transport_set_ptr.argtypes = [session_t, transport_ptr_t]
720
transport_set_ptr.restype = None
722
bye = _library.gnutls_bye
723
bye.argtypes = [session_t, close_request_t]
724
bye.restype = _error_code
725
bye.errcheck = _retry_on_error
727
check_version = _library.gnutls_check_version
728
check_version.argtypes = [ctypes.c_char_p]
729
check_version.restype = ctypes.c_char_p
731
# All the function declarations below are from gnutls/openpgp.h
733
openpgp_crt_init = _library.gnutls_openpgp_crt_init
734
openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
735
openpgp_crt_init.restype = _error_code
737
openpgp_crt_import = _library.gnutls_openpgp_crt_import
738
openpgp_crt_import.argtypes = [openpgp_crt_t,
739
ctypes.POINTER(datum_t),
741
openpgp_crt_import.restype = _error_code
743
openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
744
openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
745
ctypes.POINTER(ctypes.c_uint)]
746
openpgp_crt_verify_self.restype = _error_code
748
openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
749
openpgp_crt_deinit.argtypes = [openpgp_crt_t]
750
openpgp_crt_deinit.restype = None
752
openpgp_crt_get_fingerprint = (
753
_library.gnutls_openpgp_crt_get_fingerprint)
754
openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
758
openpgp_crt_get_fingerprint.restype = _error_code
760
# Remove non-public functions
761
del _error_code, _retry_on_error
762
# Create the global "gnutls" object, simulating a module
766
438
def call_pipe(connection, # : multiprocessing.Connection
767
439
func, *args, **kwargs):
768
440
"""This function is meant to be called by multiprocessing.Process
770
442
This function runs func(*args, **kwargs), and writes the resulting
771
443
return value on the provided multiprocessing.Connection.
773
445
connection.send(func(*args, **kwargs))
774
446
connection.close()
777
448
class Client(object):
778
449
"""A representation of a client host served by this server.
781
452
approved: bool(); 'None' if not yet approved/disapproved
782
453
approval_delay: datetime.timedelta(); Time to wait for approval
957
626
logger.info("Disabling client %s", self.name)
958
627
if getattr(self, "disable_initiator_tag", None) is not None:
959
GLib.source_remove(self.disable_initiator_tag)
628
gobject.source_remove(self.disable_initiator_tag)
960
629
self.disable_initiator_tag = None
961
630
self.expires = None
962
631
if getattr(self, "checker_initiator_tag", None) is not None:
963
GLib.source_remove(self.checker_initiator_tag)
632
gobject.source_remove(self.checker_initiator_tag)
964
633
self.checker_initiator_tag = None
965
634
self.stop_checker()
966
635
self.enabled = False
968
637
self.send_changedstate()
969
# Do not run this again if called by a GLib.timeout_add
638
# Do not run this again if called by a gobject.timeout_add
972
641
def __del__(self):
975
644
def init_checker(self):
976
645
# Schedule a new checker to be started an 'interval' from now,
977
646
# and every interval from then on.
978
647
if self.checker_initiator_tag is not None:
979
GLib.source_remove(self.checker_initiator_tag)
980
self.checker_initiator_tag = GLib.timeout_add(
648
gobject.source_remove(self.checker_initiator_tag)
649
self.checker_initiator_tag = gobject.timeout_add(
981
650
int(self.interval.total_seconds() * 1000),
982
651
self.start_checker)
983
652
# Schedule a disable() when 'timeout' has passed
984
653
if self.disable_initiator_tag is not None:
985
GLib.source_remove(self.disable_initiator_tag)
986
self.disable_initiator_tag = GLib.timeout_add(
654
gobject.source_remove(self.disable_initiator_tag)
655
self.disable_initiator_tag = gobject.timeout_add(
987
656
int(self.timeout.total_seconds() * 1000), self.disable)
988
657
# Also start a new checker *right now*.
989
658
self.start_checker()
991
660
def checker_callback(self, source, condition, connection,
993
662
"""The checker has completed, so take appropriate actions."""
1227
895
for cls in self.__class__.__mro__
1228
896
for name, athing in
1229
897
inspect.getmembers(cls, self._is_dbus_thing(thing)))
1231
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1233
path_keyword='object_path',
1234
connection_keyword='connection')
1235
def Introspect(self, object_path, connection):
1236
"""Overloading of standard D-Bus method.
1238
Inserts annotation tags on methods and signals.
1240
xmlstring = dbus.service.Object.Introspect(self, object_path,
1243
document = xml.dom.minidom.parseString(xmlstring)
1245
for if_tag in document.getElementsByTagName("interface"):
1246
# Add annotation tags
1247
for typ in ("method", "signal"):
1248
for tag in if_tag.getElementsByTagName(typ):
1250
for name, prop in (self.
1251
_get_all_dbus_things(typ)):
1252
if (name == tag.getAttribute("name")
1253
and prop._dbus_interface
1254
== if_tag.getAttribute("name")):
1255
annots.update(getattr(
1256
prop, "_dbus_annotations", {}))
1257
for name, value in annots.items():
1258
ann_tag = document.createElement(
1260
ann_tag.setAttribute("name", name)
1261
ann_tag.setAttribute("value", value)
1262
tag.appendChild(ann_tag)
1263
# Add interface annotation tags
1264
for annotation, value in dict(
1265
itertools.chain.from_iterable(
1266
annotations().items()
1267
for name, annotations
1268
in self._get_all_dbus_things("interface")
1269
if name == if_tag.getAttribute("name")
1271
ann_tag = document.createElement("annotation")
1272
ann_tag.setAttribute("name", annotation)
1273
ann_tag.setAttribute("value", value)
1274
if_tag.appendChild(ann_tag)
1275
# Fix argument name for the Introspect method itself
1276
if (if_tag.getAttribute("name")
1277
== dbus.INTROSPECTABLE_IFACE):
1278
for cn in if_tag.getElementsByTagName("method"):
1279
if cn.getAttribute("name") == "Introspect":
1280
for arg in cn.getElementsByTagName("arg"):
1281
if (arg.getAttribute("direction")
1283
arg.setAttribute("name",
1285
xmlstring = document.toxml("utf-8")
1287
except (AttributeError, xml.dom.DOMException,
1288
xml.parsers.expat.ExpatError) as error:
1289
logger.error("Failed to override Introspection method",
1294
class DBusObjectWithProperties(DBusObjectWithAnnotations):
1295
"""A D-Bus object with properties.
1297
Classes inheriting from this can use the dbus_service_property
1298
decorator to expose methods as D-Bus properties. It exposes the
1299
standard Get(), Set(), and GetAll() methods on the D-Bus.
1302
899
def _get_dbus_property(self, interface_name, property_name):
1303
900
"""Returns a bound method if one exists which is a D-Bus
1304
901
property with the specified name and interface.
1419
1006
if prop._dbus_interface
1420
1007
== if_tag.getAttribute("name")):
1421
1008
if_tag.appendChild(tag)
1422
# Add annotation tags for properties
1423
for tag in if_tag.getElementsByTagName("property"):
1425
for name, prop in self._get_all_dbus_things(
1427
if (name == tag.getAttribute("name")
1428
and prop._dbus_interface
1429
== if_tag.getAttribute("name")):
1430
annots.update(getattr(
1431
prop, "_dbus_annotations", {}))
1432
for name, value in annots.items():
1433
ann_tag = document.createElement(
1435
ann_tag.setAttribute("name", name)
1436
ann_tag.setAttribute("value", value)
1437
tag.appendChild(ann_tag)
1009
# Add annotation tags
1010
for typ in ("method", "signal", "property"):
1011
for tag in if_tag.getElementsByTagName(typ):
1013
for name, prop in (self.
1014
_get_all_dbus_things(typ)):
1015
if (name == tag.getAttribute("name")
1016
and prop._dbus_interface
1017
== if_tag.getAttribute("name")):
1018
annots.update(getattr(
1019
prop, "_dbus_annotations", {}))
1020
for name, value in annots.items():
1021
ann_tag = document.createElement(
1023
ann_tag.setAttribute("name", name)
1024
ann_tag.setAttribute("value", value)
1025
tag.appendChild(ann_tag)
1026
# Add interface annotation tags
1027
for annotation, value in dict(
1028
itertools.chain.from_iterable(
1029
annotations().items()
1030
for name, annotations
1031
in self._get_all_dbus_things("interface")
1032
if name == if_tag.getAttribute("name")
1034
ann_tag = document.createElement("annotation")
1035
ann_tag.setAttribute("name", annotation)
1036
ann_tag.setAttribute("value", value)
1037
if_tag.appendChild(ann_tag)
1438
1038
# Add the names to the return values for the
1439
1039
# "org.freedesktop.DBus.Properties" methods
1440
1040
if (if_tag.getAttribute("name")
1458
1058
exc_info=error)
1459
1059
return xmlstring
1462
dbus.OBJECT_MANAGER_IFACE
1463
except AttributeError:
1464
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1467
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1468
"""A D-Bus object with an ObjectManager.
1470
Classes inheriting from this exposes the standard
1471
GetManagedObjects call and the InterfacesAdded and
1472
InterfacesRemoved signals on the standard
1473
"org.freedesktop.DBus.ObjectManager" interface.
1475
Note: No signals are sent automatically; they must be sent
1478
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1479
out_signature="a{oa{sa{sv}}}")
1480
def GetManagedObjects(self):
1481
"""This function must be overridden"""
1482
raise NotImplementedError()
1484
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1485
signature="oa{sa{sv}}")
1486
def InterfacesAdded(self, object_path, interfaces_and_properties):
1489
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature="oas")
1490
def InterfacesRemoved(self, object_path, interfaces):
1493
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1495
path_keyword='object_path',
1496
connection_keyword='connection')
1497
def Introspect(self, object_path, connection):
1498
"""Overloading of standard D-Bus method.
1500
Override return argument name of GetManagedObjects to be
1501
"objpath_interfaces_and_properties"
1503
xmlstring = DBusObjectWithAnnotations.Introspect(self,
1507
document = xml.dom.minidom.parseString(xmlstring)
1509
for if_tag in document.getElementsByTagName("interface"):
1510
# Fix argument name for the GetManagedObjects method
1511
if (if_tag.getAttribute("name")
1512
== dbus.OBJECT_MANAGER_IFACE):
1513
for cn in if_tag.getElementsByTagName("method"):
1514
if (cn.getAttribute("name")
1515
== "GetManagedObjects"):
1516
for arg in cn.getElementsByTagName("arg"):
1517
if (arg.getAttribute("direction")
1521
"objpath_interfaces"
1523
xmlstring = document.toxml("utf-8")
1525
except (AttributeError, xml.dom.DOMException,
1526
xml.parsers.expat.ExpatError) as error:
1527
logger.error("Failed to override Introspection method",
1532
1062
def datetime_to_dbus(dt, variant_level=0):
1533
1063
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1535
return dbus.String("", variant_level=variant_level)
1065
return dbus.String("", variant_level = variant_level)
1536
1066
return dbus.String(dt.isoformat(), variant_level=variant_level)
2186
1725
class ClientHandler(socketserver.BaseRequestHandler, object):
2187
1726
"""A class to handle client connections.
2189
1728
Instantiated once for each connection to handle it.
2190
1729
Note: This will run in its own forked process."""
2192
1731
def handle(self):
2193
1732
with contextlib.closing(self.server.child_pipe) as child_pipe:
2194
1733
logger.info("TCP connection from: %s",
2195
1734
str(self.client_address))
2196
1735
logger.debug("Pipe FD: %d",
2197
1736
self.server.child_pipe.fileno())
2199
session = gnutls.ClientSession(self.request)
2201
# priority = ':'.join(("NONE", "+VERS-TLS1.1",
2202
# "+AES-256-CBC", "+SHA1",
2203
# "+COMP-NULL", "+CTYPE-OPENPGP",
1738
session = gnutls.connection.ClientSession(
1739
self.request, gnutls.connection .X509Credentials())
1741
# Note: gnutls.connection.X509Credentials is really a
1742
# generic GnuTLS certificate credentials object so long as
1743
# no X.509 keys are added to it. Therefore, we can use it
1744
# here despite using OpenPGP certificates.
1746
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1747
# "+AES-256-CBC", "+SHA1",
1748
# "+COMP-NULL", "+CTYPE-OPENPGP",
2205
1750
# Use a fallback default, since this MUST be set.
2206
1751
priority = self.server.gnutls_priority
2207
1752
if priority is None:
2208
1753
priority = "NORMAL"
2209
gnutls.priority_set_direct(session._c_object,
2210
priority.encode("utf-8"),
1754
gnutls.library.functions.gnutls_priority_set_direct(
1755
session._c_object, priority, None)
2213
1757
# Start communication using the Mandos protocol
2214
1758
# Get protocol number
2215
1759
line = self.request.makefile().readline()
2301
1846
delay -= time2 - time
2304
session.send(client.secret)
2305
except gnutls.Error as error:
2306
logger.warning("gnutls send failed",
1849
while sent_size < len(client.secret):
1851
sent = session.send(client.secret[sent_size:])
1852
except gnutls.errors.GNUTLSError as error:
1853
logger.warning("gnutls send failed",
1856
logger.debug("Sent: %d, remaining: %d", sent,
1857
len(client.secret) - (sent_size
2310
1861
logger.info("Sending secret to %s", client.name)
2311
1862
# bump the timeout using extended_timeout
2312
1863
client.bump_timeout(client.extended_timeout)
2313
1864
if self.server.use_dbus:
2314
1865
# Emit D-Bus signal
2315
1866
client.GotSecret()
2318
1869
if approval_required:
2319
1870
client.approvals_pending -= 1
2322
except gnutls.Error as error:
1873
except gnutls.errors.GNUTLSError as error:
2323
1874
logger.warning("GnuTLS bye failed",
2324
1875
exc_info=error)
2327
1878
def peer_certificate(session):
2328
1879
"Return the peer's OpenPGP certificate as a bytestring"
2329
1880
# If not an OpenPGP certificate...
2330
if (gnutls.certificate_type_get(session._c_object)
2331
!= gnutls.CRT_OPENPGP):
2332
# ...return invalid data
1881
if (gnutls.library.functions.gnutls_certificate_type_get(
1883
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1884
# ...do the normal thing
1885
return session.peer_certificate
2334
1886
list_size = ctypes.c_uint(1)
2335
cert_list = (gnutls.certificate_get_peers
1887
cert_list = (gnutls.library.functions
1888
.gnutls_certificate_get_peers
2336
1889
(session._c_object, ctypes.byref(list_size)))
2337
1890
if not bool(cert_list) and list_size.value != 0:
2338
raise gnutls.Error("error getting peer certificate")
1891
raise gnutls.errors.GNUTLSError("error getting peer"
2339
1893
if list_size.value == 0:
2341
1895
cert = cert_list[0]
2342
1896
return ctypes.string_at(cert.data, cert.size)
2345
1899
def fingerprint(openpgp):
2346
1900
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
2347
1901
# New GnuTLS "datum" with the OpenPGP public key
2348
datum = gnutls.datum_t(
1902
datum = gnutls.library.types.gnutls_datum_t(
2349
1903
ctypes.cast(ctypes.c_char_p(openpgp),
2350
1904
ctypes.POINTER(ctypes.c_ubyte)),
2351
1905
ctypes.c_uint(len(openpgp)))
2352
1906
# New empty GnuTLS certificate
2353
crt = gnutls.openpgp_crt_t()
2354
gnutls.openpgp_crt_init(ctypes.byref(crt))
1907
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1908
gnutls.library.functions.gnutls_openpgp_crt_init(
2355
1910
# Import the OpenPGP public key into the certificate
2356
gnutls.openpgp_crt_import(crt, ctypes.byref(datum),
2357
gnutls.OPENPGP_FMT_RAW)
1911
gnutls.library.functions.gnutls_openpgp_crt_import(
1912
crt, ctypes.byref(datum),
1913
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
2358
1914
# Verify the self signature in the key
2359
1915
crtverify = ctypes.c_uint()
2360
gnutls.openpgp_crt_verify_self(crt, 0,
2361
ctypes.byref(crtverify))
1916
gnutls.library.functions.gnutls_openpgp_crt_verify_self(
1917
crt, 0, ctypes.byref(crtverify))
2362
1918
if crtverify.value != 0:
2363
gnutls.openpgp_crt_deinit(crt)
2364
raise gnutls.CertificateSecurityError("Verify failed")
1919
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1920
raise gnutls.errors.CertificateSecurityError(
2365
1922
# New buffer for the fingerprint
2366
1923
buf = ctypes.create_string_buffer(20)
2367
1924
buf_len = ctypes.c_size_t()
2368
1925
# Get the fingerprint from the certificate into the buffer
2369
gnutls.openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
2370
ctypes.byref(buf_len))
1926
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint(
1927
crt, ctypes.byref(buf), ctypes.byref(buf_len))
2371
1928
# Deinit the certificate
2372
gnutls.openpgp_crt_deinit(crt)
1929
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
2373
1930
# Convert the buffer to a Python bytestring
2374
1931
fpr = ctypes.string_at(buf, buf_len.value)
2375
1932
# Convert the bytestring to hexadecimal notation
2464
2020
# socket_wrapper(), if socketfd was set.
2465
2021
socketserver.TCPServer.__init__(self, server_address,
2466
2022
RequestHandlerClass)
2468
2024
def server_bind(self):
2469
2025
"""This overrides the normal server_bind() function
2470
2026
to bind to an interface if one was specified, and also NOT to
2471
2027
bind to an address or port if they were not specified."""
2472
global SO_BINDTODEVICE
2473
2028
if self.interface is not None:
2474
2029
if SO_BINDTODEVICE is None:
2475
# Fall back to a hard-coded value which seems to be
2477
logger.warning("SO_BINDTODEVICE not found, trying 25")
2478
SO_BINDTODEVICE = 25
2480
self.socket.setsockopt(
2481
socket.SOL_SOCKET, SO_BINDTODEVICE,
2482
(self.interface + "\0").encode("utf-8"))
2483
except socket.error as error:
2484
if error.errno == errno.EPERM:
2485
logger.error("No permission to bind to"
2486
" interface %s", self.interface)
2487
elif error.errno == errno.ENOPROTOOPT:
2488
logger.error("SO_BINDTODEVICE not available;"
2489
" cannot bind to interface %s",
2491
elif error.errno == errno.ENODEV:
2492
logger.error("Interface %s does not exist,"
2493
" cannot bind", self.interface)
2030
logger.error("SO_BINDTODEVICE does not exist;"
2031
" cannot bind to interface %s",
2035
self.socket.setsockopt(
2036
socket.SOL_SOCKET, SO_BINDTODEVICE,
2037
(self.interface + "\0").encode("utf-8"))
2038
except socket.error as error:
2039
if error.errno == errno.EPERM:
2040
logger.error("No permission to bind to"
2041
" interface %s", self.interface)
2042
elif error.errno == errno.ENOPROTOOPT:
2043
logger.error("SO_BINDTODEVICE not available;"
2044
" cannot bind to interface %s",
2046
elif error.errno == errno.ENODEV:
2047
logger.error("Interface %s does not exist,"
2048
" cannot bind", self.interface)
2496
2051
# Only bind(2) the socket if we really need to.
2497
2052
if self.server_address[0] or self.server_address[1]:
2498
2053
if not self.server_address[0]:
2499
2054
if self.address_family == socket.AF_INET6:
2500
any_address = "::" # in6addr_any
2055
any_address = "::" # in6addr_any
2502
any_address = "0.0.0.0" # INADDR_ANY
2057
any_address = "0.0.0.0" # INADDR_ANY
2503
2058
self.server_address = (any_address,
2504
2059
self.server_address[1])
2505
2060
elif not self.server_address[1]:
3059
2605
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
3060
2606
service = AvahiServiceToSyslog(
3061
name=server_settings["servicename"],
3062
servicetype="_mandos._tcp",
2607
name = server_settings["servicename"],
2608
servicetype = "_mandos._tcp",
2609
protocol = protocol,
3065
2611
if server_settings["interface"]:
3066
2612
service.interface = if_nametoindex(
3067
2613
server_settings["interface"].encode("utf-8"))
3069
2615
global multiprocessing_manager
3070
2616
multiprocessing_manager = multiprocessing.Manager()
3072
2618
client_class = Client
3074
client_class = functools.partial(ClientDBus, bus=bus)
2620
client_class = functools.partial(ClientDBus, bus = bus)
3076
2622
client_settings = Client.config_parser(client_config)
3077
2623
old_client_settings = {}
3078
2624
clients_data = {}
3080
2626
# This is used to redirect stdout and stderr for checker processes
3082
wnull = open(os.devnull, "w") # A writable /dev/null
2628
wnull = open(os.devnull, "w") # A writable /dev/null
3083
2629
# Only used if server is running in foreground but not in debug
3085
2631
if debug or not foreground:
3088
2634
# Get client data and settings from last running state.
3089
2635
if server_settings["restore"]:
3091
2637
with open(stored_state_path, "rb") as stored_state:
3092
if sys.version_info.major == 2:
3093
clients_data, old_client_settings = pickle.load(
3096
bytes_clients_data, bytes_old_client_settings = (
3097
pickle.load(stored_state, encoding="bytes"))
3098
# Fix bytes to strings
3101
clients_data = {(key.decode("utf-8")
3102
if isinstance(key, bytes)
3105
bytes_clients_data.items()}
3106
del bytes_clients_data
3107
for key in clients_data:
3108
value = {(k.decode("utf-8")
3109
if isinstance(k, bytes) else k): v
3111
clients_data[key].items()}
3112
clients_data[key] = value
3114
value["client_structure"] = [
3116
if isinstance(s, bytes)
3118
value["client_structure"]]
3120
for k in ("name", "host"):
3121
if isinstance(value[k], bytes):
3122
value[k] = value[k].decode("utf-8")
3123
# old_client_settings
3125
old_client_settings = {
3126
(key.decode("utf-8")
3127
if isinstance(key, bytes)
3130
bytes_old_client_settings.items()}
3131
del bytes_old_client_settings
3133
for value in old_client_settings.values():
3134
if isinstance(value["host"], bytes):
3135
value["host"] = (value["host"]
2638
clients_data, old_client_settings = pickle.load(
3137
2640
os.remove(stored_state_path)
3138
2641
except IOError as e:
3139
2642
if e.errno == errno.ENOENT:
3239
2742
pidfilename, pid)
3241
2744
del pidfilename
3243
for termsig in (signal.SIGHUP, signal.SIGTERM):
3244
GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig,
3245
lambda: main_loop.quit() and False)
2746
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2747
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
3249
2751
@alternate_dbus_interfaces(
3250
{"se.recompile.Mandos": "se.bsnet.fukt.Mandos"})
3251
class MandosDBusService(DBusObjectWithObjectManager):
2752
{ "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
2753
class MandosDBusService(DBusObjectWithProperties):
3252
2754
"""A D-Bus proxy object"""
3254
2756
def __init__(self):
3255
2757
dbus.service.Object.__init__(self, bus, "/")
3257
2759
_interface = "se.recompile.Mandos"
2761
@dbus_interface_annotations(_interface)
2764
"org.freedesktop.DBus.Property.EmitsChangedSignal":
3259
2767
@dbus.service.signal(_interface, signature="o")
3260
2768
def ClientAdded(self, objpath):
3264
2772
@dbus.service.signal(_interface, signature="ss")
3265
2773
def ClientNotFound(self, fingerprint, address):
3269
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3271
2777
@dbus.service.signal(_interface, signature="os")
3272
2778
def ClientRemoved(self, objpath, name):
3276
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
3278
2782
@dbus.service.method(_interface, out_signature="ao")
3279
2783
def GetAllClients(self):
3281
2785
return dbus.Array(c.dbus_object_path for c in
3282
tcp_server.clients.values())
3284
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2786
tcp_server.clients.itervalues())
3286
2788
@dbus.service.method(_interface,
3287
2789
out_signature="a{oa{sv}}")
3288
2790
def GetAllClientsWithProperties(self):
3290
2792
return dbus.Dictionary(
3291
{c.dbus_object_path: c.GetAll(
3292
"se.recompile.Mandos.Client")
3293
for c in tcp_server.clients.values()},
2793
{ c.dbus_object_path: c.GetAll("")
2794
for c in tcp_server.clients.itervalues() },
3294
2795
signature="oa{sv}")
3296
2797
@dbus.service.method(_interface, in_signature="o")
3297
2798
def RemoveClient(self, object_path):
3299
for c in tcp_server.clients.values():
2800
for c in tcp_server.clients.itervalues():
3300
2801
if c.dbus_object_path == object_path:
3301
2802
del tcp_server.clients[c.name]
3302
2803
c.remove_from_connection()
3303
# Don't signal the disabling
2804
# Don't signal anything except ClientRemoved
3304
2805
c.disable(quiet=True)
3305
# Emit D-Bus signal for removal
3306
self.client_removed_signal(c)
2807
self.ClientRemoved(object_path, c.name)
3308
2809
raise KeyError(object_path)
3312
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
3313
out_signature="a{oa{sa{sv}}}")
3314
def GetManagedObjects(self):
3316
return dbus.Dictionary(
3317
{client.dbus_object_path:
3319
{interface: client.GetAll(interface)
3321
client._get_all_interface_names()})
3322
for client in tcp_server.clients.values()})
3324
def client_added_signal(self, client):
3325
"""Send the new standard signal and the old signal"""
3327
# New standard signal
3328
self.InterfacesAdded(
3329
client.dbus_object_path,
3331
{interface: client.GetAll(interface)
3333
client._get_all_interface_names()}))
3335
self.ClientAdded(client.dbus_object_path)
3337
def client_removed_signal(self, client):
3338
"""Send the new standard signal and the old signal"""
3340
# New standard signal
3341
self.InterfacesRemoved(
3342
client.dbus_object_path,
3343
client._get_all_interface_names())
3345
self.ClientRemoved(client.dbus_object_path,
3348
2813
mandos_dbus_service = MandosDBusService()
3350
# Save modules to variables to exempt the modules from being
3351
# unloaded before the function registered with atexit() is run.
3352
mp = multiprocessing
3356
2816
"Cleanup function; run on exit"
3358
2818
service.cleanup()
3360
mp.active_children()
2820
multiprocessing.active_children()
3362
2822
if not (tcp_server.clients or client_settings):
3365
2825
# Store client before exiting. Secrets are encrypted with key
3366
2826
# based on what config file has. If config file is
3367
2827
# removed/edited, old secret will thus be unrecovable.
3369
2829
with PGPEngine() as pgp:
3370
for client in tcp_server.clients.values():
2830
for client in tcp_server.clients.itervalues():
3371
2831
key = client_settings[client.name]["secret"]
3372
2832
client.encrypted_secret = pgp.encrypt(client.secret,
3374
2834
client_dict = {}
3376
2836
# A list of attributes that can not be pickled
3378
exclude = {"bus", "changedstate", "secret",
3379
"checker", "server_settings"}
2838
exclude = { "bus", "changedstate", "secret",
2839
"checker", "server_settings" }
3380
2840
for name, typ in inspect.getmembers(dbus.service
3382
2842
exclude.add(name)
3384
2844
client_dict["encrypted_secret"] = (client
3385
2845
.encrypted_secret)
3386
2846
for attr in client.client_structure:
3387
2847
if attr not in exclude:
3388
2848
client_dict[attr] = getattr(client, attr)
3390
2850
clients[client.name] = client_dict
3391
2851
del client_settings[client.name]["secret"]
3394
2854
with tempfile.NamedTemporaryFile(