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"):
899
852
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
900
"""Like socketserver.ForkingMixIn, but also pass a pipe."""
853
"""Like socketserver.ForkingMixIn, but also pass a pipe.
855
Assumes a gobject.MainLoop event loop.
901
857
def process_request(self, request, client_address):
902
858
"""Overrides and wraps the original process_request().
904
This function creates a new pipe in self.pipe
860
This function creates a new pipe in self.pipe
906
862
self.pipe = os.pipe()
907
863
super(ForkingMixInWithPipe,
908
864
self).process_request(request, client_address)
909
865
os.close(self.pipe[1]) # close write end
910
self.add_pipe(self.pipe[0])
911
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):
912
871
"""Dummy function; override as necessary"""
916
876
class IPv6_TCPServer(ForkingMixInWithPipe,
921
881
enabled: Boolean; whether this server is activated yet
922
882
interface: None or a network interface name (string)
923
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
925
889
def __init__(self, server_address, RequestHandlerClass,
926
interface=None, use_ipv6=True):
890
interface=None, use_ipv6=True, clients=None,
891
gnutls_priority=None, use_dbus=True):
927
893
self.interface = interface
929
895
self.address_family = socket.AF_INET6
896
self.clients = clients
897
self.use_dbus = use_dbus
898
self.gnutls_priority = gnutls_priority
930
899
socketserver.TCPServer.__init__(self, server_address,
931
900
RequestHandlerClass)
932
901
def server_bind(self):
934
903
to bind to an interface if one was specified, and also NOT to
935
904
bind to an address or port if they were not specified."""
936
905
if self.interface is not None:
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",
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",
958
917
# Only bind(2) the socket if we really need to.
959
918
if self.server_address[0] or self.server_address[1]:
960
919
if not self.server_address[0]:
975
934
# (self.interface))
976
935
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)
1005
936
def server_activate(self):
1006
937
if self.enabled:
1007
938
return socketserver.TCPServer.server_activate(self)
1008
939
def enable(self):
1009
940
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,
1014
941
def handle_ipc(self, source, condition, file_objects={}):
1015
942
condition_names = {
1016
943
gobject.IO_IN: u"IN", # There is data to read.
1122
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))
1125
1075
def if_nametoindex(interface):
1126
1076
"""Call the C function if_nametoindex(), or equivalent
1280
1230
global mandos_dbus_service
1281
1231
mandos_dbus_service = None
1283
tcp_server = MandosServer((server_settings[u"address"],
1284
server_settings[u"port"]),
1286
interface=server_settings[u"interface"],
1289
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"],
1291
1244
pidfilename = u"/var/run/mandos.pid"
1293
1246
pidfile = open(pidfilename, u"w")
1328
1281
(gnutls.library.functions
1329
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"])))
1331
1293
global main_loop
1332
1296
# From the Avahi example code
1333
1297
DBusGMainLoop(set_as_default=True )
1334
1298
main_loop = gobject.MainLoop()
1335
1299
bus = dbus.SystemBus()
1300
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1301
avahi.DBUS_PATH_SERVER),
1302
avahi.DBUS_INTERFACE_SERVER)
1336
1303
# End of Avahi example code
1338
1305
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1339
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1340
service = AvahiService(name = server_settings[u"servicename"],
1341
servicetype = u"_mandos._tcp",
1342
protocol = protocol, bus = bus)
1343
if server_settings["interface"]:
1344
service.interface = (if_nametoindex
1345
(str(server_settings[u"interface"])))
1347
1307
client_class = Client
1349
client_class = functools.partial(ClientDBus, bus = bus)
1350
tcp_server.clients.update(set(
1309
client_class = ClientDBus
1351
1311
client_class(name = section,
1352
1312
config= dict(client_config.items(section)))
1353
1313
for section in client_config.sections()))
1354
if not tcp_server.clients:
1355
1315
logger.warning(u"No clients defined")
1430
1394
return dbus.Dictionary(
1431
1395
((c.dbus_object_path, c.GetAllProperties())
1432
for c in tcp_server.clients),
1433
1397
signature=u"oa{sv}")
1435
1399
@dbus.service.method(_interface, in_signature=u"o")
1436
1400
def RemoveClient(self, object_path):
1438
for c in tcp_server.clients:
1439
1403
if c.dbus_object_path == object_path:
1440
tcp_server.clients.remove(c)
1441
1405
c.remove_from_connection()
1442
1406
# Don't signal anything except ClientRemoved
1443
1407
c.disable(signal=False)