154
133
u" after %i retries, exiting.",
155
134
self.rename_count)
156
135
raise AvahiServiceError(u"Too many renames")
157
self.name = self.server.GetAlternativeServiceName(self.name)
136
self.name = server.GetAlternativeServiceName(self.name)
158
137
logger.info(u"Changing Zeroconf service name to %r ...",
160
139
syslogger.setFormatter(logging.Formatter
161
(u'Mandos (%s) [%%(process)d]:'
162
u' %%(levelname)s: %%(message)s'
140
('Mandos (%s) [%%(process)d]:'
141
' %%(levelname)s: %%(message)s'
166
145
self.rename_count += 1
167
146
def remove(self):
168
147
"""Derived from the Avahi example code"""
169
if self.group is not None:
148
if group is not None:
172
151
"""Derived from the Avahi example code"""
173
if self.group is None:
174
self.group = dbus.Interface(
175
self.bus.get_object(avahi.DBUS_NAME,
176
self.server.EntryGroupNew()),
177
avahi.DBUS_INTERFACE_ENTRY_GROUP)
178
self.group.connect_to_signal('StateChanged',
180
.entry_group_state_changed)
154
group = dbus.Interface(bus.get_object
156
server.EntryGroupNew()),
157
avahi.DBUS_INTERFACE_ENTRY_GROUP)
158
group.connect_to_signal('StateChanged',
159
entry_group_state_changed)
181
160
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
182
self.name, self.type)
183
self.group.AddService(
186
dbus.UInt32(0), # flags
187
self.name, self.type,
188
self.domain, self.host,
189
dbus.UInt16(self.port),
190
avahi.string_array_to_txt_array(self.TXT))
192
def entry_group_state_changed(self, state, error):
193
"""Derived from the Avahi example code"""
194
logger.debug(u"Avahi state change: %i", state)
196
if state == avahi.ENTRY_GROUP_ESTABLISHED:
197
logger.debug(u"Zeroconf service established.")
198
elif state == avahi.ENTRY_GROUP_COLLISION:
199
logger.warning(u"Zeroconf service name collision.")
201
elif state == avahi.ENTRY_GROUP_FAILURE:
202
logger.critical(u"Avahi: Error in group state changed %s",
204
raise AvahiGroupError(u"State changed: %s"
207
"""Derived from the Avahi example code"""
208
if self.group is not None:
211
def server_state_changed(self, state):
212
"""Derived from the Avahi example code"""
213
if state == avahi.SERVER_COLLISION:
214
logger.error(u"Zeroconf server name collision")
216
elif state == avahi.SERVER_RUNNING:
219
"""Derived from the Avahi example code"""
220
if self.server is None:
221
self.server = dbus.Interface(
222
self.bus.get_object(avahi.DBUS_NAME,
223
avahi.DBUS_PATH_SERVER),
224
avahi.DBUS_INTERFACE_SERVER)
225
self.server.connect_to_signal(u"StateChanged",
226
self.server_state_changed)
227
self.server_state_changed(self.server.GetState())
161
service.name, service.type)
163
self.interface, # interface
164
self.protocol, # protocol
165
dbus.UInt32(0), # flags
166
self.name, self.type,
167
self.domain, self.host,
168
dbus.UInt16(self.port),
169
avahi.string_array_to_txt_array(self.TXT))
172
# From the Avahi example code:
173
group = None # our entry group
174
# End of Avahi example code
177
def _datetime_to_dbus(dt, variant_level=0):
178
"""Convert a UTC datetime.datetime() to a D-Bus type."""
179
return dbus.String(dt.isoformat(), variant_level=variant_level)
230
182
class Client(object):
231
183
"""A representation of a client host served by this server.
234
185
name: string; from the config file, used in log messages and
235
186
D-Bus identifiers
284
231
# Uppercase and remove spaces from fingerprint for later
285
232
# comparison purposes with return value from the fingerprint()
287
self.fingerprint = (config[u"fingerprint"].upper()
234
self.fingerprint = (config["fingerprint"].upper()
288
235
.replace(u" ", u""))
289
236
logger.debug(u" Fingerprint: %s", self.fingerprint)
290
if u"secret" in config:
291
self.secret = config[u"secret"].decode(u"base64")
292
elif u"secfile" in config:
237
if "secret" in config:
238
self.secret = config["secret"].decode(u"base64")
239
elif "secfile" in config:
293
240
with closing(open(os.path.expanduser
294
241
(os.path.expandvars
295
(config[u"secfile"])),
242
(config["secfile"])))) as secfile:
297
243
self.secret = secfile.read()
299
245
raise TypeError(u"No secret or secfile for client %s"
301
self.host = config.get(u"host", u"")
247
self.host = config.get("host", "")
302
248
self.created = datetime.datetime.utcnow()
303
249
self.enabled = False
304
250
self.last_enabled = None
305
251
self.last_checked_ok = None
306
self.timeout = string_to_delta(config[u"timeout"])
307
self.interval = string_to_delta(config[u"interval"])
252
self.timeout = string_to_delta(config["timeout"])
253
self.interval = string_to_delta(config["interval"])
308
254
self.disable_hook = disable_hook
309
255
self.checker = None
310
256
self.checker_initiator_tag = None
311
257
self.disable_initiator_tag = None
312
258
self.checker_callback_tag = None
313
self.checker_command = config[u"checker"]
259
self.checker_command = config["checker"]
314
260
self.current_checker_command = None
315
261
self.last_connect = None
317
263
def enable(self):
318
264
"""Start this client's checker and timeout hooks"""
319
if getattr(self, u"enabled", False):
322
265
self.last_enabled = datetime.datetime.utcnow()
323
266
# Schedule a new checker to be started an 'interval' from now,
324
267
# and every interval from then on.
486
419
return now < (self.last_checked_ok + self.timeout)
489
def dbus_service_property(dbus_interface, signature=u"v",
490
access=u"readwrite", byte_arrays=False):
491
"""Decorators for marking methods of a DBusObjectWithProperties to
492
become properties on the D-Bus.
494
The decorated method will be called with no arguments by "Get"
495
and with one argument by "Set".
497
The parameters, where they are supported, are the same as
498
dbus.service.method, except there is only "signature", since the
499
type from Get() and the type sent to Set() is the same.
502
func._dbus_is_property = True
503
func._dbus_interface = dbus_interface
504
func._dbus_signature = signature
505
func._dbus_access = access
506
func._dbus_name = func.__name__
507
if func._dbus_name.endswith(u"_dbus_property"):
508
func._dbus_name = func._dbus_name[:-14]
509
func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
514
class DBusPropertyException(dbus.exceptions.DBusException):
515
"""A base class for D-Bus property-related exceptions
517
def __unicode__(self):
518
return unicode(str(self))
521
class DBusPropertyAccessException(DBusPropertyException):
522
"""A property's access permissions disallows an operation.
527
class DBusPropertyNotFound(DBusPropertyException):
528
"""An attempt was made to access a non-existing property.
533
class DBusObjectWithProperties(dbus.service.Object):
534
"""A D-Bus object with properties.
536
Classes inheriting from this can use the dbus_service_property
537
decorator to expose methods as D-Bus properties. It exposes the
538
standard Get(), Set(), and GetAll() methods on the D-Bus.
542
def _is_dbus_property(obj):
543
return getattr(obj, u"_dbus_is_property", False)
545
def _get_all_dbus_properties(self):
546
"""Returns a generator of (name, attribute) pairs
548
return ((prop._dbus_name, prop)
550
inspect.getmembers(self, self._is_dbus_property))
552
def _get_dbus_property(self, interface_name, property_name):
553
"""Returns a bound method if one exists which is a D-Bus
554
property with the specified name and interface.
556
for name in (property_name,
557
property_name + u"_dbus_property"):
558
prop = getattr(self, name, None)
560
or not self._is_dbus_property(prop)
561
or prop._dbus_name != property_name
562
or (interface_name and prop._dbus_interface
563
and interface_name != prop._dbus_interface)):
567
raise DBusPropertyNotFound(self.dbus_object_path + u":"
568
+ interface_name + u"."
571
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
573
def Get(self, interface_name, property_name):
574
"""Standard D-Bus property Get() method, see D-Bus standard.
576
prop = self._get_dbus_property(interface_name, property_name)
577
if prop._dbus_access == u"write":
578
raise DBusPropertyAccessException(property_name)
580
if not hasattr(value, u"variant_level"):
582
return type(value)(value, variant_level=value.variant_level+1)
584
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
585
def Set(self, interface_name, property_name, value):
586
"""Standard D-Bus property Set() method, see D-Bus standard.
588
prop = self._get_dbus_property(interface_name, property_name)
589
if prop._dbus_access == u"read":
590
raise DBusPropertyAccessException(property_name)
591
if prop._dbus_get_args_options[u"byte_arrays"]:
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):
422
class ClientDBus(Client, dbus.service.Object):
670
423
"""A Client class using D-Bus
673
dbus_object_path: dbus.ObjectPath
674
bus: dbus.SystemBus()
425
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
676
427
# dbus.service.Object doesn't use super(), so we can't either.
678
def __init__(self, bus = None, *args, **kwargs):
429
def __init__(self, *args, **kwargs):
680
430
Client.__init__(self, *args, **kwargs)
681
431
# Only now, when this client is initialized, can it show up on
683
433
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)
435
+ self.name.replace(".", "_")))
436
dbus.service.Object.__init__(self, bus,
437
self.dbus_object_path)
695
438
def enable(self):
696
oldstate = getattr(self, u"enabled", False)
439
oldstate = getattr(self, "enabled", False)
697
440
r = Client.enable(self)
698
441
if oldstate != self.enabled:
699
442
# Emit D-Bus signals
700
443
self.PropertyChanged(dbus.String(u"enabled"),
701
444
dbus.Boolean(True, variant_level=1))
702
self.PropertyChanged(
703
dbus.String(u"last_enabled"),
704
self._datetime_to_dbus(self.last_enabled,
445
self.PropertyChanged(dbus.String(u"last_enabled"),
446
(_datetime_to_dbus(self.last_enabled,
708
450
def disable(self, signal = True):
709
oldstate = getattr(self, u"enabled", False)
451
oldstate = getattr(self, "enabled", False)
710
452
r = Client.disable(self)
711
453
if signal and oldstate != self.enabled:
712
454
# Emit D-Bus signal
784
526
_interface = u"se.bsnet.fukt.Mandos.Client"
786
528
# CheckedOK - method
787
@dbus.service.method(_interface)
789
return self.checked_ok()
529
CheckedOK = dbus.service.method(_interface)(checked_ok)
530
CheckedOK.__name__ = "CheckedOK"
791
532
# CheckerCompleted - signal
792
@dbus.service.signal(_interface, signature=u"nxs")
533
@dbus.service.signal(_interface, signature="nxs")
793
534
def CheckerCompleted(self, exitcode, waitstatus, command):
797
538
# CheckerStarted - signal
798
@dbus.service.signal(_interface, signature=u"s")
539
@dbus.service.signal(_interface, signature="s")
799
540
def CheckerStarted(self, command):
544
# GetAllProperties - method
545
@dbus.service.method(_interface, out_signature="a{sv}")
546
def GetAllProperties(self):
548
return dbus.Dictionary({
550
dbus.String(self.name, variant_level=1),
551
dbus.String("fingerprint"):
552
dbus.String(self.fingerprint, variant_level=1),
554
dbus.String(self.host, variant_level=1),
555
dbus.String("created"):
556
_datetime_to_dbus(self.created, variant_level=1),
557
dbus.String("last_enabled"):
558
(_datetime_to_dbus(self.last_enabled,
560
if self.last_enabled is not None
561
else dbus.Boolean(False, variant_level=1)),
562
dbus.String("enabled"):
563
dbus.Boolean(self.enabled, variant_level=1),
564
dbus.String("last_checked_ok"):
565
(_datetime_to_dbus(self.last_checked_ok,
567
if self.last_checked_ok is not None
568
else dbus.Boolean (False, variant_level=1)),
569
dbus.String("timeout"):
570
dbus.UInt64(self.timeout_milliseconds(),
572
dbus.String("interval"):
573
dbus.UInt64(self.interval_milliseconds(),
575
dbus.String("checker"):
576
dbus.String(self.checker_command,
578
dbus.String("checker_running"):
579
dbus.Boolean(self.checker is not None,
581
dbus.String("object_path"):
582
dbus.ObjectPath(self.dbus_object_path,
586
# IsStillValid - method
587
@dbus.service.method(_interface, out_signature="b")
588
def IsStillValid(self):
589
return self.still_valid()
803
591
# PropertyChanged - signal
804
@dbus.service.signal(_interface, signature=u"sv")
592
@dbus.service.signal(_interface, signature="sv")
805
593
def PropertyChanged(self, property, value):
597
# ReceivedSecret - signal
810
598
@dbus.service.signal(_interface)
599
def ReceivedSecret(self):
609
# SetChecker - method
610
@dbus.service.method(_interface, in_signature="s")
611
def SetChecker(self, checker):
612
"D-Bus setter method"
613
self.checker_command = checker
615
self.PropertyChanged(dbus.String(u"checker"),
616
dbus.String(self.checker_command,
620
@dbus.service.method(_interface, in_signature="s")
621
def SetHost(self, host):
622
"D-Bus setter method"
625
self.PropertyChanged(dbus.String(u"host"),
626
dbus.String(self.host, variant_level=1))
628
# SetInterval - method
629
@dbus.service.method(_interface, in_signature="t")
630
def SetInterval(self, milliseconds):
631
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
633
self.PropertyChanged(dbus.String(u"interval"),
634
(dbus.UInt64(self.interval_milliseconds(),
638
@dbus.service.method(_interface, in_signature="ay",
640
def SetSecret(self, secret):
641
"D-Bus setter method"
642
self.secret = str(secret)
644
# SetTimeout - method
645
@dbus.service.method(_interface, in_signature="t")
646
def SetTimeout(self, milliseconds):
647
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
649
self.PropertyChanged(dbus.String(u"timeout"),
650
(dbus.UInt64(self.timeout_milliseconds(),
821
653
# Enable - method
822
@dbus.service.method(_interface)
654
Enable = dbus.service.method(_interface)(enable)
655
Enable.__name__ = "Enable"
827
657
# StartChecker - method
828
658
@dbus.service.method(_interface)
839
669
# 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)
670
StopChecker = dbus.service.method(_interface)(stop_checker)
671
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.
676
def peer_certificate(session):
677
"Return the peer's OpenPGP certificate as a bytestring"
678
# If not an OpenPGP certificate...
679
if (gnutls.library.functions
680
.gnutls_certificate_type_get(session._c_object)
681
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
682
# ...do the normal thing
683
return session.peer_certificate
684
list_size = ctypes.c_uint(1)
685
cert_list = (gnutls.library.functions
686
.gnutls_certificate_get_peers
687
(session._c_object, ctypes.byref(list_size)))
688
if not bool(cert_list) and list_size.value != 0:
689
raise gnutls.errors.GNUTLSError("error getting peer"
691
if list_size.value == 0:
694
return ctypes.string_at(cert.data, cert.size)
697
def fingerprint(openpgp):
698
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
699
# New GnuTLS "datum" with the OpenPGP public key
700
datum = (gnutls.library.types
701
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
704
ctypes.c_uint(len(openpgp))))
705
# New empty GnuTLS certificate
706
crt = gnutls.library.types.gnutls_openpgp_crt_t()
707
(gnutls.library.functions
708
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
709
# Import the OpenPGP public key into the certificate
710
(gnutls.library.functions
711
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
712
gnutls.library.constants
713
.GNUTLS_OPENPGP_FMT_RAW))
714
# Verify the self signature in the key
715
crtverify = ctypes.c_uint()
716
(gnutls.library.functions
717
.gnutls_openpgp_crt_verify_self(crt, 0, ctypes.byref(crtverify)))
718
if crtverify.value != 0:
719
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
720
raise gnutls.errors.CertificateSecurityError("Verify failed")
721
# New buffer for the fingerprint
722
buf = ctypes.create_string_buffer(20)
723
buf_len = ctypes.c_size_t()
724
# Get the fingerprint from the certificate into the buffer
725
(gnutls.library.functions
726
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
727
ctypes.byref(buf_len)))
728
# Deinit the certificate
729
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
730
# Convert the buffer to a Python bytestring
731
fpr = ctypes.string_at(buf, buf_len.value)
732
# Convert the bytestring to hexadecimal notation
733
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
737
class TCP_handler(SocketServer.BaseRequestHandler, object):
738
"""A TCP request handler class.
739
Instantiated by IPv6_TCPServer for each request to handle it.
987
740
Note: This will run in its own forked process."""
989
742
def handle(self):
1064
818
- (sent_size + sent))
1065
819
sent_size += sent
1069
def peer_certificate(session):
1070
"Return the peer's OpenPGP certificate as a bytestring"
1071
# If not an OpenPGP certificate...
1072
if (gnutls.library.functions
1073
.gnutls_certificate_type_get(session._c_object)
1074
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1075
# ...do the normal thing
1076
return session.peer_certificate
1077
list_size = ctypes.c_uint(1)
1078
cert_list = (gnutls.library.functions
1079
.gnutls_certificate_get_peers
1080
(session._c_object, ctypes.byref(list_size)))
1081
if not bool(cert_list) and list_size.value != 0:
1082
raise gnutls.errors.GNUTLSError(u"error getting peer"
1084
if list_size.value == 0:
1087
return ctypes.string_at(cert.data, cert.size)
1090
def fingerprint(openpgp):
1091
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1092
# New GnuTLS "datum" with the OpenPGP public key
1093
datum = (gnutls.library.types
1094
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1097
ctypes.c_uint(len(openpgp))))
1098
# New empty GnuTLS certificate
1099
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1100
(gnutls.library.functions
1101
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1102
# Import the OpenPGP public key into the certificate
1103
(gnutls.library.functions
1104
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1105
gnutls.library.constants
1106
.GNUTLS_OPENPGP_FMT_RAW))
1107
# Verify the self signature in the key
1108
crtverify = ctypes.c_uint()
1109
(gnutls.library.functions
1110
.gnutls_openpgp_crt_verify_self(crt, 0,
1111
ctypes.byref(crtverify)))
1112
if crtverify.value != 0:
1113
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1114
raise (gnutls.errors.CertificateSecurityError
1116
# New buffer for the fingerprint
1117
buf = ctypes.create_string_buffer(20)
1118
buf_len = ctypes.c_size_t()
1119
# Get the fingerprint from the certificate into the buffer
1120
(gnutls.library.functions
1121
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1122
ctypes.byref(buf_len)))
1123
# Deinit the certificate
1124
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1125
# Convert the buffer to a Python bytestring
1126
fpr = ctypes.string_at(buf, buf_len.value)
1127
# Convert the bytestring to hexadecimal notation
1128
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1132
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
1133
"""Like socketserver.ForkingMixIn, but also pass a pipe."""
823
class ForkingMixInWithPipe(SocketServer.ForkingMixIn, object):
824
"""Like SocketServer.ForkingMixIn, but also pass a pipe.
825
Assumes a gobject.MainLoop event loop.
1134
827
def process_request(self, request, client_address):
1135
"""Overrides and wraps the original process_request().
1137
This function creates a new pipe in self.pipe
828
"""This overrides and wraps the original process_request().
829
This function creates a new pipe in self.pipe
1139
831
self.pipe = os.pipe()
1140
832
super(ForkingMixInWithPipe,
1141
833
self).process_request(request, client_address)
1142
834
os.close(self.pipe[1]) # close write end
1143
self.add_pipe(self.pipe[0])
1144
def add_pipe(self, pipe):
835
# Call "handle_ipc" for both data and EOF events
836
gobject.io_add_watch(self.pipe[0],
837
gobject.IO_IN | gobject.IO_HUP,
839
def handle_ipc(source, condition):
1145
840
"""Dummy function; override as necessary"""
1149
845
class IPv6_TCPServer(ForkingMixInWithPipe,
1150
socketserver.TCPServer, object):
846
SocketServer.TCPServer, object):
1151
847
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
849
settings: Server settings
850
clients: Set() of Client objects
1154
851
enabled: Boolean; whether this server is activated yet
1155
interface: None or a network interface name (string)
1156
use_ipv6: Boolean; to use IPv6 or not
1158
def __init__(self, server_address, RequestHandlerClass,
1159
interface=None, use_ipv6=True):
1160
self.interface = interface
1162
self.address_family = socket.AF_INET6
1163
socketserver.TCPServer.__init__(self, server_address,
1164
RequestHandlerClass)
853
address_family = socket.AF_INET6
854
def __init__(self, *args, **kwargs):
855
if "settings" in kwargs:
856
self.settings = kwargs["settings"]
857
del kwargs["settings"]
858
if "clients" in kwargs:
859
self.clients = kwargs["clients"]
860
del kwargs["clients"]
861
if "use_ipv6" in kwargs:
862
if not kwargs["use_ipv6"]:
863
self.address_family = socket.AF_INET
864
del kwargs["use_ipv6"]
866
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
1165
867
def server_bind(self):
1166
868
"""This overrides the normal server_bind() function
1167
869
to bind to an interface if one was specified, and also NOT to
1168
870
bind to an address or port if they were not specified."""
1169
if self.interface is not None:
1170
if SO_BINDTODEVICE is None:
1171
logger.error(u"SO_BINDTODEVICE does not exist;"
1172
u" cannot bind to interface %s",
1176
self.socket.setsockopt(socket.SOL_SOCKET,
1180
except socket.error, error:
1181
if error[0] == errno.EPERM:
1182
logger.error(u"No permission to"
1183
u" bind to interface %s",
1185
elif error[0] == errno.ENOPROTOOPT:
1186
logger.error(u"SO_BINDTODEVICE not available;"
1187
u" cannot bind to interface %s",
871
if self.settings["interface"]:
872
# 25 is from /usr/include/asm-i486/socket.h
873
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
875
self.socket.setsockopt(socket.SOL_SOCKET,
877
self.settings["interface"])
878
except socket.error, error:
879
if error[0] == errno.EPERM:
880
logger.error(u"No permission to"
881
u" bind to interface %s",
882
self.settings["interface"])
1191
885
# Only bind(2) the socket if we really need to.
1192
886
if self.server_address[0] or self.server_address[1]:
1193
887
if not self.server_address[0]:
1194
888
if self.address_family == socket.AF_INET6:
1195
any_address = u"::" # in6addr_any
889
any_address = "::" # in6addr_any
1197
891
any_address = socket.INADDR_ANY
1198
892
self.server_address = (any_address,
1200
894
elif not self.server_address[1]:
1201
895
self.server_address = (self.server_address[0],
1203
# if self.interface:
897
# if self.settings["interface"]:
1204
898
# self.server_address = (self.server_address[0],
1207
901
# if_nametoindex
1209
return socketserver.TCPServer.server_bind(self)
1212
class MandosServer(IPv6_TCPServer):
1216
clients: set of Client objects
1217
gnutls_priority GnuTLS priority string
1218
use_dbus: Boolean; to emit D-Bus signals or not
1220
Assumes a gobject.MainLoop event loop.
1222
def __init__(self, server_address, RequestHandlerClass,
1223
interface=None, use_ipv6=True, clients=None,
1224
gnutls_priority=None, use_dbus=True):
1225
self.enabled = False
1226
self.clients = clients
1227
if self.clients is None:
1228
self.clients = set()
1229
self.use_dbus = use_dbus
1230
self.gnutls_priority = gnutls_priority
1231
IPv6_TCPServer.__init__(self, server_address,
1232
RequestHandlerClass,
1233
interface = interface,
1234
use_ipv6 = use_ipv6)
904
return super(IPv6_TCPServer, self).server_bind()
1235
905
def server_activate(self):
1236
906
if self.enabled:
1237
return socketserver.TCPServer.server_activate(self)
907
return super(IPv6_TCPServer, self).server_activate()
1238
908
def enable(self):
1239
909
self.enabled = True
1240
def add_pipe(self, pipe):
1241
# Call "handle_ipc" for both data and EOF events
1242
gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1244
910
def handle_ipc(self, source, condition, file_objects={}):
1245
911
condition_names = {
1246
gobject.IO_IN: u"IN", # There is data to read.
1247
gobject.IO_OUT: u"OUT", # Data can be written (without
1249
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1250
gobject.IO_ERR: u"ERR", # Error condition.
1251
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1252
# broken, usually for pipes and
912
gobject.IO_IN: "IN", # There is data to read.
913
gobject.IO_OUT: "OUT", # Data can be written (without
915
gobject.IO_PRI: "PRI", # There is urgent data to read.
916
gobject.IO_ERR: "ERR", # Error condition.
917
gobject.IO_HUP: "HUP" # Hung up (the connection has been
918
# broken, usually for pipes and
1255
921
conditions_string = ' | '.join(name
1256
922
for cond, name in
1257
923
condition_names.iteritems()
1258
924
if cond & condition)
1259
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
925
logger.debug("Handling IPC: FD = %d, condition = %s", source,
1260
926
conditions_string)
1262
928
# Turn the pipe file descriptor into a Python file object
1263
929
if source not in file_objects:
1264
file_objects[source] = os.fdopen(source, u"r", 1)
930
file_objects[source] = os.fdopen(source, "r", 1)
1266
932
# Read a line from the file object
1267
933
cmdline = file_objects[source].readline()
1352
1008
return timevalue
1011
def server_state_changed(state):
1012
"""Derived from the Avahi example code"""
1013
if state == avahi.SERVER_COLLISION:
1014
logger.error(u"Zeroconf server name collision")
1016
elif state == avahi.SERVER_RUNNING:
1020
def entry_group_state_changed(state, error):
1021
"""Derived from the Avahi example code"""
1022
logger.debug(u"Avahi state change: %i", state)
1024
if state == avahi.ENTRY_GROUP_ESTABLISHED:
1025
logger.debug(u"Zeroconf service established.")
1026
elif state == avahi.ENTRY_GROUP_COLLISION:
1027
logger.warning(u"Zeroconf service name collision.")
1029
elif state == avahi.ENTRY_GROUP_FAILURE:
1030
logger.critical(u"Avahi: Error in group state changed %s",
1032
raise AvahiGroupError(u"State changed: %s" % unicode(error))
1355
1034
def if_nametoindex(interface):
1356
"""Call the C function if_nametoindex(), or equivalent
1358
Note: This function cannot accept a unicode string."""
1035
"""Call the C function if_nametoindex(), or equivalent"""
1359
1036
global if_nametoindex
1361
1038
if_nametoindex = (ctypes.cdll.LoadLibrary
1362
(ctypes.util.find_library(u"c"))
1039
(ctypes.util.find_library("c"))
1363
1040
.if_nametoindex)
1364
1041
except (OSError, AttributeError):
1365
logger.warning(u"Doing if_nametoindex the hard way")
1042
if "struct" not in sys.modules:
1044
if "fcntl" not in sys.modules:
1366
1046
def if_nametoindex(interface):
1367
1047
"Get an interface index the hard way, i.e. using fcntl()"
1368
1048
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1369
1049
with closing(socket.socket()) as s:
1370
1050
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1371
struct.pack(str(u"16s16x"),
1373
interface_index = struct.unpack(str(u"I"),
1051
struct.pack("16s16x", interface))
1052
interface_index = struct.unpack("I", ifreq[16:20])[0]
1375
1053
return interface_index
1376
1054
return if_nametoindex(interface)
1379
1057
def daemon(nochdir = False, noclose = False):
1380
1058
"""See daemon(3). Standard BSD Unix function.
1382
1059
This should really exist as os.daemon, but it doesn't (yet)."""
1386
1063
if not nochdir:
1390
1067
if not noclose:
1405
##################################################################
1082
######################################################################
1406
1083
# Parsing of options, both command line and config file
1408
1085
parser = optparse.OptionParser(version = "%%prog %s" % version)
1409
parser.add_option("-i", u"--interface", type=u"string",
1410
metavar="IF", help=u"Bind to interface IF")
1411
parser.add_option("-a", u"--address", type=u"string",
1412
help=u"Address to listen for requests on")
1413
parser.add_option("-p", u"--port", type=u"int",
1414
help=u"Port number to receive requests on")
1415
parser.add_option("--check", action=u"store_true",
1416
help=u"Run self-test")
1417
parser.add_option("--debug", action=u"store_true",
1418
help=u"Debug mode; run in foreground and log to"
1420
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1421
u" priority string (see GnuTLS documentation)")
1422
parser.add_option("--servicename", type=u"string",
1423
metavar=u"NAME", help=u"Zeroconf service name")
1424
parser.add_option("--configdir", type=u"string",
1425
default=u"/etc/mandos", metavar=u"DIR",
1426
help=u"Directory to search for configuration"
1428
parser.add_option("--no-dbus", action=u"store_false",
1429
dest=u"use_dbus", help=u"Do not provide D-Bus"
1430
u" system bus interface")
1431
parser.add_option("--no-ipv6", action=u"store_false",
1432
dest=u"use_ipv6", help=u"Do not use IPv6")
1086
parser.add_option("-i", "--interface", type="string",
1087
metavar="IF", help="Bind to interface IF")
1088
parser.add_option("-a", "--address", type="string",
1089
help="Address to listen for requests on")
1090
parser.add_option("-p", "--port", type="int",
1091
help="Port number to receive requests on")
1092
parser.add_option("--check", action="store_true",
1093
help="Run self-test")
1094
parser.add_option("--debug", action="store_true",
1095
help="Debug mode; run in foreground and log to"
1097
parser.add_option("--priority", type="string", help="GnuTLS"
1098
" priority string (see GnuTLS documentation)")
1099
parser.add_option("--servicename", type="string", metavar="NAME",
1100
help="Zeroconf service name")
1101
parser.add_option("--configdir", type="string",
1102
default="/etc/mandos", metavar="DIR",
1103
help="Directory to search for configuration"
1105
parser.add_option("--no-dbus", action="store_false",
1107
help="Do not provide D-Bus system bus"
1109
parser.add_option("--no-ipv6", action="store_false",
1110
dest="use_ipv6", help="Do not use IPv6")
1433
1111
options = parser.parse_args()[0]
1435
1113
if options.check:
1440
1118
# Default values for config file for server-global settings
1441
server_defaults = { u"interface": u"",
1446
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1447
u"servicename": u"Mandos",
1448
u"use_dbus": u"True",
1449
u"use_ipv6": u"True",
1119
server_defaults = { "interface": "",
1124
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1125
"servicename": "Mandos",
1452
1130
# Parse config file for server-global settings
1453
server_config = configparser.SafeConfigParser(server_defaults)
1131
server_config = ConfigParser.SafeConfigParser(server_defaults)
1454
1132
del server_defaults
1455
server_config.read(os.path.join(options.configdir,
1133
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1457
1134
# Convert the SafeConfigParser object to a dict
1458
1135
server_settings = server_config.defaults()
1459
1136
# Use the appropriate methods on the non-string config options
1460
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1461
server_settings[option] = server_config.getboolean(u"DEFAULT",
1137
server_settings["debug"] = server_config.getboolean("DEFAULT",
1139
server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
1141
server_settings["use_ipv6"] = server_config.getboolean("DEFAULT",
1463
1143
if server_settings["port"]:
1464
server_settings["port"] = server_config.getint(u"DEFAULT",
1144
server_settings["port"] = server_config.getint("DEFAULT",
1466
1146
del server_config
1468
1148
# Override the settings from the config file with command line
1469
1149
# options, if set.
1470
for option in (u"interface", u"address", u"port", u"debug",
1471
u"priority", u"servicename", u"configdir",
1472
u"use_dbus", u"use_ipv6"):
1150
for option in ("interface", "address", "port", "debug",
1151
"priority", "servicename", "configdir",
1152
"use_dbus", "use_ipv6"):
1473
1153
value = getattr(options, option)
1474
1154
if value is not None:
1475
1155
server_settings[option] = value
1477
# Force all strings to be unicode
1478
for option in server_settings.keys():
1479
if type(server_settings[option]) is str:
1480
server_settings[option] = unicode(server_settings[option])
1481
1157
# Now we have our good server settings in "server_settings"
1483
1159
##################################################################
1485
1161
# For convenience
1486
debug = server_settings[u"debug"]
1487
use_dbus = server_settings[u"use_dbus"]
1488
use_ipv6 = server_settings[u"use_ipv6"]
1162
debug = server_settings["debug"]
1163
use_dbus = server_settings["use_dbus"]
1164
use_ipv6 = server_settings["use_ipv6"]
1491
1167
syslogger.setLevel(logging.WARNING)
1492
1168
console.setLevel(logging.WARNING)
1494
if server_settings[u"servicename"] != u"Mandos":
1170
if server_settings["servicename"] != "Mandos":
1495
1171
syslogger.setFormatter(logging.Formatter
1496
(u'Mandos (%s) [%%(process)d]:'
1497
u' %%(levelname)s: %%(message)s'
1498
% server_settings[u"servicename"]))
1172
('Mandos (%s) [%%(process)d]:'
1173
' %%(levelname)s: %%(message)s'
1174
% server_settings["servicename"]))
1500
1176
# Parse config file with clients
1501
client_defaults = { u"timeout": u"1h",
1503
u"checker": u"fping -q -- %%(host)s",
1177
client_defaults = { "timeout": "1h",
1179
"checker": "fping -q -- %%(host)s",
1506
client_config = configparser.SafeConfigParser(client_defaults)
1507
client_config.read(os.path.join(server_settings[u"configdir"],
1182
client_config = ConfigParser.SafeConfigParser(client_defaults)
1183
client_config.read(os.path.join(server_settings["configdir"],
1510
1186
global mandos_dbus_service
1511
1187
mandos_dbus_service = None
1513
tcp_server = MandosServer((server_settings[u"address"],
1514
server_settings[u"port"]),
1516
interface=server_settings[u"interface"],
1519
server_settings[u"priority"],
1521
pidfilename = u"/var/run/mandos.pid"
1190
tcp_server = IPv6_TCPServer((server_settings["address"],
1191
server_settings["port"]),
1193
settings=server_settings,
1194
clients=clients, use_ipv6=use_ipv6)
1195
pidfilename = "/var/run/mandos.pid"
1523
pidfile = open(pidfilename, u"w")
1197
pidfile = open(pidfilename, "w")
1524
1198
except IOError:
1525
logger.error(u"Could not open file %r", pidfilename)
1199
logger.error("Could not open file %r", pidfilename)
1528
uid = pwd.getpwnam(u"_mandos").pw_uid
1529
gid = pwd.getpwnam(u"_mandos").pw_gid
1202
uid = pwd.getpwnam("_mandos").pw_uid
1203
gid = pwd.getpwnam("_mandos").pw_gid
1530
1204
except KeyError:
1532
uid = pwd.getpwnam(u"mandos").pw_uid
1533
gid = pwd.getpwnam(u"mandos").pw_gid
1206
uid = pwd.getpwnam("mandos").pw_uid
1207
gid = pwd.getpwnam("mandos").pw_gid
1534
1208
except KeyError:
1536
uid = pwd.getpwnam(u"nobody").pw_uid
1537
gid = pwd.getpwnam(u"nobody").pw_gid
1210
uid = pwd.getpwnam("nobody").pw_uid
1211
gid = pwd.getpwnam("nogroup").pw_gid
1538
1212
except KeyError:
1554
1228
@gnutls.library.types.gnutls_log_func
1555
1229
def debug_gnutls(level, string):
1556
logger.debug(u"GnuTLS: %s", string[:-1])
1230
logger.debug("GnuTLS: %s", string[:-1])
1558
1232
(gnutls.library.functions
1559
1233
.gnutls_global_set_log_function(debug_gnutls))
1236
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1237
service = AvahiService(name = server_settings["servicename"],
1238
servicetype = "_mandos._tcp",
1239
protocol = protocol)
1240
if server_settings["interface"]:
1241
service.interface = (if_nametoindex
1242
(server_settings["interface"]))
1561
1244
global main_loop
1562
1247
# From the Avahi example code
1563
1248
DBusGMainLoop(set_as_default=True )
1564
1249
main_loop = gobject.MainLoop()
1565
1250
bus = dbus.SystemBus()
1251
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1252
avahi.DBUS_PATH_SERVER),
1253
avahi.DBUS_INTERFACE_SERVER)
1566
1254
# End of Avahi example code
1568
1256
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1569
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1570
service = AvahiService(name = server_settings[u"servicename"],
1571
servicetype = u"_mandos._tcp",
1572
protocol = protocol, bus = bus)
1573
if server_settings["interface"]:
1574
service.interface = (if_nametoindex
1575
(str(server_settings[u"interface"])))
1577
1258
client_class = Client
1579
client_class = functools.partial(ClientDBus, bus = bus)
1580
tcp_server.clients.update(set(
1260
client_class = ClientDBus
1581
1262
client_class(name = section,
1582
1263
config= dict(client_config.items(section)))
1583
1264
for section in client_config.sections()))
1584
if not tcp_server.clients:
1585
1266
logger.warning(u"No clients defined")
1629
1315
class MandosDBusService(dbus.service.Object):
1630
1316
"""A D-Bus proxy object"""
1631
1317
def __init__(self):
1632
dbus.service.Object.__init__(self, bus, u"/")
1318
dbus.service.Object.__init__(self, bus, "/")
1633
1319
_interface = u"se.bsnet.fukt.Mandos"
1635
@dbus.service.signal(_interface, signature=u"oa{sv}")
1321
@dbus.service.signal(_interface, signature="oa{sv}")
1636
1322
def ClientAdded(self, objpath, properties):
1640
@dbus.service.signal(_interface, signature=u"s")
1326
@dbus.service.signal(_interface, signature="s")
1641
1327
def ClientNotFound(self, fingerprint):
1645
@dbus.service.signal(_interface, signature=u"os")
1331
@dbus.service.signal(_interface, signature="os")
1646
1332
def ClientRemoved(self, objpath, name):
1650
@dbus.service.method(_interface, out_signature=u"ao")
1336
@dbus.service.method(_interface, out_signature="ao")
1651
1337
def GetAllClients(self):
1653
return dbus.Array(c.dbus_object_path
1654
for c in tcp_server.clients)
1339
return dbus.Array(c.dbus_object_path for c in clients)
1656
@dbus.service.method(_interface,
1657
out_signature=u"a{oa{sv}}")
1341
@dbus.service.method(_interface, out_signature="a{oa{sv}}")
1658
1342
def GetAllClientsWithProperties(self):
1660
1344
return dbus.Dictionary(
1661
((c.dbus_object_path, c.GetAll(u""))
1662
for c in tcp_server.clients),
1663
signature=u"oa{sv}")
1345
((c.dbus_object_path, c.GetAllProperties())
1665
@dbus.service.method(_interface, in_signature=u"o")
1349
@dbus.service.method(_interface, in_signature="o")
1666
1350
def RemoveClient(self, object_path):
1668
for c in tcp_server.clients:
1669
1353
if c.dbus_object_path == object_path:
1670
tcp_server.clients.remove(c)
1671
1355
c.remove_from_connection()
1672
1356
# Don't signal anything except ClientRemoved
1673
1357
c.disable(signal=False)