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
124
max_renames: integer; maximum number of renames
125
125
rename_count: integer; counter so we only rename after collisions
126
126
a sensible number of times
127
group: D-Bus Entry Group
129
bus: dbus.SystemBus()
131
128
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
132
129
servicetype = None, port = None, TXT = None,
133
130
domain = u"", host = u"", max_renames = 32768,
134
protocol = avahi.PROTO_UNSPEC, bus = None):
131
protocol = avahi.PROTO_UNSPEC):
135
132
self.interface = interface
137
134
self.type = servicetype
152
146
u" after %i retries, exiting.",
153
147
self.rename_count)
154
148
raise AvahiServiceError(u"Too many renames")
155
self.name = self.server.GetAlternativeServiceName(self.name)
149
self.name = server.GetAlternativeServiceName(self.name)
156
150
logger.info(u"Changing Zeroconf service name to %r ...",
158
152
syslogger.setFormatter(logging.Formatter
159
153
(u'Mandos (%s) [%%(process)d]:'
160
154
u' %%(levelname)s: %%(message)s'
164
158
self.rename_count += 1
165
159
def remove(self):
166
160
"""Derived from the Avahi example code"""
167
if self.group is not None:
161
if group is not None:
170
164
"""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)
167
group = dbus.Interface(bus.get_object
169
server.EntryGroupNew()),
170
avahi.DBUS_INTERFACE_ENTRY_GROUP)
171
group.connect_to_signal('StateChanged',
172
entry_group_state_changed)
178
173
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())
174
service.name, service.type)
176
self.interface, # interface
177
self.protocol, # protocol
178
dbus.UInt32(0), # flags
179
self.name, self.type,
180
self.domain, self.host,
181
dbus.UInt16(self.port),
182
avahi.string_array_to_txt_array(self.TXT))
185
# From the Avahi example code:
186
group = None # our entry group
187
# End of Avahi example code
190
def _datetime_to_dbus(dt, variant_level=0):
191
"""Convert a UTC datetime.datetime() to a D-Bus type."""
192
return dbus.String(dt.isoformat(), variant_level=variant_level)
227
195
class Client(object):
481
446
"""A Client class using D-Bus
484
dbus_object_path: dbus.ObjectPath
485
bus: dbus.SystemBus()
449
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
487
451
# dbus.service.Object doesn't use super(), so we can't either.
489
def __init__(self, bus = None, *args, **kwargs):
453
def __init__(self, *args, **kwargs):
491
454
Client.__init__(self, *args, **kwargs)
492
455
# Only now, when this client is initialized, can it show up on
494
457
self.dbus_object_path = (dbus.ObjectPath
496
459
+ self.name.replace(u".", u"_")))
497
dbus.service.Object.__init__(self, self.bus,
460
dbus.service.Object.__init__(self, bus,
498
461
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
462
def enable(self):
507
463
oldstate = getattr(self, u"enabled", False)
508
464
r = Client.enable(self)
510
466
# Emit D-Bus signals
511
467
self.PropertyChanged(dbus.String(u"enabled"),
512
468
dbus.Boolean(True, variant_level=1))
513
self.PropertyChanged(
514
dbus.String(u"last_enabled"),
515
self._datetime_to_dbus(self.last_enabled,
469
self.PropertyChanged(dbus.String(u"last_enabled"),
470
(_datetime_to_dbus(self.last_enabled,
519
474
def disable(self, signal = True):
623
578
dbus.String(u"host"):
624
579
dbus.String(self.host, variant_level=1),
625
580
dbus.String(u"created"):
626
self._datetime_to_dbus(self.created,
581
_datetime_to_dbus(self.created, variant_level=1),
628
582
dbus.String(u"last_enabled"):
629
(self._datetime_to_dbus(self.last_enabled,
583
(_datetime_to_dbus(self.last_enabled,
631
585
if self.last_enabled is not None
632
586
else dbus.Boolean(False, variant_level=1)),
633
587
dbus.String(u"enabled"):
634
588
dbus.Boolean(self.enabled, variant_level=1),
635
589
dbus.String(u"last_checked_ok"):
636
(self._datetime_to_dbus(self.last_checked_ok,
590
(_datetime_to_dbus(self.last_checked_ok,
638
592
if self.last_checked_ok is not None
639
593
else dbus.Boolean (False, variant_level=1)),
640
594
dbus.String(u"timeout"):
898
852
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
899
"""Like socketserver.ForkingMixIn, but also pass a pipe."""
853
"""Like socketserver.ForkingMixIn, but also pass a pipe.
855
Assumes a gobject.MainLoop event loop.
900
857
def process_request(self, request, client_address):
901
858
"""Overrides and wraps the original process_request().
906
863
super(ForkingMixInWithPipe,
907
864
self).process_request(request, client_address)
908
865
os.close(self.pipe[1]) # close write end
909
self.add_pipe(self.pipe[0])
910
def add_pipe(self, pipe):
866
# Call "handle_ipc" for both data and EOF events
867
gobject.io_add_watch(self.pipe[0],
868
gobject.IO_IN | gobject.IO_HUP,
870
def handle_ipc(source, condition):
911
871
"""Dummy function; override as necessary"""
915
876
class IPv6_TCPServer(ForkingMixInWithPipe,
920
881
enabled: Boolean; whether this server is activated yet
921
882
interface: None or a network interface name (string)
922
883
use_ipv6: Boolean; to use IPv6 or not
885
clients: set of Client objects
886
gnutls_priority GnuTLS priority string
887
use_dbus: Boolean; to emit D-Bus signals or not
924
889
def __init__(self, server_address, RequestHandlerClass,
925
interface=None, use_ipv6=True):
890
interface=None, use_ipv6=True, clients=None,
891
gnutls_priority=None, use_dbus=True):
926
893
self.interface = interface
928
895
self.address_family = socket.AF_INET6
896
self.clients = clients
897
self.use_dbus = use_dbus
898
self.gnutls_priority = gnutls_priority
929
899
socketserver.TCPServer.__init__(self, server_address,
930
900
RequestHandlerClass)
931
901
def server_bind(self):
933
903
to bind to an interface if one was specified, and also NOT to
934
904
bind to an address or port if they were not specified."""
935
905
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",
907
self.socket.setsockopt(socket.SOL_SOCKET,
909
str(self.interface + u'\0'))
910
except socket.error, error:
911
if error[0] == errno.EPERM:
912
logger.error(u"No permission to"
913
u" bind to interface %s",
957
917
# Only bind(2) the socket if we really need to.
958
918
if self.server_address[0] or self.server_address[1]:
959
919
if not self.server_address[0]:
974
934
# (self.interface))
975
935
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)
1004
936
def server_activate(self):
1005
937
if self.enabled:
1006
938
return socketserver.TCPServer.server_activate(self)
1007
939
def enable(self):
1008
940
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
941
def handle_ipc(self, source, condition, file_objects={}):
1014
942
condition_names = {
1015
943
gobject.IO_IN: u"IN", # There is data to read.
1121
1049
return timevalue
1052
def server_state_changed(state):
1053
"""Derived from the Avahi example code"""
1054
if state == avahi.SERVER_COLLISION:
1055
logger.error(u"Zeroconf server name collision")
1057
elif state == avahi.SERVER_RUNNING:
1061
def entry_group_state_changed(state, error):
1062
"""Derived from the Avahi example code"""
1063
logger.debug(u"Avahi state change: %i", state)
1065
if state == avahi.ENTRY_GROUP_ESTABLISHED:
1066
logger.debug(u"Zeroconf service established.")
1067
elif state == avahi.ENTRY_GROUP_COLLISION:
1068
logger.warning(u"Zeroconf service name collision.")
1070
elif state == avahi.ENTRY_GROUP_FAILURE:
1071
logger.critical(u"Avahi: Error in group state changed %s",
1073
raise AvahiGroupError(u"State changed: %s" % unicode(error))
1124
1075
def if_nametoindex(interface):
1125
1076
"""Call the C function if_nametoindex(), or equivalent
1279
1230
global mandos_dbus_service
1280
1231
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"],
1234
tcp_server = IPv6_TCPServer((server_settings[u"address"],
1235
server_settings[u"port"]),
1238
server_settings[u"interface"],
1242
server_settings[u"priority"],
1290
1244
pidfilename = u"/var/run/mandos.pid"
1292
1246
pidfile = open(pidfilename, u"w")
1327
1281
(gnutls.library.functions
1328
1282
.gnutls_global_set_log_function(debug_gnutls))
1285
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1286
service = AvahiService(name = server_settings[u"servicename"],
1287
servicetype = u"_mandos._tcp",
1288
protocol = protocol)
1289
if server_settings["interface"]:
1290
service.interface = (if_nametoindex
1291
(str(server_settings[u"interface"])))
1330
1293
global main_loop
1331
1296
# From the Avahi example code
1332
1297
DBusGMainLoop(set_as_default=True )
1333
1298
main_loop = gobject.MainLoop()
1334
1299
bus = dbus.SystemBus()
1300
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1301
avahi.DBUS_PATH_SERVER),
1302
avahi.DBUS_INTERFACE_SERVER)
1335
1303
# End of Avahi example code
1337
1305
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
1307
client_class = Client
1348
client_class = functools.partial(ClientDBus, bus = bus)
1349
tcp_server.clients.update(set(
1309
client_class = ClientDBus
1350
1311
client_class(name = section,
1351
1312
config= dict(client_config.items(section)))
1352
1313
for section in client_config.sections()))
1353
if not tcp_server.clients:
1354
1315
logger.warning(u"No clients defined")
1429
1394
return dbus.Dictionary(
1430
1395
((c.dbus_object_path, c.GetAllProperties())
1431
for c in tcp_server.clients),
1432
1397
signature=u"oa{sv}")
1434
1399
@dbus.service.method(_interface, in_signature=u"o")
1435
1400
def RemoveClient(self, object_path):
1437
for c in tcp_server.clients:
1438
1403
if c.dbus_object_path == object_path:
1439
tcp_server.clients.remove(c)
1440
1405
c.remove_from_connection()
1441
1406
# Don't signal anything except ClientRemoved
1442
1407
c.disable(signal=False)