191
193
self.group.Commit()
192
194
def entry_group_state_changed(self, state, error):
193
195
"""Derived from the Avahi example code"""
194
logger.debug(u"Avahi state change: %i", state)
196
logger.debug(u"Avahi entry group state change: %i", state)
196
198
if state == avahi.ENTRY_GROUP_ESTABLISHED:
197
199
logger.debug(u"Zeroconf service established.")
243
246
last_checked_ok: datetime.datetime(); (UTC) or None
244
247
timeout: datetime.timedelta(); How long from last_checked_ok
245
until this client is invalid
248
until this client is disabled
246
249
interval: datetime.timedelta(); How often to start a new checker
247
250
disable_hook: If set, called by disable() as disable_hook(self)
248
251
checker: subprocess.Popen(); a running checker process used
396
398
# client would inevitably timeout, since no checker would get
397
399
# a chance to run to completion. If we instead leave running
398
400
# checkers alone, the checker would have to take more time
399
# than 'timeout' for the client to be declared invalid, which
400
# is as it should be.
401
# than 'timeout' for the client to be disabled, which is as it
402
404
# If a checker exists, make sure it is not a zombie
475
477
if error.errno != errno.ESRCH: # No such process
477
479
self.checker = None
479
def still_valid(self):
480
"""Has the timeout not yet passed for this client?"""
481
if not getattr(self, u"enabled", False):
483
now = datetime.datetime.utcnow()
484
if self.last_checked_ok is None:
485
return now < (self.created + self.timeout)
487
return now < (self.last_checked_ok + self.timeout)
490
482
def dbus_service_property(dbus_interface, signature=u"v",
499
491
dbus.service.method, except there is only "signature", since the
500
492
type from Get() and the type sent to Set() is the same.
494
# Encoding deeply encoded byte arrays is not supported yet by the
495
# "Set" method, so we fail early here:
496
if byte_arrays and signature != u"ay":
497
raise ValueError(u"Byte arrays not supported for non-'ay'"
498
u" signature %r" % signature)
502
499
def decorator(func):
503
500
func._dbus_is_property = True
504
501
func._dbus_interface = dbus_interface
781
782
dbus.Boolean(False, variant_level=1))
784
## D-Bus methods & signals
785
## D-Bus methods, signals & properties
785
786
_interface = u"se.bsnet.fukt.Mandos.Client"
788
@dbus.service.method(_interface)
790
return self.checked_ok()
792
790
# CheckerCompleted - signal
793
791
@dbus.service.signal(_interface, signature=u"nxs")
990
997
def handle(self):
991
998
logger.info(u"TCP connection from: %s",
992
999
unicode(self.client_address))
993
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
1000
logger.debug(u"IPC Pipe FD: %d",
1001
self.server.child_pipe[1].fileno())
994
1002
# Open IPC pipe to parent process
995
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
1003
with contextlib.nested(self.server.child_pipe[1],
1004
self.server.parent_pipe[0]
1005
) as (ipc, ipc_return):
996
1006
session = (gnutls.connection
997
1007
.ClientSession(self.request,
998
1008
gnutls.connection
999
1009
.X509Credentials()))
1001
line = self.request.makefile().readline()
1002
logger.debug(u"Protocol version: %r", line)
1004
if int(line.strip().split()[0]) > 1:
1006
except (ValueError, IndexError, RuntimeError), error:
1007
logger.error(u"Unknown protocol version: %s", error)
1010
1011
# Note: gnutls.connection.X509Credentials is really a
1011
1012
# generic GnuTLS certificate credentials object so long as
1012
1013
# no X.509 keys are added to it. Therefore, we can use it
1024
1025
.gnutls_priority_set_direct(session._c_object,
1025
1026
priority, None))
1028
# Start communication using the Mandos protocol
1029
# Get protocol number
1030
line = self.request.makefile().readline()
1031
logger.debug(u"Protocol version: %r", line)
1033
if int(line.strip().split()[0]) > 1:
1035
except (ValueError, IndexError, RuntimeError), error:
1036
logger.error(u"Unknown protocol version: %s", error)
1039
# Start GnuTLS connection
1028
1041
session.handshake()
1029
1042
except gnutls.errors.GNUTLSError, error:
1034
1047
logger.debug(u"Handshake succeeded")
1036
fpr = self.fingerprint(self.peer_certificate(session))
1037
except (TypeError, gnutls.errors.GNUTLSError), error:
1038
logger.warning(u"Bad certificate: %s", error)
1041
logger.debug(u"Fingerprint: %s", fpr)
1043
for c in self.server.clients:
1044
if c.fingerprint == fpr:
1048
ipc.write(u"NOTFOUND %s %s\n"
1049
% (fpr, unicode(self.client_address)))
1052
# Have to check if client.still_valid(), since it is
1053
# possible that the client timed out while establishing
1054
# the GnuTLS session.
1055
if not client.still_valid():
1056
ipc.write(u"INVALID %s\n" % client.name)
1059
ipc.write(u"SENDING %s\n" % client.name)
1061
while sent_size < len(client.secret):
1062
sent = session.send(client.secret[sent_size:])
1063
logger.debug(u"Sent: %d, remaining: %d",
1064
sent, len(client.secret)
1065
- (sent_size + sent))
1050
fpr = self.fingerprint(self.peer_certificate
1052
except (TypeError, gnutls.errors.GNUTLSError), error:
1053
logger.warning(u"Bad certificate: %s", error)
1055
logger.debug(u"Fingerprint: %s", fpr)
1057
for c in self.server.clients:
1058
if c.fingerprint == fpr:
1062
ipc.write(u"NOTFOUND %s %s\n"
1063
% (fpr, unicode(self.client_address)))
1066
class ClientProxy(object):
1067
"""Client proxy object. Not for calling methods."""
1068
def __init__(self, client):
1069
self.client = client
1070
def __getattr__(self, name):
1071
if name.startswith("ipc_"):
1073
ipc.write("%s %s\n" % (name[4:].upper(),
1076
if not hasattr(self.client, name):
1077
raise AttributeError
1078
ipc.write(u"GETATTR %s %s\n"
1079
% (name, self.client.fingerprint))
1080
return pickle.load(ipc_return)
1081
clientproxy = ClientProxy(client)
1082
# Have to check if client.enabled, since it is
1083
# possible that the client was disabled since the
1084
# GnuTLS session was established.
1085
if not clientproxy.enabled:
1086
clientproxy.ipc_disabled()
1089
clientproxy.ipc_sending()
1091
while sent_size < len(client.secret):
1092
sent = session.send(client.secret[sent_size:])
1093
logger.debug(u"Sent: %d, remaining: %d",
1094
sent, len(client.secret)
1095
- (sent_size + sent))
1070
1101
def peer_certificate(session):
1133
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
1134
"""Like socketserver.ForkingMixIn, but also pass a pipe."""
1164
class ForkingMixInWithPipes(socketserver.ForkingMixIn, object):
1165
"""Like socketserver.ForkingMixIn, but also pass a pipe pair."""
1135
1166
def process_request(self, request, client_address):
1136
1167
"""Overrides and wraps the original process_request().
1138
1169
This function creates a new pipe in self.pipe
1140
self.pipe = os.pipe()
1141
super(ForkingMixInWithPipe,
1171
# Child writes to child_pipe
1172
self.child_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0))
1173
# Parent writes to parent_pipe
1174
self.parent_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0))
1175
super(ForkingMixInWithPipes,
1142
1176
self).process_request(request, client_address)
1143
os.close(self.pipe[1]) # close write end
1144
self.add_pipe(self.pipe[0])
1145
def add_pipe(self, pipe):
1177
# Close unused ends for parent
1178
self.parent_pipe[0].close() # close read end
1179
self.child_pipe[1].close() # close write end
1180
self.add_pipe_fds(self.child_pipe[0], self.parent_pipe[1])
1181
def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
1146
1182
"""Dummy function; override as necessary"""
1150
class IPv6_TCPServer(ForkingMixInWithPipe,
1183
child_pipe_fd.close()
1184
parent_pipe_fd.close()
1187
class IPv6_TCPServer(ForkingMixInWithPipes,
1151
1188
socketserver.TCPServer, object):
1152
1189
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
1238
1275
return socketserver.TCPServer.server_activate(self)
1239
1276
def enable(self):
1240
1277
self.enabled = True
1241
def add_pipe(self, pipe):
1278
def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
1242
1279
# Call "handle_ipc" for both data and EOF events
1243
gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1245
def handle_ipc(self, source, condition, file_objects={}):
1280
gobject.io_add_watch(child_pipe_fd.fileno(),
1281
gobject.IO_IN | gobject.IO_HUP,
1282
functools.partial(self.handle_ipc,
1283
reply = parent_pipe_fd,
1284
sender= child_pipe_fd))
1285
def handle_ipc(self, source, condition, reply=None, sender=None):
1246
1286
condition_names = {
1247
1287
gobject.IO_IN: u"IN", # There is data to read.
1248
1288
gobject.IO_OUT: u"OUT", # Data can be written (without
1260
1300
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1261
1301
conditions_string)
1263
# Turn the pipe file descriptor into a Python file object
1264
if source not in file_objects:
1265
file_objects[source] = os.fdopen(source, u"r", 1)
1267
1303
# Read a line from the file object
1268
cmdline = file_objects[source].readline()
1304
cmdline = sender.readline()
1269
1305
if not cmdline: # Empty line means end of file
1270
# close the IPC pipe
1271
file_objects[source].close()
1272
del file_objects[source]
1306
# close the IPC pipes
1274
1310
# Stop calling this function
1280
1316
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1282
1318
if cmd == u"NOTFOUND":
1283
logger.warning(u"Client not found for fingerprint: %s",
1319
fpr, address = args.split(None, 1)
1320
logger.warning(u"Client not found for fingerprint: %s, ad"
1321
u"dress: %s", fpr, address)
1285
1322
if self.use_dbus:
1286
1323
# Emit D-Bus signal
1287
mandos_dbus_service.ClientNotFound(args)
1288
elif cmd == u"INVALID":
1324
mandos_dbus_service.ClientNotFound(fpr, address)
1325
elif cmd == u"DISABLED":
1289
1326
for client in self.clients:
1290
1327
if client.name == args:
1291
logger.warning(u"Client %s is invalid", args)
1328
logger.warning(u"Client %s is disabled", args)
1292
1329
if self.use_dbus:
1293
1330
# Emit D-Bus signal
1294
1331
client.Rejected()
1297
logger.error(u"Unknown client %s is invalid", args)
1334
logger.error(u"Unknown client %s is disabled", args)
1298
1335
elif cmd == u"SENDING":
1299
1336
for client in self.clients:
1300
1337
if client.name == args:
1308
1345
logger.error(u"Sending secret to unknown client %s",
1347
elif cmd == u"GETATTR":
1348
attr_name, fpr = args.split(None, 1)
1349
for client in self.clients:
1350
if client.fingerprint == fpr:
1351
attr_value = getattr(client, attr_name, None)
1352
logger.debug("IPC reply: %r", attr_value)
1353
pickle.dump(attr_value, reply)
1356
logger.error(u"Client %s on address %s requesting "
1357
u"attribute %s not found", fpr, address,
1359
pickle.dump(None, reply)
1311
1361
logger.error(u"Unknown IPC command: %r", cmdline)
1367
1417
def if_nametoindex(interface):
1368
1418
"Get an interface index the hard way, i.e. using fcntl()"
1369
1419
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1370
with closing(socket.socket()) as s:
1420
with contextlib.closing(socket.socket()) as s:
1371
1421
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1372
1422
struct.pack(str(u"16s16x"),
1630
1681
dbus.service.Object.__init__(self, bus, u"/")
1631
1682
_interface = u"se.bsnet.fukt.Mandos"
1633
@dbus.service.signal(_interface, signature=u"oa{sv}")
1634
def ClientAdded(self, objpath, properties):
1684
@dbus.service.signal(_interface, signature=u"o")
1685
def ClientAdded(self, objpath):
1638
@dbus.service.signal(_interface, signature=u"s")
1639
def ClientNotFound(self, fingerprint):
1689
@dbus.service.signal(_interface, signature=u"ss")
1690
def ClientNotFound(self, fingerprint, address):