257
264
runtime with vars(self) as dict, so that for
258
265
instance %(name)s can be used in the command.
259
266
current_checker_command: string; current running checker_command
260
changesignal: File descriptor; written to on object change
261
_reset: File descriptor; for flushing changesignal
262
delay: datetime.timedelta(); how long to wait for approval/secret
263
approved: bool(); None if not yet approved/disapproved
267
approved_delay: datetime.timedelta(); Time to wait for approval
268
_approved: bool(); 'None' if not yet approved/disapproved
269
approved_duration: datetime.timedelta(); Duration of one approval
293
302
.replace(u" ", u""))
294
303
logger.debug(u" Fingerprint: %s", self.fingerprint)
295
304
if u"secret" in config:
296
self._secret = config[u"secret"].decode(u"base64")
305
self.secret = config[u"secret"].decode(u"base64")
297
306
elif u"secfile" in config:
298
307
with open(os.path.expanduser(os.path.expandvars
299
308
(config[u"secfile"])),
300
309
"rb") as secfile:
301
self._secret = secfile.read()
310
self.secret = secfile.read()
303
312
#XXX Need to allow secret on demand!
304
313
raise TypeError(u"No secret or secfile for client %s"
318
327
self.checker_command = config[u"checker"]
319
328
self.current_checker_command = None
320
329
self.last_connect = None
321
self.changesignal, self._reset = os.pipe()
322
self._approved = None #XXX should be based on configfile
323
self.delay = 10; #XXX Should be based on configfile
325
def setsecret(self, value):
327
os.write(self.changesignal, "\0")
328
os.read(self._reset, 1)
330
secret = property(lambda self: self._secret, setsecret)
333
def setapproved(self, value):
334
self._approved = value
335
os.write(self.changesignal, "\0")
336
os.read(self._reset, 1)
338
approved = property(lambda self: self._approved, setapproved)
330
self._approved = None
331
self.approved_by_default = config.get(u"approved_by_default",
333
self.approved_delay = string_to_delta(
334
config[u"approved_delay"])
335
self.approved_duration = string_to_delta(
336
config[u"approved_duration"])
337
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
339
def send_changedstate(self):
340
self.changedstate.acquire()
341
self.changedstate.notify_all()
342
self.changedstate.release()
341
344
def enable(self):
342
345
"""Start this client's checker and timeout hooks"""
343
346
if getattr(self, u"enabled", False):
344
347
# Already enabled
349
self.send_changedstate()
346
350
self.last_enabled = datetime.datetime.utcnow()
347
351
# Schedule a new checker to be started an 'interval' from now,
348
352
# and every interval from then on.
803
808
self.PropertyChanged(dbus.String(u"checker_running"),
804
809
dbus.Boolean(False, variant_level=1))
812
def _reset_approved(self):
813
self._approved = None
816
def approve(self, value=True):
817
self._approved = value
818
gobject.timeout_add(self._timedelta_to_milliseconds(self.approved_duration, self._reset_approved))
807
820
## D-Bus methods, signals & properties
808
821
_interface = u"se.bsnet.fukt.Mandos.Client"
836
849
# Rejected - signal
837
@dbus.service.signal(_interface)
850
@dbus.service.signal(_interface, signature=u"s")
851
def Rejected(self, reason):
855
# NeedApproval - signal
856
@dbus.service.signal(_interface, signature=u"db")
857
def NeedApproval(self, timeout, default):
864
@dbus.service.method(_interface, in_signature=u"b")
865
def Approve(self, value):
844
868
# CheckedOK - method
845
869
@dbus.service.method(_interface)
846
870
def CheckedOK(self):
1039
class ProxyClient(object):
1040
def __init__(self, child_pipe, fpr, address):
1041
self._pipe = child_pipe
1042
self._pipe.send(('init', fpr, address))
1043
if not self._pipe.recv():
1046
def __getattribute__(self, name):
1047
if(name == '_pipe'):
1048
return super(ProxyClient, self).__getattribute__(name)
1049
self._pipe.send(('getattr', name))
1050
data = self._pipe.recv()
1051
if data[0] == 'data':
1053
if data[0] == 'function':
1054
def func(*args, **kwargs):
1055
self._pipe.send(('funcall', name, args, kwargs))
1056
return self._pipe.recv()[1]
1059
def __setattr__(self, name, value):
1060
if(name == '_pipe'):
1061
return super(ProxyClient, self).__setattr__(name, value)
1062
self._pipe.send(('setattr', name, value))
1013
1065
class ClientHandler(socketserver.BaseRequestHandler, object):
1014
1066
"""A class to handle client connections.
1017
1069
Note: This will run in its own forked process."""
1019
1071
def handle(self):
1020
logger.info(u"TCP connection from: %s",
1021
unicode(self.client_address))
1022
logger.debug(u"IPC Pipe FD: %d",
1023
self.server.child_pipe[1].fileno())
1024
# Open IPC pipe to parent process
1025
with contextlib.nested(self.server.child_pipe[1],
1026
self.server.parent_pipe[0]
1027
) as (ipc, ipc_return):
1072
with contextlib.closing(self.server.child_pipe) as child_pipe:
1073
logger.info(u"TCP connection from: %s",
1074
unicode(self.client_address))
1075
logger.debug(u"Pipe FD: %d",
1076
self.server.child_pipe.fileno())
1028
1078
session = (gnutls.connection
1029
1079
.ClientSession(self.request,
1030
1080
gnutls.connection
1031
1081
.X509Credentials()))
1033
line = self.request.makefile().readline()
1034
logger.debug(u"Protocol version: %r", line)
1036
if int(line.strip().split()[0]) > 1:
1038
except (ValueError, IndexError, RuntimeError), error:
1039
logger.error(u"Unknown protocol version: %s", error)
1042
1083
# Note: gnutls.connection.X509Credentials is really a
1043
1084
# generic GnuTLS certificate credentials object so long as
1044
1085
# no X.509 keys are added to it. Therefore, we can use it
1045
1086
# here despite using OpenPGP certificates.
1047
1088
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1048
1089
# u"+AES-256-CBC", u"+SHA1",
1049
1090
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1055
1096
(gnutls.library.functions
1056
1097
.gnutls_priority_set_direct(session._c_object,
1057
1098
priority, None))
1100
# Start communication using the Mandos protocol
1101
# Get protocol number
1102
line = self.request.makefile().readline()
1103
logger.debug(u"Protocol version: %r", line)
1105
if int(line.strip().split()[0]) > 1:
1107
except (ValueError, IndexError, RuntimeError), error:
1108
logger.error(u"Unknown protocol version: %s", error)
1111
# Start GnuTLS connection
1060
1113
session.handshake()
1061
1114
except gnutls.errors.GNUTLSError, error:
1074
1127
logger.debug(u"Fingerprint: %s", fpr)
1076
for c in self.server.clients:
1077
if c.fingerprint == fpr:
1081
ipc.write(u"NOTFOUND %s %s\n"
1082
% (fpr, unicode(self.client_address)))
1130
client = ProxyClient(child_pipe, fpr,
1131
self.client_address)
1085
class proxyclient(object):
1086
def __getattribute__(self, name):
1087
ipc.write(u"GETATTR %s %s\n" % name, client.fpr)
1088
return pickle.load(ipc_reply)
1089
p = proxyclient(client)
1135
delay = client.approved_delay
1092
if not p.client.enabled:
1093
icp.write("DISABLED %s\n" % client.fpr)
1095
if p.client.approved == False:
1096
icp.write("Disaproved")
1099
if not p.client.secret:
1100
icp.write("No password")
1101
elif not p.client.approved:
1102
icp.write("Need approval"):
1137
if not client.enabled:
1138
logger.warning(u"Client %s is disabled",
1140
if self.server.use_dbus:
1142
client.Rejected("Disabled")
1144
if client._approved is None:
1145
logger.info(u"Client %s need approval",
1147
if self.server.use_dbus:
1149
client.NeedApproval(
1150
client.approved_delay_milliseconds(),
1151
client.approved_by_default)
1152
elif client._approved:
1104
1153
#We have a password and are approved
1106
i, o, e = select(p.changesignal, (), (), client.delay)
1108
icp.write("Timeout passed")
1156
logger.warning(u"Client %s was not approved",
1158
if self.server.use_dbus:
1160
client.Rejected("Disapproved")
1111
ipc.write(u"SENDING %s\n" % client.name)
1163
#wait until timeout or approved
1164
#x = float(client._timedelta_to_milliseconds(delay))
1165
time = datetime.datetime.now()
1166
client.changedstate.acquire()
1167
client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1168
client.changedstate.release()
1169
time2 = datetime.datetime.now()
1170
if (time2 - time) >= delay:
1171
if not client.approved_by_default:
1172
logger.warning("Client %s timed out while"
1173
" waiting for approval",
1175
if self.server.use_dbus:
1177
client.Rejected("Time out")
1182
delay -= time2 - time
1113
1185
while sent_size < len(client.secret):
1186
# XXX handle session exception
1114
1187
sent = session.send(client.secret[sent_size:])
1115
1188
logger.debug(u"Sent: %d, remaining: %d",
1116
1189
sent, len(client.secret)
1117
1190
- (sent_size + sent))
1118
1191
sent_size += sent
1193
logger.info(u"Sending secret to %s", client.name)
1194
# bump the timeout as if seen
1196
if self.server.use_dbus:
1186
class ForkingMixInWithPipes(socketserver.ForkingMixIn, object):
1187
"""Like socketserver.ForkingMixIn, but also pass a pipe pair."""
1267
class MultiprocessingMixIn(object):
1268
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1269
def sub_process_main(self, request, address):
1271
self.finish_request(request, address)
1273
self.handle_error(request, address)
1274
self.close_request(request)
1276
def process_request(self, request, address):
1277
"""Start a new process to process the request."""
1278
multiprocessing.Process(target = self.sub_process_main,
1279
args = (request, address)).start()
1281
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1282
""" adds a pipe to the MixIn """
1188
1283
def process_request(self, request, client_address):
1189
1284
"""Overrides and wraps the original process_request().
1191
1286
This function creates a new pipe in self.pipe
1193
# Child writes to child_pipe
1194
self.child_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0))
1195
# Parent writes to parent_pipe
1196
self.parent_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0))
1197
super(ForkingMixInWithPipes,
1288
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1290
super(MultiprocessingMixInWithPipe,
1198
1291
self).process_request(request, client_address)
1199
# Close unused ends for parent
1200
self.parent_pipe[0].close() # close read end
1201
self.child_pipe[1].close() # close write end
1202
self.add_pipe_fds(self.child_pipe[0], self.parent_pipe[1])
1203
def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
1292
self.add_pipe(parent_pipe)
1293
def add_pipe(self, parent_pipe):
1204
1294
"""Dummy function; override as necessary"""
1205
child_pipe_fd.close()
1206
parent_pipe_fd.close()
1209
class IPv6_TCPServer(ForkingMixInWithPipes,
1297
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1210
1298
socketserver.TCPServer, object):
1211
1299
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
1297
1385
return socketserver.TCPServer.server_activate(self)
1298
1386
def enable(self):
1299
1387
self.enabled = True
1300
def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
1388
def add_pipe(self, parent_pipe):
1301
1389
# Call "handle_ipc" for both data and EOF events
1302
gobject.io_add_watch(child_pipe_fd.fileno(),
1390
gobject.io_add_watch(parent_pipe.fileno(),
1303
1391
gobject.IO_IN | gobject.IO_HUP,
1304
1392
functools.partial(self.handle_ipc,
1305
reply = parent_pipe_fd,
1306
sender= child_pipe_fd))
1307
def handle_ipc(self, source, condition, reply=None, sender=None):
1393
parent_pipe = parent_pipe))
1395
def handle_ipc(self, source, condition, parent_pipe=None,
1396
client_object=None):
1308
1397
condition_names = {
1309
1398
gobject.IO_IN: u"IN", # There is data to read.
1310
1399
gobject.IO_OUT: u"OUT", # Data can be written (without
1322
1411
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1323
1412
conditions_string)
1325
# Read a line from the file object
1326
cmdline = sender.readline()
1327
if not cmdline: # Empty line means end of file
1328
# close the IPC pipes
1332
# Stop calling this function
1414
# Read a request from the child
1415
request = parent_pipe.recv()
1416
command = request[0]
1418
if command == 'init':
1420
address = request[2]
1422
for c in self.clients:
1423
if c.fingerprint == fpr:
1427
logger.warning(u"Client not found for fingerprint: %s, ad"
1428
u"dress: %s", fpr, address)
1431
mandos_dbus_service.ClientNotFound(fpr, address)
1432
parent_pipe.send(False)
1435
gobject.io_add_watch(parent_pipe.fileno(),
1436
gobject.IO_IN | gobject.IO_HUP,
1437
functools.partial(self.handle_ipc,
1438
parent_pipe = parent_pipe,
1439
client_object = client))
1440
parent_pipe.send(True)
1441
# remove the old hook in favor of the new above hook on same fileno
1335
logger.debug(u"IPC command: %r", cmdline)
1337
# Parse and act on command
1338
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1340
if cmd == u"NOTFOUND":
1341
fpr, address = args.split(None, 1)
1342
logger.warning(u"Client not found for fingerprint: %s, ad"
1343
u"dress: %s", fpr, address)
1346
mandos_dbus_service.ClientNotFound(fpr, address)
1347
elif cmd == u"DISABLED":
1348
for client in self.clients:
1349
if client.name == args:
1350
logger.warning(u"Client %s is disabled", args)
1356
logger.error(u"Unknown client %s is disabled", args)
1357
elif cmd == u"SENDING":
1358
for client in self.clients:
1359
if client.name == args:
1360
logger.info(u"Sending secret to %s", client.name)
1367
logger.error(u"Sending secret to unknown client %s",
1369
elif cmd == u"GETATTR":
1370
attr_name, fpr = args.split(None, 1)
1371
for client in self.clients:
1372
if client.fingerprint == fpr:
1373
attr_value = getattr(client, attr_name, None)
1374
logger.debug("IPC reply: %r", attr_value)
1375
pickle.dump(attr_value, reply)
1378
logger.error(u"Client %s on address %s requesting "
1379
u"attribute %s not found", fpr, address,
1381
pickle.dump(None, reply)
1383
logger.error(u"Unknown IPC command: %r", cmdline)
1385
# Keep calling this function
1443
if command == 'funcall':
1444
funcname = request[1]
1448
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1450
if command == 'getattr':
1451
attrname = request[1]
1452
if callable(client_object.__getattribute__(attrname)):
1453
parent_pipe.send(('function',))
1455
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1457
if command == 'setattr':
1458
attrname = request[1]
1460
setattr(client_object, attrname, value)
1658
1736
client_class = Client
1660
1738
client_class = functools.partial(ClientDBus, bus = bus)
1739
def client_config_items(config, section):
1740
special_settings = {
1741
"approve_by_default":
1742
lambda: config.getboolean(section,
1743
"approve_by_default"),
1745
for name, value in config.items(section):
1747
yield special_settings[name]()
1661
1751
tcp_server.clients.update(set(
1662
1752
client_class(name = section,
1663
config= dict(client_config.items(section)))
1753
config= dict(client_config_items(
1754
client_config, section)))
1664
1755
for section in client_config.sections()))
1665
1756
if not tcp_server.clients:
1666
1757
logger.warning(u"No clients defined")