6
6
# This program is partly derived from an example program for an Avahi
7
7
# service publisher, downloaded from
8
8
# <http://avahi.org/wiki/PythonPublishExample>. This includes the
9
# methods "add", "remove", "server_state_changed",
10
# "entry_group_state_changed", "cleanup", and "activate" in the
11
# "AvahiService" class, and some lines in "main".
9
# methods "add" and "remove" in the "AvahiService" class, the
10
# "server_state_changed" and "entry_group_state_changed" functions,
11
# and some lines in "main".
13
13
# Everything else is
14
14
# Copyright © 2008,2009 Teddy Hogeborn
124
125
max_renames: integer; maximum number of renames
125
126
rename_count: integer; counter so we only rename after collisions
126
127
a sensible number of times
127
group: D-Bus Entry Group
129
bus: dbus.SystemBus()
131
129
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
132
130
servicetype = None, port = None, TXT = None,
133
131
domain = u"", host = u"", max_renames = 32768,
134
protocol = avahi.PROTO_UNSPEC, bus = None):
132
protocol = avahi.PROTO_UNSPEC):
135
133
self.interface = interface
137
135
self.type = servicetype
164
159
self.rename_count += 1
165
160
def remove(self):
166
161
"""Derived from the Avahi example code"""
167
if self.group is not None:
162
if group is not None:
170
165
"""Derived from the Avahi example code"""
171
if self.group is None:
172
self.group = dbus.Interface(
173
self.bus.get_object(avahi.DBUS_NAME,
174
self.server.EntryGroupNew()),
175
avahi.DBUS_INTERFACE_ENTRY_GROUP)
176
self.group.connect_to_signal('StateChanged',
177
self.entry_group_state_changed)
168
group = dbus.Interface(bus.get_object
170
server.EntryGroupNew()),
171
avahi.DBUS_INTERFACE_ENTRY_GROUP)
172
group.connect_to_signal('StateChanged',
173
entry_group_state_changed)
178
174
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
179
self.name, self.type)
180
self.group.AddService(
183
dbus.UInt32(0), # flags
184
self.name, self.type,
185
self.domain, self.host,
186
dbus.UInt16(self.port),
187
avahi.string_array_to_txt_array(self.TXT))
189
def entry_group_state_changed(self, state, error):
190
"""Derived from the Avahi example code"""
191
logger.debug(u"Avahi state change: %i", state)
193
if state == avahi.ENTRY_GROUP_ESTABLISHED:
194
logger.debug(u"Zeroconf service established.")
195
elif state == avahi.ENTRY_GROUP_COLLISION:
196
logger.warning(u"Zeroconf service name collision.")
198
elif state == avahi.ENTRY_GROUP_FAILURE:
199
logger.critical(u"Avahi: Error in group state changed %s",
201
raise AvahiGroupError(u"State changed: %s"
204
"""Derived from the Avahi example code"""
205
if self.group is not None:
208
def server_state_changed(self, state):
209
"""Derived from the Avahi example code"""
210
if state == avahi.SERVER_COLLISION:
211
logger.error(u"Zeroconf server name collision")
213
elif state == avahi.SERVER_RUNNING:
216
"""Derived from the Avahi example code"""
217
if self.server is None:
218
self.server = dbus.Interface(
219
self.bus.get_object(avahi.DBUS_NAME,
220
avahi.DBUS_PATH_SERVER),
221
avahi.DBUS_INTERFACE_SERVER)
222
self.server.connect_to_signal(u"StateChanged",
223
self.server_state_changed)
224
self.server_state_changed(self.server.GetState())
175
service.name, service.type)
177
self.interface, # interface
178
self.protocol, # protocol
179
dbus.UInt32(0), # flags
180
self.name, self.type,
181
self.domain, self.host,
182
dbus.UInt16(self.port),
183
avahi.string_array_to_txt_array(self.TXT))
186
# From the Avahi example code:
187
group = None # our entry group
188
# End of Avahi example code
191
def _datetime_to_dbus(dt, variant_level=0):
192
"""Convert a UTC datetime.datetime() to a D-Bus type."""
193
return dbus.String(dt.isoformat(), variant_level=variant_level)
227
196
class Client(object):
481
447
"""A Client class using D-Bus
484
dbus_object_path: dbus.ObjectPath
485
bus: dbus.SystemBus()
450
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
487
452
# dbus.service.Object doesn't use super(), so we can't either.
489
def __init__(self, bus = None, *args, **kwargs):
454
def __init__(self, *args, **kwargs):
491
455
Client.__init__(self, *args, **kwargs)
492
456
# Only now, when this client is initialized, can it show up on
494
458
self.dbus_object_path = (dbus.ObjectPath
496
460
+ self.name.replace(u".", u"_")))
497
dbus.service.Object.__init__(self, self.bus,
461
dbus.service.Object.__init__(self, bus,
498
462
self.dbus_object_path)
501
def _datetime_to_dbus(dt, variant_level=0):
502
"""Convert a UTC datetime.datetime() to a D-Bus type."""
503
return dbus.String(dt.isoformat(),
504
variant_level=variant_level)
506
463
def enable(self):
507
464
oldstate = getattr(self, u"enabled", False)
508
465
r = Client.enable(self)
510
467
# Emit D-Bus signals
511
468
self.PropertyChanged(dbus.String(u"enabled"),
512
469
dbus.Boolean(True, variant_level=1))
513
self.PropertyChanged(
514
dbus.String(u"last_enabled"),
515
self._datetime_to_dbus(self.last_enabled,
470
self.PropertyChanged(dbus.String(u"last_enabled"),
471
(_datetime_to_dbus(self.last_enabled,
519
475
def disable(self, signal = True):
623
579
dbus.String(u"host"):
624
580
dbus.String(self.host, variant_level=1),
625
581
dbus.String(u"created"):
626
self._datetime_to_dbus(self.created,
582
_datetime_to_dbus(self.created, variant_level=1),
628
583
dbus.String(u"last_enabled"):
629
(self._datetime_to_dbus(self.last_enabled,
584
(_datetime_to_dbus(self.last_enabled,
631
586
if self.last_enabled is not None
632
587
else dbus.Boolean(False, variant_level=1)),
633
588
dbus.String(u"enabled"):
634
589
dbus.Boolean(self.enabled, variant_level=1),
635
590
dbus.String(u"last_checked_ok"):
636
(self._datetime_to_dbus(self.last_checked_ok,
591
(_datetime_to_dbus(self.last_checked_ok,
638
593
if self.last_checked_ok is not None
639
594
else dbus.Boolean (False, variant_level=1)),
640
595
dbus.String(u"timeout"):
898
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
899
"""Like socketserver.ForkingMixIn, but also pass a pipe."""
853
class ForkingMixInWithPipe(SocketServer.ForkingMixIn, object):
854
"""Like SocketServer.ForkingMixIn, but also pass a pipe.
856
Assumes a gobject.MainLoop event loop.
900
858
def process_request(self, request, client_address):
901
859
"""Overrides and wraps the original process_request().
906
864
super(ForkingMixInWithPipe,
907
865
self).process_request(request, client_address)
908
866
os.close(self.pipe[1]) # close write end
909
self.add_pipe(self.pipe[0])
910
def add_pipe(self, pipe):
867
# Call "handle_ipc" for both data and EOF events
868
gobject.io_add_watch(self.pipe[0],
869
gobject.IO_IN | gobject.IO_HUP,
871
def handle_ipc(source, condition):
911
872
"""Dummy function; override as necessary"""
915
877
class IPv6_TCPServer(ForkingMixInWithPipe,
916
socketserver.TCPServer, object):
878
SocketServer.TCPServer, object):
917
879
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
920
882
enabled: Boolean; whether this server is activated yet
921
883
interface: None or a network interface name (string)
922
884
use_ipv6: Boolean; to use IPv6 or not
886
clients: Set() of Client objects
887
gnutls_priority GnuTLS priority string
888
use_dbus: Boolean; to emit D-Bus signals or not
924
890
def __init__(self, server_address, RequestHandlerClass,
925
interface=None, use_ipv6=True):
891
interface=None, use_ipv6=True, clients=None,
892
gnutls_priority=None, use_dbus=True):
926
894
self.interface = interface
928
896
self.address_family = socket.AF_INET6
929
socketserver.TCPServer.__init__(self, server_address,
897
self.clients = clients
898
self.use_dbus = use_dbus
899
self.gnutls_priority = gnutls_priority
900
SocketServer.TCPServer.__init__(self, server_address,
930
901
RequestHandlerClass)
931
902
def server_bind(self):
932
903
"""This overrides the normal server_bind() function
933
904
to bind to an interface if one was specified, and also NOT to
934
905
bind to an address or port if they were not specified."""
935
906
if self.interface is not None:
936
if SO_BINDTODEVICE is None:
937
logger.error(u"SO_BINDTODEVICE does not exist;"
938
u" cannot bind to interface %s",
942
self.socket.setsockopt(socket.SOL_SOCKET,
946
except socket.error, error:
947
if error[0] == errno.EPERM:
948
logger.error(u"No permission to"
949
u" bind to interface %s",
951
elif error[0] == errno.ENOPROTOOPT:
952
logger.error(u"SO_BINDTODEVICE not available;"
953
u" cannot bind to interface %s",
908
self.socket.setsockopt(socket.SOL_SOCKET,
910
str(self.interface + u'\0'))
911
except socket.error, error:
912
if error[0] == errno.EPERM:
913
logger.error(u"No permission to"
914
u" bind to interface %s",
957
918
# Only bind(2) the socket if we really need to.
958
919
if self.server_address[0] or self.server_address[1]:
959
920
if not self.server_address[0]:
974
935
# (self.interface))
975
return socketserver.TCPServer.server_bind(self)
978
class MandosServer(IPv6_TCPServer):
982
clients: set of Client objects
983
gnutls_priority GnuTLS priority string
984
use_dbus: Boolean; to emit D-Bus signals or not
985
clients: set of Client objects
986
gnutls_priority GnuTLS priority string
987
use_dbus: Boolean; to emit D-Bus signals or not
989
Assumes a gobject.MainLoop event loop.
991
def __init__(self, server_address, RequestHandlerClass,
992
interface=None, use_ipv6=True, clients=None,
993
gnutls_priority=None, use_dbus=True):
995
self.clients = clients
996
if self.clients is None:
998
self.use_dbus = use_dbus
999
self.gnutls_priority = gnutls_priority
1000
IPv6_TCPServer.__init__(self, server_address,
1001
RequestHandlerClass,
1002
interface = interface,
1003
use_ipv6 = use_ipv6)
936
return SocketServer.TCPServer.server_bind(self)
1004
937
def server_activate(self):
1005
938
if self.enabled:
1006
return socketserver.TCPServer.server_activate(self)
939
return SocketServer.TCPServer.server_activate(self)
1007
940
def enable(self):
1008
941
self.enabled = True
1009
def add_pipe(self, pipe):
1010
# Call "handle_ipc" for both data and EOF events
1011
gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1013
942
def handle_ipc(self, source, condition, file_objects={}):
1014
943
condition_names = {
1015
944
gobject.IO_IN: u"IN", # There is data to read.
1121
1050
return timevalue
1053
def server_state_changed(state):
1054
"""Derived from the Avahi example code"""
1055
if state == avahi.SERVER_COLLISION:
1056
logger.error(u"Zeroconf server name collision")
1058
elif state == avahi.SERVER_RUNNING:
1062
def entry_group_state_changed(state, error):
1063
"""Derived from the Avahi example code"""
1064
logger.debug(u"Avahi state change: %i", state)
1066
if state == avahi.ENTRY_GROUP_ESTABLISHED:
1067
logger.debug(u"Zeroconf service established.")
1068
elif state == avahi.ENTRY_GROUP_COLLISION:
1069
logger.warning(u"Zeroconf service name collision.")
1071
elif state == avahi.ENTRY_GROUP_FAILURE:
1072
logger.critical(u"Avahi: Error in group state changed %s",
1074
raise AvahiGroupError(u"State changed: %s" % unicode(error))
1124
1076
def if_nametoindex(interface):
1125
1077
"""Call the C function if_nametoindex(), or equivalent
1272
1224
u"checker": u"fping -q -- %%(host)s",
1275
client_config = configparser.SafeConfigParser(client_defaults)
1227
client_config = ConfigParser.SafeConfigParser(client_defaults)
1276
1228
client_config.read(os.path.join(server_settings[u"configdir"],
1277
1229
u"clients.conf"))
1279
1231
global mandos_dbus_service
1280
1232
mandos_dbus_service = None
1282
tcp_server = MandosServer((server_settings[u"address"],
1283
server_settings[u"port"]),
1285
interface=server_settings[u"interface"],
1288
server_settings[u"priority"],
1235
tcp_server = IPv6_TCPServer((server_settings[u"address"],
1236
server_settings[u"port"]),
1239
server_settings[u"interface"],
1243
server_settings[u"priority"],
1290
1245
pidfilename = u"/var/run/mandos.pid"
1292
1247
pidfile = open(pidfilename, u"w")
1327
1282
(gnutls.library.functions
1328
1283
.gnutls_global_set_log_function(debug_gnutls))
1286
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1287
service = AvahiService(name = server_settings[u"servicename"],
1288
servicetype = u"_mandos._tcp",
1289
protocol = protocol)
1290
if server_settings["interface"]:
1291
service.interface = (if_nametoindex
1292
(str(server_settings[u"interface"])))
1330
1294
global main_loop
1331
1297
# From the Avahi example code
1332
1298
DBusGMainLoop(set_as_default=True )
1333
1299
main_loop = gobject.MainLoop()
1334
1300
bus = dbus.SystemBus()
1301
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1302
avahi.DBUS_PATH_SERVER),
1303
avahi.DBUS_INTERFACE_SERVER)
1335
1304
# End of Avahi example code
1337
1306
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1338
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1339
service = AvahiService(name = server_settings[u"servicename"],
1340
servicetype = u"_mandos._tcp",
1341
protocol = protocol, bus = bus)
1342
if server_settings["interface"]:
1343
service.interface = (if_nametoindex
1344
(str(server_settings[u"interface"])))
1346
1308
client_class = Client
1348
client_class = functools.partial(ClientDBus, bus = bus)
1349
tcp_server.clients.update(set(
1310
client_class = ClientDBus
1350
1312
client_class(name = section,
1351
1313
config= dict(client_config.items(section)))
1352
1314
for section in client_config.sections()))
1353
if not tcp_server.clients:
1354
1316
logger.warning(u"No clients defined")
1429
1395
return dbus.Dictionary(
1430
1396
((c.dbus_object_path, c.GetAllProperties())
1431
for c in tcp_server.clients),
1432
1398
signature=u"oa{sv}")
1434
1400
@dbus.service.method(_interface, in_signature=u"o")
1435
1401
def RemoveClient(self, object_path):
1437
for c in tcp_server.clients:
1438
1404
if c.dbus_object_path == object_path:
1439
tcp_server.clients.remove(c)
1440
1406
c.remove_from_connection()
1441
1407
# Don't signal anything except ClientRemoved
1442
1408
c.disable(signal=False)