243
245
last_checked_ok: datetime.datetime(); (UTC) or None
244
246
timeout: datetime.timedelta(); How long from last_checked_ok
245
until this client is invalid
247
until this client is disabled
246
248
interval: datetime.timedelta(); How often to start a new checker
247
249
disable_hook: If set, called by disable() as disable_hook(self)
248
250
checker: subprocess.Popen(); a running checker process used
396
397
# client would inevitably timeout, since no checker would get
397
398
# a chance to run to completion. If we instead leave running
398
399
# 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.
400
# than 'timeout' for the client to be disabled, which is as it
402
403
# If a checker exists, make sure it is not a zombie
475
476
if error.errno != errno.ESRCH: # No such process
477
478
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
481
def dbus_service_property(dbus_interface, signature=u"v",
499
490
dbus.service.method, except there is only "signature", since the
500
491
type from Get() and the type sent to Set() is the same.
493
# Encoding deeply encoded byte arrays is not supported yet by the
494
# "Set" method, so we fail early here:
495
if byte_arrays and signature != u"ay":
496
raise ValueError(u"Byte arrays not supported for non-'ay'"
497
u" signature %r" % signature)
502
498
def decorator(func):
503
499
func._dbus_is_property = True
504
500
func._dbus_interface = dbus_interface
781
781
dbus.Boolean(False, variant_level=1))
784
## D-Bus methods & signals
784
## D-Bus methods, signals & properties
785
785
_interface = u"se.bsnet.fukt.Mandos.Client"
788
@dbus.service.method(_interface)
790
return self.checked_ok()
792
789
# CheckerCompleted - signal
793
790
@dbus.service.signal(_interface, signature=u"nxs")
990
996
def handle(self):
991
997
logger.info(u"TCP connection from: %s",
992
998
unicode(self.client_address))
993
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
999
logger.debug(u"IPC Pipe FD: %d",
1000
self.server.child_pipe[1].fileno())
994
1001
# Open IPC pipe to parent process
995
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
1002
with contextlib.nested(self.server.child_pipe[1],
1003
self.server.parent_pipe[0]
1004
) as (ipc, ipc_return):
996
1005
session = (gnutls.connection
997
1006
.ClientSession(self.request,
998
1007
gnutls.connection
999
1008
.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
1010
# Note: gnutls.connection.X509Credentials is really a
1011
1011
# generic GnuTLS certificate credentials object so long as
1012
1012
# no X.509 keys are added to it. Therefore, we can use it
1024
1024
.gnutls_priority_set_direct(session._c_object,
1025
1025
priority, None))
1027
# Start communication using the Mandos protocol
1028
# Get protocol number
1029
line = self.request.makefile().readline()
1030
logger.debug(u"Protocol version: %r", line)
1032
if int(line.strip().split()[0]) > 1:
1034
except (ValueError, IndexError, RuntimeError), error:
1035
logger.error(u"Unknown protocol version: %s", error)
1038
# Start GnuTLS connection
1028
1040
session.handshake()
1029
1041
except gnutls.errors.GNUTLSError, error:
1034
1046
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))
1049
fpr = self.fingerprint(self.peer_certificate
1051
except (TypeError, gnutls.errors.GNUTLSError), error:
1052
logger.warning(u"Bad certificate: %s", error)
1054
logger.debug(u"Fingerprint: %s", fpr)
1056
for c in self.server.clients:
1057
if c.fingerprint == fpr:
1061
ipc.write(u"NOTFOUND %s %s\n"
1062
% (fpr, unicode(self.client_address)))
1065
class ClientProxy(object):
1066
"""Client proxy object. Not for calling methods."""
1067
def __init__(self, client):
1068
self.client = client
1069
def __getattr__(self, name):
1070
if name.startswith("ipc_"):
1072
ipc.write("%s %s\n" % (name[4:].upper(),
1075
if not hasattr(self.client, name):
1076
raise AttributeError
1077
ipc.write(u"GETATTR %s %s\n"
1078
% (name, self.client.fingerprint))
1079
return pickle.load(ipc_return)
1080
clientproxy = ClientProxy(client)
1081
# Have to check if client.enabled, since it is
1082
# possible that the client was disabled since the
1083
# GnuTLS session was established.
1084
if not clientproxy.enabled:
1085
clientproxy.ipc_disabled()
1088
clientproxy.ipc_sending()
1090
while sent_size < len(client.secret):
1091
sent = session.send(client.secret[sent_size:])
1092
logger.debug(u"Sent: %d, remaining: %d",
1093
sent, len(client.secret)
1094
- (sent_size + sent))
1070
1100
def peer_certificate(session):
1133
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
1134
"""Like socketserver.ForkingMixIn, but also pass a pipe."""
1163
class ForkingMixInWithPipes(socketserver.ForkingMixIn, object):
1164
"""Like socketserver.ForkingMixIn, but also pass a pipe pair."""
1135
1165
def process_request(self, request, client_address):
1136
1166
"""Overrides and wraps the original process_request().
1138
1168
This function creates a new pipe in self.pipe
1140
self.pipe = os.pipe()
1141
super(ForkingMixInWithPipe,
1170
# Child writes to child_pipe
1171
self.child_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0))
1172
# Parent writes to parent_pipe
1173
self.parent_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0))
1174
super(ForkingMixInWithPipes,
1142
1175
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):
1176
# Close unused ends for parent
1177
self.parent_pipe[0].close() # close read end
1178
self.child_pipe[1].close() # close write end
1179
self.add_pipe_fds(self.child_pipe[0], self.parent_pipe[1])
1180
def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
1146
1181
"""Dummy function; override as necessary"""
1150
class IPv6_TCPServer(ForkingMixInWithPipe,
1182
child_pipe_fd.close()
1183
parent_pipe_fd.close()
1186
class IPv6_TCPServer(ForkingMixInWithPipes,
1151
1187
socketserver.TCPServer, object):
1152
1188
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
1238
1274
return socketserver.TCPServer.server_activate(self)
1239
1275
def enable(self):
1240
1276
self.enabled = True
1241
def add_pipe(self, pipe):
1277
def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
1242
1278
# 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={}):
1279
gobject.io_add_watch(child_pipe_fd.fileno(),
1280
gobject.IO_IN | gobject.IO_HUP,
1281
functools.partial(self.handle_ipc,
1282
reply = parent_pipe_fd,
1283
sender= child_pipe_fd))
1284
def handle_ipc(self, source, condition, reply=None, sender=None):
1246
1285
condition_names = {
1247
1286
gobject.IO_IN: u"IN", # There is data to read.
1248
1287
gobject.IO_OUT: u"OUT", # Data can be written (without
1260
1299
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1261
1300
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
1302
# Read a line from the file object
1268
cmdline = file_objects[source].readline()
1303
cmdline = sender.readline()
1269
1304
if not cmdline: # Empty line means end of file
1270
# close the IPC pipe
1271
file_objects[source].close()
1272
del file_objects[source]
1305
# close the IPC pipes
1274
1309
# Stop calling this function
1280
1315
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1282
1317
if cmd == u"NOTFOUND":
1283
logger.warning(u"Client not found for fingerprint: %s",
1318
fpr, address = args.split(None, 1)
1319
logger.warning(u"Client not found for fingerprint: %s, ad"
1320
u"dress: %s", fpr, address)
1285
1321
if self.use_dbus:
1286
1322
# Emit D-Bus signal
1287
mandos_dbus_service.ClientNotFound(args)
1288
elif cmd == u"INVALID":
1323
mandos_dbus_service.ClientNotFound(fpr, address)
1324
elif cmd == u"DISABLED":
1289
1325
for client in self.clients:
1290
1326
if client.name == args:
1291
logger.warning(u"Client %s is invalid", args)
1327
logger.warning(u"Client %s is disabled", args)
1292
1328
if self.use_dbus:
1293
1329
# Emit D-Bus signal
1294
1330
client.Rejected()
1297
logger.error(u"Unknown client %s is invalid", args)
1333
logger.error(u"Unknown client %s is disabled", args)
1298
1334
elif cmd == u"SENDING":
1299
1335
for client in self.clients:
1300
1336
if client.name == args:
1308
1344
logger.error(u"Sending secret to unknown client %s",
1346
elif cmd == u"GETATTR":
1347
attr_name, fpr = args.split(None, 1)
1348
for client in self.clients:
1349
if client.fingerprint == fpr:
1350
attr_value = getattr(client, attr_name, None)
1351
logger.debug("IPC reply: %r", attr_value)
1352
pickle.dump(attr_value, reply)
1355
logger.error(u"Client %s on address %s requesting "
1356
u"attribute %s not found", fpr, address,
1358
pickle.dump(None, reply)
1311
1360
logger.error(u"Unknown IPC command: %r", cmdline)
1367
1416
def if_nametoindex(interface):
1368
1417
"Get an interface index the hard way, i.e. using fcntl()"
1369
1418
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1370
with closing(socket.socket()) as s:
1419
with contextlib.closing(socket.socket()) as s:
1371
1420
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1372
1421
struct.pack(str(u"16s16x"),
1630
1679
dbus.service.Object.__init__(self, bus, u"/")
1631
1680
_interface = u"se.bsnet.fukt.Mandos"
1633
@dbus.service.signal(_interface, signature=u"oa{sv}")
1634
def ClientAdded(self, objpath, properties):
1682
@dbus.service.signal(_interface, signature=u"o")
1683
def ClientAdded(self, objpath):
1638
@dbus.service.signal(_interface, signature=u"s")
1639
def ClientNotFound(self, fingerprint):
1687
@dbus.service.signal(_interface, signature=u"ss")
1688
def ClientNotFound(self, fingerprint, address):