I'm trying to make this simple server script where multiple clients connect to it and are stored in the factory.connected_clients dictionary. I then want to show a menu so the user executing the server can select what client does he want to work with and what commands does he want to send them (like in a reverse shell). Here's the code:
class RshServer(protocol.Protocol):
def __init__(self, factory, addr):
self.addr = addr
self.factory = factory
def connectionMade(self):
address = self.addr.host + ':' + str(self.addr.port)
self.factory.connected_clients[address] = self
print '[*] Connection made with ' + address
class RshFactory(Factory):
def __init__(self):
self.connected_clients = {}
def buildProtocol(self, addr):
return RshServer(self, addr)
def show_server_menu():
print '1. Show connected clients'
print '2. Open client interface'
msg = raw_input('Select an option: ')
return msg
def show_client_menu():
print '1. Show all processes'
msg = raw_input('Select an option: ')
return msg
def server_interface():
while 1:
msg = show_server_menu()
command = server_commands.get(msg, None)
if command: command()
def client_interface():
protocol_name = raw_input('Enter client address: ')
protoc = rsh_factory.connected_clients.get(protocol_name, None)
if protoc:
msg = show_client_menu()
protoc.transport.write(msg)
rsh_factory = RshFactory()
server_commands = {
'1' : lambda: sys.stdout.write(str(rsh_factory.connected_clients)+'\n'),
'2' : client_interface
}
reactor.listenTCP(8000, rsh_factory)
reactor.callInThread(server_interface)
reactor.run()
However, when I select a client and execute the client interface function I get this error:
Unhandled Error
Traceback (most recent call last):
File "rsh_twisted_serv.py", line 58, in <module>
reactor.run()
File "/usr/lib/python2.7/dist-packages/twisted/internet/base.py", line 1192, in run
self.mainLoop()
File "/usr/lib/python2.7/dist-packages/twisted/internet/base.py", line 1204, in mainLoop
self.doIteration(t)
File "/usr/lib/python2.7/dist-packages/twisted/internet/epollreactor.py", line 396, in doPoll
log.callWithLogger(selectable, _drdw, selectable, fd, event)
--- <exception caught here> ---
File "/usr/lib/python2.7/dist-packages/twisted/python/log.py", line 88, in callWithLogger
return callWithContext({"system": lp}, func, *args, **kw)
File "/usr/lib/python2.7/dist-packages/twisted/python/log.py", line 73, in callWithContext
return context.call({ILogContext: newCtx}, func, *args, **kw)
File "/usr/lib/python2.7/dist-packages/twisted/python/context.py", line 118, in callWithContext
return self.currentContext().callWithContext(ctx, func, *args, **kw)
File "/usr/lib/python2.7/dist-packages/twisted/python/context.py", line 81, in callWithContext
return func(*args,**kw)
File "/usr/lib/python2.7/dist-packages/twisted/internet/posixbase.py", line 627, in _doReadOrWrite
self._disconnectSelectable(selectable, why, inRead)
File "/usr/lib/python2.7/dist-packages/twisted/internet/posixbase.py", line 257, in _disconnectSelectable
selectable.readConnectionLost(f)
File "/usr/lib/python2.7/dist-packages/twisted/internet/tcp.py", line 279, in readConnectionLost
self.connectionLost(reason)
File "/usr/lib/python2.7/dist-packages/twisted/internet/tcp.py", line 293, in connectionLost
abstract.FileDescriptor.connectionLost(self, reason)
File "/usr/lib/python2.7/dist-packages/twisted/internet/abstract.py", line 207, in connectionLost
self.stopWriting()
File "/usr/lib/python2.7/dist-packages/twisted/internet/abstract.py", line 429, in stopWriting
self.reactor.removeWriter(self)
File "/usr/lib/python2.7/dist-packages/twisted/internet/epollreactor.py", line 344, in removeWriter
EPOLLOUT, EPOLLIN)
File "/usr/lib/python2.7/dist-packages/twisted/internet/epollreactor.py", line 321, in _remove
self._poller.unregister(fd)
exceptions.IOError: [Errno 2] No such file or directory
I believe this is error is due to the transport.write() being called from a non-reactor thread. Am I right? How should I solve this?
SOLUTION: as @Glyph stated, the problem was indeed that I was calling transport.write() from a non-reactor thread (more details in his answer). I used the solution he proposed by changing protoc.transport.write()
to reactor.callFromThread(lambda: protoc.transport.write(msg))
I think you mean "called from a non-reactor thread". No code in Twisted is thread-safe unless explicitly specified. See the threading documentation for more information. Use reactor.callFromThread
to invoke Twisted methods from your stdio-reading thread, or use StandardIO
to tell Twisted to read from standard input instead.