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
325
326
self.checker_initiator_tag = (gobject.timeout_add
326
327
(self.interval_milliseconds(),
327
328
self.start_checker))
328
# Also start a new checker *right now*.
330
329
# Schedule a disable() when 'timeout' has passed
331
330
self.disable_initiator_tag = (gobject.timeout_add
332
331
(self.timeout_milliseconds(),
334
333
self.enabled = True
334
# Also start a new checker *right now*.
337
def disable(self, quiet=True):
337
338
"""Disable this client."""
338
339
if not getattr(self, "enabled", False):
340
logger.info(u"Disabling client %s", self.name)
342
logger.info(u"Disabling client %s", self.name)
341
343
if getattr(self, u"disable_initiator_tag", False):
342
344
gobject.source_remove(self.disable_initiator_tag)
343
345
self.disable_initiator_tag = None
395
397
# client would inevitably timeout, since no checker would get
396
398
# a chance to run to completion. If we instead leave running
397
399
# checkers alone, the checker would have to take more time
398
# than 'timeout' for the client to be declared invalid, which
399
# is as it should be.
400
# than 'timeout' for the client to be disabled, which is as it
401
403
# If a checker exists, make sure it is not a zombie
467
469
logger.debug(u"Stopping checker for %(name)s", vars(self))
469
471
os.kill(self.checker.pid, signal.SIGTERM)
471
473
#if self.checker.poll() is None:
472
474
# os.kill(self.checker.pid, signal.SIGKILL)
473
475
except OSError, error:
474
476
if error.errno != errno.ESRCH: # No such process
476
478
self.checker = None
478
def still_valid(self):
479
"""Has the timeout not yet passed for this client?"""
480
if not getattr(self, u"enabled", False):
482
now = datetime.datetime.utcnow()
483
if self.last_checked_ok is None:
484
return now < (self.created + self.timeout)
486
return now < (self.last_checked_ok + self.timeout)
489
481
def dbus_service_property(dbus_interface, signature=u"v",
498
490
dbus.service.method, except there is only "signature", since the
499
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)
501
498
def decorator(func):
502
499
func._dbus_is_property = True
503
500
func._dbus_interface = dbus_interface
705
706
variant_level=1))
708
def disable(self, signal = True):
709
def disable(self, quiet = False):
709
710
oldstate = getattr(self, u"enabled", False)
710
r = Client.disable(self)
711
if signal and oldstate != self.enabled:
711
r = Client.disable(self, quiet=quiet)
712
if not quiet and oldstate != self.enabled:
712
713
# Emit D-Bus signal
713
714
self.PropertyChanged(dbus.String(u"enabled"),
714
715
dbus.Boolean(False, variant_level=1))
780
781
dbus.Boolean(False, variant_level=1))
783
## D-Bus methods & signals
784
## D-Bus methods, signals & properties
784
785
_interface = u"se.bsnet.fukt.Mandos.Client"
787
@dbus.service.method(_interface)
789
return self.checked_ok()
791
789
# CheckerCompleted - signal
792
790
@dbus.service.signal(_interface, signature=u"nxs")
989
996
def handle(self):
990
997
logger.info(u"TCP connection from: %s",
991
998
unicode(self.client_address))
992
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())
993
1001
# Open IPC pipe to parent process
994
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):
995
1005
session = (gnutls.connection
996
1006
.ClientSession(self.request,
997
1007
gnutls.connection
998
1008
.X509Credentials()))
1000
line = self.request.makefile().readline()
1001
logger.debug(u"Protocol version: %r", line)
1003
if int(line.strip().split()[0]) > 1:
1005
except (ValueError, IndexError, RuntimeError), error:
1006
logger.error(u"Unknown protocol version: %s", error)
1009
1010
# Note: gnutls.connection.X509Credentials is really a
1010
1011
# generic GnuTLS certificate credentials object so long as
1011
1012
# no X.509 keys are added to it. Therefore, we can use it
1023
1024
.gnutls_priority_set_direct(session._c_object,
1024
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
1027
1040
session.handshake()
1028
1041
except gnutls.errors.GNUTLSError, error:
1033
1046
logger.debug(u"Handshake succeeded")
1035
fpr = self.fingerprint(self.peer_certificate(session))
1036
except (TypeError, gnutls.errors.GNUTLSError), error:
1037
logger.warning(u"Bad certificate: %s", error)
1040
logger.debug(u"Fingerprint: %s", fpr)
1042
for c in self.server.clients:
1043
if c.fingerprint == fpr:
1047
ipc.write(u"NOTFOUND %s %s\n"
1048
% (fpr, unicode(self.client_address)))
1051
# Have to check if client.still_valid(), since it is
1052
# possible that the client timed out while establishing
1053
# the GnuTLS session.
1054
if not client.still_valid():
1055
ipc.write(u"INVALID %s\n" % client.name)
1058
ipc.write(u"SENDING %s\n" % client.name)
1060
while sent_size < len(client.secret):
1061
sent = session.send(client.secret[sent_size:])
1062
logger.debug(u"Sent: %d, remaining: %d",
1063
sent, len(client.secret)
1064
- (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))
1069
1100
def peer_certificate(session):
1132
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
1133
"""Like socketserver.ForkingMixIn, but also pass a pipe."""
1163
class ForkingMixInWithPipes(socketserver.ForkingMixIn, object):
1164
"""Like socketserver.ForkingMixIn, but also pass a pipe pair."""
1134
1165
def process_request(self, request, client_address):
1135
1166
"""Overrides and wraps the original process_request().
1137
1168
This function creates a new pipe in self.pipe
1139
self.pipe = os.pipe()
1140
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,
1141
1175
self).process_request(request, client_address)
1142
os.close(self.pipe[1]) # close write end
1143
self.add_pipe(self.pipe[0])
1144
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):
1145
1181
"""Dummy function; override as necessary"""
1149
class IPv6_TCPServer(ForkingMixInWithPipe,
1182
child_pipe_fd.close()
1183
parent_pipe_fd.close()
1186
class IPv6_TCPServer(ForkingMixInWithPipes,
1150
1187
socketserver.TCPServer, object):
1151
1188
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
1237
1274
return socketserver.TCPServer.server_activate(self)
1238
1275
def enable(self):
1239
1276
self.enabled = True
1240
def add_pipe(self, pipe):
1277
def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
1241
1278
# Call "handle_ipc" for both data and EOF events
1242
gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1244
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):
1245
1285
condition_names = {
1246
1286
gobject.IO_IN: u"IN", # There is data to read.
1247
1287
gobject.IO_OUT: u"OUT", # Data can be written (without
1259
1299
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1260
1300
conditions_string)
1262
# Turn the pipe file descriptor into a Python file object
1263
if source not in file_objects:
1264
file_objects[source] = os.fdopen(source, u"r", 1)
1266
1302
# Read a line from the file object
1267
cmdline = file_objects[source].readline()
1303
cmdline = sender.readline()
1268
1304
if not cmdline: # Empty line means end of file
1269
# close the IPC pipe
1270
file_objects[source].close()
1271
del file_objects[source]
1305
# close the IPC pipes
1273
1309
# Stop calling this function
1279
1315
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1281
1317
if cmd == u"NOTFOUND":
1282
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)
1284
1321
if self.use_dbus:
1285
1322
# Emit D-Bus signal
1286
mandos_dbus_service.ClientNotFound(args)
1287
elif cmd == u"INVALID":
1323
mandos_dbus_service.ClientNotFound(fpr, address)
1324
elif cmd == u"DISABLED":
1288
1325
for client in self.clients:
1289
1326
if client.name == args:
1290
logger.warning(u"Client %s is invalid", args)
1327
logger.warning(u"Client %s is disabled", args)
1291
1328
if self.use_dbus:
1292
1329
# Emit D-Bus signal
1293
1330
client.Rejected()
1296
logger.error(u"Unknown client %s is invalid", args)
1333
logger.error(u"Unknown client %s is disabled", args)
1297
1334
elif cmd == u"SENDING":
1298
1335
for client in self.clients:
1299
1336
if client.name == args:
1307
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)
1310
1360
logger.error(u"Unknown IPC command: %r", cmdline)
1345
1395
elif suffix == u"w":
1346
1396
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1349
except (ValueError, IndexError):
1398
raise ValueError(u"Unknown suffix %r" % suffix)
1399
except (ValueError, IndexError), e:
1400
raise ValueError(e.message)
1351
1401
timevalue += delta
1352
1402
return timevalue
1366
1416
def if_nametoindex(interface):
1367
1417
"Get an interface index the hard way, i.e. using fcntl()"
1368
1418
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1369
with closing(socket.socket()) as s:
1419
with contextlib.closing(socket.socket()) as s:
1370
1420
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1371
1421
struct.pack(str(u"16s16x"),
1566
1616
bus = dbus.SystemBus()
1567
1617
# End of Avahi example code
1569
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1620
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1621
bus, do_not_queue=True)
1622
except dbus.exceptions.NameExistsException, e:
1623
logger.error(unicode(e) + u", disabling D-Bus")
1625
server_settings[u"use_dbus"] = False
1626
tcp_server.use_dbus = False
1570
1627
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1571
1628
service = AvahiService(name = server_settings[u"servicename"],
1572
1629
servicetype = u"_mandos._tcp",
1611
1668
del pidfilename
1614
"Cleanup function; run on exit"
1617
while tcp_server.clients:
1618
client = tcp_server.clients.pop()
1619
client.disable_hook = None
1622
atexit.register(cleanup)
1625
1671
signal.signal(signal.SIGINT, signal.SIG_IGN)
1626
1672
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1633
1679
dbus.service.Object.__init__(self, bus, u"/")
1634
1680
_interface = u"se.bsnet.fukt.Mandos"
1636
@dbus.service.signal(_interface, signature=u"oa{sv}")
1637
def ClientAdded(self, objpath, properties):
1682
@dbus.service.signal(_interface, signature=u"o")
1683
def ClientAdded(self, objpath):
1641
@dbus.service.signal(_interface, signature=u"s")
1642
def ClientNotFound(self, fingerprint):
1687
@dbus.service.signal(_interface, signature=u"ss")
1688
def ClientNotFound(self, fingerprint, address):
1671
1717
tcp_server.clients.remove(c)
1672
1718
c.remove_from_connection()
1673
1719
# Don't signal anything except ClientRemoved
1674
c.disable(signal=False)
1720
c.disable(quiet=True)
1675
1721
# Emit D-Bus signal
1676
1722
self.ClientRemoved(object_path, c.name)
1724
raise KeyError(object_path)
1682
1728
mandos_dbus_service = MandosDBusService()
1731
"Cleanup function; run on exit"
1734
while tcp_server.clients:
1735
client = tcp_server.clients.pop()
1737
client.remove_from_connection()
1738
client.disable_hook = None
1739
# Don't signal anything except ClientRemoved
1740
client.disable(quiet=True)
1743
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
1746
atexit.register(cleanup)
1684
1748
for client in tcp_server.clients:
1686
1750
# Emit D-Bus signal
1687
mandos_dbus_service.ClientAdded(client.dbus_object_path,
1751
mandos_dbus_service.ClientAdded(client.dbus_object_path)
1689
1752
client.enable()
1691
1754
tcp_server.enable()
1721
1785
main_loop.run()
1722
1786
except AvahiError, error:
1723
1787
logger.critical(u"AvahiError: %s", error)
1725
1790
except KeyboardInterrupt:
1727
1792
print >> sys.stderr
1728
1793
logger.debug(u"Server received KeyboardInterrupt")
1729
1794
logger.debug(u"Server exiting")
1795
# Must run before the D-Bus bus name gets deregistered
1731
1798
if __name__ == '__main__':