ssh / wsshell.py
anlaki's picture
Update wsshell.py
1445c39 verified
Raw
History Blame Contribute Delete
3.31 kB
import asyncio
import fcntl
import json
import os
import pty
import signal
import struct
import termios
import threading
from websockets.asyncio.server import serve
def set_winsize(fd, rows, cols):
"""Set terminal window size via TIOCSWINSZ."""
winsize = struct.pack("HHHH", rows, cols, 0, 0)
fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
async def shell(ws):
# Create PTY; child process gets it as controlling terminal
pid, fd = pty.fork()
if pid == 0:
os.environ["TERM"] = "xterm-256color"
os.execvp("bash", ["bash"])
# Set initial terminal size (client overrides via resize message)
rows, cols = 24, 80
set_winsize(fd, rows, cols)
q = asyncio.Queue()
def reader():
while True:
try:
data = os.read(fd, 65536)
if not data:
break
q.put_nowait(data)
except Exception:
break
rt = threading.Thread(target=reader, daemon=True)
rt.start()
try:
async def sender():
while True:
data = await q.get()
await ws.send(data)
async def receiver():
nonlocal rows, cols
async for raw in ws:
if isinstance(raw, str):
# Text frames: try JSON resize command first
try:
msg = json.loads(raw)
if "rows" in msg and "cols" in msg:
rows, cols = msg["rows"], msg["cols"]
set_winsize(fd, rows, cols)
os.kill(pid, signal.SIGWINCH)
continue
except (json.JSONDecodeError, ValueError):
pass
# Plain text from wscat: no trailing newline, append one
os.write(fd, raw.encode() + b"\n")
else:
# Binary frames: raw keystrokes from TUI client, pass through
os.write(fd, raw)
await asyncio.gather(sender(), receiver())
finally:
os.close(fd)
os.waitpid(pid, 0)
HTML_PAGE = """<!DOCTYPE html>
<html><head><title>WebSocket Shell</title><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>body{font:14px monospace;max-width:720px;margin:40px auto;padding:0 20px;
background:#0d1117;color:#c9d1d9}a{color:#58a6ff}pre{background:#161b22;
padding:16px;border-radius:6px;overflow-x:auto}</style></head>
<body><h1>WebSocket Shell</h1><p>Connect with <code>wspty</code>:</p>
<pre>./wspty wss://HOST</pre>
<p>Or with <code>wscat</code> (basic, no TUIs):</p>
<pre>wscat -c wss://HOST</pre>
<p><a href="https://github.com">Source</a></p></body></html>"""
async def process_request(connection, request):
if request.headers.get("Upgrade", "").lower() != "websocket":
if request.path == "/healthz":
return connection.respond(200, "OK")
return connection.respond(200, HTML_PAGE)
async def main():
async with serve(shell, "0.0.0.0", 7860,
ping_interval=None, max_size=2**20,
process_request=process_request) as srv:
await srv.serve_forever()
asyncio.run(main())