diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..be75011 --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +BIN = ./.venv/bin/ +PYTHON = ./.venv/bin/python +PIP = ./.venv/bin/pip + +APP_NAME=shadowssh + +all: install build + +ensure_repo: + -@git init + +ensure_env: ensure_repo + -@python3 -m venv .venv + +install: + $(PIP) install -e . + +format: + $(PIP) install shed + . $(BIN)/activate && shed + +build: install format + $(PIP) install build + $(PYTHON) -m build + +serve: + $(BIN)$(APP_NAME).serve --host=0.0.0.0 --port=443 + +run: serve + + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..07de284 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..3da5fc3 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,25 @@ +[metadata] +name = shadowssh +version = 1.0.0 +description = Run ssh on other service port! +author = retoor +author_email = retoor@molodetz.nl +license = MIT +long_description = file: README.md +long_description_content_type = text/markdown + +[options] +packages = find: +package_dir = + = src +python_requires = >=3.7 +install_requires = + app @ git+https://retoor.molodetz.nl/retoor/app.git + mololog @git+https://retoor.molodetz.nl/retoor/mololog.git + +[options.packages.find] +where = src + +[options.entry_points] +console_scripts = + shadowssh.serve = shadowssh.__main__:serve diff --git a/src/shadowssh/__init__.py b/src/shadowssh/__init__.py new file mode 100644 index 0000000..db4920a --- /dev/null +++ b/src/shadowssh/__init__.py @@ -0,0 +1,9 @@ +import logging +from mololog.client import patch + +logging.basicConfig( + level = logging.INFO +) + +log = logging.getLogger() +patch("https://mololog.molodetz.nl/") diff --git a/src/shadowssh/__main__.py b/src/shadowssh/__main__.py new file mode 100644 index 0000000..69ccc2d --- /dev/null +++ b/src/shadowssh/__main__.py @@ -0,0 +1,25 @@ +from shadowssh.app import Application +import asyncio +import argparse + +def parse_args(): + parser = argparse.ArgumentParser(description="Shadowssh") + + parser.add_argument("--host",type=str,required=False,default="0.0.0.0") + parser.add_argument("--port",type=int,required=False,default=443) + parser.add_argument("--host-service",type=str,required=False,default="127.0.0.1") + parser.add_argument("--port-service",type=int,required=False,default=4433) + parser.add_argument("--host-ssh", type=str,required=False,default="127.0.0.1") + parser.add_argument("--port-ssh", type=int,required=False,default=22) + return parser.parse_args() + + +def serve(): + args = parse_args() + app = Application() + asyncio.run(app.serve(args.host,args.port,args.host_service,args.port_service,args.host_ssh,args.port_ssh)) + + + + + diff --git a/src/shadowssh/app.py b/src/shadowssh/app.py new file mode 100644 index 0000000..3e75e9e --- /dev/null +++ b/src/shadowssh/app.py @@ -0,0 +1,78 @@ +import asyncio +from app.app import Application as BaseApplication +from shadowssh import log + +class Application(BaseApplication): + + def __init__(self, host_forward_service=None, port_forward_service=None, host_forward_ssh=None, port_forward_ssh=None, *args,**kwargs): + self.host_forward_service = host_forward_service + self.port_forward_service = port_forward_service + self.host_forward_ssh = host_forward_ssh + self.port_forward_ssh = port_forward_ssh + super().__init__(*args,**kwargs) + + async def connect(self, host, port): + log.info("Connected to {}:{}.".format(host,port)) + reader, writer = await asyncio.open_connection(host, port) + return reader,writer + + async def stream(self, reader, writer,port): + while True: + chunk = await reader.read(4096) + if not chunk: + writer.close() + log.info("Forward connection closed.") + return + writer.write(chunk) + await writer.drain() + + async def forward(self, reader, writer): + + + data = await reader.read(10) + if not data: + return + port = None + host = None + if b'SSH-2.0' in data: + port = self.port_forward_ssh + host = self.host_forward_ssh + else: + port = self.port_forward_service + host = self.host_forward_service + + log.info("Streaming to port: {}.".format(port)) + upstream_reader, upstream_writer = await self.connect(host, port) + upstream_writer.write(data) + tasks = [] + tasks.append(asyncio.create_task(self.stream(reader, upstream_writer,port))) + tasks.append(asyncio.create_task(self.stream(upstream_reader, writer,port))) + await asyncio.gather(*tasks) + + + + async def route(self, host, port): + + async def handle_client(reader, writer): + await self.forward(reader, writer) + + server = await asyncio.start_server(handle_client, host, port) + addr = server.sockets[0].getsockname() + log.info(f'Serving on {addr}') + + async with server: + await server.serve_forever() + + async def serve(self, host,port,host_forward_service, port_forward_service, host_forward_ssh, port_forward_ssh): + self.host_forward_service = host_forward_service + self.port_forward_service = port_forward_service + self.host_forward_ssh = host_forward_ssh + self.port_forward_ssh = port_forward_ssh + tasks = [ + self.route(host,port) + ] + await asyncio.gather(*tasks) + + + +