144
154
u" after %i retries, exiting.",
145
155
self.rename_count)
146
156
raise AvahiServiceError(u"Too many renames")
147
self.name = server.GetAlternativeServiceName(self.name)
157
self.name = self.server.GetAlternativeServiceName(self.name)
148
158
logger.info(u"Changing Zeroconf service name to %r ...",
150
160
syslogger.setFormatter(logging.Formatter
151
('Mandos (%s) [%%(process)d]:'
152
' %%(levelname)s: %%(message)s'
161
(u'Mandos (%s) [%%(process)d]:'
162
u' %%(levelname)s: %%(message)s'
156
166
self.rename_count += 1
157
167
def remove(self):
158
168
"""Derived from the Avahi example code"""
159
if group is not None:
169
if self.group is not None:
162
172
"""Derived from the Avahi example code"""
165
group = dbus.Interface(bus.get_object
167
server.EntryGroupNew()),
168
avahi.DBUS_INTERFACE_ENTRY_GROUP)
169
group.connect_to_signal('StateChanged',
170
entry_group_state_changed)
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)
171
181
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
172
service.name, service.type)
174
self.interface, # interface
175
self.protocol, # protocol
176
dbus.UInt32(0), # flags
177
self.name, self.type,
178
self.domain, self.host,
179
dbus.UInt16(self.port),
180
avahi.string_array_to_txt_array(self.TXT))
183
# From the Avahi example code:
184
group = None # our entry group
185
# End of Avahi example code
188
def _datetime_to_dbus(dt, variant_level=0):
189
"""Convert a UTC datetime.datetime() to a D-Bus type."""
190
return dbus.String(dt.isoformat(), variant_level=variant_level)
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())
193
230
class Client(object):
243
284
# Uppercase and remove spaces from fingerprint for later
244
285
# comparison purposes with return value from the fingerprint()
246
self.fingerprint = (config["fingerprint"].upper()
287
self.fingerprint = (config[u"fingerprint"].upper()
247
288
.replace(u" ", u""))
248
289
logger.debug(u" Fingerprint: %s", self.fingerprint)
249
if "secret" in config:
250
self.secret = config["secret"].decode(u"base64")
251
elif "secfile" in config:
290
if u"secret" in config:
291
self.secret = config[u"secret"].decode(u"base64")
292
elif u"secfile" in config:
252
293
with closing(open(os.path.expanduser
253
294
(os.path.expandvars
254
(config["secfile"])))) as secfile:
295
(config[u"secfile"])),
255
297
self.secret = secfile.read()
257
299
raise TypeError(u"No secret or secfile for client %s"
259
self.host = config.get("host", "")
301
self.host = config.get(u"host", u"")
260
302
self.created = datetime.datetime.utcnow()
261
303
self.enabled = False
262
304
self.last_enabled = None
263
305
self.last_checked_ok = None
264
self.timeout = string_to_delta(config["timeout"])
265
self.interval = string_to_delta(config["interval"])
306
self.timeout = string_to_delta(config[u"timeout"])
307
self.interval = string_to_delta(config[u"interval"])
266
308
self.disable_hook = disable_hook
267
309
self.checker = None
268
310
self.checker_initiator_tag = None
269
311
self.disable_initiator_tag = None
270
312
self.checker_callback_tag = None
271
self.checker_command = config["checker"]
313
self.checker_command = config[u"checker"]
272
314
self.current_checker_command = None
273
315
self.last_connect = None
275
317
def enable(self):
276
318
"""Start this client's checker and timeout hooks"""
319
if getattr(self, u"enabled", False):
277
322
self.last_enabled = datetime.datetime.utcnow()
278
323
# Schedule a new checker to be started an 'interval' from now,
279
324
# and every interval from then on.
280
325
self.checker_initiator_tag = (gobject.timeout_add
281
326
(self.interval_milliseconds(),
282
327
self.start_checker))
283
# Also start a new checker *right now*.
285
328
# Schedule a disable() when 'timeout' has passed
286
329
self.disable_initiator_tag = (gobject.timeout_add
287
330
(self.timeout_milliseconds(),
289
332
self.enabled = True
333
# Also start a new checker *right now*.
336
def disable(self, quiet=True):
292
337
"""Disable this client."""
293
338
if not getattr(self, "enabled", False):
295
logger.info(u"Disabling client %s", self.name)
296
if getattr(self, "disable_initiator_tag", False):
341
logger.info(u"Disabling client %s", self.name)
342
if getattr(self, u"disable_initiator_tag", False):
297
343
gobject.source_remove(self.disable_initiator_tag)
298
344
self.disable_initiator_tag = None
299
if getattr(self, "checker_initiator_tag", False):
345
if getattr(self, u"checker_initiator_tag", False):
300
346
gobject.source_remove(self.checker_initiator_tag)
301
347
self.checker_initiator_tag = None
302
348
self.stop_checker()
433
487
return now < (self.last_checked_ok + self.timeout)
436
class ClientDBus(Client, dbus.service.Object):
490
def dbus_service_property(dbus_interface, signature=u"v",
491
access=u"readwrite", byte_arrays=False):
492
"""Decorators for marking methods of a DBusObjectWithProperties to
493
become properties on the D-Bus.
495
The decorated method will be called with no arguments by "Get"
496
and with one argument by "Set".
498
The parameters, where they are supported, are the same as
499
dbus.service.method, except there is only "signature", since the
500
type from Get() and the type sent to Set() is the same.
503
func._dbus_is_property = True
504
func._dbus_interface = dbus_interface
505
func._dbus_signature = signature
506
func._dbus_access = access
507
func._dbus_name = func.__name__
508
if func._dbus_name.endswith(u"_dbus_property"):
509
func._dbus_name = func._dbus_name[:-14]
510
func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
515
class DBusPropertyException(dbus.exceptions.DBusException):
516
"""A base class for D-Bus property-related exceptions
518
def __unicode__(self):
519
return unicode(str(self))
522
class DBusPropertyAccessException(DBusPropertyException):
523
"""A property's access permissions disallows an operation.
528
class DBusPropertyNotFound(DBusPropertyException):
529
"""An attempt was made to access a non-existing property.
534
class DBusObjectWithProperties(dbus.service.Object):
535
"""A D-Bus object with properties.
537
Classes inheriting from this can use the dbus_service_property
538
decorator to expose methods as D-Bus properties. It exposes the
539
standard Get(), Set(), and GetAll() methods on the D-Bus.
543
def _is_dbus_property(obj):
544
return getattr(obj, u"_dbus_is_property", False)
546
def _get_all_dbus_properties(self):
547
"""Returns a generator of (name, attribute) pairs
549
return ((prop._dbus_name, prop)
551
inspect.getmembers(self, self._is_dbus_property))
553
def _get_dbus_property(self, interface_name, property_name):
554
"""Returns a bound method if one exists which is a D-Bus
555
property with the specified name and interface.
557
for name in (property_name,
558
property_name + u"_dbus_property"):
559
prop = getattr(self, name, None)
561
or not self._is_dbus_property(prop)
562
or prop._dbus_name != property_name
563
or (interface_name and prop._dbus_interface
564
and interface_name != prop._dbus_interface)):
568
raise DBusPropertyNotFound(self.dbus_object_path + u":"
569
+ interface_name + u"."
572
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
574
def Get(self, interface_name, property_name):
575
"""Standard D-Bus property Get() method, see D-Bus standard.
577
prop = self._get_dbus_property(interface_name, property_name)
578
if prop._dbus_access == u"write":
579
raise DBusPropertyAccessException(property_name)
581
if not hasattr(value, u"variant_level"):
583
return type(value)(value, variant_level=value.variant_level+1)
585
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
586
def Set(self, interface_name, property_name, value):
587
"""Standard D-Bus property Set() method, see D-Bus standard.
589
prop = self._get_dbus_property(interface_name, property_name)
590
if prop._dbus_access == u"read":
591
raise DBusPropertyAccessException(property_name)
592
if prop._dbus_get_args_options[u"byte_arrays"]:
593
value = dbus.ByteArray(''.join(unichr(byte)
597
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
598
out_signature=u"a{sv}")
599
def GetAll(self, interface_name):
600
"""Standard D-Bus property GetAll() method, see D-Bus
603
Note: Will not include properties with access="write".
606
for name, prop in self._get_all_dbus_properties():
608
and interface_name != prop._dbus_interface):
609
# Interface non-empty but did not match
611
# Ignore write-only properties
612
if prop._dbus_access == u"write":
615
if not hasattr(value, u"variant_level"):
618
all[name] = type(value)(value, variant_level=
619
value.variant_level+1)
620
return dbus.Dictionary(all, signature=u"sv")
622
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
624
path_keyword='object_path',
625
connection_keyword='connection')
626
def Introspect(self, object_path, connection):
627
"""Standard D-Bus method, overloaded to insert property tags.
629
xmlstring = dbus.service.Object.Introspect(self, object_path,
632
document = xml.dom.minidom.parseString(xmlstring)
633
def make_tag(document, name, prop):
634
e = document.createElement(u"property")
635
e.setAttribute(u"name", name)
636
e.setAttribute(u"type", prop._dbus_signature)
637
e.setAttribute(u"access", prop._dbus_access)
639
for if_tag in document.getElementsByTagName(u"interface"):
640
for tag in (make_tag(document, name, prop)
642
in self._get_all_dbus_properties()
643
if prop._dbus_interface
644
== if_tag.getAttribute(u"name")):
645
if_tag.appendChild(tag)
646
# Add the names to the return values for the
647
# "org.freedesktop.DBus.Properties" methods
648
if (if_tag.getAttribute(u"name")
649
== u"org.freedesktop.DBus.Properties"):
650
for cn in if_tag.getElementsByTagName(u"method"):
651
if cn.getAttribute(u"name") == u"Get":
652
for arg in cn.getElementsByTagName(u"arg"):
653
if (arg.getAttribute(u"direction")
655
arg.setAttribute(u"name", u"value")
656
elif cn.getAttribute(u"name") == u"GetAll":
657
for arg in cn.getElementsByTagName(u"arg"):
658
if (arg.getAttribute(u"direction")
660
arg.setAttribute(u"name", u"props")
661
xmlstring = document.toxml(u"utf-8")
663
except (AttributeError, xml.dom.DOMException,
664
xml.parsers.expat.ExpatError), error:
665
logger.error(u"Failed to override Introspection method",
670
class ClientDBus(Client, DBusObjectWithProperties):
437
671
"""A Client class using D-Bus
440
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
674
dbus_object_path: dbus.ObjectPath
675
bus: dbus.SystemBus()
442
677
# dbus.service.Object doesn't use super(), so we can't either.
444
def __init__(self, *args, **kwargs):
679
def __init__(self, bus = None, *args, **kwargs):
445
681
Client.__init__(self, *args, **kwargs)
446
682
# Only now, when this client is initialized, can it show up on
448
684
self.dbus_object_path = (dbus.ObjectPath
450
+ self.name.replace(".", "_")))
451
dbus.service.Object.__init__(self, bus,
452
self.dbus_object_path)
686
+ self.name.replace(u".", u"_")))
687
DBusObjectWithProperties.__init__(self, self.bus,
688
self.dbus_object_path)
691
def _datetime_to_dbus(dt, variant_level=0):
692
"""Convert a UTC datetime.datetime() to a D-Bus type."""
693
return dbus.String(dt.isoformat(),
694
variant_level=variant_level)
453
696
def enable(self):
454
oldstate = getattr(self, "enabled", False)
697
oldstate = getattr(self, u"enabled", False)
455
698
r = Client.enable(self)
456
699
if oldstate != self.enabled:
457
700
# Emit D-Bus signals
458
701
self.PropertyChanged(dbus.String(u"enabled"),
459
702
dbus.Boolean(True, variant_level=1))
460
self.PropertyChanged(dbus.String(u"last_enabled"),
461
(_datetime_to_dbus(self.last_enabled,
703
self.PropertyChanged(
704
dbus.String(u"last_enabled"),
705
self._datetime_to_dbus(self.last_enabled,
465
def disable(self, signal = True):
466
oldstate = getattr(self, "enabled", False)
467
r = Client.disable(self)
468
if signal and oldstate != self.enabled:
709
def disable(self, quiet = False):
710
oldstate = getattr(self, u"enabled", False)
711
r = Client.disable(self, quiet=quiet)
712
if not quiet and oldstate != self.enabled:
469
713
# Emit D-Bus signal
470
714
self.PropertyChanged(dbus.String(u"enabled"),
471
715
dbus.Boolean(False, variant_level=1))
541
785
_interface = u"se.bsnet.fukt.Mandos.Client"
543
787
# CheckedOK - method
544
CheckedOK = dbus.service.method(_interface)(checked_ok)
545
CheckedOK.__name__ = "CheckedOK"
788
@dbus.service.method(_interface)
790
return self.checked_ok()
547
792
# CheckerCompleted - signal
548
@dbus.service.signal(_interface, signature="nxs")
793
@dbus.service.signal(_interface, signature=u"nxs")
549
794
def CheckerCompleted(self, exitcode, waitstatus, command):
553
798
# CheckerStarted - signal
554
@dbus.service.signal(_interface, signature="s")
799
@dbus.service.signal(_interface, signature=u"s")
555
800
def CheckerStarted(self, command):
559
# GetAllProperties - method
560
@dbus.service.method(_interface, out_signature="a{sv}")
561
def GetAllProperties(self):
563
return dbus.Dictionary({
565
dbus.String(self.name, variant_level=1),
566
dbus.String("fingerprint"):
567
dbus.String(self.fingerprint, variant_level=1),
569
dbus.String(self.host, variant_level=1),
570
dbus.String("created"):
571
_datetime_to_dbus(self.created, variant_level=1),
572
dbus.String("last_enabled"):
573
(_datetime_to_dbus(self.last_enabled,
575
if self.last_enabled is not None
576
else dbus.Boolean(False, variant_level=1)),
577
dbus.String("enabled"):
578
dbus.Boolean(self.enabled, variant_level=1),
579
dbus.String("last_checked_ok"):
580
(_datetime_to_dbus(self.last_checked_ok,
582
if self.last_checked_ok is not None
583
else dbus.Boolean (False, variant_level=1)),
584
dbus.String("timeout"):
585
dbus.UInt64(self.timeout_milliseconds(),
587
dbus.String("interval"):
588
dbus.UInt64(self.interval_milliseconds(),
590
dbus.String("checker"):
591
dbus.String(self.checker_command,
593
dbus.String("checker_running"):
594
dbus.Boolean(self.checker is not None,
596
dbus.String("object_path"):
597
dbus.ObjectPath(self.dbus_object_path,
601
# IsStillValid - method
602
@dbus.service.method(_interface, out_signature="b")
603
def IsStillValid(self):
604
return self.still_valid()
606
804
# PropertyChanged - signal
607
@dbus.service.signal(_interface, signature="sv")
805
@dbus.service.signal(_interface, signature=u"sv")
608
806
def PropertyChanged(self, property, value):
612
# ReceivedSecret - signal
613
811
@dbus.service.signal(_interface)
614
def ReceivedSecret(self):
624
# SetChecker - method
625
@dbus.service.method(_interface, in_signature="s")
626
def SetChecker(self, checker):
627
"D-Bus setter method"
628
self.checker_command = checker
630
self.PropertyChanged(dbus.String(u"checker"),
631
dbus.String(self.checker_command,
635
@dbus.service.method(_interface, in_signature="s")
636
def SetHost(self, host):
637
"D-Bus setter method"
640
self.PropertyChanged(dbus.String(u"host"),
641
dbus.String(self.host, variant_level=1))
643
# SetInterval - method
644
@dbus.service.method(_interface, in_signature="t")
645
def SetInterval(self, milliseconds):
646
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
648
self.PropertyChanged(dbus.String(u"interval"),
649
(dbus.UInt64(self.interval_milliseconds(),
653
@dbus.service.method(_interface, in_signature="ay",
655
def SetSecret(self, secret):
656
"D-Bus setter method"
657
self.secret = str(secret)
659
# SetTimeout - method
660
@dbus.service.method(_interface, in_signature="t")
661
def SetTimeout(self, milliseconds):
662
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
664
self.PropertyChanged(dbus.String(u"timeout"),
665
(dbus.UInt64(self.timeout_milliseconds(),
668
822
# Enable - method
669
Enable = dbus.service.method(_interface)(enable)
670
Enable.__name__ = "Enable"
823
@dbus.service.method(_interface)
672
828
# StartChecker - method
673
829
@dbus.service.method(_interface)
684
840
# StopChecker - method
685
StopChecker = dbus.service.method(_interface)(stop_checker)
686
StopChecker.__name__ = "StopChecker"
841
@dbus.service.method(_interface)
842
def StopChecker(self):
846
@dbus_service_property(_interface, signature=u"s", access=u"read")
847
def name_dbus_property(self):
848
return dbus.String(self.name)
850
# fingerprint - property
851
@dbus_service_property(_interface, signature=u"s", access=u"read")
852
def fingerprint_dbus_property(self):
853
return dbus.String(self.fingerprint)
856
@dbus_service_property(_interface, signature=u"s",
858
def host_dbus_property(self, value=None):
859
if value is None: # get
860
return dbus.String(self.host)
863
self.PropertyChanged(dbus.String(u"host"),
864
dbus.String(value, variant_level=1))
867
@dbus_service_property(_interface, signature=u"s", access=u"read")
868
def created_dbus_property(self):
869
return dbus.String(self._datetime_to_dbus(self.created))
871
# last_enabled - property
872
@dbus_service_property(_interface, signature=u"s", access=u"read")
873
def last_enabled_dbus_property(self):
874
if self.last_enabled is None:
875
return dbus.String(u"")
876
return dbus.String(self._datetime_to_dbus(self.last_enabled))
879
@dbus_service_property(_interface, signature=u"b",
881
def enabled_dbus_property(self, value=None):
882
if value is None: # get
883
return dbus.Boolean(self.enabled)
889
# last_checked_ok - property
890
@dbus_service_property(_interface, signature=u"s",
892
def last_checked_ok_dbus_property(self, value=None):
893
if value is not None:
896
if self.last_checked_ok is None:
897
return dbus.String(u"")
898
return dbus.String(self._datetime_to_dbus(self
902
@dbus_service_property(_interface, signature=u"t",
904
def timeout_dbus_property(self, value=None):
905
if value is None: # get
906
return dbus.UInt64(self.timeout_milliseconds())
907
self.timeout = datetime.timedelta(0, 0, 0, value)
909
self.PropertyChanged(dbus.String(u"timeout"),
910
dbus.UInt64(value, variant_level=1))
911
if getattr(self, u"disable_initiator_tag", None) is None:
914
gobject.source_remove(self.disable_initiator_tag)
915
self.disable_initiator_tag = None
917
_timedelta_to_milliseconds((self
923
# The timeout has passed
926
self.disable_initiator_tag = (gobject.timeout_add
927
(time_to_die, self.disable))
929
# interval - property
930
@dbus_service_property(_interface, signature=u"t",
932
def interval_dbus_property(self, value=None):
933
if value is None: # get
934
return dbus.UInt64(self.interval_milliseconds())
935
self.interval = datetime.timedelta(0, 0, 0, value)
937
self.PropertyChanged(dbus.String(u"interval"),
938
dbus.UInt64(value, variant_level=1))
939
if getattr(self, u"checker_initiator_tag", None) is None:
941
# Reschedule checker run
942
gobject.source_remove(self.checker_initiator_tag)
943
self.checker_initiator_tag = (gobject.timeout_add
944
(value, self.start_checker))
945
self.start_checker() # Start one now, too
948
@dbus_service_property(_interface, signature=u"s",
950
def checker_dbus_property(self, value=None):
951
if value is None: # get
952
return dbus.String(self.checker_command)
953
self.checker_command = value
955
self.PropertyChanged(dbus.String(u"checker"),
956
dbus.String(self.checker_command,
959
# checker_running - property
960
@dbus_service_property(_interface, signature=u"b",
962
def checker_running_dbus_property(self, value=None):
963
if value is None: # get
964
return dbus.Boolean(self.checker is not None)
970
# object_path - property
971
@dbus_service_property(_interface, signature=u"o", access=u"read")
972
def object_path_dbus_property(self):
973
return self.dbus_object_path # is already a dbus.ObjectPath
976
@dbus_service_property(_interface, signature=u"ay",
977
access=u"write", byte_arrays=True)
978
def secret_dbus_property(self, value):
979
self.secret = str(value)
691
class ClientHandler(SocketServer.BaseRequestHandler, object):
984
class ClientHandler(socketserver.BaseRequestHandler, object):
692
985
"""A class to handle client connections.
694
987
Instantiated once for each connection to handle it.
839
class ForkingMixInWithPipe(SocketServer.ForkingMixIn, object):
840
"""Like SocketServer.ForkingMixIn, but also pass a pipe.
842
Assumes a gobject.MainLoop event loop.
1133
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
1134
"""Like socketserver.ForkingMixIn, but also pass a pipe."""
844
1135
def process_request(self, request, client_address):
845
1136
"""Overrides and wraps the original process_request().
847
This function creates a new pipe in self.pipe
1138
This function creates a new pipe in self.pipe
849
1140
self.pipe = os.pipe()
850
1141
super(ForkingMixInWithPipe,
851
1142
self).process_request(request, client_address)
852
1143
os.close(self.pipe[1]) # close write end
853
# Call "handle_ipc" for both data and EOF events
854
gobject.io_add_watch(self.pipe[0],
855
gobject.IO_IN | gobject.IO_HUP,
857
def handle_ipc(source, condition):
1144
self.add_pipe(self.pipe[0])
1145
def add_pipe(self, pipe):
858
1146
"""Dummy function; override as necessary"""
863
1150
class IPv6_TCPServer(ForkingMixInWithPipe,
864
SocketServer.TCPServer, object):
1151
socketserver.TCPServer, object):
865
1152
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
868
1155
enabled: Boolean; whether this server is activated yet
869
1156
interface: None or a network interface name (string)
870
1157
use_ipv6: Boolean; to use IPv6 or not
872
clients: Set() of Client objects
873
gnutls_priority GnuTLS priority string
874
use_dbus: Boolean; to emit D-Bus signals or not
876
1159
def __init__(self, server_address, RequestHandlerClass,
877
interface=None, use_ipv6=True, clients=None,
878
gnutls_priority=None, use_dbus=True):
1160
interface=None, use_ipv6=True):
880
1161
self.interface = interface
882
1163
self.address_family = socket.AF_INET6
883
self.clients = clients
884
self.use_dbus = use_dbus
885
self.gnutls_priority = gnutls_priority
886
SocketServer.TCPServer.__init__(self, server_address,
1164
socketserver.TCPServer.__init__(self, server_address,
887
1165
RequestHandlerClass)
888
1166
def server_bind(self):
889
1167
"""This overrides the normal server_bind() function
890
1168
to bind to an interface if one was specified, and also NOT to
891
1169
bind to an address or port if they were not specified."""
892
1170
if self.interface is not None:
894
self.socket.setsockopt(socket.SOL_SOCKET,
896
self.interface + '\0')
897
except socket.error, error:
898
if error[0] == errno.EPERM:
899
logger.error(u"No permission to"
900
u" bind to interface %s",
1171
if SO_BINDTODEVICE is None:
1172
logger.error(u"SO_BINDTODEVICE does not exist;"
1173
u" cannot bind to interface %s",
1177
self.socket.setsockopt(socket.SOL_SOCKET,
1181
except socket.error, error:
1182
if error[0] == errno.EPERM:
1183
logger.error(u"No permission to"
1184
u" bind to interface %s",
1186
elif error[0] == errno.ENOPROTOOPT:
1187
logger.error(u"SO_BINDTODEVICE not available;"
1188
u" cannot bind to interface %s",
904
1192
# Only bind(2) the socket if we really need to.
905
1193
if self.server_address[0] or self.server_address[1]:
906
1194
if not self.server_address[0]:
907
1195
if self.address_family == socket.AF_INET6:
908
any_address = "::" # in6addr_any
1196
any_address = u"::" # in6addr_any
910
1198
any_address = socket.INADDR_ANY
911
1199
self.server_address = (any_address,
920
1208
# if_nametoindex
921
1209
# (self.interface))
922
return SocketServer.TCPServer.server_bind(self)
1210
return socketserver.TCPServer.server_bind(self)
1213
class MandosServer(IPv6_TCPServer):
1217
clients: set of Client objects
1218
gnutls_priority GnuTLS priority string
1219
use_dbus: Boolean; to emit D-Bus signals or not
1221
Assumes a gobject.MainLoop event loop.
1223
def __init__(self, server_address, RequestHandlerClass,
1224
interface=None, use_ipv6=True, clients=None,
1225
gnutls_priority=None, use_dbus=True):
1226
self.enabled = False
1227
self.clients = clients
1228
if self.clients is None:
1229
self.clients = set()
1230
self.use_dbus = use_dbus
1231
self.gnutls_priority = gnutls_priority
1232
IPv6_TCPServer.__init__(self, server_address,
1233
RequestHandlerClass,
1234
interface = interface,
1235
use_ipv6 = use_ipv6)
923
1236
def server_activate(self):
924
1237
if self.enabled:
925
return SocketServer.TCPServer.server_activate(self)
1238
return socketserver.TCPServer.server_activate(self)
926
1239
def enable(self):
927
1240
self.enabled = True
1241
def add_pipe(self, pipe):
1242
# Call "handle_ipc" for both data and EOF events
1243
gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
928
1245
def handle_ipc(self, source, condition, file_objects={}):
929
1246
condition_names = {
930
gobject.IO_IN: "IN", # There is data to read.
931
gobject.IO_OUT: "OUT", # Data can be written (without
933
gobject.IO_PRI: "PRI", # There is urgent data to read.
934
gobject.IO_ERR: "ERR", # Error condition.
935
gobject.IO_HUP: "HUP" # Hung up (the connection has been
936
# broken, usually for pipes and
1247
gobject.IO_IN: u"IN", # There is data to read.
1248
gobject.IO_OUT: u"OUT", # Data can be written (without
1250
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1251
gobject.IO_ERR: u"ERR", # Error condition.
1252
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1253
# broken, usually for pipes and
939
1256
conditions_string = ' | '.join(name
940
1257
for cond, name in
941
1258
condition_names.iteritems()
942
1259
if cond & condition)
943
logger.debug("Handling IPC: FD = %d, condition = %s", source,
1260
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
944
1261
conditions_string)
946
1263
# Turn the pipe file descriptor into a Python file object
947
1264
if source not in file_objects:
948
file_objects[source] = os.fdopen(source, "r", 1)
1265
file_objects[source] = os.fdopen(source, u"r", 1)
950
1267
# Read a line from the file object
951
1268
cmdline = file_objects[source].readline()
1029
1347
elif suffix == u"w":
1030
1348
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1033
except (ValueError, IndexError):
1350
raise ValueError(u"Unknown suffix %r" % suffix)
1351
except (ValueError, IndexError), e:
1352
raise ValueError(e.message)
1035
1353
timevalue += delta
1036
1354
return timevalue
1039
def server_state_changed(state):
1040
"""Derived from the Avahi example code"""
1041
if state == avahi.SERVER_COLLISION:
1042
logger.error(u"Zeroconf server name collision")
1044
elif state == avahi.SERVER_RUNNING:
1048
def entry_group_state_changed(state, error):
1049
"""Derived from the Avahi example code"""
1050
logger.debug(u"Avahi state change: %i", state)
1052
if state == avahi.ENTRY_GROUP_ESTABLISHED:
1053
logger.debug(u"Zeroconf service established.")
1054
elif state == avahi.ENTRY_GROUP_COLLISION:
1055
logger.warning(u"Zeroconf service name collision.")
1057
elif state == avahi.ENTRY_GROUP_FAILURE:
1058
logger.critical(u"Avahi: Error in group state changed %s",
1060
raise AvahiGroupError(u"State changed: %s" % unicode(error))
1062
1357
def if_nametoindex(interface):
1063
"""Call the C function if_nametoindex(), or equivalent"""
1358
"""Call the C function if_nametoindex(), or equivalent
1360
Note: This function cannot accept a unicode string."""
1064
1361
global if_nametoindex
1066
1363
if_nametoindex = (ctypes.cdll.LoadLibrary
1067
(ctypes.util.find_library("c"))
1364
(ctypes.util.find_library(u"c"))
1068
1365
.if_nametoindex)
1069
1366
except (OSError, AttributeError):
1070
if "struct" not in sys.modules:
1072
if "fcntl" not in sys.modules:
1367
logger.warning(u"Doing if_nametoindex the hard way")
1074
1368
def if_nametoindex(interface):
1075
1369
"Get an interface index the hard way, i.e. using fcntl()"
1076
1370
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1077
1371
with closing(socket.socket()) as s:
1078
1372
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1079
struct.pack("16s16x", interface))
1080
interface_index = struct.unpack("I", ifreq[16:20])[0]
1373
struct.pack(str(u"16s16x"),
1375
interface_index = struct.unpack(str(u"I"),
1081
1377
return interface_index
1082
1378
return if_nametoindex(interface)
1111
######################################################################
1408
##################################################################
1112
1409
# Parsing of options, both command line and config file
1114
1411
parser = optparse.OptionParser(version = "%%prog %s" % version)
1115
parser.add_option("-i", "--interface", type="string",
1116
metavar="IF", help="Bind to interface IF")
1117
parser.add_option("-a", "--address", type="string",
1118
help="Address to listen for requests on")
1119
parser.add_option("-p", "--port", type="int",
1120
help="Port number to receive requests on")
1121
parser.add_option("--check", action="store_true",
1122
help="Run self-test")
1123
parser.add_option("--debug", action="store_true",
1124
help="Debug mode; run in foreground and log to"
1126
parser.add_option("--priority", type="string", help="GnuTLS"
1127
" priority string (see GnuTLS documentation)")
1128
parser.add_option("--servicename", type="string", metavar="NAME",
1129
help="Zeroconf service name")
1130
parser.add_option("--configdir", type="string",
1131
default="/etc/mandos", metavar="DIR",
1132
help="Directory to search for configuration"
1134
parser.add_option("--no-dbus", action="store_false",
1136
help="Do not provide D-Bus system bus"
1138
parser.add_option("--no-ipv6", action="store_false",
1139
dest="use_ipv6", help="Do not use IPv6")
1412
parser.add_option("-i", u"--interface", type=u"string",
1413
metavar="IF", help=u"Bind to interface IF")
1414
parser.add_option("-a", u"--address", type=u"string",
1415
help=u"Address to listen for requests on")
1416
parser.add_option("-p", u"--port", type=u"int",
1417
help=u"Port number to receive requests on")
1418
parser.add_option("--check", action=u"store_true",
1419
help=u"Run self-test")
1420
parser.add_option("--debug", action=u"store_true",
1421
help=u"Debug mode; run in foreground and log to"
1423
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1424
u" priority string (see GnuTLS documentation)")
1425
parser.add_option("--servicename", type=u"string",
1426
metavar=u"NAME", help=u"Zeroconf service name")
1427
parser.add_option("--configdir", type=u"string",
1428
default=u"/etc/mandos", metavar=u"DIR",
1429
help=u"Directory to search for configuration"
1431
parser.add_option("--no-dbus", action=u"store_false",
1432
dest=u"use_dbus", help=u"Do not provide D-Bus"
1433
u" system bus interface")
1434
parser.add_option("--no-ipv6", action=u"store_false",
1435
dest=u"use_ipv6", help=u"Do not use IPv6")
1140
1436
options = parser.parse_args()[0]
1142
1438
if options.check:
1147
1443
# Default values for config file for server-global settings
1148
server_defaults = { "interface": "",
1153
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1154
"servicename": "Mandos",
1444
server_defaults = { u"interface": u"",
1449
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1450
u"servicename": u"Mandos",
1451
u"use_dbus": u"True",
1452
u"use_ipv6": u"True",
1159
1455
# Parse config file for server-global settings
1160
server_config = ConfigParser.SafeConfigParser(server_defaults)
1456
server_config = configparser.SafeConfigParser(server_defaults)
1161
1457
del server_defaults
1162
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1458
server_config.read(os.path.join(options.configdir,
1163
1460
# Convert the SafeConfigParser object to a dict
1164
1461
server_settings = server_config.defaults()
1165
1462
# Use the appropriate methods on the non-string config options
1166
server_settings["debug"] = server_config.getboolean("DEFAULT",
1168
server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
1170
server_settings["use_ipv6"] = server_config.getboolean("DEFAULT",
1463
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1464
server_settings[option] = server_config.getboolean(u"DEFAULT",
1172
1466
if server_settings["port"]:
1173
server_settings["port"] = server_config.getint("DEFAULT",
1467
server_settings["port"] = server_config.getint(u"DEFAULT",
1175
1469
del server_config
1177
1471
# Override the settings from the config file with command line
1178
1472
# options, if set.
1179
for option in ("interface", "address", "port", "debug",
1180
"priority", "servicename", "configdir",
1181
"use_dbus", "use_ipv6"):
1473
for option in (u"interface", u"address", u"port", u"debug",
1474
u"priority", u"servicename", u"configdir",
1475
u"use_dbus", u"use_ipv6"):
1182
1476
value = getattr(options, option)
1183
1477
if value is not None:
1184
1478
server_settings[option] = value
1480
# Force all strings to be unicode
1481
for option in server_settings.keys():
1482
if type(server_settings[option]) is str:
1483
server_settings[option] = unicode(server_settings[option])
1186
1484
# Now we have our good server settings in "server_settings"
1188
1486
##################################################################
1190
1488
# For convenience
1191
debug = server_settings["debug"]
1192
use_dbus = server_settings["use_dbus"]
1193
use_ipv6 = server_settings["use_ipv6"]
1489
debug = server_settings[u"debug"]
1490
use_dbus = server_settings[u"use_dbus"]
1491
use_ipv6 = server_settings[u"use_ipv6"]
1196
1494
syslogger.setLevel(logging.WARNING)
1197
1495
console.setLevel(logging.WARNING)
1199
if server_settings["servicename"] != "Mandos":
1497
if server_settings[u"servicename"] != u"Mandos":
1200
1498
syslogger.setFormatter(logging.Formatter
1201
('Mandos (%s) [%%(process)d]:'
1202
' %%(levelname)s: %%(message)s'
1203
% server_settings["servicename"]))
1499
(u'Mandos (%s) [%%(process)d]:'
1500
u' %%(levelname)s: %%(message)s'
1501
% server_settings[u"servicename"]))
1205
1503
# Parse config file with clients
1206
client_defaults = { "timeout": "1h",
1208
"checker": "fping -q -- %%(host)s",
1504
client_defaults = { u"timeout": u"1h",
1506
u"checker": u"fping -q -- %%(host)s",
1211
client_config = ConfigParser.SafeConfigParser(client_defaults)
1212
client_config.read(os.path.join(server_settings["configdir"],
1509
client_config = configparser.SafeConfigParser(client_defaults)
1510
client_config.read(os.path.join(server_settings[u"configdir"],
1215
1513
global mandos_dbus_service
1216
1514
mandos_dbus_service = None
1219
tcp_server = IPv6_TCPServer((server_settings["address"],
1220
server_settings["port"]),
1223
server_settings["interface"],
1227
server_settings["priority"],
1229
pidfilename = "/var/run/mandos.pid"
1516
tcp_server = MandosServer((server_settings[u"address"],
1517
server_settings[u"port"]),
1519
interface=server_settings[u"interface"],
1522
server_settings[u"priority"],
1524
pidfilename = u"/var/run/mandos.pid"
1231
pidfile = open(pidfilename, "w")
1526
pidfile = open(pidfilename, u"w")
1232
1527
except IOError:
1233
logger.error("Could not open file %r", pidfilename)
1528
logger.error(u"Could not open file %r", pidfilename)
1236
uid = pwd.getpwnam("_mandos").pw_uid
1237
gid = pwd.getpwnam("_mandos").pw_gid
1531
uid = pwd.getpwnam(u"_mandos").pw_uid
1532
gid = pwd.getpwnam(u"_mandos").pw_gid
1238
1533
except KeyError:
1240
uid = pwd.getpwnam("mandos").pw_uid
1241
gid = pwd.getpwnam("mandos").pw_gid
1535
uid = pwd.getpwnam(u"mandos").pw_uid
1536
gid = pwd.getpwnam(u"mandos").pw_gid
1242
1537
except KeyError:
1244
uid = pwd.getpwnam("nobody").pw_uid
1245
gid = pwd.getpwnam("nogroup").pw_gid
1539
uid = pwd.getpwnam(u"nobody").pw_uid
1540
gid = pwd.getpwnam(u"nobody").pw_gid
1246
1541
except KeyError:
1262
1557
@gnutls.library.types.gnutls_log_func
1263
1558
def debug_gnutls(level, string):
1264
logger.debug("GnuTLS: %s", string[:-1])
1559
logger.debug(u"GnuTLS: %s", string[:-1])
1266
1561
(gnutls.library.functions
1267
1562
.gnutls_global_set_log_function(debug_gnutls))
1270
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1271
service = AvahiService(name = server_settings["servicename"],
1272
servicetype = "_mandos._tcp",
1273
protocol = protocol)
1274
if server_settings["interface"]:
1275
service.interface = (if_nametoindex
1276
(server_settings["interface"]))
1278
1564
global main_loop
1281
1565
# From the Avahi example code
1282
1566
DBusGMainLoop(set_as_default=True )
1283
1567
main_loop = gobject.MainLoop()
1284
1568
bus = dbus.SystemBus()
1285
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1286
avahi.DBUS_PATH_SERVER),
1287
avahi.DBUS_INTERFACE_SERVER)
1288
1569
# End of Avahi example code
1290
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1572
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1573
bus, do_not_queue=True)
1574
except dbus.exceptions.NameExistsException, e:
1575
logger.error(unicode(e) + u", disabling D-Bus")
1577
server_settings[u"use_dbus"] = False
1578
tcp_server.use_dbus = False
1579
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1580
service = AvahiService(name = server_settings[u"servicename"],
1581
servicetype = u"_mandos._tcp",
1582
protocol = protocol, bus = bus)
1583
if server_settings["interface"]:
1584
service.interface = (if_nametoindex
1585
(str(server_settings[u"interface"])))
1292
1587
client_class = Client
1294
client_class = ClientDBus
1589
client_class = functools.partial(ClientDBus, bus = bus)
1590
tcp_server.clients.update(set(
1296
1591
client_class(name = section,
1297
1592
config= dict(client_config.items(section)))
1298
1593
for section in client_config.sections()))
1594
if not tcp_server.clients:
1300
1595
logger.warning(u"No clients defined")
1349
1628
class MandosDBusService(dbus.service.Object):
1350
1629
"""A D-Bus proxy object"""
1351
1630
def __init__(self):
1352
dbus.service.Object.__init__(self, bus, "/")
1631
dbus.service.Object.__init__(self, bus, u"/")
1353
1632
_interface = u"se.bsnet.fukt.Mandos"
1355
@dbus.service.signal(_interface, signature="oa{sv}")
1634
@dbus.service.signal(_interface, signature=u"oa{sv}")
1356
1635
def ClientAdded(self, objpath, properties):
1360
@dbus.service.signal(_interface, signature="s")
1361
def ClientNotFound(self, fingerprint):
1639
@dbus.service.signal(_interface, signature=u"ss")
1640
def ClientNotFound(self, fingerprint, address):
1365
@dbus.service.signal(_interface, signature="os")
1644
@dbus.service.signal(_interface, signature=u"os")
1366
1645
def ClientRemoved(self, objpath, name):
1370
@dbus.service.method(_interface, out_signature="ao")
1649
@dbus.service.method(_interface, out_signature=u"ao")
1371
1650
def GetAllClients(self):
1373
return dbus.Array(c.dbus_object_path for c in clients)
1652
return dbus.Array(c.dbus_object_path
1653
for c in tcp_server.clients)
1375
@dbus.service.method(_interface, out_signature="a{oa{sv}}")
1655
@dbus.service.method(_interface,
1656
out_signature=u"a{oa{sv}}")
1376
1657
def GetAllClientsWithProperties(self):
1378
1659
return dbus.Dictionary(
1379
((c.dbus_object_path, c.GetAllProperties())
1660
((c.dbus_object_path, c.GetAll(u""))
1661
for c in tcp_server.clients),
1662
signature=u"oa{sv}")
1383
@dbus.service.method(_interface, in_signature="o")
1664
@dbus.service.method(_interface, in_signature=u"o")
1384
1665
def RemoveClient(self, object_path):
1667
for c in tcp_server.clients:
1387
1668
if c.dbus_object_path == object_path:
1669
tcp_server.clients.remove(c)
1389
1670
c.remove_from_connection()
1390
1671
# Don't signal anything except ClientRemoved
1391
c.disable(signal=False)
1672
c.disable(quiet=True)
1392
1673
# Emit D-Bus signal
1393
1674
self.ClientRemoved(object_path, c.name)
1676
raise KeyError(object_path)
1399
1680
mandos_dbus_service = MandosDBusService()
1401
for client in clients:
1683
"Cleanup function; run on exit"
1686
while tcp_server.clients:
1687
client = tcp_server.clients.pop()
1689
client.remove_from_connection()
1690
client.disable_hook = None
1691
# Don't signal anything except ClientRemoved
1692
client.disable(quiet=True)
1695
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
1698
atexit.register(cleanup)
1700
for client in tcp_server.clients:
1403
1702
# Emit D-Bus signal
1404
1703
mandos_dbus_service.ClientAdded(client.dbus_object_path,
1405
client.GetAllProperties())
1406
1705
client.enable()
1408
1707
tcp_server.enable()