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" and "remove" in the "AvahiService" class, the
10
# "server_state_changed" and "entry_group_state_changed" functions,
11
# and some lines in "main".
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".
13
13
# Everything else is
14
14
# Copyright © 2008,2009 Teddy Hogeborn
125
124
max_renames: integer; maximum number of renames
126
125
rename_count: integer; counter so we only rename after collisions
127
126
a sensible number of times
127
group: D-Bus Entry Group
129
bus: dbus.SystemBus()
129
131
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
130
132
servicetype = None, port = None, TXT = None,
131
133
domain = u"", host = u"", max_renames = 32768,
132
protocol = avahi.PROTO_UNSPEC):
134
protocol = avahi.PROTO_UNSPEC, bus = None):
133
135
self.interface = interface
135
137
self.type = servicetype
159
164
self.rename_count += 1
160
165
def remove(self):
161
166
"""Derived from the Avahi example code"""
162
if group is not None:
167
if self.group is not None:
165
170
"""Derived from the Avahi example code"""
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)
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)
174
178
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
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)
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())
196
227
class Client(object):
447
481
"""A Client class using D-Bus
450
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
484
dbus_object_path: dbus.ObjectPath
485
bus: dbus.SystemBus()
452
487
# dbus.service.Object doesn't use super(), so we can't either.
454
def __init__(self, *args, **kwargs):
489
def __init__(self, bus = None, *args, **kwargs):
455
491
Client.__init__(self, *args, **kwargs)
456
492
# Only now, when this client is initialized, can it show up on
458
494
self.dbus_object_path = (dbus.ObjectPath
460
496
+ self.name.replace(u".", u"_")))
461
dbus.service.Object.__init__(self, bus,
497
dbus.service.Object.__init__(self, self.bus,
462
498
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)
463
506
def enable(self):
464
507
oldstate = getattr(self, u"enabled", False)
465
508
r = Client.enable(self)
579
623
dbus.String(u"host"):
580
624
dbus.String(self.host, variant_level=1),
581
625
dbus.String(u"created"):
582
_datetime_to_dbus(self.created, variant_level=1),
626
self._datetime_to_dbus(self.created,
583
628
dbus.String(u"last_enabled"):
584
(_datetime_to_dbus(self.last_enabled,
629
(self._datetime_to_dbus(self.last_enabled,
586
631
if self.last_enabled is not None
587
632
else dbus.Boolean(False, variant_level=1)),
588
633
dbus.String(u"enabled"):
589
634
dbus.Boolean(self.enabled, variant_level=1),
590
635
dbus.String(u"last_checked_ok"):
591
(_datetime_to_dbus(self.last_checked_ok,
636
(self._datetime_to_dbus(self.last_checked_ok,
593
638
if self.last_checked_ok is not None
594
639
else dbus.Boolean (False, variant_level=1)),
595
640
dbus.String(u"timeout"):
853
class ForkingMixInWithPipe(SocketServer.ForkingMixIn, object):
854
"""Like SocketServer.ForkingMixIn, but also pass a pipe.
856
Assumes a gobject.MainLoop event loop.
899
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
900
"""Like socketserver.ForkingMixIn, but also pass a pipe."""
858
901
def process_request(self, request, client_address):
859
902
"""Overrides and wraps the original process_request().
861
This function creates a new pipe in self.pipe
904
This function creates a new pipe in self.pipe
863
906
self.pipe = os.pipe()
864
907
super(ForkingMixInWithPipe,
865
908
self).process_request(request, client_address)
866
909
os.close(self.pipe[1]) # close write end
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):
910
self.add_pipe(self.pipe[0])
911
def add_pipe(self, pipe):
872
912
"""Dummy function; override as necessary"""
877
916
class IPv6_TCPServer(ForkingMixInWithPipe,
878
SocketServer.TCPServer, object):
917
socketserver.TCPServer, object):
879
918
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
882
921
enabled: Boolean; whether this server is activated yet
883
922
interface: None or a network interface name (string)
884
923
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
890
925
def __init__(self, server_address, RequestHandlerClass,
891
interface=None, use_ipv6=True, clients=None,
892
gnutls_priority=None, use_dbus=True):
926
interface=None, use_ipv6=True):
894
927
self.interface = interface
896
929
self.address_family = socket.AF_INET6
897
self.clients = clients
898
self.use_dbus = use_dbus
899
self.gnutls_priority = gnutls_priority
900
SocketServer.TCPServer.__init__(self, server_address,
930
socketserver.TCPServer.__init__(self, server_address,
901
931
RequestHandlerClass)
902
932
def server_bind(self):
903
933
"""This overrides the normal server_bind() function
904
934
to bind to an interface if one was specified, and also NOT to
905
935
bind to an address or port if they were not specified."""
906
936
if self.interface is not None:
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",
937
if SO_BINDTODEVICE is None:
938
logger.error(u"SO_BINDTODEVICE does not exist;"
939
u" cannot bind to interface %s",
943
self.socket.setsockopt(socket.SOL_SOCKET,
947
except socket.error, error:
948
if error[0] == errno.EPERM:
949
logger.error(u"No permission to"
950
u" bind to interface %s",
952
elif error[0] == errno.ENOPROTOOPT:
953
logger.error(u"SO_BINDTODEVICE not available;"
954
u" cannot bind to interface %s",
918
958
# Only bind(2) the socket if we really need to.
919
959
if self.server_address[0] or self.server_address[1]:
920
960
if not self.server_address[0]:
935
975
# (self.interface))
936
return SocketServer.TCPServer.server_bind(self)
976
return socketserver.TCPServer.server_bind(self)
979
class MandosServer(IPv6_TCPServer):
983
clients: set of Client objects
984
gnutls_priority GnuTLS priority string
985
use_dbus: Boolean; to emit D-Bus signals or not
986
clients: set of Client objects
987
gnutls_priority GnuTLS priority string
988
use_dbus: Boolean; to emit D-Bus signals or not
990
Assumes a gobject.MainLoop event loop.
992
def __init__(self, server_address, RequestHandlerClass,
993
interface=None, use_ipv6=True, clients=None,
994
gnutls_priority=None, use_dbus=True):
996
self.clients = clients
997
if self.clients is None:
999
self.use_dbus = use_dbus
1000
self.gnutls_priority = gnutls_priority
1001
IPv6_TCPServer.__init__(self, server_address,
1002
RequestHandlerClass,
1003
interface = interface,
1004
use_ipv6 = use_ipv6)
937
1005
def server_activate(self):
938
1006
if self.enabled:
939
return SocketServer.TCPServer.server_activate(self)
1007
return socketserver.TCPServer.server_activate(self)
940
1008
def enable(self):
941
1009
self.enabled = True
1010
def add_pipe(self, pipe):
1011
# Call "handle_ipc" for both data and EOF events
1012
gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
942
1014
def handle_ipc(self, source, condition, file_objects={}):
943
1015
condition_names = {
944
1016
gobject.IO_IN: u"IN", # There is data to read.
1050
1122
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))
1076
1125
def if_nametoindex(interface):
1077
1126
"""Call the C function if_nametoindex(), or equivalent
1224
1274
u"checker": u"fping -q -- %%(host)s",
1227
client_config = ConfigParser.SafeConfigParser(client_defaults)
1277
client_config = configparser.SafeConfigParser(client_defaults)
1228
1278
client_config.read(os.path.join(server_settings[u"configdir"],
1229
1279
u"clients.conf"))
1231
1281
global mandos_dbus_service
1232
1282
mandos_dbus_service = None
1235
tcp_server = IPv6_TCPServer((server_settings[u"address"],
1236
server_settings[u"port"]),
1239
server_settings[u"interface"],
1243
server_settings[u"priority"],
1284
tcp_server = MandosServer((server_settings[u"address"],
1285
server_settings[u"port"]),
1287
interface=server_settings[u"interface"],
1290
server_settings[u"priority"],
1245
1292
pidfilename = u"/var/run/mandos.pid"
1247
1294
pidfile = open(pidfilename, u"w")
1282
1329
(gnutls.library.functions
1283
1330
.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"])))
1294
1332
global main_loop
1297
1333
# From the Avahi example code
1298
1334
DBusGMainLoop(set_as_default=True )
1299
1335
main_loop = gobject.MainLoop()
1300
1336
bus = dbus.SystemBus()
1301
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1302
avahi.DBUS_PATH_SERVER),
1303
avahi.DBUS_INTERFACE_SERVER)
1304
1337
# End of Avahi example code
1306
1339
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1340
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1341
service = AvahiService(name = server_settings[u"servicename"],
1342
servicetype = u"_mandos._tcp",
1343
protocol = protocol, bus = bus)
1344
if server_settings["interface"]:
1345
service.interface = (if_nametoindex
1346
(str(server_settings[u"interface"])))
1308
1348
client_class = Client
1310
client_class = ClientDBus
1350
client_class = functools.partial(ClientDBus, bus = bus)
1351
tcp_server.clients.update(set(
1312
1352
client_class(name = section,
1313
1353
config= dict(client_config.items(section)))
1314
1354
for section in client_config.sections()))
1355
if not tcp_server.clients:
1316
1356
logger.warning(u"No clients defined")