282
229
if config is None:
284
231
logger.debug(u"Creating client %r", self.name)
232
self.use_dbus = False # During __init__
285
233
# Uppercase and remove spaces from fingerprint for later
286
234
# comparison purposes with return value from the fingerprint()
288
self.fingerprint = (config[u"fingerprint"].upper()
236
self.fingerprint = (config["fingerprint"].upper()
289
237
.replace(u" ", u""))
290
238
logger.debug(u" Fingerprint: %s", self.fingerprint)
291
if u"secret" in config:
292
self.secret = config[u"secret"].decode(u"base64")
293
elif u"secfile" in config:
294
with open(os.path.expanduser(os.path.expandvars
295
(config[u"secfile"])),
239
if "secret" in config:
240
self.secret = config["secret"].decode(u"base64")
241
elif "secfile" in config:
242
with closing(open(os.path.expanduser
244
(config["secfile"])))) as secfile:
297
245
self.secret = secfile.read()
299
247
raise TypeError(u"No secret or secfile for client %s"
301
self.host = config.get(u"host", u"")
249
self.host = config.get("host", "")
302
250
self.created = datetime.datetime.utcnow()
303
251
self.enabled = False
304
252
self.last_enabled = None
305
253
self.last_checked_ok = None
306
self.timeout = string_to_delta(config[u"timeout"])
307
self.interval = string_to_delta(config[u"interval"])
254
self.timeout = string_to_delta(config["timeout"])
255
self.interval = string_to_delta(config["interval"])
308
256
self.disable_hook = disable_hook
309
257
self.checker = None
310
258
self.checker_initiator_tag = None
311
259
self.disable_initiator_tag = None
312
260
self.checker_callback_tag = None
313
self.checker_command = config[u"checker"]
314
self.current_checker_command = None
261
self.checker_command = config["checker"]
315
262
self.last_connect = None
263
# Only now, when this client is initialized, can it show up on
265
self.use_dbus = use_dbus
267
self.dbus_object_path = (dbus.ObjectPath
269
+ self.name.replace(".", "_")))
270
dbus.service.Object.__init__(self, bus,
271
self.dbus_object_path)
317
273
def enable(self):
318
274
"""Start this client's checker and timeout hooks"""
319
if getattr(self, u"enabled", False):
322
275
self.last_enabled = datetime.datetime.utcnow()
323
276
# Schedule a new checker to be started an 'interval' from now,
324
277
# and every interval from then on.
325
278
self.checker_initiator_tag = (gobject.timeout_add
326
279
(self.interval_milliseconds(),
327
280
self.start_checker))
281
# Also start a new checker *right now*.
328
283
# Schedule a disable() when 'timeout' has passed
329
284
self.disable_initiator_tag = (gobject.timeout_add
330
285
(self.timeout_milliseconds(),
332
287
self.enabled = True
333
# Also start a new checker *right now*.
290
self.PropertyChanged(dbus.String(u"enabled"),
291
dbus.Boolean(True, variant_level=1))
292
self.PropertyChanged(dbus.String(u"last_enabled"),
293
(_datetime_to_dbus(self.last_enabled,
336
def disable(self, quiet=True):
337
297
"""Disable this client."""
338
298
if not getattr(self, "enabled", False):
341
logger.info(u"Disabling client %s", self.name)
342
if getattr(self, u"disable_initiator_tag", False):
300
logger.info(u"Disabling client %s", self.name)
301
if getattr(self, "disable_initiator_tag", False):
343
302
gobject.source_remove(self.disable_initiator_tag)
344
303
self.disable_initiator_tag = None
345
if getattr(self, u"checker_initiator_tag", False):
304
if getattr(self, "checker_initiator_tag", False):
346
305
gobject.source_remove(self.checker_initiator_tag)
347
306
self.checker_initiator_tag = None
348
307
self.stop_checker()
349
308
if self.disable_hook:
350
309
self.disable_hook(self)
351
310
self.enabled = False
313
self.PropertyChanged(dbus.String(u"enabled"),
314
dbus.Boolean(False, variant_level=1))
352
315
# Do not run this again if called by a gobject.timeout_add
463
431
if self.checker_callback_tag:
464
432
gobject.source_remove(self.checker_callback_tag)
465
433
self.checker_callback_tag = None
466
if getattr(self, u"checker", None) is None:
434
if getattr(self, "checker", None) is None:
468
436
logger.debug(u"Stopping checker for %(name)s", vars(self))
470
438
os.kill(self.checker.pid, signal.SIGTERM)
472
440
#if self.checker.poll() is None:
473
441
# os.kill(self.checker.pid, signal.SIGKILL)
474
442
except OSError, error:
475
443
if error.errno != errno.ESRCH: # No such process
477
445
self.checker = None
480
def dbus_service_property(dbus_interface, signature=u"v",
481
access=u"readwrite", byte_arrays=False):
482
"""Decorators for marking methods of a DBusObjectWithProperties to
483
become properties on the D-Bus.
485
The decorated method will be called with no arguments by "Get"
486
and with one argument by "Set".
488
The parameters, where they are supported, are the same as
489
dbus.service.method, except there is only "signature", since the
490
type from Get() and the type sent to Set() is the same.
492
# Encoding deeply encoded byte arrays is not supported yet by the
493
# "Set" method, so we fail early here:
494
if byte_arrays and signature != u"ay":
495
raise ValueError(u"Byte arrays not supported for non-'ay'"
496
u" signature %r" % signature)
498
func._dbus_is_property = True
499
func._dbus_interface = dbus_interface
500
func._dbus_signature = signature
501
func._dbus_access = access
502
func._dbus_name = func.__name__
503
if func._dbus_name.endswith(u"_dbus_property"):
504
func._dbus_name = func._dbus_name[:-14]
505
func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
510
class DBusPropertyException(dbus.exceptions.DBusException):
511
"""A base class for D-Bus property-related exceptions
513
def __unicode__(self):
514
return unicode(str(self))
517
class DBusPropertyAccessException(DBusPropertyException):
518
"""A property's access permissions disallows an operation.
523
class DBusPropertyNotFound(DBusPropertyException):
524
"""An attempt was made to access a non-existing property.
529
class DBusObjectWithProperties(dbus.service.Object):
530
"""A D-Bus object with properties.
532
Classes inheriting from this can use the dbus_service_property
533
decorator to expose methods as D-Bus properties. It exposes the
534
standard Get(), Set(), and GetAll() methods on the D-Bus.
538
def _is_dbus_property(obj):
539
return getattr(obj, u"_dbus_is_property", False)
541
def _get_all_dbus_properties(self):
542
"""Returns a generator of (name, attribute) pairs
544
return ((prop._dbus_name, prop)
546
inspect.getmembers(self, self._is_dbus_property))
548
def _get_dbus_property(self, interface_name, property_name):
549
"""Returns a bound method if one exists which is a D-Bus
550
property with the specified name and interface.
552
for name in (property_name,
553
property_name + u"_dbus_property"):
554
prop = getattr(self, name, None)
556
or not self._is_dbus_property(prop)
557
or prop._dbus_name != property_name
558
or (interface_name and prop._dbus_interface
559
and interface_name != prop._dbus_interface)):
563
raise DBusPropertyNotFound(self.dbus_object_path + u":"
564
+ interface_name + u"."
567
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
569
def Get(self, interface_name, property_name):
570
"""Standard D-Bus property Get() method, see D-Bus standard.
572
prop = self._get_dbus_property(interface_name, property_name)
573
if prop._dbus_access == u"write":
574
raise DBusPropertyAccessException(property_name)
576
if not hasattr(value, u"variant_level"):
578
return type(value)(value, variant_level=value.variant_level+1)
580
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
581
def Set(self, interface_name, property_name, value):
582
"""Standard D-Bus property Set() method, see D-Bus standard.
584
prop = self._get_dbus_property(interface_name, property_name)
585
if prop._dbus_access == u"read":
586
raise DBusPropertyAccessException(property_name)
587
if prop._dbus_get_args_options[u"byte_arrays"]:
588
# The byte_arrays option is not supported yet on
589
# signatures other than "ay".
590
if prop._dbus_signature != u"ay":
592
value = dbus.ByteArray(''.join(unichr(byte)
596
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
597
out_signature=u"a{sv}")
598
def GetAll(self, interface_name):
599
"""Standard D-Bus property GetAll() method, see D-Bus
602
Note: Will not include properties with access="write".
605
for name, prop in self._get_all_dbus_properties():
607
and interface_name != prop._dbus_interface):
608
# Interface non-empty but did not match
610
# Ignore write-only properties
611
if prop._dbus_access == u"write":
614
if not hasattr(value, u"variant_level"):
617
all[name] = type(value)(value, variant_level=
618
value.variant_level+1)
619
return dbus.Dictionary(all, signature=u"sv")
621
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
623
path_keyword='object_path',
624
connection_keyword='connection')
625
def Introspect(self, object_path, connection):
626
"""Standard D-Bus method, overloaded to insert property tags.
628
xmlstring = dbus.service.Object.Introspect(self, object_path,
631
document = xml.dom.minidom.parseString(xmlstring)
632
def make_tag(document, name, prop):
633
e = document.createElement(u"property")
634
e.setAttribute(u"name", name)
635
e.setAttribute(u"type", prop._dbus_signature)
636
e.setAttribute(u"access", prop._dbus_access)
638
for if_tag in document.getElementsByTagName(u"interface"):
639
for tag in (make_tag(document, name, prop)
641
in self._get_all_dbus_properties()
642
if prop._dbus_interface
643
== if_tag.getAttribute(u"name")):
644
if_tag.appendChild(tag)
645
# Add the names to the return values for the
646
# "org.freedesktop.DBus.Properties" methods
647
if (if_tag.getAttribute(u"name")
648
== u"org.freedesktop.DBus.Properties"):
649
for cn in if_tag.getElementsByTagName(u"method"):
650
if cn.getAttribute(u"name") == u"Get":
651
for arg in cn.getElementsByTagName(u"arg"):
652
if (arg.getAttribute(u"direction")
654
arg.setAttribute(u"name", u"value")
655
elif cn.getAttribute(u"name") == u"GetAll":
656
for arg in cn.getElementsByTagName(u"arg"):
657
if (arg.getAttribute(u"direction")
659
arg.setAttribute(u"name", u"props")
660
xmlstring = document.toxml(u"utf-8")
662
except (AttributeError, xml.dom.DOMException,
663
xml.parsers.expat.ExpatError), error:
664
logger.error(u"Failed to override Introspection method",
669
class ClientDBus(Client, DBusObjectWithProperties):
670
"""A Client class using D-Bus
673
dbus_object_path: dbus.ObjectPath
674
bus: dbus.SystemBus()
676
# dbus.service.Object doesn't use super(), so we can't either.
678
def __init__(self, bus = None, *args, **kwargs):
680
Client.__init__(self, *args, **kwargs)
681
# Only now, when this client is initialized, can it show up on
683
self.dbus_object_path = (dbus.ObjectPath
685
+ self.name.replace(u".", u"_")))
686
DBusObjectWithProperties.__init__(self, self.bus,
687
self.dbus_object_path)
690
def _datetime_to_dbus(dt, variant_level=0):
691
"""Convert a UTC datetime.datetime() to a D-Bus type."""
692
return dbus.String(dt.isoformat(),
693
variant_level=variant_level)
696
oldstate = getattr(self, u"enabled", False)
697
r = Client.enable(self)
698
if oldstate != self.enabled:
700
self.PropertyChanged(dbus.String(u"enabled"),
701
dbus.Boolean(True, variant_level=1))
702
self.PropertyChanged(
703
dbus.String(u"last_enabled"),
704
self._datetime_to_dbus(self.last_enabled,
708
def disable(self, quiet = False):
709
oldstate = getattr(self, u"enabled", False)
710
r = Client.disable(self, quiet=quiet)
711
if not quiet and oldstate != self.enabled:
713
self.PropertyChanged(dbus.String(u"enabled"),
714
dbus.Boolean(False, variant_level=1))
717
def __del__(self, *args, **kwargs):
719
self.remove_from_connection()
722
if hasattr(DBusObjectWithProperties, u"__del__"):
723
DBusObjectWithProperties.__del__(self, *args, **kwargs)
724
Client.__del__(self, *args, **kwargs)
726
def checker_callback(self, pid, condition, command,
728
self.checker_callback_tag = None
731
self.PropertyChanged(dbus.String(u"checker_running"),
732
dbus.Boolean(False, variant_level=1))
733
if os.WIFEXITED(condition):
734
exitstatus = os.WEXITSTATUS(condition)
736
self.CheckerCompleted(dbus.Int16(exitstatus),
737
dbus.Int64(condition),
738
dbus.String(command))
741
self.CheckerCompleted(dbus.Int16(-1),
742
dbus.Int64(condition),
743
dbus.String(command))
745
return Client.checker_callback(self, pid, condition, command,
748
def checked_ok(self, *args, **kwargs):
749
r = Client.checked_ok(self, *args, **kwargs)
751
self.PropertyChanged(
752
dbus.String(u"last_checked_ok"),
753
(self._datetime_to_dbus(self.last_checked_ok,
757
def start_checker(self, *args, **kwargs):
758
old_checker = self.checker
759
if self.checker is not None:
760
old_checker_pid = self.checker.pid
762
old_checker_pid = None
763
r = Client.start_checker(self, *args, **kwargs)
764
# Only if new checker process was started
765
if (self.checker is not None
766
and old_checker_pid != self.checker.pid):
768
self.CheckerStarted(self.current_checker_command)
769
self.PropertyChanged(
770
dbus.String(u"checker_running"),
771
dbus.Boolean(True, variant_level=1))
774
def stop_checker(self, *args, **kwargs):
775
old_checker = getattr(self, u"checker", None)
776
r = Client.stop_checker(self, *args, **kwargs)
777
if (old_checker is not None
778
and getattr(self, u"checker", None) is None):
779
447
self.PropertyChanged(dbus.String(u"checker_running"),
780
448
dbus.Boolean(False, variant_level=1))
450
def still_valid(self):
451
"""Has the timeout not yet passed for this client?"""
452
if not getattr(self, "enabled", False):
454
now = datetime.datetime.utcnow()
455
if self.last_checked_ok is None:
456
return now < (self.created + self.timeout)
458
return now < (self.last_checked_ok + self.timeout)
783
460
## D-Bus methods & signals
784
461
_interface = u"se.bsnet.fukt.Mandos.Client"
786
463
# CheckedOK - method
787
@dbus.service.method(_interface)
789
return self.checked_ok()
464
CheckedOK = dbus.service.method(_interface)(checked_ok)
465
CheckedOK.__name__ = "CheckedOK"
791
467
# CheckerCompleted - signal
792
@dbus.service.signal(_interface, signature=u"nxs")
468
@dbus.service.signal(_interface, signature="nxs")
793
469
def CheckerCompleted(self, exitcode, waitstatus, command):
797
473
# CheckerStarted - signal
798
@dbus.service.signal(_interface, signature=u"s")
474
@dbus.service.signal(_interface, signature="s")
799
475
def CheckerStarted(self, command):
479
# GetAllProperties - method
480
@dbus.service.method(_interface, out_signature="a{sv}")
481
def GetAllProperties(self):
483
return dbus.Dictionary({
485
dbus.String(self.name, variant_level=1),
486
dbus.String("fingerprint"):
487
dbus.String(self.fingerprint, variant_level=1),
489
dbus.String(self.host, variant_level=1),
490
dbus.String("created"):
491
_datetime_to_dbus(self.created, variant_level=1),
492
dbus.String("last_enabled"):
493
(_datetime_to_dbus(self.last_enabled,
495
if self.last_enabled is not None
496
else dbus.Boolean(False, variant_level=1)),
497
dbus.String("enabled"):
498
dbus.Boolean(self.enabled, variant_level=1),
499
dbus.String("last_checked_ok"):
500
(_datetime_to_dbus(self.last_checked_ok,
502
if self.last_checked_ok is not None
503
else dbus.Boolean (False, variant_level=1)),
504
dbus.String("timeout"):
505
dbus.UInt64(self.timeout_milliseconds(),
507
dbus.String("interval"):
508
dbus.UInt64(self.interval_milliseconds(),
510
dbus.String("checker"):
511
dbus.String(self.checker_command,
513
dbus.String("checker_running"):
514
dbus.Boolean(self.checker is not None,
516
dbus.String("object_path"):
517
dbus.ObjectPath(self.dbus_object_path,
521
# IsStillValid - method
522
IsStillValid = (dbus.service.method(_interface, out_signature="b")
524
IsStillValid.__name__ = "IsStillValid"
803
526
# PropertyChanged - signal
804
@dbus.service.signal(_interface, signature=u"sv")
527
@dbus.service.signal(_interface, signature="sv")
805
528
def PropertyChanged(self, property, value):
810
@dbus.service.signal(_interface)
816
@dbus.service.signal(_interface)
532
# SetChecker - method
533
@dbus.service.method(_interface, in_signature="s")
534
def SetChecker(self, checker):
535
"D-Bus setter method"
536
self.checker_command = checker
538
self.PropertyChanged(dbus.String(u"checker"),
539
dbus.String(self.checker_command,
543
@dbus.service.method(_interface, in_signature="s")
544
def SetHost(self, host):
545
"D-Bus setter method"
548
self.PropertyChanged(dbus.String(u"host"),
549
dbus.String(self.host, variant_level=1))
551
# SetInterval - method
552
@dbus.service.method(_interface, in_signature="t")
553
def SetInterval(self, milliseconds):
554
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
556
self.PropertyChanged(dbus.String(u"interval"),
557
(dbus.UInt64(self.interval_milliseconds(),
561
@dbus.service.method(_interface, in_signature="ay",
563
def SetSecret(self, secret):
564
"D-Bus setter method"
565
self.secret = str(secret)
567
# SetTimeout - method
568
@dbus.service.method(_interface, in_signature="t")
569
def SetTimeout(self, milliseconds):
570
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
572
self.PropertyChanged(dbus.String(u"timeout"),
573
(dbus.UInt64(self.timeout_milliseconds(),
821
576
# Enable - method
822
@dbus.service.method(_interface)
577
Enable = dbus.service.method(_interface)(enable)
578
Enable.__name__ = "Enable"
827
580
# StartChecker - method
828
581
@dbus.service.method(_interface)
839
592
# StopChecker - method
840
@dbus.service.method(_interface)
841
def StopChecker(self):
845
@dbus_service_property(_interface, signature=u"s", access=u"read")
846
def name_dbus_property(self):
847
return dbus.String(self.name)
849
# fingerprint - property
850
@dbus_service_property(_interface, signature=u"s", access=u"read")
851
def fingerprint_dbus_property(self):
852
return dbus.String(self.fingerprint)
855
@dbus_service_property(_interface, signature=u"s",
857
def host_dbus_property(self, value=None):
858
if value is None: # get
859
return dbus.String(self.host)
862
self.PropertyChanged(dbus.String(u"host"),
863
dbus.String(value, variant_level=1))
866
@dbus_service_property(_interface, signature=u"s", access=u"read")
867
def created_dbus_property(self):
868
return dbus.String(self._datetime_to_dbus(self.created))
870
# last_enabled - property
871
@dbus_service_property(_interface, signature=u"s", access=u"read")
872
def last_enabled_dbus_property(self):
873
if self.last_enabled is None:
874
return dbus.String(u"")
875
return dbus.String(self._datetime_to_dbus(self.last_enabled))
878
@dbus_service_property(_interface, signature=u"b",
880
def enabled_dbus_property(self, value=None):
881
if value is None: # get
882
return dbus.Boolean(self.enabled)
888
# last_checked_ok - property
889
@dbus_service_property(_interface, signature=u"s",
891
def last_checked_ok_dbus_property(self, value=None):
892
if value is not None:
895
if self.last_checked_ok is None:
896
return dbus.String(u"")
897
return dbus.String(self._datetime_to_dbus(self
901
@dbus_service_property(_interface, signature=u"t",
903
def timeout_dbus_property(self, value=None):
904
if value is None: # get
905
return dbus.UInt64(self.timeout_milliseconds())
906
self.timeout = datetime.timedelta(0, 0, 0, value)
908
self.PropertyChanged(dbus.String(u"timeout"),
909
dbus.UInt64(value, variant_level=1))
910
if getattr(self, u"disable_initiator_tag", None) is None:
913
gobject.source_remove(self.disable_initiator_tag)
914
self.disable_initiator_tag = None
916
_timedelta_to_milliseconds((self
922
# The timeout has passed
925
self.disable_initiator_tag = (gobject.timeout_add
926
(time_to_die, self.disable))
928
# interval - property
929
@dbus_service_property(_interface, signature=u"t",
931
def interval_dbus_property(self, value=None):
932
if value is None: # get
933
return dbus.UInt64(self.interval_milliseconds())
934
self.interval = datetime.timedelta(0, 0, 0, value)
936
self.PropertyChanged(dbus.String(u"interval"),
937
dbus.UInt64(value, variant_level=1))
938
if getattr(self, u"checker_initiator_tag", None) is None:
940
# Reschedule checker run
941
gobject.source_remove(self.checker_initiator_tag)
942
self.checker_initiator_tag = (gobject.timeout_add
943
(value, self.start_checker))
944
self.start_checker() # Start one now, too
947
@dbus_service_property(_interface, signature=u"s",
949
def checker_dbus_property(self, value=None):
950
if value is None: # get
951
return dbus.String(self.checker_command)
952
self.checker_command = value
954
self.PropertyChanged(dbus.String(u"checker"),
955
dbus.String(self.checker_command,
958
# checker_running - property
959
@dbus_service_property(_interface, signature=u"b",
961
def checker_running_dbus_property(self, value=None):
962
if value is None: # get
963
return dbus.Boolean(self.checker is not None)
969
# object_path - property
970
@dbus_service_property(_interface, signature=u"o", access=u"read")
971
def object_path_dbus_property(self):
972
return self.dbus_object_path # is already a dbus.ObjectPath
975
@dbus_service_property(_interface, signature=u"ay",
976
access=u"write", byte_arrays=True)
977
def secret_dbus_property(self, value):
978
self.secret = str(value)
593
StopChecker = dbus.service.method(_interface)(stop_checker)
594
StopChecker.__name__ = "StopChecker"
983
class ClientHandler(socketserver.BaseRequestHandler, object):
984
"""A class to handle client connections.
986
Instantiated once for each connection to handle it.
599
def peer_certificate(session):
600
"Return the peer's OpenPGP certificate as a bytestring"
601
# If not an OpenPGP certificate...
602
if (gnutls.library.functions
603
.gnutls_certificate_type_get(session._c_object)
604
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
605
# ...do the normal thing
606
return session.peer_certificate
607
list_size = ctypes.c_uint(1)
608
cert_list = (gnutls.library.functions
609
.gnutls_certificate_get_peers
610
(session._c_object, ctypes.byref(list_size)))
611
if not bool(cert_list) and list_size.value != 0:
612
raise gnutls.errors.GNUTLSError("error getting peer"
614
if list_size.value == 0:
617
return ctypes.string_at(cert.data, cert.size)
620
def fingerprint(openpgp):
621
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
622
# New GnuTLS "datum" with the OpenPGP public key
623
datum = (gnutls.library.types
624
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
627
ctypes.c_uint(len(openpgp))))
628
# New empty GnuTLS certificate
629
crt = gnutls.library.types.gnutls_openpgp_crt_t()
630
(gnutls.library.functions
631
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
632
# Import the OpenPGP public key into the certificate
633
(gnutls.library.functions
634
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
635
gnutls.library.constants
636
.GNUTLS_OPENPGP_FMT_RAW))
637
# Verify the self signature in the key
638
crtverify = ctypes.c_uint()
639
(gnutls.library.functions
640
.gnutls_openpgp_crt_verify_self(crt, 0, ctypes.byref(crtverify)))
641
if crtverify.value != 0:
642
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
643
raise gnutls.errors.CertificateSecurityError("Verify failed")
644
# New buffer for the fingerprint
645
buf = ctypes.create_string_buffer(20)
646
buf_len = ctypes.c_size_t()
647
# Get the fingerprint from the certificate into the buffer
648
(gnutls.library.functions
649
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
650
ctypes.byref(buf_len)))
651
# Deinit the certificate
652
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
653
# Convert the buffer to a Python bytestring
654
fpr = ctypes.string_at(buf, buf_len.value)
655
# Convert the bytestring to hexadecimal notation
656
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
660
class TCP_handler(SocketServer.BaseRequestHandler, object):
661
"""A TCP request handler class.
662
Instantiated by IPv6_TCPServer for each request to handle it.
987
663
Note: This will run in its own forked process."""
989
665
def handle(self):
990
666
logger.info(u"TCP connection from: %s",
991
667
unicode(self.client_address))
992
logger.debug(u"IPC Pipe FD: %d", self.server.child_pipe[1])
993
# Open IPC pipe to parent process
994
with contextlib.nested(os.fdopen(self.server.child_pipe[1],
996
os.fdopen(self.server.parent_pipe[0],
999
session = (gnutls.connection
1000
.ClientSession(self.request,
1002
.X509Credentials()))
1004
line = self.request.makefile().readline()
1005
logger.debug(u"Protocol version: %r", line)
1007
if int(line.strip().split()[0]) > 1:
1009
except (ValueError, IndexError, RuntimeError), error:
1010
logger.error(u"Unknown protocol version: %s", error)
1013
# Note: gnutls.connection.X509Credentials is really a
1014
# generic GnuTLS certificate credentials object so long as
1015
# no X.509 keys are added to it. Therefore, we can use it
1016
# here despite using OpenPGP certificates.
1018
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1019
# u"+AES-256-CBC", u"+SHA1",
1020
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1022
# Use a fallback default, since this MUST be set.
1023
priority = self.server.gnutls_priority
1024
if priority is None:
1025
priority = u"NORMAL"
1026
(gnutls.library.functions
1027
.gnutls_priority_set_direct(session._c_object,
1032
except gnutls.errors.GNUTLSError, error:
1033
logger.warning(u"Handshake failed: %s", error)
1034
# Do not run session.bye() here: the session is not
1035
# established. Just abandon the request.
1037
logger.debug(u"Handshake succeeded")
1040
fpr = self.fingerprint(self.peer_certificate
1042
except (TypeError, gnutls.errors.GNUTLSError), error:
1043
logger.warning(u"Bad certificate: %s", error)
1045
logger.debug(u"Fingerprint: %s", fpr)
1047
for c in self.server.clients:
1048
if c.fingerprint == fpr:
1052
ipc.write(u"NOTFOUND %s %s\n"
1053
% (fpr, unicode(self.client_address)))
1055
# Have to check if client.enabled, since it is
1056
# possible that the client was disabled since the
1057
# GnuTLS session was established.
1058
ipc.write(u"GETATTR enabled %s\n" % fpr)
1059
enabled = pickle.load(ipc_return)
1061
ipc.write(u"DISABLED %s\n" % client.name)
1063
ipc.write(u"SENDING %s\n" % client.name)
1065
while sent_size < len(client.secret):
1066
sent = session.send(client.secret[sent_size:])
1067
logger.debug(u"Sent: %d, remaining: %d",
1068
sent, len(client.secret)
1069
- (sent_size + sent))
1075
def peer_certificate(session):
1076
"Return the peer's OpenPGP certificate as a bytestring"
1077
# If not an OpenPGP certificate...
1078
if (gnutls.library.functions
1079
.gnutls_certificate_type_get(session._c_object)
1080
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1081
# ...do the normal thing
1082
return session.peer_certificate
1083
list_size = ctypes.c_uint(1)
1084
cert_list = (gnutls.library.functions
1085
.gnutls_certificate_get_peers
1086
(session._c_object, ctypes.byref(list_size)))
1087
if not bool(cert_list) and list_size.value != 0:
1088
raise gnutls.errors.GNUTLSError(u"error getting peer"
1090
if list_size.value == 0:
1093
return ctypes.string_at(cert.data, cert.size)
1096
def fingerprint(openpgp):
1097
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1098
# New GnuTLS "datum" with the OpenPGP public key
1099
datum = (gnutls.library.types
1100
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1103
ctypes.c_uint(len(openpgp))))
1104
# New empty GnuTLS certificate
1105
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1106
(gnutls.library.functions
1107
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1108
# Import the OpenPGP public key into the certificate
1109
(gnutls.library.functions
1110
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1111
gnutls.library.constants
1112
.GNUTLS_OPENPGP_FMT_RAW))
1113
# Verify the self signature in the key
1114
crtverify = ctypes.c_uint()
1115
(gnutls.library.functions
1116
.gnutls_openpgp_crt_verify_self(crt, 0,
1117
ctypes.byref(crtverify)))
1118
if crtverify.value != 0:
1119
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1120
raise (gnutls.errors.CertificateSecurityError
1122
# New buffer for the fingerprint
1123
buf = ctypes.create_string_buffer(20)
1124
buf_len = ctypes.c_size_t()
1125
# Get the fingerprint from the certificate into the buffer
1126
(gnutls.library.functions
1127
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1128
ctypes.byref(buf_len)))
1129
# Deinit the certificate
1130
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1131
# Convert the buffer to a Python bytestring
1132
fpr = ctypes.string_at(buf, buf_len.value)
1133
# Convert the bytestring to hexadecimal notation
1134
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1138
class ForkingMixInWithPipes(socketserver.ForkingMixIn, object):
1139
"""Like socketserver.ForkingMixIn, but also pass a pipe pair."""
1140
def process_request(self, request, client_address):
1141
"""Overrides and wraps the original process_request().
1143
This function creates a new pipe in self.pipe
1145
self.child_pipe = os.pipe() # Child writes here
1146
self.parent_pipe = os.pipe() # Parent writes here
1147
super(ForkingMixInWithPipes,
1148
self).process_request(request, client_address)
1149
# Close unused ends for parent
1150
os.close(self.parent_pipe[0]) # close read end
1151
os.close(self.child_pipe[1]) # close write end
1152
self.add_pipe_fds(self.child_pipe[0], self.parent_pipe[1])
1153
def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
1154
"""Dummy function; override as necessary"""
1155
os.close(child_pipe_fd)
1156
os.close(parent_pipe_fd)
1159
class IPv6_TCPServer(ForkingMixInWithPipes,
1160
socketserver.TCPServer, object):
1161
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
668
session = (gnutls.connection
669
.ClientSession(self.request,
673
line = self.request.makefile().readline()
674
logger.debug(u"Protocol version: %r", line)
676
if int(line.strip().split()[0]) > 1:
678
except (ValueError, IndexError, RuntimeError), error:
679
logger.error(u"Unknown protocol version: %s", error)
682
# Note: gnutls.connection.X509Credentials is really a generic
683
# GnuTLS certificate credentials object so long as no X.509
684
# keys are added to it. Therefore, we can use it here despite
685
# using OpenPGP certificates.
687
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
688
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
690
# Use a fallback default, since this MUST be set.
691
priority = self.server.settings.get("priority", "NORMAL")
692
(gnutls.library.functions
693
.gnutls_priority_set_direct(session._c_object,
698
except gnutls.errors.GNUTLSError, error:
699
logger.warning(u"Handshake failed: %s", error)
700
# Do not run session.bye() here: the session is not
701
# established. Just abandon the request.
703
logger.debug(u"Handshake succeeded")
705
fpr = fingerprint(peer_certificate(session))
706
except (TypeError, gnutls.errors.GNUTLSError), error:
707
logger.warning(u"Bad certificate: %s", error)
710
logger.debug(u"Fingerprint: %s", fpr)
712
for c in self.server.clients:
713
if c.fingerprint == fpr:
717
logger.warning(u"Client not found for fingerprint: %s",
721
# Have to check if client.still_valid(), since it is possible
722
# that the client timed out while establishing the GnuTLS
724
if not client.still_valid():
725
logger.warning(u"Client %(name)s is invalid",
729
## This won't work here, since we're in a fork.
730
# client.checked_ok()
732
while sent_size < len(client.secret):
733
sent = session.send(client.secret[sent_size:])
734
logger.debug(u"Sent: %d, remaining: %d",
735
sent, len(client.secret)
736
- (sent_size + sent))
741
class IPv6_TCPServer(SocketServer.ForkingMixIn,
742
SocketServer.TCPServer, object):
743
"""IPv6-capable TCP server. Accepts 'None' as address and/or port.
745
settings: Server settings
746
clients: Set() of Client objects
1164
747
enabled: Boolean; whether this server is activated yet
1165
interface: None or a network interface name (string)
1166
use_ipv6: Boolean; to use IPv6 or not
1168
def __init__(self, server_address, RequestHandlerClass,
1169
interface=None, use_ipv6=True):
1170
self.interface = interface
1172
self.address_family = socket.AF_INET6
1173
socketserver.TCPServer.__init__(self, server_address,
1174
RequestHandlerClass)
749
address_family = socket.AF_INET6
750
def __init__(self, *args, **kwargs):
751
if "settings" in kwargs:
752
self.settings = kwargs["settings"]
753
del kwargs["settings"]
754
if "clients" in kwargs:
755
self.clients = kwargs["clients"]
756
del kwargs["clients"]
757
if "use_ipv6" in kwargs:
758
if not kwargs["use_ipv6"]:
759
self.address_family = socket.AF_INET
760
del kwargs["use_ipv6"]
762
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
1175
763
def server_bind(self):
1176
764
"""This overrides the normal server_bind() function
1177
765
to bind to an interface if one was specified, and also NOT to
1178
766
bind to an address or port if they were not specified."""
1179
if self.interface is not None:
1180
if SO_BINDTODEVICE is None:
1181
logger.error(u"SO_BINDTODEVICE does not exist;"
1182
u" cannot bind to interface %s",
1186
self.socket.setsockopt(socket.SOL_SOCKET,
1190
except socket.error, error:
1191
if error[0] == errno.EPERM:
1192
logger.error(u"No permission to"
1193
u" bind to interface %s",
1195
elif error[0] == errno.ENOPROTOOPT:
1196
logger.error(u"SO_BINDTODEVICE not available;"
1197
u" cannot bind to interface %s",
767
if self.settings["interface"]:
768
# 25 is from /usr/include/asm-i486/socket.h
769
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
771
self.socket.setsockopt(socket.SOL_SOCKET,
773
self.settings["interface"])
774
except socket.error, error:
775
if error[0] == errno.EPERM:
776
logger.error(u"No permission to"
777
u" bind to interface %s",
778
self.settings["interface"])
1201
781
# Only bind(2) the socket if we really need to.
1202
782
if self.server_address[0] or self.server_address[1]:
1203
783
if not self.server_address[0]:
1204
784
if self.address_family == socket.AF_INET6:
1205
any_address = u"::" # in6addr_any
785
any_address = "::" # in6addr_any
1207
787
any_address = socket.INADDR_ANY
1208
788
self.server_address = (any_address,
1210
790
elif not self.server_address[1]:
1211
791
self.server_address = (self.server_address[0],
1213
# if self.interface:
793
# if self.settings["interface"]:
1214
794
# self.server_address = (self.server_address[0],
1217
797
# if_nametoindex
1219
return socketserver.TCPServer.server_bind(self)
1222
class MandosServer(IPv6_TCPServer):
1226
clients: set of Client objects
1227
gnutls_priority GnuTLS priority string
1228
use_dbus: Boolean; to emit D-Bus signals or not
1230
Assumes a gobject.MainLoop event loop.
1232
def __init__(self, server_address, RequestHandlerClass,
1233
interface=None, use_ipv6=True, clients=None,
1234
gnutls_priority=None, use_dbus=True):
1235
self.enabled = False
1236
self.clients = clients
1237
if self.clients is None:
1238
self.clients = set()
1239
self.use_dbus = use_dbus
1240
self.gnutls_priority = gnutls_priority
1241
IPv6_TCPServer.__init__(self, server_address,
1242
RequestHandlerClass,
1243
interface = interface,
1244
use_ipv6 = use_ipv6)
800
return super(IPv6_TCPServer, self).server_bind()
1245
801
def server_activate(self):
1246
802
if self.enabled:
1247
return socketserver.TCPServer.server_activate(self)
803
return super(IPv6_TCPServer, self).server_activate()
1248
804
def enable(self):
1249
805
self.enabled = True
1250
def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
1251
# Call "handle_ipc" for both data and EOF events
1252
gobject.io_add_watch(child_pipe_fd,
1253
gobject.IO_IN | gobject.IO_HUP,
1254
functools.partial(self.handle_ipc,
1257
def handle_ipc(self, source, condition, reply_fd=None,
1260
gobject.IO_IN: u"IN", # There is data to read.
1261
gobject.IO_OUT: u"OUT", # Data can be written (without
1263
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1264
gobject.IO_ERR: u"ERR", # Error condition.
1265
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1266
# broken, usually for pipes and
1269
conditions_string = ' | '.join(name
1271
condition_names.iteritems()
1272
if cond & condition)
1273
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1276
# Turn the pipe file descriptors into Python file objects
1277
if source not in file_objects:
1278
file_objects[source] = os.fdopen(source, u"r", 1)
1279
if reply_fd not in file_objects:
1280
file_objects[reply_fd] = os.fdopen(reply_fd, u"w", 0)
1282
# Read a line from the file object
1283
cmdline = file_objects[source].readline()
1284
if not cmdline: # Empty line means end of file
1285
# close the IPC pipes
1286
file_objects[source].close()
1287
del file_objects[source]
1288
file_objects[reply_fd].close()
1289
del file_objects[reply_fd]
1291
# Stop calling this function
1294
logger.debug(u"IPC command: %r", cmdline)
1296
# Parse and act on command
1297
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1299
if cmd == u"NOTFOUND":
1300
fpr, address = args.split(None, 1)
1301
logger.warning(u"Client not found for fingerprint: %s, ad"
1302
u"dress: %s", fpr, address)
1305
mandos_dbus_service.ClientNotFound(fpr, address)
1306
elif cmd == u"DISABLED":
1307
for client in self.clients:
1308
if client.name == args:
1309
logger.warning(u"Client %s is disabled", args)
1315
logger.error(u"Unknown client %s is disabled", args)
1316
elif cmd == u"SENDING":
1317
for client in self.clients:
1318
if client.name == args:
1319
logger.info(u"Sending secret to %s", client.name)
1326
logger.error(u"Sending secret to unknown client %s",
1328
elif cmd == u"GETATTR":
1329
attr_name, fpr = args.split(None, 1)
1330
for client in self.clients:
1331
if client.fingerprint == fpr:
1332
attr_value = getattr(client, attr_name, None)
1333
logger.debug("IPC reply: %r", attr_value)
1334
pickle.dump(attr_value, file_objects[reply_fd])
1337
logger.error(u"Client %s on address %s requesting "
1338
u"attribute %s not found", fpr, address,
1340
pickle.dump(None, file_objects[reply_fd])
1342
logger.error(u"Unknown IPC command: %r", cmdline)
1344
# Keep calling this function
1348
808
def string_to_delta(interval):
1349
809
"""Parse a string and return a datetime.timedelta
1351
>>> string_to_delta(u'7d')
811
>>> string_to_delta('7d')
1352
812
datetime.timedelta(7)
1353
>>> string_to_delta(u'60s')
813
>>> string_to_delta('60s')
1354
814
datetime.timedelta(0, 60)
1355
>>> string_to_delta(u'60m')
815
>>> string_to_delta('60m')
1356
816
datetime.timedelta(0, 3600)
1357
>>> string_to_delta(u'24h')
817
>>> string_to_delta('24h')
1358
818
datetime.timedelta(1)
1359
819
>>> string_to_delta(u'1w')
1360
820
datetime.timedelta(7)
1361
>>> string_to_delta(u'5m 30s')
821
>>> string_to_delta('5m 30s')
1362
822
datetime.timedelta(0, 330)
1364
824
timevalue = datetime.timedelta(0)
1473
950
# Default values for config file for server-global settings
1474
server_defaults = { u"interface": u"",
1479
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1480
u"servicename": u"Mandos",
1481
u"use_dbus": u"True",
1482
u"use_ipv6": u"True",
951
server_defaults = { "interface": "",
956
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
957
"servicename": "Mandos",
1485
962
# Parse config file for server-global settings
1486
server_config = configparser.SafeConfigParser(server_defaults)
963
server_config = ConfigParser.SafeConfigParser(server_defaults)
1487
964
del server_defaults
1488
server_config.read(os.path.join(options.configdir,
965
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1490
966
# Convert the SafeConfigParser object to a dict
1491
967
server_settings = server_config.defaults()
1492
968
# Use the appropriate methods on the non-string config options
1493
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1494
server_settings[option] = server_config.getboolean(u"DEFAULT",
969
server_settings["debug"] = server_config.getboolean("DEFAULT",
971
server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
973
server_settings["use_ipv6"] = server_config.getboolean("DEFAULT",
1496
975
if server_settings["port"]:
1497
server_settings["port"] = server_config.getint(u"DEFAULT",
976
server_settings["port"] = server_config.getint("DEFAULT",
1499
978
del server_config
1501
980
# Override the settings from the config file with command line
1502
981
# options, if set.
1503
for option in (u"interface", u"address", u"port", u"debug",
1504
u"priority", u"servicename", u"configdir",
1505
u"use_dbus", u"use_ipv6"):
982
for option in ("interface", "address", "port", "debug",
983
"priority", "servicename", "configdir",
984
"use_dbus", "use_ipv6"):
1506
985
value = getattr(options, option)
1507
986
if value is not None:
1508
987
server_settings[option] = value
1510
# Force all strings to be unicode
1511
for option in server_settings.keys():
1512
if type(server_settings[option]) is str:
1513
server_settings[option] = unicode(server_settings[option])
1514
989
# Now we have our good server settings in "server_settings"
1516
##################################################################
1518
991
# For convenience
1519
debug = server_settings[u"debug"]
1520
use_dbus = server_settings[u"use_dbus"]
1521
use_ipv6 = server_settings[u"use_ipv6"]
992
debug = server_settings["debug"]
993
use_dbus = server_settings["use_dbus"]
994
use_ipv6 = server_settings["use_ipv6"]
1524
997
syslogger.setLevel(logging.WARNING)
1525
998
console.setLevel(logging.WARNING)
1527
if server_settings[u"servicename"] != u"Mandos":
1000
if server_settings["servicename"] != "Mandos":
1528
1001
syslogger.setFormatter(logging.Formatter
1529
(u'Mandos (%s) [%%(process)d]:'
1530
u' %%(levelname)s: %%(message)s'
1531
% server_settings[u"servicename"]))
1002
('Mandos (%s): %%(levelname)s:'
1004
% server_settings["servicename"]))
1533
1006
# Parse config file with clients
1534
client_defaults = { u"timeout": u"1h",
1536
u"checker": u"fping -q -- %%(host)s",
1007
client_defaults = { "timeout": "1h",
1009
"checker": "fping -q -- %%(host)s",
1539
client_config = configparser.SafeConfigParser(client_defaults)
1540
client_config.read(os.path.join(server_settings[u"configdir"],
1543
global mandos_dbus_service
1544
mandos_dbus_service = None
1546
tcp_server = MandosServer((server_settings[u"address"],
1547
server_settings[u"port"]),
1549
interface=server_settings[u"interface"],
1552
server_settings[u"priority"],
1554
pidfilename = u"/var/run/mandos.pid"
1012
client_config = ConfigParser.SafeConfigParser(client_defaults)
1013
client_config.read(os.path.join(server_settings["configdir"],
1017
tcp_server = IPv6_TCPServer((server_settings["address"],
1018
server_settings["port"]),
1020
settings=server_settings,
1021
clients=clients, use_ipv6=use_ipv6)
1022
pidfilename = "/var/run/mandos.pid"
1556
pidfile = open(pidfilename, u"w")
1024
pidfile = open(pidfilename, "w")
1557
1025
except IOError:
1558
logger.error(u"Could not open file %r", pidfilename)
1026
logger.error("Could not open file %r", pidfilename)
1561
uid = pwd.getpwnam(u"_mandos").pw_uid
1562
gid = pwd.getpwnam(u"_mandos").pw_gid
1029
uid = pwd.getpwnam("_mandos").pw_uid
1030
gid = pwd.getpwnam("_mandos").pw_gid
1563
1031
except KeyError:
1565
uid = pwd.getpwnam(u"mandos").pw_uid
1566
gid = pwd.getpwnam(u"mandos").pw_gid
1033
uid = pwd.getpwnam("mandos").pw_uid
1034
gid = pwd.getpwnam("mandos").pw_gid
1567
1035
except KeyError:
1569
uid = pwd.getpwnam(u"nobody").pw_uid
1570
gid = pwd.getpwnam(u"nobody").pw_gid
1037
uid = pwd.getpwnam("nobody").pw_uid
1038
gid = pwd.getpwnam("nogroup").pw_gid
1571
1039
except KeyError: