87 lines
2.9 KiB
Python
87 lines
2.9 KiB
Python
"""Loopback-only SSH/SFTP fixture; password arrives through stdin, never logs."""
|
|
import io
|
|
import socket
|
|
import sys
|
|
import threading
|
|
import time
|
|
import paramiko
|
|
|
|
password = sys.stdin.readline().rstrip('\n')
|
|
key = paramiko.RSAKey.generate(2048)
|
|
|
|
class Server(paramiko.ServerInterface):
|
|
def __init__(self):
|
|
self.direct = set()
|
|
|
|
def check_auth_password(self, username, supplied):
|
|
return paramiko.AUTH_SUCCESSFUL if supplied == password else paramiko.AUTH_FAILED
|
|
|
|
def get_allowed_auths(self, username):
|
|
return 'password'
|
|
|
|
def check_channel_request(self, kind, chanid):
|
|
return paramiko.OPEN_SUCCEEDED if kind == 'session' else paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
|
|
|
|
def check_channel_direct_tcpip_request(self, chanid, origin, destination):
|
|
self.direct.add(chanid)
|
|
return paramiko.OPEN_SUCCEEDED
|
|
|
|
def check_channel_exec_request(self, channel, command):
|
|
def finish():
|
|
time.sleep(0.1)
|
|
channel.send_exit_status(0)
|
|
channel.close()
|
|
threading.Thread(target=finish, daemon=True).start()
|
|
return True
|
|
|
|
class SFTP(paramiko.SFTPServerInterface):
|
|
def stat(self, path):
|
|
return paramiko.SFTP_NO_SUCH_FILE
|
|
|
|
def open(self, path, flags, attr):
|
|
class Handle(paramiko.SFTPHandle):
|
|
def chattr(self, attr):
|
|
return paramiko.SFTP_OK
|
|
handle = Handle(flags)
|
|
handle.writefile = io.BytesIO()
|
|
return handle
|
|
|
|
def connection(client):
|
|
transport = paramiko.Transport(client)
|
|
transport.add_server_key(key)
|
|
class SFTPSubsystem(paramiko.SFTPServer):
|
|
def start_subsystem(self, name, transport, channel):
|
|
super().start_subsystem(name, transport, channel)
|
|
channel.send_exit_status(0)
|
|
transport.set_subsystem_handler('sftp', SFTPSubsystem, SFTP)
|
|
try:
|
|
server = Server()
|
|
channels = []
|
|
transport.start_server(server=server)
|
|
while transport.is_active():
|
|
channel = transport.accept(0.5)
|
|
if channel:
|
|
channels.append(channel)
|
|
if channel.chanid not in server.direct:
|
|
continue
|
|
def echo(c):
|
|
while not c.closed:
|
|
if c.recv_ready():
|
|
data = c.recv(1024)
|
|
if data:
|
|
c.sendall(data)
|
|
time.sleep(0.01)
|
|
threading.Thread(target=echo, args=(channel,), daemon=True).start()
|
|
except (EOFError, OSError, paramiko.SSHException):
|
|
pass
|
|
finally:
|
|
transport.close()
|
|
|
|
listener = socket.socket()
|
|
listener.bind(('127.0.0.1', 0))
|
|
listener.listen(10)
|
|
print(listener.getsockname()[1], flush=True)
|
|
while True:
|
|
client, _ = listener.accept()
|
|
threading.Thread(target=connection, args=(client,), daemon=True).start()
|