282
230
if config is None:
284
232
logger.debug(u"Creating client %r", self.name)
233
self.use_dbus = False # During __init__
285
234
# Uppercase and remove spaces from fingerprint for later
286
235
# comparison purposes with return value from the fingerprint()
288
self.fingerprint = (config[u"fingerprint"].upper()
237
self.fingerprint = (config["fingerprint"].upper()
289
238
.replace(u" ", u""))
290
239
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"])),
240
if "secret" in config:
241
self.secret = config["secret"].decode(u"base64")
242
elif "secfile" in config:
243
with closing(open(os.path.expanduser
245
(config["secfile"])))) as secfile:
297
246
self.secret = secfile.read()
299
248
raise TypeError(u"No secret or secfile for client %s"
301
self.host = config.get(u"host", u"")
250
self.host = config.get("host", "")
302
251
self.created = datetime.datetime.utcnow()
303
252
self.enabled = False
304
253
self.last_enabled = None
305
254
self.last_checked_ok = None
306
self.timeout = string_to_delta(config[u"timeout"])
307
self.interval = string_to_delta(config[u"interval"])
255
self.timeout = string_to_delta(config["timeout"])
256
self.interval = string_to_delta(config["interval"])
308
257
self.disable_hook = disable_hook
309
258
self.checker = None
310
259
self.checker_initiator_tag = None
311
260
self.disable_initiator_tag = None
312
261
self.checker_callback_tag = None
313
self.checker_command = config[u"checker"]
262
self.checker_command = config["checker"]
314
263
self.current_checker_command = None
315
264
self.last_connect = None
265
# Only now, when this client is initialized, can it show up on
267
self.use_dbus = use_dbus
269
self.dbus_object_path = (dbus.ObjectPath
271
+ self.name.replace(".", "_")))
272
dbus.service.Object.__init__(self, bus,
273
self.dbus_object_path)
317
275
def enable(self):
318
276
"""Start this client's checker and timeout hooks"""
319
if getattr(self, u"enabled", False):
322
277
self.last_enabled = datetime.datetime.utcnow()
323
278
# Schedule a new checker to be started an 'interval' from now,
324
279
# and every interval from then on.
325
280
self.checker_initiator_tag = (gobject.timeout_add
326
281
(self.interval_milliseconds(),
327
282
self.start_checker))
283
# Also start a new checker *right now*.
328
285
# Schedule a disable() when 'timeout' has passed
329
286
self.disable_initiator_tag = (gobject.timeout_add
330
287
(self.timeout_milliseconds(),
332
289
self.enabled = True
333
# Also start a new checker *right now*.
292
self.PropertyChanged(dbus.String(u"enabled"),
293
dbus.Boolean(True, variant_level=1))
294
self.PropertyChanged(dbus.String(u"last_enabled"),
295
(_datetime_to_dbus(self.last_enabled,
336
def disable(self, quiet=True):
337
299
"""Disable this client."""
338
300
if not getattr(self, "enabled", False):
341
logger.info(u"Disabling client %s", self.name)
342
if getattr(self, u"disable_initiator_tag", False):
302
logger.info(u"Disabling client %s", self.name)
303
if getattr(self, "disable_initiator_tag", False):
343
304
gobject.source_remove(self.disable_initiator_tag)
344
305
self.disable_initiator_tag = None
345
if getattr(self, u"checker_initiator_tag", False):
306
if getattr(self, "checker_initiator_tag", False):
346
307
gobject.source_remove(self.checker_initiator_tag)
347
308
self.checker_initiator_tag = None
348
309
self.stop_checker()
349
310
if self.disable_hook:
350
311
self.disable_hook(self)
351
312
self.enabled = False
315
self.PropertyChanged(dbus.String(u"enabled"),
316
dbus.Boolean(False, variant_level=1))
352
317
# Do not run this again if called by a gobject.timeout_add
463
444
if self.checker_callback_tag:
464
445
gobject.source_remove(self.checker_callback_tag)
465
446
self.checker_callback_tag = None
466
if getattr(self, u"checker", None) is None:
447
if getattr(self, "checker", None) is None:
468
449
logger.debug(u"Stopping checker for %(name)s", vars(self))
470
451
os.kill(self.checker.pid, signal.SIGTERM)
472
453
#if self.checker.poll() is None:
473
454
# os.kill(self.checker.pid, signal.SIGKILL)
474
455
except OSError, error:
475
456
if error.errno != errno.ESRCH: # No such process
477
458
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
460
self.PropertyChanged(dbus.String(u"checker_running"),
780
461
dbus.Boolean(False, variant_level=1))
783
## D-Bus methods, signals & properties
463
def still_valid(self):
464
"""Has the timeout not yet passed for this client?"""
465
if not getattr(self, "enabled", False):
467
now = datetime.datetime.utcnow()
468
if self.last_checked_ok is None:
469
return now < (self.created + self.timeout)
471
return now < (self.last_checked_ok + self.timeout)
473
## D-Bus methods & signals
784
474
_interface = u"se.bsnet.fukt.Mandos.Client"
477
CheckedOK = dbus.service.method(_interface)(checked_ok)
478
CheckedOK.__name__ = "CheckedOK"
788
480
# CheckerCompleted - signal
789
@dbus.service.signal(_interface, signature=u"nxs")
481
@dbus.service.signal(_interface, signature="nxs")
790
482
def CheckerCompleted(self, exitcode, waitstatus, command):
794
486
# CheckerStarted - signal
795
@dbus.service.signal(_interface, signature=u"s")
487
@dbus.service.signal(_interface, signature="s")
796
488
def CheckerStarted(self, command):
492
# GetAllProperties - method
493
@dbus.service.method(_interface, out_signature="a{sv}")
494
def GetAllProperties(self):
496
return dbus.Dictionary({
498
dbus.String(self.name, variant_level=1),
499
dbus.String("fingerprint"):
500
dbus.String(self.fingerprint, variant_level=1),
502
dbus.String(self.host, variant_level=1),
503
dbus.String("created"):
504
_datetime_to_dbus(self.created, variant_level=1),
505
dbus.String("last_enabled"):
506
(_datetime_to_dbus(self.last_enabled,
508
if self.last_enabled is not None
509
else dbus.Boolean(False, variant_level=1)),
510
dbus.String("enabled"):
511
dbus.Boolean(self.enabled, variant_level=1),
512
dbus.String("last_checked_ok"):
513
(_datetime_to_dbus(self.last_checked_ok,
515
if self.last_checked_ok is not None
516
else dbus.Boolean (False, variant_level=1)),
517
dbus.String("timeout"):
518
dbus.UInt64(self.timeout_milliseconds(),
520
dbus.String("interval"):
521
dbus.UInt64(self.interval_milliseconds(),
523
dbus.String("checker"):
524
dbus.String(self.checker_command,
526
dbus.String("checker_running"):
527
dbus.Boolean(self.checker is not None,
529
dbus.String("object_path"):
530
dbus.ObjectPath(self.dbus_object_path,
534
# IsStillValid - method
535
IsStillValid = (dbus.service.method(_interface, out_signature="b")
537
IsStillValid.__name__ = "IsStillValid"
800
539
# PropertyChanged - signal
801
@dbus.service.signal(_interface, signature=u"sv")
540
@dbus.service.signal(_interface, signature="sv")
802
541
def PropertyChanged(self, property, value):
807
@dbus.service.signal(_interface)
813
@dbus.service.signal(_interface)
821
@dbus.service.method(_interface)
823
return self.checked_ok()
545
# SetChecker - method
546
@dbus.service.method(_interface, in_signature="s")
547
def SetChecker(self, checker):
548
"D-Bus setter method"
549
self.checker_command = checker
551
self.PropertyChanged(dbus.String(u"checker"),
552
dbus.String(self.checker_command,
556
@dbus.service.method(_interface, in_signature="s")
557
def SetHost(self, host):
558
"D-Bus setter method"
561
self.PropertyChanged(dbus.String(u"host"),
562
dbus.String(self.host, variant_level=1))
564
# SetInterval - method
565
@dbus.service.method(_interface, in_signature="t")
566
def SetInterval(self, milliseconds):
567
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
569
self.PropertyChanged(dbus.String(u"interval"),
570
(dbus.UInt64(self.interval_milliseconds(),
574
@dbus.service.method(_interface, in_signature="ay",
576
def SetSecret(self, secret):
577
"D-Bus setter method"
578
self.secret = str(secret)
580
# SetTimeout - method
581
@dbus.service.method(_interface, in_signature="t")
582
def SetTimeout(self, milliseconds):
583
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
585
self.PropertyChanged(dbus.String(u"timeout"),
586
(dbus.UInt64(self.timeout_milliseconds(),
825
589
# Enable - method
826
@dbus.service.method(_interface)
590
Enable = dbus.service.method(_interface)(enable)
591
Enable.__name__ = "Enable"
831
593
# StartChecker - method
832
594
@dbus.service.method(_interface)
843
605
# StopChecker - method
844
@dbus.service.method(_interface)
845
def StopChecker(self):
851
@dbus_service_property(_interface, signature=u"s", access=u"read")
852
def name_dbus_property(self):
853
return dbus.String(self.name)
855
# fingerprint - property
856
@dbus_service_property(_interface, signature=u"s", access=u"read")
857
def fingerprint_dbus_property(self):
858
return dbus.String(self.fingerprint)
861
@dbus_service_property(_interface, signature=u"s",
863
def host_dbus_property(self, value=None):
864
if value is None: # get
865
return dbus.String(self.host)
868
self.PropertyChanged(dbus.String(u"host"),
869
dbus.String(value, variant_level=1))
872
@dbus_service_property(_interface, signature=u"s", access=u"read")
873
def created_dbus_property(self):
874
return dbus.String(self._datetime_to_dbus(self.created))
876
# last_enabled - property
877
@dbus_service_property(_interface, signature=u"s", access=u"read")
878
def last_enabled_dbus_property(self):
879
if self.last_enabled is None:
880
return dbus.String(u"")
881
return dbus.String(self._datetime_to_dbus(self.last_enabled))
884
@dbus_service_property(_interface, signature=u"b",
886
def enabled_dbus_property(self, value=None):
887
if value is None: # get
888
return dbus.Boolean(self.enabled)
894
# last_checked_ok - property
895
@dbus_service_property(_interface, signature=u"s",
897
def last_checked_ok_dbus_property(self, value=None):
898
if value is not None:
901
if self.last_checked_ok is None:
902
return dbus.String(u"")
903
return dbus.String(self._datetime_to_dbus(self
907
@dbus_service_property(_interface, signature=u"t",
909
def timeout_dbus_property(self, value=None):
910
if value is None: # get
911
return dbus.UInt64(self.timeout_milliseconds())
912
self.timeout = datetime.timedelta(0, 0, 0, value)
914
self.PropertyChanged(dbus.String(u"timeout"),
915
dbus.UInt64(value, variant_level=1))
916
if getattr(self, u"disable_initiator_tag", None) is None:
919
gobject.source_remove(self.disable_initiator_tag)
920
self.disable_initiator_tag = None
922
_timedelta_to_milliseconds((self
928
# The timeout has passed
931
self.disable_initiator_tag = (gobject.timeout_add
932
(time_to_die, self.disable))
934
# interval - property
935
@dbus_service_property(_interface, signature=u"t",
937
def interval_dbus_property(self, value=None):
938
if value is None: # get
939
return dbus.UInt64(self.interval_milliseconds())
940
self.interval = datetime.timedelta(0, 0, 0, value)
942
self.PropertyChanged(dbus.String(u"interval"),
943
dbus.UInt64(value, variant_level=1))
944
if getattr(self, u"checker_initiator_tag", None) is None:
946
# Reschedule checker run
947
gobject.source_remove(self.checker_initiator_tag)
948
self.checker_initiator_tag = (gobject.timeout_add
949
(value, self.start_checker))
950
self.start_checker() # Start one now, too
953
@dbus_service_property(_interface, signature=u"s",
955
def checker_dbus_property(self, value=None):
956
if value is None: # get
957
return dbus.String(self.checker_command)
958
self.checker_command = value
960
self.PropertyChanged(dbus.String(u"checker"),
961
dbus.String(self.checker_command,
964
# checker_running - property
965
@dbus_service_property(_interface, signature=u"b",
967
def checker_running_dbus_property(self, value=None):
968
if value is None: # get
969
return dbus.Boolean(self.checker is not None)
975
# object_path - property
976
@dbus_service_property(_interface, signature=u"o", access=u"read")
977
def object_path_dbus_property(self):
978
return self.dbus_object_path # is already a dbus.ObjectPath
981
@dbus_service_property(_interface, signature=u"ay",
982
access=u"write", byte_arrays=True)
983
def secret_dbus_property(self, value):
984
self.secret = str(value)
606
StopChecker = dbus.service.method(_interface)(stop_checker)
607
StopChecker.__name__ = "StopChecker"
989
class ClientHandler(socketserver.BaseRequestHandler, object):
990
"""A class to handle client connections.
992
Instantiated once for each connection to handle it.
612
def peer_certificate(session):
613
"Return the peer's OpenPGP certificate as a bytestring"
614
# If not an OpenPGP certificate...
615
if (gnutls.library.functions
616
.gnutls_certificate_type_get(session._c_object)
617
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
618
# ...do the normal thing
619
return session.peer_certificate
620
list_size = ctypes.c_uint(1)
621
cert_list = (gnutls.library.functions
622
.gnutls_certificate_get_peers
623
(session._c_object, ctypes.byref(list_size)))
624
if not bool(cert_list) and list_size.value != 0:
625
raise gnutls.errors.GNUTLSError("error getting peer"
627
if list_size.value == 0:
630
return ctypes.string_at(cert.data, cert.size)
633
def fingerprint(openpgp):
634
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
635
# New GnuTLS "datum" with the OpenPGP public key
636
datum = (gnutls.library.types
637
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
640
ctypes.c_uint(len(openpgp))))
641
# New empty GnuTLS certificate
642
crt = gnutls.library.types.gnutls_openpgp_crt_t()
643
(gnutls.library.functions
644
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
645
# Import the OpenPGP public key into the certificate
646
(gnutls.library.functions
647
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
648
gnutls.library.constants
649
.GNUTLS_OPENPGP_FMT_RAW))
650
# Verify the self signature in the key
651
crtverify = ctypes.c_uint()
652
(gnutls.library.functions
653
.gnutls_openpgp_crt_verify_self(crt, 0, ctypes.byref(crtverify)))
654
if crtverify.value != 0:
655
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
656
raise gnutls.errors.CertificateSecurityError("Verify failed")
657
# New buffer for the fingerprint
658
buf = ctypes.create_string_buffer(20)
659
buf_len = ctypes.c_size_t()
660
# Get the fingerprint from the certificate into the buffer
661
(gnutls.library.functions
662
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
663
ctypes.byref(buf_len)))
664
# Deinit the certificate
665
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
666
# Convert the buffer to a Python bytestring
667
fpr = ctypes.string_at(buf, buf_len.value)
668
# Convert the bytestring to hexadecimal notation
669
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
673
class TCP_handler(SocketServer.BaseRequestHandler, object):
674
"""A TCP request handler class.
675
Instantiated by IPv6_TCPServer for each request to handle it.
993
676
Note: This will run in its own forked process."""
995
678
def handle(self):
996
679
logger.info(u"TCP connection from: %s",
997
680
unicode(self.client_address))
998
logger.debug(u"IPC Pipe FD: %d",
999
self.server.child_pipe[1].fileno())
1000
# Open IPC pipe to parent process
1001
with contextlib.nested(self.server.child_pipe[1],
1002
self.server.parent_pipe[0]
1003
) as (ipc, ipc_return):
1004
session = (gnutls.connection
1005
.ClientSession(self.request,
1007
.X509Credentials()))
1009
line = self.request.makefile().readline()
1010
logger.debug(u"Protocol version: %r", line)
1012
if int(line.strip().split()[0]) > 1:
1014
except (ValueError, IndexError, RuntimeError), error:
1015
logger.error(u"Unknown protocol version: %s", error)
1018
# Note: gnutls.connection.X509Credentials is really a
1019
# generic GnuTLS certificate credentials object so long as
1020
# no X.509 keys are added to it. Therefore, we can use it
1021
# here despite using OpenPGP certificates.
1023
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1024
# u"+AES-256-CBC", u"+SHA1",
1025
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1027
# Use a fallback default, since this MUST be set.
1028
priority = self.server.gnutls_priority
1029
if priority is None:
1030
priority = u"NORMAL"
1031
(gnutls.library.functions
1032
.gnutls_priority_set_direct(session._c_object,
1037
except gnutls.errors.GNUTLSError, error:
1038
logger.warning(u"Handshake failed: %s", error)
1039
# Do not run session.bye() here: the session is not
1040
# established. Just abandon the request.
1042
logger.debug(u"Handshake succeeded")
1045
fpr = self.fingerprint(self.peer_certificate
1047
except (TypeError, gnutls.errors.GNUTLSError), error:
1048
logger.warning(u"Bad certificate: %s", error)
1050
logger.debug(u"Fingerprint: %s", fpr)
1052
for c in self.server.clients:
1053
if c.fingerprint == fpr:
1057
ipc.write(u"NOTFOUND %s %s\n"
1058
% (fpr, unicode(self.client_address)))
1060
# Have to check if client.enabled, since it is
1061
# possible that the client was disabled since the
1062
# GnuTLS session was established.
1063
ipc.write(u"GETATTR enabled %s\n" % fpr)
1064
enabled = pickle.load(ipc_return)
1066
ipc.write(u"DISABLED %s\n" % client.name)
1068
# Send "NEED_APPROVAL" here and hang waiting
1069
# for response? Leave timeout to parent process?
1070
ipc.write(u"SENDING %s\n" % client.name)
1072
while sent_size < len(client.secret):
1073
sent = session.send(client.secret[sent_size:])
1074
logger.debug(u"Sent: %d, remaining: %d",
1075
sent, len(client.secret)
1076
- (sent_size + sent))
1082
def peer_certificate(session):
1083
"Return the peer's OpenPGP certificate as a bytestring"
1084
# If not an OpenPGP certificate...
1085
if (gnutls.library.functions
1086
.gnutls_certificate_type_get(session._c_object)
1087
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1088
# ...do the normal thing
1089
return session.peer_certificate
1090
list_size = ctypes.c_uint(1)
1091
cert_list = (gnutls.library.functions
1092
.gnutls_certificate_get_peers
1093
(session._c_object, ctypes.byref(list_size)))
1094
if not bool(cert_list) and list_size.value != 0:
1095
raise gnutls.errors.GNUTLSError(u"error getting peer"
1097
if list_size.value == 0:
1100
return ctypes.string_at(cert.data, cert.size)
1103
def fingerprint(openpgp):
1104
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1105
# New GnuTLS "datum" with the OpenPGP public key
1106
datum = (gnutls.library.types
1107
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1110
ctypes.c_uint(len(openpgp))))
1111
# New empty GnuTLS certificate
1112
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1113
(gnutls.library.functions
1114
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1115
# Import the OpenPGP public key into the certificate
1116
(gnutls.library.functions
1117
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1118
gnutls.library.constants
1119
.GNUTLS_OPENPGP_FMT_RAW))
1120
# Verify the self signature in the key
1121
crtverify = ctypes.c_uint()
1122
(gnutls.library.functions
1123
.gnutls_openpgp_crt_verify_self(crt, 0,
1124
ctypes.byref(crtverify)))
1125
if crtverify.value != 0:
1126
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1127
raise (gnutls.errors.CertificateSecurityError
1129
# New buffer for the fingerprint
1130
buf = ctypes.create_string_buffer(20)
1131
buf_len = ctypes.c_size_t()
1132
# Get the fingerprint from the certificate into the buffer
1133
(gnutls.library.functions
1134
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1135
ctypes.byref(buf_len)))
1136
# Deinit the certificate
1137
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1138
# Convert the buffer to a Python bytestring
1139
fpr = ctypes.string_at(buf, buf_len.value)
1140
# Convert the bytestring to hexadecimal notation
1141
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1145
class ForkingMixInWithPipes(socketserver.ForkingMixIn, object):
1146
"""Like socketserver.ForkingMixIn, but also pass a pipe pair."""
1147
def process_request(self, request, client_address):
1148
"""Overrides and wraps the original process_request().
1150
This function creates a new pipe in self.pipe
1152
# Child writes to child_pipe
1153
self.child_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0))
1154
# Parent writes to parent_pipe
1155
self.parent_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0))
1156
super(ForkingMixInWithPipes,
1157
self).process_request(request, client_address)
1158
# Close unused ends for parent
1159
self.parent_pipe[0].close() # close read end
1160
self.child_pipe[1].close() # close write end
1161
self.add_pipe_fds(self.child_pipe[0], self.parent_pipe[1])
1162
def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
1163
"""Dummy function; override as necessary"""
1164
child_pipe_fd.close()
1165
parent_pipe_fd.close()
1168
class IPv6_TCPServer(ForkingMixInWithPipes,
1169
socketserver.TCPServer, object):
681
session = (gnutls.connection
682
.ClientSession(self.request,
686
line = self.request.makefile().readline()
687
logger.debug(u"Protocol version: %r", line)
689
if int(line.strip().split()[0]) > 1:
691
except (ValueError, IndexError, RuntimeError), error:
692
logger.error(u"Unknown protocol version: %s", error)
695
# Note: gnutls.connection.X509Credentials is really a generic
696
# GnuTLS certificate credentials object so long as no X.509
697
# keys are added to it. Therefore, we can use it here despite
698
# using OpenPGP certificates.
700
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
701
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
703
# Use a fallback default, since this MUST be set.
704
priority = self.server.settings.get("priority", "NORMAL")
705
(gnutls.library.functions
706
.gnutls_priority_set_direct(session._c_object,
711
except gnutls.errors.GNUTLSError, error:
712
logger.warning(u"Handshake failed: %s", error)
713
# Do not run session.bye() here: the session is not
714
# established. Just abandon the request.
716
logger.debug(u"Handshake succeeded")
718
fpr = fingerprint(peer_certificate(session))
719
except (TypeError, gnutls.errors.GNUTLSError), error:
720
logger.warning(u"Bad certificate: %s", error)
723
logger.debug(u"Fingerprint: %s", fpr)
725
for c in self.server.clients:
726
if c.fingerprint == fpr:
730
logger.warning(u"Client not found for fingerprint: %s",
734
# Have to check if client.still_valid(), since it is possible
735
# that the client timed out while establishing the GnuTLS
737
if not client.still_valid():
738
logger.warning(u"Client %(name)s is invalid",
742
## This won't work here, since we're in a fork.
743
# client.checked_ok()
745
while sent_size < len(client.secret):
746
sent = session.send(client.secret[sent_size:])
747
logger.debug(u"Sent: %d, remaining: %d",
748
sent, len(client.secret)
749
- (sent_size + sent))
754
class IPv6_TCPServer(SocketServer.ForkingMixIn,
755
SocketServer.TCPServer, object):
1170
756
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
758
settings: Server settings
759
clients: Set() of Client objects
1173
760
enabled: Boolean; whether this server is activated yet
1174
interface: None or a network interface name (string)
1175
use_ipv6: Boolean; to use IPv6 or not
1177
def __init__(self, server_address, RequestHandlerClass,
1178
interface=None, use_ipv6=True):
1179
self.interface = interface
1181
self.address_family = socket.AF_INET6
1182
socketserver.TCPServer.__init__(self, server_address,
1183
RequestHandlerClass)
762
address_family = socket.AF_INET6
763
def __init__(self, *args, **kwargs):
764
if "settings" in kwargs:
765
self.settings = kwargs["settings"]
766
del kwargs["settings"]
767
if "clients" in kwargs:
768
self.clients = kwargs["clients"]
769
del kwargs["clients"]
770
if "use_ipv6" in kwargs:
771
if not kwargs["use_ipv6"]:
772
self.address_family = socket.AF_INET
773
del kwargs["use_ipv6"]
775
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
1184
776
def server_bind(self):
1185
777
"""This overrides the normal server_bind() function
1186
778
to bind to an interface if one was specified, and also NOT to
1187
779
bind to an address or port if they were not specified."""
1188
if self.interface is not None:
1189
if SO_BINDTODEVICE is None:
1190
logger.error(u"SO_BINDTODEVICE does not exist;"
1191
u" cannot bind to interface %s",
1195
self.socket.setsockopt(socket.SOL_SOCKET,
1199
except socket.error, error:
1200
if error[0] == errno.EPERM:
1201
logger.error(u"No permission to"
1202
u" bind to interface %s",
1204
elif error[0] == errno.ENOPROTOOPT:
1205
logger.error(u"SO_BINDTODEVICE not available;"
1206
u" cannot bind to interface %s",
780
if self.settings["interface"]:
781
# 25 is from /usr/include/asm-i486/socket.h
782
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
784
self.socket.setsockopt(socket.SOL_SOCKET,
786
self.settings["interface"])
787
except socket.error, error:
788
if error[0] == errno.EPERM:
789
logger.error(u"No permission to"
790
u" bind to interface %s",
791
self.settings["interface"])
1210
794
# Only bind(2) the socket if we really need to.
1211
795
if self.server_address[0] or self.server_address[1]:
1212
796
if not self.server_address[0]:
1213
797
if self.address_family == socket.AF_INET6:
1214
any_address = u"::" # in6addr_any
798
any_address = "::" # in6addr_any
1216
800
any_address = socket.INADDR_ANY
1217
801
self.server_address = (any_address,
1219
803
elif not self.server_address[1]:
1220
804
self.server_address = (self.server_address[0],
1222
# if self.interface:
806
# if self.settings["interface"]:
1223
807
# self.server_address = (self.server_address[0],
1226
810
# if_nametoindex
1228
return socketserver.TCPServer.server_bind(self)
1231
class MandosServer(IPv6_TCPServer):
1235
clients: set of Client objects
1236
gnutls_priority GnuTLS priority string
1237
use_dbus: Boolean; to emit D-Bus signals or not
1239
Assumes a gobject.MainLoop event loop.
1241
def __init__(self, server_address, RequestHandlerClass,
1242
interface=None, use_ipv6=True, clients=None,
1243
gnutls_priority=None, use_dbus=True):
1244
self.enabled = False
1245
self.clients = clients
1246
if self.clients is None:
1247
self.clients = set()
1248
self.use_dbus = use_dbus
1249
self.gnutls_priority = gnutls_priority
1250
IPv6_TCPServer.__init__(self, server_address,
1251
RequestHandlerClass,
1252
interface = interface,
1253
use_ipv6 = use_ipv6)
813
return super(IPv6_TCPServer, self).server_bind()
1254
814
def server_activate(self):
1255
815
if self.enabled:
1256
return socketserver.TCPServer.server_activate(self)
816
return super(IPv6_TCPServer, self).server_activate()
1257
817
def enable(self):
1258
818
self.enabled = True
1259
def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
1260
# Call "handle_ipc" for both data and EOF events
1261
gobject.io_add_watch(child_pipe_fd.fileno(),
1262
gobject.IO_IN | gobject.IO_HUP,
1263
functools.partial(self.handle_ipc,
1264
reply = parent_pipe_fd,
1265
sender= child_pipe_fd))
1266
def handle_ipc(self, source, condition, reply=None, sender=None):
1268
gobject.IO_IN: u"IN", # There is data to read.
1269
gobject.IO_OUT: u"OUT", # Data can be written (without
1271
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1272
gobject.IO_ERR: u"ERR", # Error condition.
1273
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1274
# broken, usually for pipes and
1277
conditions_string = ' | '.join(name
1279
condition_names.iteritems()
1280
if cond & condition)
1281
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1284
# Read a line from the file object
1285
cmdline = sender.readline()
1286
if not cmdline: # Empty line means end of file
1287
# close the IPC pipes
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, reply)
1337
logger.error(u"Client %s on address %s requesting "
1338
u"attribute %s not found", fpr, address,
1340
pickle.dump(None, reply)
1342
logger.error(u"Unknown IPC command: %r", cmdline)
1344
# Keep calling this function
1348
821
def string_to_delta(interval):
1349
822
"""Parse a string and return a datetime.timedelta
1351
>>> string_to_delta(u'7d')
824
>>> string_to_delta('7d')
1352
825
datetime.timedelta(7)
1353
>>> string_to_delta(u'60s')
826
>>> string_to_delta('60s')
1354
827
datetime.timedelta(0, 60)
1355
>>> string_to_delta(u'60m')
828
>>> string_to_delta('60m')
1356
829
datetime.timedelta(0, 3600)
1357
>>> string_to_delta(u'24h')
830
>>> string_to_delta('24h')
1358
831
datetime.timedelta(1)
1359
832
>>> string_to_delta(u'1w')
1360
833
datetime.timedelta(7)
1361
>>> string_to_delta(u'5m 30s')
834
>>> string_to_delta('5m 30s')
1362
835
datetime.timedelta(0, 330)
1364
837
timevalue = datetime.timedelta(0)
1473
962
# 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",
963
server_defaults = { "interface": "",
968
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
969
"servicename": "Mandos",
1485
974
# Parse config file for server-global settings
1486
server_config = configparser.SafeConfigParser(server_defaults)
975
server_config = ConfigParser.SafeConfigParser(server_defaults)
1487
976
del server_defaults
1488
server_config.read(os.path.join(options.configdir,
977
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1490
978
# Convert the SafeConfigParser object to a dict
1491
979
server_settings = server_config.defaults()
1492
980
# 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",
981
server_settings["debug"] = server_config.getboolean("DEFAULT",
983
server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
985
server_settings["use_ipv6"] = server_config.getboolean("DEFAULT",
1496
987
if server_settings["port"]:
1497
server_settings["port"] = server_config.getint(u"DEFAULT",
988
server_settings["port"] = server_config.getint("DEFAULT",
1499
990
del server_config
1501
992
# Override the settings from the config file with command line
1502
993
# 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"):
994
for option in ("interface", "address", "port", "debug",
995
"priority", "servicename", "configdir",
996
"use_dbus", "use_ipv6"):
1506
997
value = getattr(options, option)
1507
998
if value is not None:
1508
999
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
1001
# Now we have our good server settings in "server_settings"
1516
##################################################################
1518
1003
# For convenience
1519
debug = server_settings[u"debug"]
1520
use_dbus = server_settings[u"use_dbus"]
1521
use_ipv6 = server_settings[u"use_ipv6"]
1004
debug = server_settings["debug"]
1005
use_dbus = server_settings["use_dbus"]
1006
use_dbus = False # XXX: Not done yet
1007
use_ipv6 = server_settings["use_ipv6"]
1524
1010
syslogger.setLevel(logging.WARNING)
1525
1011
console.setLevel(logging.WARNING)
1527
if server_settings[u"servicename"] != u"Mandos":
1013
if server_settings["servicename"] != "Mandos":
1528
1014
syslogger.setFormatter(logging.Formatter
1529
(u'Mandos (%s) [%%(process)d]:'
1530
u' %%(levelname)s: %%(message)s'
1531
% server_settings[u"servicename"]))
1015
('Mandos (%s): %%(levelname)s:'
1017
% server_settings["servicename"]))
1533
1019
# Parse config file with clients
1534
client_defaults = { u"timeout": u"1h",
1536
u"checker": u"fping -q -- %%(host)s",
1020
client_defaults = { "timeout": "1h",
1022
"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"
1025
client_config = ConfigParser.SafeConfigParser(client_defaults)
1026
client_config.read(os.path.join(server_settings["configdir"],
1030
tcp_server = IPv6_TCPServer((server_settings["address"],
1031
server_settings["port"]),
1033
settings=server_settings,
1034
clients=clients, use_ipv6=use_ipv6)
1035
pidfilename = "/var/run/mandos.pid"
1556
pidfile = open(pidfilename, u"w")
1037
pidfile = open(pidfilename, "w")
1557
1038
except IOError:
1558
logger.error(u"Could not open file %r", pidfilename)
1039
logger.error("Could not open file %r", pidfilename)
1561
uid = pwd.getpwnam(u"_mandos").pw_uid
1562
gid = pwd.getpwnam(u"_mandos").pw_gid
1042
uid = pwd.getpwnam("_mandos").pw_uid
1043
gid = pwd.getpwnam("_mandos").pw_gid
1563
1044
except KeyError:
1565
uid = pwd.getpwnam(u"mandos").pw_uid
1566
gid = pwd.getpwnam(u"mandos").pw_gid
1046
uid = pwd.getpwnam("mandos").pw_uid
1047
gid = pwd.getpwnam("mandos").pw_gid
1567
1048
except KeyError:
1569
uid = pwd.getpwnam(u"nobody").pw_uid
1570
gid = pwd.getpwnam(u"nobody").pw_gid
1050
uid = pwd.getpwnam("nobody").pw_uid
1051
gid = pwd.getpwnam("nogroup").pw_gid
1571
1052
except KeyError: