pax_global_header00006660000000000000000000000064151003445760014517gustar00rootroot0000000000000052 comment=c454dbbc8acfacd34d3bb606444d6dec25401a22 asysocks-0.2.18/000077500000000000000000000000001510034457600134465ustar00rootroot00000000000000asysocks-0.2.18/.github/000077500000000000000000000000001510034457600150065ustar00rootroot00000000000000asysocks-0.2.18/.github/workflows/000077500000000000000000000000001510034457600170435ustar00rootroot00000000000000asysocks-0.2.18/.github/workflows/python-windows-exe.yml000066400000000000000000000065361510034457600233700ustar00rootroot00000000000000name: Build Windows Executable - PyInstaller # Description: # Most of my projects come with a build.bat script that uses PyInstaller to freeze the examples # to an executable file. This Action will set up the envrionment and run this build.bat script, # then upload the resulting executables to a google cloud bucket. # Additionally the executables will be compressed and encrypted using 7z on: push: branches: - main # Trigger on push to master branch jobs: build: runs-on: windows-latest # Use a Windows runner permissions: contents: 'read' id-token: 'write' steps: - uses: 'actions/checkout@v4' with: fetch-depth: 0 - id: 'auth' uses: 'google-github-actions/auth@v1' with: credentials_json: '${{ secrets.GCLOUD_BUCKET_SERVICE_USER_SECRET }}' - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.9' - name: Install Dependencies run: | python -m pip install --upgrade pip pip install pyinstaller virtualenv - name: Run Batch File to Build Executable run: builder\pyinstaller\build.bat - name: Compress executables run: | "C:\Program Files\7-Zip\7z.exe" a secure.7z *.exe -pprotected working-directory: ${{ github.workspace }}/builder/pyinstaller shell: cmd #- name: Upload Executable # uses: actions/upload-artifact@v2 # with: # name: executable # path: builder\pyinstaller\*.exe - name: 'Set up Cloud SDK' uses: 'google-github-actions/setup-gcloud@v1' with: version: '>= 390.0.0' - name: Upload Executables to GCS run: | $PROJVERSION = python -c "import sys; sys.path.append('${{ github.event.repository.name }}'); import _version; print(_version.__version__)" Write-Host "Detected Version: $PROJVERSION" gsutil cp builder\pyinstaller\*.exe gs://skelsec-github-foss/${{ github.event.repository.name }}/$PROJVERSION/ gsutil cp builder\pyinstaller\*.7z gs://skelsec-github-foss/${{ github.event.repository.name }}/$PROJVERSION/ shell: powershell - uses: sarisia/actions-status-discord@v1 if: always() with: webhook: ${{ secrets.DISCORD_WEBHOOK }} status: ${{ job.status }} content: | ${{ github.event_name == 'push' && format('Hey all! A new commit was pushed to {0}!', github.repository) || '' }} ${{ github.event_name == 'pull_request' && format('Hey all! A new pull request has been opened on {0}!', github.repository) || '' }} ${{ github.event_name == 'release' && format('Hey all! A new release was created for project {0}!', github.event.repository.name) || '' }} title: | ${{ github.event_name == 'push' && 'Push Notification' || '' }} ${{ github.event_name == 'pull_request' && 'Pull Request Notification' || '' }} ${{ github.event_name == 'release' && 'Release Notification' || '' }} color: | ${{ github.event_name == 'push' && '0x00ff00' || '' }} ${{ github.event_name == 'pull_request' && '0xff0000' || '' }} ${{ github.event_name == 'release' && '0x0000ff' || '' }} url: "${{ github.server_url }}/${{ github.repository }}" username: GitHub Actions avatar_url: "https://avatars.githubusercontent.com/u/19204702" asysocks-0.2.18/.gitignore000066400000000000000000000034141510034457600154400ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ *.exeasysocks-0.2.18/LICENSE000066400000000000000000000020651510034457600144560ustar00rootroot00000000000000MIT License Copyright (c) 2020 Tamas Jos (@skelsec) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. asysocks-0.2.18/Makefile000066400000000000000000000005631510034457600151120ustar00rootroot00000000000000clean: rm -f -r build/ rm -f -r dist/ rm -f -r *.egg-info find . -name '*.pyc' -exec rm -f {} + find . -name '*.pyo' -exec rm -f {} + find . -name '*~' -exec rm -f {} + publish: clean python3 setup.py sdist bdist_wheel python3 -m twine upload dist/* rebuild: clean python3 setup.py install build: python3 setup.py install install: python3 setup.py installasysocks-0.2.18/Manifest.in000066400000000000000000000000421510034457600155400ustar00rootroot00000000000000include LICENSE include README.md asysocks-0.2.18/README.md000066400000000000000000000061761510034457600147370ustar00rootroot00000000000000![Supported Python versions](https://img.shields.io/badge/python-3.7+-blue.svg) [![Twitter](https://img.shields.io/twitter/follow/skelsec?label=skelsec&style=social)](https://twitter.com/intent/follow?screen_name=skelsec) ## :triangular_flag_on_post: Sponsors If you like this project, consider purchasing licenses of [OctoPwn](https://octopwn.com/), our full pentesting suite that runs in your browser! For notifications on new builds/releases and other info, hop on to our [Discord](https://discord.gg/PM8utcNxMS) # asysocks Asynchronous Socks5 / Socks4 / HTTP proxy client and server library in pure python. ## :triangular_flag_on_post: Runs in the browser This project, alongside with many other pentester tools runs in the browser with the power of OctoPwn! Check out the community version at [OctoPwn - Live](https://live.octopwn.com/) ## what is? The primary goal of this project to act as a universal proxy clinet/server library which you can use in your projects. Secondary goal is to have simple command-line tools to interact with socks proxyies or to start one yourself. See [tools](https://github.com/skelsec/asysocks#tools) ## how can I use this in my code? This library has three main components, [client](https://github.com/skelsec/asysocks#client) , [server](https://github.com/skelsec/asysocks#server), [security](https://github.com/skelsec/asysocks#security) ### client The proxy client code is implemented in the `SOCKSClient` class which can be used to set up a connection to a target `host:port` via `HTTP` / `SCOSK4` / `SOCKS5` proxy protocols. It has two modes of operation: `LISTENER` and `QUEUE` #### listener This mode helps proxy-unaware applications to use proxies when you can set the destination `host:port` and can't use `proxychains` and alike. It will create a TCP socket server on your localhost and each incoming client will be dispatched to the destination server via the specified proxy server. #### queue This mode can be used in your scripts to support proxies. The way it works is that you will not get a direct socket rather you can delegate two `asyncio.Queue` classes (one for data in, one for data out) ### server The proxy server is implemented in the `SOCKSServer` class. It supports `HTTP` / `SCOSK4` / `SOCKS5` protocols all on the same port. The server has basic paramteres most importantly the `listen_ip` and `listen_port` . It also comes with a clever way to deal with intercepting traffic, see the `ProxyMonitor` class and the server's `monitor_dispatch_q` variable. ### security TBD ## tools These are built-in examples showing the capabilities of the library and to be used as code snipplets on setting up classes. ### asysockstunnel Creates a listening server on localhost and tunnels all incoming connections to a pre-specified destination via a pre-defined proxy server. ### asysockssec SOCKS4/SOCKS5 server security tester ### asysocksbrute SOCKS5 authentication bruteforcer. ### asysocksportscan Quick port scaning via SOCKS4/SOCKS5/HTTP proxies ### asysocksproxy SOCKS4/SOCKS5/HTTP proxy server. Supports basic monitoring of traffic flow. For more issues see [Issues](https://github.com/skelsec/asysocks/issues) asysocks-0.2.18/asysocks/000077500000000000000000000000001510034457600153055ustar00rootroot00000000000000asysocks-0.2.18/asysocks/__init__.py000066400000000000000000000005461510034457600174230ustar00rootroot00000000000000#!/usr/bin/env python3 # # Author: # Tamas Jos (@skelsec) # import logging logger = logging.getLogger('asysocks') handler = logging.StreamHandler() formatter = logging.Formatter( '%(asctime)s %(name)-12s %(levelname)-8s %(message)s') handler.setFormatter(formatter) logger.addHandler(handler) logger.setLevel(logging.INFO) logger.propagate = Falseasysocks-0.2.18/asysocks/_version.py000066400000000000000000000002021510034457600174750ustar00rootroot00000000000000__version__ = "0.2.18" __banner__ = \ """ # asysocks %s # Author: Tamas Jos @skelsec (info@skelsecprojects.com) """ % __version__asysocks-0.2.18/asysocks/authentication/000077500000000000000000000000001510034457600203245ustar00rootroot00000000000000asysocks-0.2.18/asysocks/authentication/__init__.py000066400000000000000000000000001510034457600224230ustar00rootroot00000000000000asysocks-0.2.18/asysocks/certmanager.py000066400000000000000000000226471510034457600201620ustar00rootroot00000000000000 import os import asyncio import datetime import uuid import glob import tempfile import logging import hashlib import ssl from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives import serialization from cryptography.x509.oid import NameOID logger = logging.getLogger('asysocks.certmanager') handler = logging.StreamHandler() formatter = logging.Formatter( '%(asctime)s %(name)-12s %(levelname)-8s %(message)s') handler.setFormatter(formatter) logger.addHandler(handler) logger.setLevel(logging.INFO) class CertManager: def __init__(self, ca_cert = None, ca_key = None, cache_dir = None, ca_cert_file = None, ca_key_file = None, ca_key_file_pw = None): self.ca_cert_file = ca_cert_file self.ca_cert = ca_cert self.ca_key = ca_key self.ca_key_file = ca_key_file self.ca_key_file_pw = ca_key_file_pw self.cache_dir = cache_dir self.ca = None self.setup() def setup(self): ca_cert_data = None ca_key_data = None if self.cache_dir is None: path = os.path.join(tempfile.gettempdir(), 'certstore') os.makedirs(path, exist_ok=True) self.cache_dir = path if self.ca_cert_file is None and self.ca_cert is None: self.ca_cert_file = os.path.join(self.cache_dir, 'cacert.pem') if self.ca_key_file is None and self.ca_key is None: self.ca_key_file = os.path.join(self.cache_dir, 'cakey.pem') if self.ca_cert_file is not None: try: with open(self.ca_cert_file, 'rb') as f: ca_cert_data = f.read() except: pass if self.ca_key_file is not None: try: with open(self.ca_key_file, 'rb') as f: ca_key_data = f.read() except: pass if ca_cert_data is None or ca_key_data is None: self.intialize_certstore() else: self.ca_cert = x509.load_pem_x509_certificate(ca_cert_data, default_backend()) self.ca_key = serialization.load_pem_private_key(ca_key_data, self.ca_key_file_pw, backend=default_backend()) def intialize_certstore(self, cn = 'asysocks', on = 'asysocks', ou = 'asysocks', key_exp = 65537, key_size = 2048): ca_cert, ca_key, err = CertManager.generate_ca(cn = cn, on = on, ou = ou, key_exp = key_exp, key_size = key_size) if err is not None: raise err cname = 'cacert.pem' kname = 'cakey.pem' with open(os.path.join(self.cache_dir, cname), 'wb') as f: f.write(ca_cert) with open(os.path.join(self.cache_dir, kname), 'wb') as f: f.write(ca_key) self.ca_cert = x509.load_pem_x509_certificate(ca_cert, default_backend()) self.ca_key = serialization.load_pem_private_key(ca_key, self.ca_key_file_pw, backend=default_backend()) def store_to_cache(self, new_cert, new_key, hostname = None): cert = x509.load_pem_x509_certificate(new_cert, default_backend()) if hostname is not None: bname = bname = '%s_%s' % (cert.serial_number, hostname) else: bname = '%s_%s' % (cert.serial_number, hashlib.sha1(cert.subject.rfc4514_string().encode()).hexdigest()) cname = '%s_%s.pem' % (bname, 'cert') kname = '%s_%s.pem' % (bname, 'key') with open(os.path.join(self.cache_dir, cname), 'wb') as f: f.write(new_cert) with open(os.path.join(self.cache_dir, kname), 'wb') as f: f.write(new_key) return os.path.join(self.cache_dir, cname), os.path.join(self.cache_dir, kname) def load_from_cache(self, hostname = None, cert_der_data = None, serial = None, subject = None, ret_file_path = True): cert_filename = None key_filename = None if cert_der_data is not None: cert = x509.load_der_x509_certificate(cert_der_data, default_backend()) serial = cert.serial_number for filename in glob.glob(os.path.join(self.cache_dir, '*.pem')): basename = os.path.basename(filename) if serial is not None and basename.startswith(str(serial)): if basename.endswith('_key.pem'): key_filename = filename elif basename.endswith('_cert.pem'): cert_filename = filename elif hostname is not None and basename.find('_%s_' % hostname) != -1: if basename.endswith('_key.pem'): key_filename = filename elif basename.endswith('_cert.pem'): cert_filename = filename if cert_filename is None or key_filename is None: return None, None if ret_file_path is True: return os.path.join(self.cache_dir, cert_filename), os.path.join(self.cache_dir, key_filename) with open(cert_filename, 'r') as f: certdata = f.read() with open(key_filename, 'r') as f: keydata = f.read() return certdata, keydata def resign_certificate(self, cert, is_binary = True): try: if is_binary is True: old_cert = x509.load_der_x509_certificate(cert, default_backend()) else: old_cert = x509.load_pem_x509_certificate(cert, default_backend()) # TODO: generate the same type of key with the same strength as the original cert has # https://cryptography.io/en/latest/_modules/cryptography/x509/base/#Certificate.public_key cert_key = rsa.generate_private_key( public_exponent=65537, key_size=2048, backend=default_backend() ) builder = x509.CertificateBuilder( subject_name=old_cert.subject, issuer_name=self.ca_cert.subject, public_key=cert_key.public_key(), serial_number=old_cert.serial_number, not_valid_before=old_cert.not_valid_before, not_valid_after=old_cert.not_valid_after, ) # TODO: add extensions as well maybe #for x in old_cert.extensions: # print(type(x)) # builder.add_extension(x, x.critical) new_cert_obj = builder.sign(self.ca_key, hashes.SHA256() ,default_backend()) new_cert = new_cert_obj.public_bytes(encoding=serialization.Encoding.PEM) new_key = cert_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption(), ) return new_cert, new_key, None except Exception as e: logger.exception('resign_certificate') return None, None, e async def get_cert_by_host(self, hostname, port = 443, ssl_ctx = None): try: certfile, keyfile = self.load_from_cache(hostname=hostname) if certfile is not None: logger.debug('Cache hit for %s' % hostname) return certfile, keyfile, None cert, err = await CertManager.fetch_remote_cert(hostname, port, ssl_ctx=ssl_ctx) if err is not None: raise err certfile, keyfile = self.load_from_cache(cert_der_data=cert) if certfile is not None: logger.debug('Cache hit for %s' % hostname) return certfile, keyfile, None logger.debug('Cache miss for %s' % hostname) new_cert, new_key, err = self.resign_certificate(cert) if err is not None: raise err certfile, keyfile = self.store_to_cache(new_cert, new_key) return certfile, keyfile, None except Exception as e: logger.exception('get_cert_by_host') return None, None, e @staticmethod async def fetch_remote_cert(hostname, port = 443, ssl_ctx = None, binary_form = True): writer = None try: if ssl_ctx is None: ssl_ctx = ssl.create_default_context() _, writer = await asyncio.open_connection(host=hostname, port=port, ssl=ssl_ctx, server_hostname = hostname) ssl_obj = writer.get_extra_info('ssl_object') cert = ssl_obj.getpeercert(binary_form=binary_form) return cert, None except Exception as e: logger.exception('fetch_remote_cert') return None, e finally: if writer is not None: writer.close() @staticmethod def generate_ca(cn = 'asysocks', on = '', ou = '', key_exp = 65537, key_size = 2048): try: logger.debug('Generating initial CA certificate') ca_cert = None ca_key = None one_day = datetime.timedelta(1, 0, 0) one_year = datetime.timedelta(365, 0, 0) private_key = rsa.generate_private_key( public_exponent=key_exp, key_size=key_size, backend=default_backend() ) public_key = private_key.public_key() builder = x509.CertificateBuilder() builder = builder.subject_name(x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, cn), x509.NameAttribute(NameOID.ORGANIZATION_NAME, on), x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, ou), ])) builder = builder.issuer_name(x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, cn), ])) builder = builder.not_valid_before(datetime.datetime.today() - one_day) builder = builder.not_valid_after(datetime.datetime.today() + one_year) builder = builder.serial_number(int(uuid.uuid4())) builder = builder.public_key(public_key) builder = builder.add_extension( x509.BasicConstraints(ca=True, path_length=None), critical=True, ) certificate = builder.sign( private_key=private_key, algorithm=hashes.SHA256(), backend=default_backend() ) ca_cert = certificate.public_bytes( encoding=serialization.Encoding.PEM, ) ca_key = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption() ) return ca_cert, ca_key, None except Exception as e: logger.exception('generate_ca') return None, None, e async def amain(): cert, err = await CertManager.fetch_remote_cert('444.hu') if err is not None: print(err) return print(cert) cm = CertManager() #a = cm.resign_certificate(cert) #print(a) while True: certfile, keyfile, err = await cm.get_cert_by_host('444.hu') if err is not None: print(err) return def main(): logger.setLevel(1) asyncio.run(amain()) if __name__ == '__main__': main() asysocks-0.2.18/asysocks/client.py000066400000000000000000000607671510034457600171550ustar00rootroot00000000000000 import asyncio import base64 import traceback from asysocks import logger from asysocks.common.constants import SocksServerVersion, SocksCommsMode from asysocks.protocol.http import HTTPResponse, HTTPProxyAuthRequiredException, HTTPProxyAuthFailed from asysocks.protocol.socks4 import SOCKS4Request, SOCKS4Reply, SOCKS4CDCode from asysocks.protocol.socks4a import SOCKS4ARequest, SOCKS4AReply, SOCKS4ACDCode from asysocks.protocol.socks5 import SOCKS5Method, SOCKS5Nego, SOCKS5NegoReply, SOCKS5Request, SOCKS5Reply, SOCKS5ReplyType, SOCKS5PlainAuth, SOCKS5PlainAuthReply, SOCKS5ServerErrorReply, SOCKS5AuthFailed from asysocks.utils.sslwrapper import SSLWrapper class SocksTunnelError(Exception): def __init__(self, innerexception, message="Something failed setting up the tunnel! See innerexception for more details"): self.innerexception = innerexception self.message = message super().__init__(self.message) class SOCKSClient: def __init__(self, comms, proxies, bind_evt = None, channel_open_evt = None): self.proxies = proxies if isinstance(proxies, list) is False: self.proxies = [proxies] self.comms = comms self.server_task = None self.proxytask_in = None self.proxytask_out = None self.proxy_stopped_evt = None self.proxy_running_evt = None self.http_auth_ctx = None self.bind_progress_evt = bind_evt self.bind_port = None self.ssl_wrapper = None self.channel_open_evt = channel_open_evt async def terminate(self): if self.proxy_stopped_evt is not None: self.proxy_stopped_evt.set() if self.proxytask_in is not None: self.proxytask_in.cancel() if self.proxytask_out is not None: self.proxytask_out.cancel() def get_peercert(self): if self.ssl_wrapper is not None: return self.ssl_wrapper.get_server_cert() @staticmethod async def proxy_stream(in_stream, out_stream, proxy_stopped_evt, buffer_size = 4096, timeout = None): try: while True: read_task = asyncio.create_task(asyncio.wait_for( in_stream.read(buffer_size), timeout = timeout )) wait_task = asyncio.create_task(proxy_stopped_evt.wait()) finished_tasks, pending_tasks = await asyncio.wait([read_task, wait_task], return_when=asyncio.FIRST_COMPLETED) for task in finished_tasks: result = await task last_data = b'' if result is True: try: for pt in pending_tasks: last_data = await asyncio.wait_for(pt, timeout = 0.5) except: pass else: if last_data != b'': out_stream.write(result) await out_stream.drain() logger.debug('other side disconnected!') return else: if result == b'': logger.debug('Stream channel broken!') return out_stream.write(result) await out_stream.drain() for pt in pending_tasks: pt.cancel() except asyncio.CancelledError: return except asyncio.TimeoutError: logger.debug('proxy_stream timeout! (timeout=%s)' % str(timeout)) except Exception as e: logger.debug('proxy_stream err: %s' % str(e)) finally: out_stream.close() proxy_stopped_evt.set() @staticmethod async def proxy_queue_in(in_queue, writer, proxy_stopped_evt, buffer_size = 4096): try: while not proxy_stopped_evt.is_set(): data = await in_queue.get() if data is None: logger.debug('proxy_queue_in client disconncted!') return writer.write(data) await writer.drain() except asyncio.CancelledError: return except Exception as e: logger.debug('proxy_queue_in err: %s' % str(e)) finally: proxy_stopped_evt.set() try: writer.close() except: pass @staticmethod async def proxy_queue_out(out_queue, reader, proxy_stopped_evt, buffer_size = 4096, timeout = None): try: is_wsnet = hasattr(reader, 'wsnet_reader_type') while True: if is_wsnet: data = await reader.read(buffer_size) else: data = await asyncio.wait_for( reader.read(buffer_size), timeout = timeout ) if data == b'': logger.debug('proxy_queue_out endpoint disconncted!') await out_queue.put((None, Exception('proxy_queue_out endpoint disconncted gracefully!'))) return await out_queue.put((data, None)) except asyncio.CancelledError: await out_queue.put((None, Exception('proxy_queue_out got cancelled!'))) return except Exception as e: logger.debug('proxy_queue_out exception %s' % e) try: await out_queue.put((None, e)) except: pass finally: proxy_stopped_evt.set() async def run_http(self, proxy, remote_reader, remote_writer, timeout = None, http_auth_ctx = None): try: logger.debug('[HTTP] Opening channel to %s:%s' % (proxy.endpoint_ip, proxy.endpoint_port)) connect_cmd = 'CONNECT %s:%s HTTP/1.1\r\nHost: %s:%s\r\n' % (proxy.endpoint_ip, proxy.endpoint_port, proxy.endpoint_ip, proxy.endpoint_port) connect_cmd = connect_cmd.encode() if http_auth_ctx is None: remote_writer.write(connect_cmd + b'\r\n') await asyncio.wait_for(remote_writer.drain(), timeout = timeout) resp, err = await HTTPResponse.from_streamreader(remote_reader, timeout=timeout) if err is not None: raise err if resp.status == 200: logger.debug('[HTTP] Server succsessfully connected!') return True, http_auth_ctx, None elif resp.status == 407: logger.debug('[HTTP] Server proxy auth required!') if proxy.credential is None: raise Exception('HTTP proxy auth required but no credential set!') auth_type = resp.headers_upper.get('PROXY-AUTHENTICATE', None) if auth_type is None: raise Exception('HTTP proxy requires authentication, but requested auth type could not be determined') auth_type, _ = auth_type.split(' ', 1) logger.debug('HTTP proxy requires %s auth' % auth_type) http_auth_ctx = auth_type.upper() return False, http_auth_ctx, HTTPProxyAuthRequiredException() elif http_auth_ctx == 'BASIC': auth_data = base64.b64encode(('%s:%s' % (proxy.credential.username, proxy.credential.password) ).encode() ) auth_connect = connect_cmd + b'Proxy-Authorization: Basic ' + auth_data + b'\r\n' remote_writer.write(auth_connect + b'\r\n') await asyncio.wait_for(remote_writer.drain(), timeout = timeout) resp, err = await HTTPResponse.from_streamreader(remote_reader, timeout=timeout) if err is not None: raise err if resp.status == 200: logger.debug('[HTTP] Server proxy auth succsess!') return True, http_auth_ctx, None else: raise HTTPProxyAuthFailed() #raise Exception('Proxy auth failed!') else: raise Exception('HTTP proxy requires %s authentication, but it\'s not implemented' % http_auth_ctx) except Exception as e: logger.debug('run_http') return False, None, e async def run_socks4(self, proxy, remote_reader, remote_writer, timeout = None): """ Does the intial "handshake" instructing the remote server to set up the connection to the endpoint """ try: logger.debug('[SOCKS4 %s:%s] Opening channel to %s:%s (target)' % (proxy.server_ip, proxy.server_port, proxy.endpoint_ip, proxy.endpoint_port)) req = SOCKS4Request.from_target(proxy) if proxy.credential is not None and proxy.credential.username is not None: req.USERID = proxy.credential.username remote_writer.write(req.to_bytes()) await asyncio.wait_for(remote_writer.drain(), timeout = timeout) rep, err = await asyncio.wait_for(SOCKS4Reply.from_streamreader(remote_reader), timeout = timeout) if err is not None: raise err if rep is None: raise Exception('Socks server failed to reply to CONNECT request!') if rep.CD != SOCKS4CDCode.REP_GRANTED: raise Exception('Socks server returned error on CONNECT! %s' % rep.CD.value) logger.debug('[SOCKS4] Channel opened') return True, None except Exception as e: logger.debug('run_socks4') return False, e async def run_socks4a(self, proxy, remote_reader, remote_writer, timeout = None): """ Does the intial "handshake" instructing the remote server to set up the connection to the endpoint """ try: logger.debug('[SOCKS4A %s:%s] Opening channel to %s:%s (target)' % (proxy.server_ip, proxy.server_port, proxy.endpoint_ip, proxy.endpoint_port)) req = SOCKS4ARequest.from_target(proxy) if proxy.credential is not None and proxy.credential.username is not None: req.USERID = proxy.credential.username remote_writer.write(req.to_bytes()) await asyncio.wait_for(remote_writer.drain(), timeout = timeout) rep, err = await asyncio.wait_for(SOCKS4AReply.from_streamreader(remote_reader), timeout = timeout) if err is not None: raise err if rep is None: raise Exception('Socks server failed to reply to CONNECT request!') if rep.CD != SOCKS4ACDCode.REP_GRANTED: raise Exception('Socks server returned error on CONNECT! %s' % rep.CD.value) logger.debug('[SOCKS4A] Channel opened') return True, None except Exception as e: logger.debug('run_socks4a') return False, e async def run_socks5(self, proxy, remote_reader, remote_writer, timeout = None): """ Does the intial "handshake" instructing the remote server to set up the connection to the endpoint """ sname = proxy.get_sname() tname = proxy.get_tname() methods = [SOCKS5Method.NOAUTH] if proxy.credential is not None and proxy.credential.username is not None and proxy.credential.password is not None: methods.append(SOCKS5Method.PLAIN) #methods = [SOCKS5Method.PLAIN] try: nego = SOCKS5Nego.from_methods(methods) logger.debug('[SOCKS5 %s][SETUP] Sending negotiation command to server' % sname) remote_writer.write(nego.to_bytes()) await asyncio.wait_for( remote_writer.drain(), timeout = timeout ) rep_nego = await asyncio.wait_for( SOCKS5NegoReply.from_streamreader(remote_reader), timeout = timeout ) logger.debug( '[SOCKS5 %s] Got negotiation reply! Server choosen auth type: %s' % (sname, rep_nego.METHOD.name) ) if rep_nego.METHOD == SOCKS5Method.PLAIN: if proxy.credential is None or proxy.credential.username is None or proxy.credential.password is None: raise Exception('SOCKS5 %s] server requires PLAIN authentication, but no credentials were supplied!' % sname) logger.debug('[SOCKS5 %s]Preforming plaintext auth' % sname) remote_writer.write( SOCKS5PlainAuth.construct( proxy.credential.username, proxy.credential.password ).to_bytes() ) await asyncio.wait_for( remote_writer.drain(), timeout=None ) rep_data = await asyncio.wait_for( remote_reader.read(2), timeout = timeout ) if rep_data == b'': raise SOCKS5AuthFailed() #raise Exception('Plaintext auth failed! Bad username or password') rep_nego = SOCKS5PlainAuthReply.from_bytes(rep_data) if rep_nego.STATUS != SOCKS5ReplyType.SUCCEEDED: raise SOCKS5AuthFailed() #raise Exception('Plaintext auth failed! Bad username or password') elif rep_nego.METHOD == SOCKS5Method.GSSAPI: raise Exception('[SOCKS5 %s] server requires GSSAPI authentication, but it\'s not supported at the moment' % sname) logger.debug('[SOCKS5 %s] Opening channel to %s' % (sname, tname)) if proxy.only_auth is True: return True, None remote_writer.write( SOCKS5Request.from_target( proxy ).to_bytes() ) await asyncio.wait_for( remote_writer.drain(), timeout=timeout ) rep = await asyncio.wait_for( SOCKS5Reply.from_streamreader(remote_reader), timeout=timeout ) if rep.REP != SOCKS5ReplyType.SUCCEEDED: logger.info('[SOCKS5 %s] remote end failed to connect to proxy! Reson: %s' % (sname, rep.REP.name)) raise SOCKS5ServerErrorReply(rep.REP) if proxy.is_bind is False: logger.debug('[SOCKS5 %s] Successfully set up the connection to the endpoint %s ' % (sname,tname)) return True, None logger.debug('[SOCKS5 %s] BIND first set completed, port %s available!' % (sname , rep.BIND_PORT)) #bind in progress, waiting for a second reply to notify us that the remote endpoint connected back. self.bind_port = rep.BIND_PORT self.bind_progress_evt.set() #notifying that the bind port is now available on the socks server to be used if proxy.only_bind is True: return True, None rep = await asyncio.wait_for( SOCKS5Reply.from_streamreader(remote_reader), timeout=timeout ) if rep.REP != SOCKS5ReplyType.SUCCEEDED: logger.info('[SOCKS5 %s] remote end failed to connect to proxy! Reson: %s' % (sname, rep.REP.name)) raise SOCKS5ServerErrorReply(rep.REP) return True, None except Exception as e: logger.debug('[SOCKS5 %s] Error in run_socks5 %s' % (sname, e)) return False, e async def handle_client(self, reader, writer): remote_writer = None remote_reader = None try: logger.debug('[handle_client] Client connected!') if len(self.proxies) > 1: logger.debug('Start chaining...') for _ in range(3, 0 , -1): #this is for HTTP auth... if remote_writer is not None: remote_writer.close() try: if self.proxies[0].version == SocksServerVersion.WSNET: from asysocks.network.wsnet import WSNETNetwork remote_reader, remote_writer = await WSNETNetwork.open_connection( self.proxies[0].endpoint_ip, self.proxies[0].endpoint_port, self.proxies[0].wsnet_reuse, ) elif self.proxies[0].version in [SocksServerVersion.WSNETWS, SocksServerVersion.WSNETWSS]: from asysocks.network.wsnetws import WSNETNetworkWS remote_reader, remote_writer = await WSNETNetworkWS.open_connection( self.proxies[0].endpoint_ip, self.proxies[0].endpoint_port, self.proxies[0].server_ip, self.proxies[0].server_port, self.proxies[0].version, self.proxies[0].agentid, self.proxies[0].timeout, ) if remote_reader is None: raise remote_writer else: remote_reader, remote_writer = await asyncio.wait_for( asyncio.open_connection( self.proxies[0].server_ip, self.proxies[0].server_port, ssl=self.proxies[0].ssl_ctx, ), timeout = self.proxies[0].timeout ) logger.debug('Connected to socks server!') except: logger.debug('Failed to connect to SOCKS server!') raise try: for i, proxy in enumerate(self.proxies): if proxy.version in [SocksServerVersion.SOCKS4, SocksServerVersion.SOCKS4S]: try: x = await asyncio.wait_for(self.run_socks4(proxy, remote_reader, remote_writer), timeout=self.proxies[-1].timeout) except asyncio.TimeoutError: raise Exception('Proxy Connection establishment timeout') _, err = x if err is not None: if len(self.proxies) > 1 and i != len(self.proxies)-1: raise SocksTunnelError(err) raise err continue elif proxy.version in [SocksServerVersion.SOCKS4A, SocksServerVersion.SOCKS4AS]: try: x = await asyncio.wait_for(self.run_socks4a(proxy, remote_reader, remote_writer), timeout=self.proxies[-1].timeout) except asyncio.TimeoutError: raise Exception('Proxy Connection establishment timeout') _, err = x if err is not None: if len(self.proxies) > 1 and i != len(self.proxies)-1: raise SocksTunnelError(err) raise err continue elif proxy.version in [SocksServerVersion.SOCKS5, SocksServerVersion.SOCKS5S]: try: x = await asyncio.wait_for(self.run_socks5(proxy, remote_reader, remote_writer), timeout=self.proxies[-1].timeout) except asyncio.TimeoutError: raise Exception('Proxy Connection establishment timeout') _, err = x if err is not None: if len(self.proxies) > 1 and i != len(self.proxies)-1: raise SocksTunnelError(err) raise err continue elif proxy.version in [SocksServerVersion.HTTP, SocksServerVersion.HTTPS]: try: x = await asyncio.wait_for(self.run_http(proxy, remote_reader, remote_writer, http_auth_ctx = self.http_auth_ctx), timeout=self.proxies[-1].timeout) except asyncio.TimeoutError: raise Exception('Proxy Connection establishment timeout') _, self.http_auth_ctx, err = x if err is not None: if len(self.proxies) > 1 and i != len(self.proxies)-1: raise SocksTunnelError(err) raise err continue elif proxy.version in [SocksServerVersion.WSNET, SocksServerVersion.WSNETWS, SocksServerVersion.WSNETWSS]: if i != 0: raise Exception("WSNET only supported as the first proxy in chain!") continue else: raise Exception('Unknown SOCKS version!') else: # no need to do more iterations because of HTTP at this point break except HTTPProxyAuthRequiredException: continue except: raise logger.debug('[handle_client] Starting proxy...') self.channel_open_evt.set() self.proxy_stopped_evt = asyncio.Event() self.proxytask_in = asyncio.create_task( SOCKSClient.proxy_stream( reader, remote_writer, self.proxy_stopped_evt , buffer_size = self.proxies[0].buffer_size, timeout = self.proxies[0].endpoint_timeout ) ) self.proxytask_out = asyncio.create_task( SOCKSClient.proxy_stream( remote_reader, writer, self.proxy_stopped_evt, buffer_size = self.proxies[0].buffer_size, timeout = self.proxies[0].endpoint_timeout ) ) logger.debug('[handle_client] Proxy started!') await self.proxy_stopped_evt.wait() logger.debug('[handle_client] Proxy stopped!') self.proxytask_in.cancel() self.proxytask_out.cancel() except Exception as e: logger.exception('[handle_client]') finally: if remote_writer is not None: remote_writer.close() async def handle_queue(self): logger.debug('[queue] Connecting to socks server...') remote_writer = None remote_reader = None if self.bind_progress_evt is None: self.bind_progress_evt = asyncio.Event() try: if len(self.proxies) > 1: logger.debug('Start chaining...') for _ in range(3, 0 , -1): if remote_writer is not None: remote_writer.close() try: if self.proxies[0].version == SocksServerVersion.WSNET: from asysocks.network.wsnet import WSNETNetwork remote_reader, remote_writer = await WSNETNetwork.open_connection( self.proxies[0].endpoint_ip, self.proxies[0].endpoint_port, self.proxies[0].wsnet_reuse, ) elif self.proxies[0].version in [SocksServerVersion.WSNETWS, SocksServerVersion.WSNETWSS]: from asysocks.network.wsnetws import WSNETNetworkWS remote_reader, remote_writer = await WSNETNetworkWS.open_connection( self.proxies[0].endpoint_ip, self.proxies[0].endpoint_port, self.proxies[0].server_ip, self.proxies[0].server_port, self.proxies[0].version, self.proxies[0].agentid, self.proxies[0].timeout, ) if remote_reader is None: raise remote_writer else: remote_reader, remote_writer = await asyncio.wait_for( asyncio.open_connection( self.proxies[0].server_ip, self.proxies[0].server_port ), timeout = self.proxies[0].timeout ) except: logger.debug('Failed to connect to SOCKS server!') raise logger.debug('[queue] Connected to socks server!') try: for i, proxy in enumerate(self.proxies): if proxy.version in [SocksServerVersion.SOCKS4, SocksServerVersion.SOCKS4S]: try: x = await asyncio.wait_for(self.run_socks4(proxy, remote_reader, remote_writer), timeout=self.proxies[-1].timeout) except asyncio.TimeoutError: raise Exception('SOCKS4 Connection establishment timeout') _, err = x if err is not None: if len(self.proxies) > 1 and i != len(self.proxies)-1: raise SocksTunnelError(err) raise err continue elif proxy.version in [SocksServerVersion.SOCKS4A, SocksServerVersion.SOCKS4S]: try: x = await asyncio.wait_for(self.run_socks4a(proxy, remote_reader, remote_writer), timeout=self.proxies[-1].timeout) except asyncio.TimeoutError: raise Exception('SOCKS4A Connection establishment timeout') _, err = x if err is not None: if len(self.proxies) > 1 and i != len(self.proxies)-1: raise SocksTunnelError(err) raise err continue elif proxy.version in [SocksServerVersion.SOCKS5, SocksServerVersion.SOCKS5S]: try: x = await asyncio.wait_for(self.run_socks5(proxy, remote_reader, remote_writer), timeout=self.proxies[-1].timeout) except asyncio.TimeoutError: raise Exception('SOCKS5 Connection establishment timeout') _, err = x if err is not None: if len(self.proxies) > 1 and i != len(self.proxies)-1: raise SocksTunnelError(err) raise err continue elif proxy.version in [SocksServerVersion.HTTP, SocksServerVersion.HTTPS]: try: x = await asyncio.wait_for(self.run_http(proxy, remote_reader, remote_writer, http_auth_ctx = self.http_auth_ctx), timeout=self.proxies[-1].timeout) except asyncio.TimeoutError: raise Exception('HTTP Connection establishment timeout') _, self.http_auth_ctx, err = x if err is not None: if len(self.proxies) > 1 and i != len(self.proxies)-1: raise SocksTunnelError(err) raise err continue elif proxy.version in [SocksServerVersion.WSNET, SocksServerVersion.WSNETWS, SocksServerVersion.WSNETWSS]: if i != 0: raise Exception("WSNET only supported as the first proxy in chain!") continue else: raise Exception('Unknown SOCKS version!') else: break except HTTPProxyAuthRequiredException: continue except: raise if self.proxies[-1].only_bind is True: return True, None if self.proxies[-1].only_open is True or self.proxies[-1].only_auth is True: # for auth guessing and connection testing return True, None if self.channel_open_evt is not None: self.channel_open_evt.set() logger.debug('[queue] Starting proxy...') self.proxy_stopped_evt = asyncio.Event() if self.comms.wrap_ssl is False: self.proxytask_in = asyncio.create_task( SOCKSClient.proxy_queue_in( self.comms.in_queue, remote_writer, self.proxy_stopped_evt, buffer_size = self.proxies[-1].buffer_size ) ) self.proxytask_out = asyncio.create_task( SOCKSClient.proxy_queue_out( self.comms.out_queue, remote_reader, self.proxy_stopped_evt, buffer_size = self.proxies[-1].buffer_size, timeout = self.proxies[-1].endpoint_timeout ) ) else: transport_in = asyncio.Queue() transport_out = asyncio.Queue() self.proxytask_in = asyncio.create_task( SOCKSClient.proxy_queue_in( transport_out, remote_writer, self.proxy_stopped_evt, buffer_size = self.proxies[-1].buffer_size ) ) self.proxytask_out = asyncio.create_task( SOCKSClient.proxy_queue_out( transport_in, remote_reader, self.proxy_stopped_evt, buffer_size = self.proxies[-1].buffer_size, timeout = self.proxies[-1].endpoint_timeout ) ) self.ssl_wrapper, err = await SSLWrapper.from_commsettings(self.comms, transport_in, transport_out) if err is not None: raise err await asyncio.wait_for(self.ssl_wrapper.handshake_done_evt.wait(), timeout = self.comms.handshake_timeout) self.proxy_running_evt.set() logger.debug('[queue] Proxy started!') await self.proxy_stopped_evt.wait() logger.debug('[queue] Proxy stopped!') self.proxytask_in.cancel() self.proxytask_out.cancel() return True, None except Exception as e: await self.comms.in_queue.put((None,e)) await self.comms.out_queue.put((None,e)) return False, e finally: self.proxy_running_evt.set() if remote_writer is not None: remote_writer.close() async def run(self, noblock = False): self.proxy_running_evt = asyncio.Event() if isinstance(self.proxies, list) is False and (self.proxies.is_bind is True and self.bind_progress_evt is None): self.bind_progress_evt = asyncio.Event() if self.channel_open_evt is None: self.channel_open_evt = asyncio.Event() if self.comms.mode == SocksCommsMode.LISTENER: server = await asyncio.start_server( self.handle_client, self.comms.listen_ip, self.comms.listen_port, ) logger.debug('[PROXY] Awaiting server task now...') self.proxy_running_evt.set() if noblock is False: await server.serve_forever() return server.serve_forever() else: if noblock is False: await self.handle_queue() else: return self.handle_queue() asysocks-0.2.18/asysocks/common/000077500000000000000000000000001510034457600165755ustar00rootroot00000000000000asysocks-0.2.18/asysocks/common/__init__.py000066400000000000000000000000001510034457600206740ustar00rootroot00000000000000asysocks-0.2.18/asysocks/common/aiowrappers.py000066400000000000000000000034341510034457600215070ustar00rootroot00000000000000import asyncio async def readexactly_or_exc(reader, n, timeout = None): """ Helper function to read exactly N amount of data from the wire. :param reader: The reader object :type reader: asyncio.StreamReader :param n: The maximum amount of bytes to read. :type n: int :param timeout: Time in seconds to wait for the reader to return data :type timeout: int :return: bytearray """ temp = await asyncio.gather(*[asyncio.wait_for(reader.readexactly(n), timeout = timeout)], return_exceptions=True) if isinstance(temp[0], bytes): return temp[0] else: raise temp[0] async def read_or_exc(reader, n, timeout = None): """ Helper function to read N amount of data from the wire. :param reader: The reader object :type reader: asyncio.StreamReader :param n: The maximum amount of bytes to read. BEWARE: this only sets an upper limit of the data to be read :type n: int :param timeout: Time in seconds to wait for the reader to return data :type timeout: int :return: bytearray """ temp = await asyncio.gather(*[asyncio.wait_for(reader.read(n), timeout = timeout)], return_exceptions=True) if isinstance(temp[0], bytes): return temp[0] else: raise temp[0] async def readuntil_or_exc(reader, separator = b'\n', timeout = None): """ Helper function to read stream until separator is hit. :param reader: The reader object :type reader: asyncio.StreamReader :param n: The maximum amount of bytes to read. BEWARE: this only sets an upper limit of the data to be read :type n: int :param timeout: Time in seconds to wait for the reader to return data :type timeout: int :return: bytearray """ temp = await asyncio.gather(*[asyncio.wait_for(reader.readuntil(separator), timeout = timeout)], return_exceptions=True) if isinstance(temp[0], bytes): return temp[0] else: raise temp[0]asysocks-0.2.18/asysocks/common/clienturl.py000066400000000000000000000167021510034457600211560ustar00rootroot00000000000000 from urllib.parse import urlparse, parse_qs import ssl from asysocks.common.credentials import SocksCredential from asysocks.common.constants import SocksServerVersion, SocksProtocol, SOCKS5Method from asysocks.common.target import SocksTarget def stru(x): return str(x).upper() clienturl_param2var = { 'type' : ('version', [stru, SocksServerVersion]), 'host' : ('server_ip', [str]), 'server' : ('server_ip', [str]), 'port' : ('server_port', [int]), 'bind' : ('is_bind', [bool]), 'proto': ('proto', [SocksProtocol]), 'timeout': ('timeout', [int]), 'etimeout' : ('endpoint_timeout', [int]), 'bsize' : ('buffer_size', [int]), 'user' : ('username', [str]), 'pass' : ('password', [str]), 'authtype' : ('authtype', [SOCKS5Method]), 'userid' : ('userid', [str]), 'agentid' : ('agentid', [str]), } clienturl_url2var = { 'isbind' : ('is_bind', bool), 'proto' : ('proto', SocksProtocol), 'timeout' : ('endpoint_timeout', int), 'buffersize' : ('buffer_size', int), 'userid' : ('userid', str), 'agentid' : ('agentid', str), } sockssslversions = { SocksServerVersion.SOCKS5S : 1, SocksServerVersion.SOCKS4S : 1, SocksServerVersion.HTTPS : 1, } class SocksClientURL: def __init__(self): self.version = None self.server_ip = None self.server_port = 1080 self.is_bind = False self.proto = SocksProtocol.TCP self.timeout = 10 self.buffer_size = 4096 self.ssl_ctx = None self.wsnet_reuse = False self.endpoint_ip = None self.endpoint_port = None self.endpoint_timeout = None self.username = None self.password = None self.agentid = None def get_creds(self): if self.username is None: return None creds = SocksCredential() creds.username = self.username creds.password = self.password return creds def get_target(self): target = SocksTarget() target.version = self.version target.server_ip = self.server_ip target.server_port = self.server_port target.is_bind = self.is_bind target.proto = self.proto target.timeout = self.timeout target.buffer_size = self.buffer_size target.endpoint_ip = self.endpoint_ip target.endpoint_port = self.endpoint_port target.endpoint_timeout = self.endpoint_timeout target.ssl_ctx = self.ssl_ctx target.credential = self.get_creds() target.agentid = self.agentid target.wsnet_reuse = self.wsnet_reuse return target def sanity_check(self): if self.version != SocksServerVersion.WSNET: if self.server_ip is None: raise Exception('SOCKS server IP is missing!') if self.server_port is None: raise Exception('SOCKS server port is missing!') if self.buffer_size <= 0: raise Exception('buffer_size is too low! %s' % self.buffer_size) if self.endpoint_ip is None: raise Exception('Endpoint IP address is missing!') #if self.endpoint_port is None: # raise Exception('Endpoint port is missing!') @staticmethod def from_urls(urls, endpoint_ip = None, endpoint_port = None): proxylist = [] first = True prevtarget = None for url in urls[::-1]: res = SocksClientURL.from_url(url).get_target() if first is True: res.endpoint_ip = endpoint_ip res.endpoint_port = endpoint_port first = False else: res.endpoint_ip = prevtarget.server_ip res.endpoint_port = prevtarget.server_port prevtarget = res proxylist.append(res) return proxylist[::-1] @staticmethod def from_url(url_str): res = SocksClientURL() url_e = urlparse(url_str) res.version = SocksServerVersion(url_e.scheme.upper()) res.server_ip = url_e.hostname if url_e.port is not None: res.server_port = int(url_e.port) elif res.version == SocksServerVersion.HTTP: res.server_port = 8080 elif res.version == SocksServerVersion.HTTPS: res.server_port = 8443 elif res.version == SocksServerVersion.SOCKS5: res.server_port = 1080 elif res.version == SocksServerVersion.SOCKS5S: res.server_port = 1080 elif res.version == SocksServerVersion.SOCKS4: res.server_port = 1080 elif res.version == SocksServerVersion.SOCKS4S: res.server_port = 1080 elif res.version == SocksServerVersion.SOCKS4A: res.server_port = 1080 elif res.version == SocksServerVersion.SOCKS4AS: res.server_port = 1080 elif res.version == SocksServerVersion.WSNET: res.server_port = None res.username = url_e.username res.password = url_e.password if res.version in sockssslversions: res.ssl_ctx = ssl.create_default_context() #print(url_e) if url_e.query is not None: query = parse_qs(url_e.query) for k in query: if k in clienturl_url2var: pname = clienturl_url2var[k][0] param = clienturl_url2var[k][1](query[k][0]) setattr(res, pname, param) return res @staticmethod def from_params(url_str): """ """ lastproxy = SocksClientURL() url = urlparse(url_str) lastproxy.endpoint_ip = url.hostname if url.port: lastproxy.endpoint_port = int(url.port) if url.query is not None: query = parse_qs(url.query) proxycounts = [0] proxynums = {'0' : None} for k in query: if k.startswith('proxy'): try: int(k[5]) if k[5] not in proxynums: proxynums[k[5]] = None proxycounts.append(int(k[5])) except Exception as e: #print(e) pass #print(proxynums) #print(proxycounts) if len(proxycounts) != len(proxynums): raise Exception('proxyies are not in sequential order! ERROR!') proxycounts.sort() firstiter = True prevproxy = lastproxy for i in proxycounts[::-1]: pdata = SocksClientURL() if firstiter is True: firstiter = False pdata = lastproxy else: pdata.endpoint_ip = prevproxy.server_ip pdata.endpoint_port = prevproxy.server_port prevproxy = pdata startstring = 'proxy%s' % i if i == 0: startstring = 'proxy' for k in query: if k.startswith(startstring): startpos = 6 if i == 0: startpos = 5 if k[startpos:] in clienturl_param2var: data = query[k][0] for c in clienturl_param2var[k[startpos:]][1]: #print(c) data = c(data) setattr( pdata, clienturl_param2var[k[startpos:]][0], data ) proxynums[str(i)] = pdata if len(proxynums) > 1: for k in proxynums: if proxynums[k].version in sockssslversions: raise Exception('SSL in proxy chaining not supported! That would be a lot of work...') else: if proxynums['0'].version in sockssslversions: proxynums['0'].ssl_ctx = ssl.create_default_context() for k in proxynums: proxynums[k].sanity_check() targets = [] for i in proxycounts: targets.append(proxynums[str(i)].get_target()) return targets if __name__ == '__main__': url = 'http://alma.com:80/haha?proxytype=socks5&proxyport=66&proxyhost=127.0.0.1' url = 'http://alma.com:80/haha?proxytype=socks5&proxyport=66&proxyhost=127.0.0.1&proxy1type=socks4&proxy1port=9876&proxy1host=255.255.0.0' #url = 'http://alma.com:80/haha?proxytype=socks5&proxyport=66&proxyhost=127.0.0.1&proxy2type=socks4&proxy2port=9876&proxy2host=255.255.0.0' #url = 'http://alma.com:80/haha?proxytype=socks5&proxyport=66&proxyhost=127.0.0.1&proxy2type=socks4&proxy2port=9876&proxy2host=255.255.0.0&proxy1type=socks5s&proxy1port=6666&proxy1host=127.255.0.0' url = 'http://alma.com:80/haha?proxytype=socks5&proxyport=66&proxyhost=127.0.0.1&proxy2type=socks4&proxy2port=9876&proxy2host=255.255.0.0&proxy1type=socks5&proxy1port=6666&proxy1host=127.255.0.0' o = urlparse(url) res = SocksClientURL.from_params(url) print('aaaaaaaaaaaaaaaaaaaa') for target in res: print(target.__dict__) asysocks-0.2.18/asysocks/common/comms.py000066400000000000000000000012661510034457600202720ustar00rootroot00000000000000 from asysocks.common.constants import SocksCommsMode class SocksComms: def __init__(self): self.mode = None class SocksQueueComms(SocksComms): def __init__(self, in_queue, out_queue, wrap_ssl = False, ssl_ctx = None, handshake_timeout = 10): super(SocksComms) self.mode = SocksCommsMode.QUEUE self.in_queue = in_queue self.out_queue = out_queue # wrap endpoint SSL connection self.wrap_ssl = wrap_ssl self.ssl_ctx = ssl_ctx self.handshake_timeout = handshake_timeout class SocksLitenerComms(SocksComms): def __init__(self, listen_ip, listen_port): super(SocksComms) self.mode = SocksCommsMode.LISTENER self.listen_ip = listen_ip self.listen_port = listen_portasysocks-0.2.18/asysocks/common/constants.py000066400000000000000000000011021510034457600211550ustar00rootroot00000000000000import enum class SocksServerVersion(enum.Enum): SOCKS4 = 'SOCKS4' SOCKS4A = 'SOCKS4A' SOCKS4AS = 'SOCKS4AS' SOCKS5 = 'SOCKS5' SOCKS5S = 'SOCKS5S' SOCKS4S = 'SOCKS4S' HTTP = 'HTTP' HTTPS = 'HTTPS' WSNET = 'WSNET' WSNETWS = 'WSNETWS' WSNETWSS = 'WSNETWSS' class SocksProtocol(enum.Enum): TCP = 'TCP' UDP = 'UDP' class SocksCommsMode(enum.Enum): LISTENER = 'LISTENER' QUEUE = 'QUEUE' class SOCKS5Method(enum.Enum): NOAUTH = 0x00 GSSAPI = 0x01 PLAIN = 0x02 # IANA ASSIGNED X'03' to X'7F' # RESERVED FOR PRIVATE METHODS X'80' to X'FE' NOTACCEPTABLE = 0xFFasysocks-0.2.18/asysocks/common/credentials.py000066400000000000000000000001341510034457600214420ustar00rootroot00000000000000 class SocksCredential: def __init__(self): self.username = None self.password = Noneasysocks-0.2.18/asysocks/common/target.py000066400000000000000000000021311510034457600204320ustar00rootroot00000000000000 import os from urllib.parse import urlparse, parse_qs from asysocks.common.constants import SocksServerVersion, SocksProtocol class SocksTarget: def __init__(self): self.version = None self.server_ip = None self.server_port = None self.is_bind = False self.proto = SocksProtocol.TCP self.timeout = 10 #used to create the connection self.buffer_size = 4096 self.ssl_ctx = None self.userid = os.urandom(4).hex().encode('ascii') self.agentid = None self.wsnet_reuse = False self.endpoint_ip = None self.endpoint_port = None self.endpoint_timeout = None #used after the connection is made self.credential = None self.only_open = False #These params used for security testing only! self.only_auth = False #These params used for security testing only! self.only_bind = False #These params used for security testing only! def get_sname(self): return '%s:%s' % (self.server_ip, self.server_port) def get_tname(self): return '%s:%s' % (self.endpoint_ip, self.endpoint_port) def __repr__(self): return str(self.__dict__) def __str__(self): return repr(self) asysocks-0.2.18/asysocks/common/trafficlog.py000066400000000000000000000014411510034457600212670ustar00rootroot00000000000000 class TrafficLog: def __init__(self): self.client_ip = None self.client_port = None self.destination_ip = None self.destination_hostname = None self.destination_port = None self.session_id = None self.direction = None self.module = None self.srv_module = None self.is_ssl = False self.data = None def get_dst_hostname(self): if self.destination_hostname is None: return self.destination_ip return self.destination_hostname def get_header(self): return '[%s][%s][%s][%s:%s %s %s:%s] ' % ( self.srv_module, self.module, self.session_id, self.client_ip, self.client_port, '->' if self.direction.startswith('c2d') else '<-', self.get_dst_hostname(), self.destination_port ) def __str__(self): return self.get_header() + str(self.data)asysocks-0.2.18/asysocks/examples/000077500000000000000000000000001510034457600171235ustar00rootroot00000000000000asysocks-0.2.18/asysocks/examples/__init__.py000066400000000000000000000000001510034457600212220ustar00rootroot00000000000000asysocks-0.2.18/asysocks/examples/fileserver.py000066400000000000000000002335221510034457600216520ustar00rootroot00000000000000#!/usr/bin/env python3 """ File Download Server Example This example demonstrates how to create a secure file download server using the HTTPServerHandler class. The server allows: - Listing files in a specified directory - Downloading individual files - Protection against directory traversal attacks Usage: python fileserver.py [directory_path] [host] [port] Example: python fileserver.py ./downloads 127.0.0.1 8080 """ import asyncio import os import urllib.parse import mimetypes import html import h11 import tempfile import re from pathlib import Path from asysocks.unicomm.common.target import UniTarget, UniProto from asysocks.unicomm.protocol.server.http.httpserver import HTTPServerHandler, HTTPServer class MultipartStreamProcessor: """ Memory-efficient multipart form data processor that handles boundary detection across chunk boundaries and streams file data directly to disk. """ def __init__(self, boundary_bytes, target_path, max_buffer_size=1024*1024, max_file_size=500*1024*1024, allowed_extensions=None, max_files_per_request=100, print_cb = None): self.boundary_bytes = boundary_bytes self.target_path = target_path self.max_buffer_size = max_buffer_size self.max_file_size = max_file_size self.allowed_extensions = allowed_extensions self.max_files_per_request = max_files_per_request # State tracking self.buffer = b'' self.state = 'looking_for_boundary' # 'looking_for_boundary', 'reading_headers', 'reading_file_data' self.current_file_info = None self.current_file_handle = None self.temp_files = [] # Track for cleanup self.completed_files = [] self.file_count = 0 # Track number of files processed # Boundary patterns self.boundary_with_crlf = b'\r\n' + self.boundary_bytes self.end_boundary = self.boundary_bytes + b'--' self.print_cb = print_cb async def print(self, msg=''): if self.print_cb is None: return await self.print_cb(msg) async def process_chunk(self, chunk): """ Process a chunk of data, returning any completed file info. Args: chunk (bytes): Raw chunk data Returns: list: List of completed file info dictionaries """ self.buffer += chunk completed_files = [] # Prevent buffer from growing too large if len(self.buffer) > self.max_buffer_size: if self.state == 'reading_file_data': # Flush file data to reduce buffer size await self._flush_file_data() elif len(self.buffer) > self.max_buffer_size * 2: # Emergency limit raise ValueError(f"Buffer size exceeded: {len(self.buffer)} bytes") while True: if self.state == 'looking_for_boundary': completed_files.extend(await self._process_boundary_search()) if self.state == 'looking_for_boundary': break # No more boundaries found elif self.state == 'reading_headers': if not await self._process_headers(): break # Need more data elif self.state == 'reading_file_data': file_info = await self._process_file_data() if file_info: completed_files.append(file_info) if self.state == 'reading_file_data': break # Need more data return completed_files async def _process_boundary_search(self): """Search for boundary markers in the buffer.""" completed_files = [] # Look for boundary (either at start or with CRLF prefix) boundary_pos = self.buffer.find(self.boundary_bytes) if boundary_pos == -1: # No boundary found, keep reasonable amount of data in buffer # Keep the last len(boundary) bytes in case boundary is split if len(self.buffer) > len(self.boundary_bytes) * 2: # Remove old data but keep potential partial boundary keep_size = len(self.boundary_bytes) + 10 self.buffer = self.buffer[-keep_size:] return completed_files # Found boundary - check if it's an end boundary if self.buffer[boundary_pos:boundary_pos + len(self.end_boundary)] == self.end_boundary: # End of multipart data await self._finalize_current_file() if self.current_file_info: completed_files.append(self.current_file_info) self.current_file_info = None return completed_files # Skip to after the boundary boundary_end = boundary_pos + len(self.boundary_bytes) self.buffer = self.buffer[boundary_end:] # Look for CRLF after boundary if self.buffer.startswith(b'\r\n'): self.buffer = self.buffer[2:] elif self.buffer.startswith(b'\n'): self.buffer = self.buffer[1:] self.state = 'reading_headers' return completed_files async def _process_headers(self): """Process multipart headers.""" # Look for double CRLF marking end of headers header_end = self.buffer.find(b'\r\n\r\n') if header_end == -1: # Try just LF (some clients use this) header_end = self.buffer.find(b'\n\n') if header_end == -1: # Check if we have too much header data without end marker if len(self.buffer) > 8192: # 8KB header limit raise ValueError("Multipart headers too long or malformed (missing header terminator)") return False # Need more data header_section = self.buffer[:header_end] self.buffer = self.buffer[header_end + 2:] else: header_section = self.buffer[:header_end] self.buffer = self.buffer[header_end + 4:] # Validate header size if len(header_section) > 4096: # 4KB individual header limit raise ValueError("Multipart part headers too long") try: # Parse headers headers_text = header_section.decode('utf-8', errors='strict') except UnicodeDecodeError as e: raise ValueError(f"Invalid header encoding: {e}") filename = self._extract_filename_from_headers(headers_text) if filename: # Finalize previous file if any if self.current_file_info: await self._finalize_current_file() # Start new file safe_filename = self._sanitize_upload_filename(filename) if not safe_filename: raise ValueError(f"Invalid or unsafe filename: {filename}") await self._start_new_file(safe_filename) self.state = 'reading_file_data' return True async def _process_file_data(self): """Process file data, looking for the next boundary.""" if not self.current_file_handle: # No file to write to, just consume data until boundary self.state = 'looking_for_boundary' return None # Look for next boundary in buffer next_boundary_pos = self.buffer.find(self.boundary_with_crlf) if next_boundary_pos == -1: # No boundary found, write most of buffer to file # Keep enough data to detect boundary split across chunks if len(self.buffer) > len(self.boundary_with_crlf) + 10: write_size = len(self.buffer) - len(self.boundary_with_crlf) - 10 data_to_write = self.buffer[:write_size] self.buffer = self.buffer[write_size:] await self._write_file_data(data_to_write) return None else: # Found boundary - write data before it and finalize file data_to_write = self.buffer[:next_boundary_pos] self.buffer = self.buffer[next_boundary_pos:] await self._write_file_data(data_to_write) file_info = await self._finalize_current_file() self.state = 'looking_for_boundary' return file_info def _extract_filename_from_headers(self, headers_text): """Extract filename from Content-Disposition header.""" for line in headers_text.split('\n'): line = line.strip() if line.lower().startswith('content-disposition:'): # Look for filename parameter filename_match = re.search(r'filename="([^"]*)"', line, re.IGNORECASE) if filename_match: return filename_match.group(1) # Also try without quotes filename_match = re.search(r'filename=([^;\s]+)', line, re.IGNORECASE) if filename_match: return filename_match.group(1) return None def _sanitize_upload_filename(self, filename): """ Sanitize uploaded filename to prevent directory traversal and other attacks. """ if not filename: return None # Remove any path components - only allow base filename safe_name = os.path.basename(filename) # Remove dangerous characters and patterns safe_name = re.sub(r'[<>:"|?*]', '_', safe_name) # Windows dangerous chars safe_name = re.sub(r'[^\w\-_\.]', '_', safe_name) # Only allow word chars, dash, underscore, dot safe_name = re.sub(r'\.\.+', '.', safe_name) # Replace multiple dots with single dot safe_name = safe_name.strip('._') # Remove leading/trailing dots and underscores # Check for dangerous patterns if not safe_name or safe_name in ['.', '..']: return None # Ensure reasonable length if len(safe_name) > 255: name_part, ext_part = os.path.splitext(safe_name) safe_name = name_part[:250] + ext_part[:5] return safe_name async def _start_new_file(self, filename): """Start writing a new file.""" # Check file count limit if self.file_count >= self.max_files_per_request: raise ValueError(f"Too many files in upload (max: {self.max_files_per_request})") # Check file extension if restrictions are set if self.allowed_extensions: file_ext = os.path.splitext(filename)[1].lower() if file_ext not in self.allowed_extensions: raise ValueError(f"File extension '{file_ext}' not allowed. Allowed: {', '.join(self.allowed_extensions)}") file_path = os.path.join(self.target_path, filename) # Create temporary file first temp_file_path = file_path + '.uploading' try: self.current_file_handle = open(temp_file_path, 'wb') self.temp_files.append(temp_file_path) except OSError as e: raise OSError(f"Cannot create upload file '{temp_file_path}': {e}") except PermissionError as e: raise OSError(f"Permission denied creating upload file '{temp_file_path}': {e}") self.current_file_info = { 'filename': filename, 'temp_path': temp_file_path, 'final_path': file_path, 'size': 0 } self.file_count += 1 async def _write_file_data(self, data): """Write data to current file.""" if self.current_file_handle and data: # Check file size limit before writing new_size = self.current_file_info['size'] + len(data) if new_size > self.max_file_size: raise ValueError(f"File '{self.current_file_info['filename']}' exceeds size limit: {new_size} bytes (max: {self.max_file_size})") try: self.current_file_handle.write(data) self.current_file_info['size'] += len(data) except OSError as e: raise OSError(f"Error writing to file '{self.current_file_info['filename']}': {e}") except Exception as e: raise OSError(f"Unexpected error writing file '{self.current_file_info['filename']}': {e}") async def _flush_file_data(self): """Flush file data to reduce memory usage.""" if self.current_file_handle: self.current_file_handle.flush() async def _finalize_current_file(self): """Finalize the current file and return its info.""" if not self.current_file_info: return None if self.current_file_handle: self.current_file_handle.close() self.current_file_handle = None # Move from temp location to final location temp_path = self.current_file_info['temp_path'] final_path = self.current_file_info['final_path'] try: os.rename(temp_path, final_path) if temp_path in self.temp_files: self.temp_files.remove(temp_path) file_info = { 'filename': self.current_file_info['filename'], 'size': self.current_file_info['size'], 'path': final_path } await self.print(f"[UPLOAD-SUCCESS] Uploaded: {file_info['filename']} ({file_info['size']:,} bytes)") self.completed_files.append(file_info) self.current_file_info = None return file_info except Exception as e: await self.print(f"[UPLOAD-ERROR] Error finalizing file {final_path}: {e}") # Clean up temp file try: os.unlink(temp_path) if temp_path in self.temp_files: self.temp_files.remove(temp_path) except: pass self.current_file_info = None return None async def finalize(self): """Finalize any remaining data.""" completed_files = [] # Process any remaining buffer data while self.buffer and self.state != 'looking_for_boundary': if self.state == 'reading_headers': if not await self._process_headers(): break elif self.state == 'reading_file_data': # Treat remaining data as end of file if self.current_file_handle and self.buffer: await self._write_file_data(self.buffer) self.buffer = b'' file_info = await self._finalize_current_file() if file_info: completed_files.append(file_info) break return completed_files async def cleanup(self): """Clean up any temporary files.""" if self.current_file_handle: try: self.current_file_handle.close() except: pass self.current_file_handle = None for temp_path in self.temp_files: try: if os.path.exists(temp_path): os.unlink(temp_path) await self.print(f"[UPLOAD-CLEANUP] Removed temp file: {temp_path}") except Exception as e: await self.print(f"[UPLOAD-CLEANUP] Error removing temp file {temp_path}: {e}") self.temp_files.clear() class FileDownloadHandler(HTTPServerHandler): """ HTTP server handler that provides secure file download functionality. Features: - File listing for GET requests without parameters - File download for GET requests with 'file' parameter - Directory traversal protection - MIME type detection - HTML directory listing """ def __init__(self, download_directory=None, max_upload_size=2*1024*1024*1024, max_file_size=500*1024*1024, allowed_extensions=None, max_files_per_request=100, print_cb = None): """ Initialize the file download handler. Args: download_directory (str): Directory to serve files from max_upload_size (int): Maximum total upload size per request in bytes (default: 2GB) max_file_size (int): Maximum individual file size in bytes (default: 500MB) allowed_extensions (set): Set of allowed file extensions (None = allow all) max_files_per_request (int): Maximum number of files per upload request (default: 100) """ super().__init__() self.download_directory = download_directory self.max_upload_size = max_upload_size self.max_file_size = max_file_size self.allowed_extensions = allowed_extensions self.max_files_per_request = max_files_per_request self.print_cb = print_cb if self.download_directory: # Resolve to absolute path and ensure it exists self.download_directory = os.path.abspath(self.download_directory) if not os.path.exists(self.download_directory): raise ValueError(f"Download directory does not exist: {self.download_directory}") if not os.path.isdir(self.download_directory): raise ValueError(f"Download path is not a directory: {self.download_directory}") async def print(self, msg = ''): if self.print_cb is None: return await self.print_cb(msg) def _sanitize_path(self, path_request): """ Sanitize the requested path to prevent directory traversal attacks while allowing safe navigation within the allowed directory tree. Args: path_request (str): The requested file or directory path Returns: str or None: Safe absolute path or None if invalid """ if not self.download_directory: return None if not path_request: return self.download_directory # Return root directory for empty path # Normalize the path request # Replace backslashes with forward slashes for consistency normalized_path = path_request.replace('\\', '/') # Remove any attempts to escape the directory with .. patterns # Split into components and filter out dangerous ones path_components = [] for component in normalized_path.split('/'): component = component.strip() if not component or component == '.': continue # Skip empty and current directory references if component == '..': continue # Skip parent directory references entirely if any(char in component for char in [':', '*', '?', '"', '<', '>', '|']): return None # Reject dangerous characters path_components.append(component) # Build the safe path if not path_components: return self.download_directory # Return root if no valid components safe_path = self.download_directory for component in path_components: safe_path = os.path.join(safe_path, component) safe_path = os.path.abspath(safe_path) # Ensure the path is within the download directory tree try: common_path = os.path.commonpath([safe_path, self.download_directory]) if common_path != self.download_directory: return None except ValueError: # Paths are on different drives (Windows) or other error return None return safe_path def _generate_directory_list_html(self, current_path=None): """ Generate an HTML page listing files and directories. Args: current_path (str): The current directory path to list Returns: str: HTML content """ if not self.download_directory: return "

File Server

No download directory configured.

" # Use the provided path or default to root if current_path is None: current_path = self.download_directory try: # Get relative path for display if current_path == self.download_directory: relative_path = "/" display_path = "Root Directory" else: relative_path = "/" + os.path.relpath(current_path, self.download_directory).replace(os.sep, '/') display_path = relative_path # Collect files and directories files = [] directories = [] for item in os.listdir(current_path): item_path = os.path.join(current_path, item) if os.path.isfile(item_path): size = os.path.getsize(item_path) files.append((item, size)) elif os.path.isdir(item_path): directories.append(item) # Sort alphabetically files.sort() directories.sort() escaped_directory = html.escape(current_path) escaped_display_path = html.escape(display_path) # Generate breadcrumb navigation breadcrumbs = self._generate_breadcrumbs(relative_path) # Build enhanced HTML content with modern styling and JavaScript html_content = ''' 📁 File Server - Directory Browser
''' # Add parent directory link if not at root if current_path != self.download_directory: parent_relative = os.path.dirname(relative_path.rstrip('/')) if parent_relative == '/': parent_relative = '' encoded_parent = urllib.parse.quote(parent_relative) html_content += ''' ''' # Add upload form with progress and command examples current_dir_encoded = urllib.parse.quote(relative_path.lstrip('/')) if relative_path != '/' else '' current_dir_param = f'?dir={current_dir_encoded}' if current_dir_encoded else '' server_url = 'http://YOUR_SERVER_IP:8080' # This would be dynamically set in real deployment html_content += f'''

📤 Upload Files

Uploading... 0%
💡 Drag and drop files or click to browse. Multiple files supported. Large files streamed efficiently.

📂 Directory Contents

''' # Add directories first for dirname in directories: escaped_dirname = html.escape(dirname) # Build the path for the directory if relative_path == '/': dir_path = dirname else: dir_path = relative_path.lstrip('/') + '/' + dirname encoded_dirname = urllib.parse.quote(dir_path) html_content += f''' ''' # Add files for filename, size in files: escaped_filename = html.escape(filename) # Build the path for the file if relative_path == '/': file_path = filename else: file_path = relative_path.lstrip('/') + '/' + filename encoded_filename = urllib.parse.quote(file_path) # Format file size and get appropriate icon size_str = self._format_file_size(size) # Get file extension for icon file_ext = os.path.splitext(filename)[1].lower() if file_ext in ['.jpg', '.jpeg', '.png', '.gif', '.bmp']: icon = '🖼️' elif file_ext in ['.mp4', '.avi', '.mkv', '.mov']: icon = '🎬' elif file_ext in ['.mp3', '.wav', '.flac', '.ogg']: icon = '🎵' elif file_ext in ['.pdf']: icon = '📕' elif file_ext in ['.txt', '.md']: icon = '📄' elif file_ext in ['.zip', '.rar', '.7z', '.tar', '.gz']: icon = '📦' else: icon = '📄' html_content += f''' ''' total_items = len(directories) + len(files) html_content += f'''
📋 Name 📁 Type 📏 Size 🕒 Actions
📁 {escaped_dirname} Directory - Browse
{icon} {escaped_filename} File {size_str} Download

📊 Directory Statistics

{len(directories)}
📁 Directories
{len(files)}
📄 Files
{total_items}
📋 Total Items
💾 Current path: {escaped_display_path}

💻 Command Line Upload Examples

Use these commands to upload files from your terminal or scripts.

# Upload a single file
curl -X POST -F "files=@/path/to/your/file.txt" "{server_url}/{current_dir_param}"

# Upload multiple files
curl -X POST \\
  -F "files=@file1.txt" \\
  -F "files=@file2.jpg" \\
  -F "files=@file3.pdf" \\
  "{server_url}/{current_dir_param}"

Usage: Replace /path/to/your/file.txt with your actual file path. Use -F "files=@filename" for each file.

# Create multipart form data (limited support)
echo -e "--boundary123\\r\\nContent-Disposition: form-data; name=\\"files\\"; filename=\\"test.txt\\"\\r\\nContent-Type: text/plain\\r\\n\\r\\nfile content here\\r\\n--boundary123--\\r\\n" | \\
wget --post-data=@- \\
     --header="Content-Type: multipart/form-data; boundary=boundary123" \\
     "{server_url}/{current_dir_param}" -O -

Note: wget has limited multipart support. Recommended: Use cURL for better compatibility and easier syntax.

# Upload a single file
$file = Get-Item "C:\\path\\to\\your\\file.txt"
$form = @{{'files' = $file}}
$response = Invoke-RestMethod -Uri "{server_url}/{current_dir_param}" -Method Post -Form $form
Write-Output $response

# Upload multiple files
$files = Get-ChildItem "C:\\path\\to\\files\\*" -Include *.txt,*.jpg,*.pdf
$form = @{{}}
foreach ($file in $files) {{
    $form.Add("files", $file)
}}
$response = Invoke-RestMethod -Uri "{server_url}/{current_dir_param}" -Method Post -Form $form
Write-Output $response

Usage: Replace the file paths with your actual Windows file paths. Works with PowerShell 3.0+.

#!/usr/bin/env python3
import requests
import os

# Upload a single file
def upload_single_file(file_path):
    url = "{server_url}/{current_dir_param}"
    with open(file_path, 'rb') as f:
        files = {{'files': f}}
        response = requests.post(url, files=files)
        print(f"Status: {{response.status_code}}")
        print(f"Response: {{response.text}}")
        return response

# Upload multiple files
def upload_multiple_files(file_paths):
    url = "{server_url}/{current_dir_param}"
    files = []
    try:
        for file_path in file_paths:
            files.append(('files', open(file_path, 'rb')))
        
        response = requests.post(url, files=files)
        print(f"Status: {{response.status_code}}")
        print(f"Response: {{response.text}}")
        return response
    finally:
        # Close all file handles
        for _, file_handle in files:
            file_handle.close()

# Example usage
if __name__ == "__main__":
    # Single file upload
    upload_single_file("/path/to/your/file.txt")
    
    # Multiple files upload
    file_list = [
        "/path/to/file1.txt",
        "/path/to/file2.jpg",
        "/path/to/file3.pdf"
    ]
    upload_multiple_files(file_list)

Installation: pip install requests
Usage: Replace file paths with your actual file paths. The script includes proper error handling and file cleanup.

''' return html_content except Exception as e: error_msg = str(e) return f"

Error

Failed to list directory: {html.escape(error_msg)}

" def _generate_breadcrumbs(self, relative_path): """ Generate breadcrumb navigation HTML. Args: relative_path (str): The current relative path Returns: str: HTML breadcrumb navigation """ if relative_path == '/': return '🏠 Root' breadcrumbs = ['🏠 Root'] # Split path into components parts = relative_path.strip('/').split('/') current_path = '' for i, part in enumerate(parts): if not part: continue current_path += '/' + part if current_path else part encoded_path = urllib.parse.quote(current_path) escaped_part = html.escape(part) if i == len(parts) - 1: # Current directory (not a link) breadcrumbs.append(f'📁 {escaped_part}') else: # Intermediate directory (link) breadcrumbs.append(f'📁 {escaped_part}') return ' → '.join(breadcrumbs) def _get_mime_type(self, filepath): """ Get the MIME type for a file. Args: filepath (str): Path to the file Returns: str: MIME type """ mime_type, _ = mimetypes.guess_type(filepath) return mime_type or 'application/octet-stream' async def do_GET(self, event): """ Handle GET requests for file listing, directory browsing, and downloads. Args: event: The HTTP request event """ try: # Parse the URL to get query parameters url_parts = urllib.parse.urlparse(event.target.decode('ascii')) query_params = urllib.parse.parse_qs(url_parts.query) # Check what type of request this is if 'file' in query_params and query_params['file']: # File download request file_path = query_params['file'][0] await self._serve_file(file_path, event.headers) elif 'dir' in query_params: # Directory listing request dir_path = query_params['dir'][0] if query_params['dir'][0] else '' await self._serve_directory_list(dir_path) else: # Root directory listing (no parameters) await self._serve_directory_list('') except Exception as e: await self._serve_error(500, f"Internal Server Error: {str(e)}") async def do_POST(self, event): """ Handle POST requests for file uploads. Args: event: The HTTP request event """ try: # Parse the URL to get query parameters (for target directory) url_parts = urllib.parse.urlparse(event.target.decode('ascii')) query_params = urllib.parse.parse_qs(url_parts.query) # Get target directory from query params or default to root target_dir = query_params.get('dir', [''])[0] await self._handle_file_upload(event, target_dir) except Exception as e: await self._serve_error(500, f"Upload Error: {str(e)}") async def _handle_file_upload(self, event, target_dir=''): """ Handle multipart file upload with memory-efficient streaming. Args: event: The HTTP request event target_dir (str): Target directory for upload (relative to download_directory) """ try: # Validate and sanitize target directory safe_target_path = self._sanitize_path(target_dir) if not safe_target_path or not os.path.isdir(safe_target_path): await self._serve_error(400, "Invalid target directory") return # Get Content-Type header content_type = None content_length = None for name, value in event.headers: if name.lower() == b'content-type': content_type = value.decode('ascii') elif name.lower() == b'content-length': content_length = int(value.decode('ascii')) if not content_type or not content_type.startswith('multipart/form-data'): await self._serve_error(400, "Only multipart/form-data uploads are supported") return if not content_length: await self._serve_error(400, "Content-Length header required") return # Extract boundary from Content-Type boundary_match = re.search(r'boundary=([^;]+)', content_type) if not boundary_match: await self._serve_error(400, "Missing boundary in Content-Type") return boundary = boundary_match.group(1).strip('"') boundary_bytes = f'--{boundary}'.encode('ascii') end_boundary_bytes = f'--{boundary}--'.encode('ascii') # Process the upload with streaming await self._process_multipart_upload(safe_target_path, boundary_bytes, end_boundary_bytes, content_length) except Exception as e: await self.print(f"[UPLOAD-ERROR] {e}") await self._serve_error(500, f"Upload processing error: {str(e)}") async def _process_multipart_upload(self, target_path, boundary_bytes, end_boundary_bytes, content_length): """ Process multipart upload data with streaming to avoid memory issues. Args: target_path (str): Safe target directory path boundary_bytes (bytes): Multipart boundary end_boundary_bytes (bytes): End boundary content_length (int): Total content length """ uploaded_files = [] bytes_processed = 0 chunk_size = 64 * 1024 # 64KB chunks for better performance max_buffer_size = 1024 * 1024 # 1MB max buffer to prevent memory issues # Validate upload size if content_length > self.max_upload_size: raise ValueError(f"Upload too large: {content_length} bytes (max: {self.max_upload_size})") # Initialize the multipart stream processor processor = MultipartStreamProcessor( boundary_bytes, target_path, max_buffer_size, max_file_size=self.max_file_size, allowed_extensions=self.allowed_extensions, max_files_per_request=self.max_files_per_request, print_cb = self.print_cb ) try: # Read request body in chunks while bytes_processed < content_length: remaining = content_length - bytes_processed read_size = min(chunk_size, remaining) # Read chunk from wrapper chunk = await self._read_upload_chunk(read_size) if not chunk: break bytes_processed += len(chunk) # Process chunk through streaming processor file_infos = await processor.process_chunk(chunk) uploaded_files.extend(file_infos) # Progress logging for large uploads if content_length > 10 * 1024 * 1024: # > 10MB progress = (bytes_processed / content_length) * 100 if bytes_processed % (1024 * 1024) == 0: # Log every MB await self.print(f"[UPLOAD-PROGRESS] {progress:.1f}% ({bytes_processed:,}/{content_length:,} bytes)") # Finalize any remaining data final_files = await processor.finalize() uploaded_files.extend(final_files) # Send success response await self._send_upload_success_response(uploaded_files, target_path) except ValueError as e: # Validation errors (file too large, too many files, etc.) await self.print(f"[UPLOAD-VALIDATION] {e}") await processor.cleanup() await self._serve_error(400, f"Upload validation failed: {str(e)}") except MemoryError as e: await self.print(f"[UPLOAD-MEMORY] Out of memory: {e}") await processor.cleanup() await self._serve_error(413, "Upload too large - insufficient memory") except OSError as e: # Disk space, permission issues, etc. await self.print(f"[UPLOAD-DISK] Disk error: {e}") await processor.cleanup() await self._serve_error(507, f"Server storage error: {str(e)}") except Exception as e: await self.print(f"[UPLOAD-ERROR] Unexpected error: {e}") await processor.cleanup() await self._serve_error(500, f"Upload processing failed: {str(e)}") async def _read_upload_chunk(self, size): """ Read a chunk of upload data from the HTTP request with timeout. """ try: # Add timeout to prevent hanging on slow/broken connections event = await asyncio.wait_for(self._wrapper.next_event(), timeout=30.0) if isinstance(event, h11.Data): return event.data[:size] if event.data else b'' elif isinstance(event, h11.EndOfMessage): return b'' # End of request body else: await self.print(f"[UPLOAD-ERROR] Unexpected event type: {type(event)}") return b'' except asyncio.TimeoutError: await self.print("[UPLOAD-ERROR] Timeout reading upload data") raise OSError("Upload timeout - connection too slow") except ConnectionError as e: await self.print(f"[UPLOAD-ERROR] Connection error: {e}") raise OSError(f"Upload connection lost: {e}") except Exception as e: await self.print(f"[UPLOAD-ERROR] Error reading chunk: {e}") raise OSError(f"Error reading upload data: {e}") async def _send_upload_success_response(self, uploaded_files, target_path): """ Send a success response after file upload. Args: uploaded_files (list): List of uploaded file info target_path (str): Target directory path """ try: # Get relative path for redirect if target_path == self.download_directory: redirect_path = "" else: redirect_path = os.path.relpath(target_path, self.download_directory).replace(os.sep, '/') # Create success HTML html_content = f''' Upload Successful

✅ Upload Successful!

Successfully uploaded {len(uploaded_files)} file(s):

Redirecting back to directory listing in 3 seconds...

Click here if not redirected automatically

''' body = html_content.encode('utf-8') headers = self.basic_headers() headers.extend([ ("Content-Type", "text/html; charset=utf-8".encode("ascii")), ("Content-Length", str(len(body)).encode("ascii")) ]) response = h11.Response(status_code=200, headers=headers) await self._wrapper.send(response) await self._wrapper.send(h11.Data(data=body)) await self._wrapper.send(h11.EndOfMessage()) except Exception as e: await self.print(f"[UPLOAD-ERROR] Error sending success response: {e}") raise def _format_file_size(self, size): """Format file size in human-readable format.""" if size > 1024 * 1024 * 1024: return f"{size / (1024*1024*1024):.1f} GB" elif size > 1024 * 1024: return f"{size / (1024*1024):.1f} MB" elif size > 1024: return f"{size / 1024:.1f} KB" else: return f"{size} bytes" async def _serve_directory_list(self, dir_path=''): """ Serve the HTML directory listing page. Args: dir_path (str): The directory path to list (relative to download_directory) """ try: # Sanitize the directory path safe_path = self._sanitize_path(dir_path) if not safe_path: await self._serve_error(400, "Invalid directory path") return if not os.path.exists(safe_path): await self._serve_error(404, "Directory not found") return if not os.path.isdir(safe_path): await self._serve_error(400, "Not a directory") return html_content = self._generate_directory_list_html(safe_path) body = html_content.encode('utf-8') headers = self.basic_headers() headers.extend([ ("Content-Type", "text/html; charset=utf-8".encode("ascii")), ("Content-Length", str(len(body)).encode("ascii")) ]) response = h11.Response(status_code=200, headers=headers) await self._wrapper.send(response) await self._wrapper.send(h11.Data(data=body)) await self._wrapper.send(h11.EndOfMessage()) except Exception as e: await self._serve_error(500, f"Error generating directory list: {str(e)}") async def _serve_file(self, file_path, request_headers=None): """ Serve a specific file for download with support for large files. Args: file_path (str): The requested file path (can include directories) request_headers (dict): HTTP request headers for range support """ # Sanitize the path safe_path = self._sanitize_path(file_path) if not safe_path: await self._serve_error(400, "Invalid filename") return if not os.path.exists(safe_path): await self._serve_error(404, "File not found") return if not os.path.isfile(safe_path): await self._serve_error(400, "Not a file") return try: # Get file info file_size = os.path.getsize(safe_path) mime_type = self._get_mime_type(safe_path) # Check for Range header (for partial content/resumable downloads) range_header = None if request_headers: for name, value in request_headers: if name.lower() == b'range': range_header = value.decode('ascii') break start_byte = 0 end_byte = file_size - 1 status_code = 200 # Parse range request if present if range_header and range_header.startswith('bytes='): try: range_spec = range_header[6:] # Remove 'bytes=' if '-' in range_spec: start, end = range_spec.split('-', 1) if start: start_byte = int(start) if end: end_byte = int(end) status_code = 206 # Partial Content except (ValueError, IndexError): # Invalid range, serve full file pass content_length = end_byte - start_byte + 1 # Prepare headers headers = self.basic_headers() headers.extend([ ("Content-Type", mime_type.encode("ascii")), ("Content-Length", str(content_length).encode("ascii")), ("Accept-Ranges", b"bytes"), ("Content-Disposition", f'attachment; filename="{os.path.basename(safe_path)}"'.encode("ascii")) ]) if status_code == 206: headers.append(("Content-Range", f"bytes {start_byte}-{end_byte}/{file_size}".encode("ascii"))) # Send response headers response = h11.Response(status_code=status_code, headers=headers) await self._wrapper.send(response) # Send file content in chunks (optimized for large files) chunk_size = 512 * 1024 # 512KB chunks for better performance on large files bytes_sent = 0 bytes_remaining = content_length with open(safe_path, 'rb') as f: # Seek to start position for range requests if start_byte > 0: f.seek(start_byte) while bytes_remaining > 0: # Read chunk (but not more than remaining bytes) read_size = min(chunk_size, bytes_remaining) chunk = f.read(read_size) if not chunk: break try: await self._wrapper.send(h11.Data(data=chunk)) bytes_sent += len(chunk) bytes_remaining -= len(chunk) # Optional: Log progress for very large files (>100MB) if content_length > 100 * 1024 * 1024 and bytes_sent % (10 * 1024 * 1024) == 0: progress = (bytes_sent / content_length) * 100 await self.print(f"[FILE-SERVER] Download progress: {progress:.1f}% ({bytes_sent:,}/{content_length:,} bytes)") except Exception as e: await self.print(f"[FILE-SERVER] Error sending chunk at {bytes_sent:,} bytes: {e}") raise await self._wrapper.send(h11.EndOfMessage()) except Exception as e: await self._serve_error(500, f"Error serving file: {str(e)}") async def _serve_error(self, status_code, message): """ Serve an error response. Args: status_code (int): HTTP status code message (str): Error message """ try: body = f"

Error {status_code}

{html.escape(message)}

".encode('utf-8') headers = self.basic_headers() headers.extend([ ("Content-Type", "text/html; charset=utf-8".encode("ascii")), ("Content-Length", str(len(body)).encode("ascii")) ]) response = h11.Response(status_code=status_code, headers=headers) await self._wrapper.send(response) await self._wrapper.send(h11.Data(data=body)) await self._wrapper.send(h11.EndOfMessage()) except Exception as e: await self.print(f"Error serving error response: {e}") async def run_file_server_from_target(target, download_directory=None, log_callback=None, max_upload_size=2*1024*1024*1024, max_file_size=500*1024*1024, allowed_extensions=None, max_files_per_request=100): """ Run the file download server from a target. Args: target: Server target configuration download_directory (str): Directory to serve files from log_callback: Logging callback function max_upload_size (int): Maximum total upload size per request in bytes (default: 2GB) max_file_size (int): Maximum individual file size in bytes (default: 500MB) allowed_extensions (set): Set of allowed file extensions (None = allow all) max_files_per_request (int): Maximum number of files per upload request (default: 100) """ try: handler_factory = lambda: FileDownloadHandler( download_directory, max_upload_size=max_upload_size, max_file_size=max_file_size, allowed_extensions=allowed_extensions, max_files_per_request=max_files_per_request ) server = HTTPServer(handler_factory, target, log_callback=log_callback) return await server.serve() except Exception as e: return None, e async def run_file_server(download_directory=None, host='127.0.0.1', port=8080, debug=False, max_upload_size=2*1024*1024*1024, max_file_size=500*1024*1024, allowed_extensions=None, max_files_per_request=100): """ Run the file download server. Args: download_directory (str): Directory to serve files from host (str): Host to bind to port (int): Port to bind to debug (bool): Enable debug logging max_upload_size (int): Maximum total upload size per request in bytes (default: 2GB) max_file_size (int): Maximum individual file size in bytes (default: 500MB) allowed_extensions (set): Set of allowed file extensions (None = allow all) max_files_per_request (int): Maximum number of files per upload request (default: 100) """ try: log_callback = None if debug: async def log_callback(msg): print(f"[FILE-SERVER] {msg}") if download_directory: print(f"Starting file server on {host}:{port}") print(f"Serving files from: {os.path.abspath(download_directory)}") else: print(f"Starting file server on {host}:{port} (no download directory)") if debug: print("Debug mode enabled - verbose logging active") target = UniTarget(host, port, UniProto.SERVER_TCP) server_task, err = await run_file_server_from_target( target, download_directory=download_directory, log_callback=log_callback, max_upload_size=max_upload_size, max_file_size=max_file_size, allowed_extensions=allowed_extensions, max_files_per_request=max_files_per_request ) if err is not None: print(f"Server error: {err}") await server_task except KeyboardInterrupt: print("\nServer stopped by user") except Exception as e: print(f"Server error: {e}") finally: if server_task is not None: server_task.cancel() print("Server stopped") def main(): """ Main entry point for the file server. """ import argparse import sys # Create argument parser parser = argparse.ArgumentParser( description='Asysocks File Server - Secure file upload/download server with web interface', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=''' Examples: %(prog)s # Start server on 127.0.0.1:8080 (no directory) %(prog)s /home/user/files # Serve files from directory %(prog)s /home/user/files --host 0.0.0.0 # Bind to all interfaces %(prog)s /home/user/files --port 9000 # Use custom port %(prog)s /home/user/files --debug # Enable debug logging %(prog)s --help # Show this help message Features: • Modern web interface with drag-and-drop uploads • Directory browsing with breadcrumb navigation • Real-time upload progress bars • Command-line examples (cURL, wget, PowerShell, Python) • Secure file handling with directory traversal protection • Large file streaming support ''') # Add arguments parser.add_argument( 'directory', nargs='?', help='Directory to serve files from (optional)' ) parser.add_argument( '--host', '-H', default='127.0.0.1', help='Host to bind to (default: 127.0.0.1)' ) parser.add_argument( '--port', '-p', type=int, default=8080, help='Port to bind to (default: 8080)' ) parser.add_argument( '--debug', '-d', action='store_true', help='Enable debug logging (shows detailed server activity)' ) parser.add_argument( '--max-upload-size', type=int, default=2*1024*1024*1024, help='Maximum total upload size per request in bytes (default: 2GB)' ) parser.add_argument( '--max-file-size', type=int, default=500*1024*1024, help='Maximum individual file size in bytes (default: 500MB)' ) parser.add_argument( '--max-files', type=int, default=100, help='Maximum number of files per upload request (default: 100)' ) parser.add_argument( '--allowed-extensions', type=str, help='Comma-separated list of allowed file extensions (e.g., ".txt,.jpg,.pdf")' ) parser.add_argument( '--version', '-v', action='version', version='Asysocks File Server 1.0.0' ) # Parse arguments args = parser.parse_args() # Validate download directory if provided if args.directory: if not os.path.exists(args.directory): print(f"Error: Directory '{args.directory}' does not exist") sys.exit(1) if not os.path.isdir(args.directory): print(f"Error: '{args.directory}' is not a directory") sys.exit(1) # Validate port range if args.port < 1 or args.port > 65535: print(f"Error: Port must be between 1 and 65535, got {args.port}") sys.exit(1) # Validate and parse upload limits if args.max_upload_size < 1024: # At least 1KB print(f"Error: max-upload-size must be at least 1024 bytes, got {args.max_upload_size}") sys.exit(1) if args.max_file_size < 1024: # At least 1KB print(f"Error: max-file-size must be at least 1024 bytes, got {args.max_file_size}") sys.exit(1) if args.max_files < 1: print(f"Error: max-files must be at least 1, got {args.max_files}") sys.exit(1) # Parse allowed extensions allowed_extensions = None if args.allowed_extensions: allowed_extensions = set() for ext in args.allowed_extensions.split(','): ext = ext.strip() if not ext.startswith('.'): ext = '.' + ext allowed_extensions.add(ext.lower()) # Show startup info print("Asysocks File Server") print("=" * 50) if args.directory: print(f"Directory: {os.path.abspath(args.directory)}") else: print("Directory: None (upload-only mode)") print(f"Address: http://{args.host}:{args.port}") if args.debug: print("Debug: Enabled") print(f"Max upload size: {args.max_upload_size / (1024*1024):.0f} MB") print(f"Max file size: {args.max_file_size / (1024*1024):.0f} MB") print(f"Max files per request: {args.max_files}") if allowed_extensions: print(f"Allowed extensions: {', '.join(sorted(allowed_extensions))}") else: print("Allowed extensions: All") print("=" * 50) # Run the server try: asyncio.run(run_file_server( args.directory, args.host, args.port, args.debug, max_upload_size=args.max_upload_size, max_file_size=args.max_file_size, allowed_extensions=allowed_extensions, max_files_per_request=args.max_files )) except KeyboardInterrupt: print("\nServer stopped by user") except Exception as e: print(f"Failed to start server: {e}") sys.exit(1) if __name__ == '__main__': main() asysocks-0.2.18/asysocks/examples/socksbrute.py000066400000000000000000000212421510034457600216620ustar00rootroot00000000000000 import asyncio import logging import traceback import copy from asysocks import logger from asysocks._version import __banner__ from asysocks.protocol.http import HTTPProxyAuthFailed from asysocks.protocol.socks5 import SOCKS5AuthFailed from asysocks.client import SOCKSClient, SocksTunnelError from asysocks.common.credentials import SocksCredential from asysocks.common.comms import SocksQueueComms from asysocks.protocol.socks5 import SOCKS5Method, SOCKS5Nego, SOCKS5NegoReply, SOCKS5Request, SOCKS5Reply, SOCKS5ReplyType, SOCKS5Command, SOCKS5PlainAuth, SOCKS5PlainAuthReply, SOCKS5ServerErrorReply from asysocks.common.clienturl import SocksClientURL class FileStringGen: """ Parses a file line by line """ def __init__(self, filename): self.filename = filename async def run(self, target_q): try: cnt = 0 with open(self.filename, 'r') as f: for line in f: line = line.strip() if line == '': continue await target_q.put(line) await asyncio.sleep(0) cnt += 1 return cnt, None except Exception as e: return cnt, e class ListStringGen: def __init__(self, targets): self.targets = targets async def run(self, target_q): try: cnt = 0 for target in self.targets: await target_q.put(target) await asyncio.sleep(0) cnt += 1 return cnt, None except Exception as e: return cnt, e class SOCKSBrute: def __init__(self, proxies): self.proxies = proxies self.user_gens = [] self.password_gens = [] self.passwords = {} self.worker_cnt = 100 self.timeout = 1 self.max_retries = 5 self.retries_sleep = 1 self.verify_target = '8.8.8.8' self.verify_port = 53 self.output_file = None self.only_positive = False self.__worker_tasks = [] self.__addr_queue = None self.__target_queue = None self.__result_queue = None self.__total_addrs = 0 self.__total_targets = 0 self.__total_finished = 0 async def __scanner(self): try: while True: try: temp = await self.__target_queue.get() if temp is None: return username, password, client = temp retries = self.max_retries while retries != 0: if retries != self.max_retries: await asyncio.sleep(self.retries_sleep) retries -= 1 _, err = await client.handle_queue() if err is not None: if isinstance(err, (HTTPProxyAuthFailed, SOCKS5AuthFailed)): await self.__result_queue.put((username, password, False, None)) break elif isinstance(err, SocksTunnelError): logger.debug('Tunnel setup failed!. Reason %s' % traceback.format_tb(err.__traceback__)) elif isinstance(err, OSError): logger.debug('Failed socket connection to initial proxy!. Reason %s' % traceback.format_tb(err.__traceback__)) continue await self.__result_queue.put((username, password, True, None)) break if retries == 0: await self.__result_queue.put((username, password, None, err)) except asyncio.CancelledError: return except Exception as e: pass except asyncio.CancelledError: return except Exception as e: print(e) async def __gen_targets(self): try: while True: password = await self.__passwords_queue.get() if password is None: break self.passwords[password] = 1 while True: user = await self.__users_queue.get() if user is None: break for password in self.passwords: self.__total_targets += 1 credential = SocksCredential() credential.username = user credential.password = password proxies = copy.deepcopy(self.proxies) proxies[-1].only_open = True proxies[-1].only_auth = True proxies[-1].endpoint_ip = self.verify_target proxies[-1].endpoint_port = self.verify_port proxies[-1].credential = credential in_q = asyncio.Queue() out_q = asyncio.Queue() comms = SocksQueueComms(in_q, out_q) client = SOCKSClient(comms, proxies) await self.__target_queue.put((user, password, client)) self.__target_gen_finished_evt.set() except Exception as e: print(e) async def __gen_users(self): try: for user_gen in self.user_gens: cnt, err = await user_gen.run(self.__users_queue) if err is not None: print(err) self.__total_addrs += cnt await self.__users_queue.put(None) except Exception as e: print(e) async def __gen_passwords(self): try: for password_gen in self.password_gens: cnt, err = await password_gen.run(self.__passwords_queue) if err is not None: print(err) self.__total_addrs += cnt await self.__passwords_queue.put(None) except Exception as e: print(e) async def run(self): try: self.__passwords_queue = asyncio.Queue() self.__users_queue = asyncio.Queue() self.__target_queue = asyncio.Queue(self.worker_cnt) self.__result_queue = asyncio.Queue() self.__target_gen_finished_evt = asyncio.Event() asyncio.create_task(self.__gen_passwords()) asyncio.create_task(self.__gen_users()) asyncio.create_task(self.__gen_targets()) for _ in range(self.worker_cnt): self.__worker_tasks.append(asyncio.create_task(self.__scanner())) outfile = None if self.output_file is not None: outfile = open(self.output_file, 'w', newline = '') while True: username, password, status, err = await self.__result_queue.get() self.__total_finished += 1 if err is not None: print(err) print(traceback.format_tb(err.__traceback__)) if err is None: if outfile is not None: outfile.write('%s:%s\r\n' % (username, password)) elif status is True: print('%s:%s -> %s' % (username, password, status)) elif self.only_positive is False: print('%s:%s -> %s' % (username, password, status)) else: if self.only_positive is False: print('%s:%s -> %s' % (username, password, status)) if self.__target_gen_finished_evt.is_set() is True: if self.__total_finished == self.__total_targets: break for _ in range(len(self.__worker_tasks)): await self.__target_queue.put(None) for worker in self.__worker_tasks: worker.cancel() return True, None except Exception as e: return False, e def main(): import argparse parser = argparse.ArgumentParser(description='SOCKS5 proxy auth bruteforcer') parser.add_argument('-u', '--users', action='append', help='User or users file with one user per line. can be stacked') parser.add_argument('-p', '--passwords', action='append', help='Password or password file with one password per line. can be stacked') parser.add_argument('-t', '--timeout', type = int, default = None, help='Brute retries sleep time') parser.add_argument('-w', '--worker-count', type = int, default = 1, help='Parallelism') parser.add_argument('-v', '--verbose', action='count', default=0, help='Verbosity') parser.add_argument('-s', '--silent', action='store_true', help = 'dont print banner') parser.add_argument('-o', '--out-file', help = 'output file') parser.add_argument('--positive', action='store_true', help = 'only show sucsessful results') parser.add_argument('proxies', nargs='*', help='connection string(s) of the target proxy. if you want a chain of proxies to use before reaching the target then supply the chain in order, the target must be the LAST OF THE URLS!!!') args = parser.parse_args() if args.silent is False: print(__banner__) logger.setLevel(100) if args.verbose >=1: logger.setLevel(logging.DEBUG) elif args.verbose > 2: logger.setLevel(1) proxychain = SocksClientURL.from_urls(args.proxies) brute = SOCKSBrute(proxychain) brute.timeout = args.timeout brute.worker_cnt = args.worker_count brute.output_file = args.out_file brute.only_positive = args.positive if args.users is None or args.passwords is None: print('Users "-u" and Passwords "-p" must be set! Exiting') return notfile = [] for target in args.users: try: f = open(target, 'r') f.close() brute.user_gens.append(FileStringGen(target)) except: notfile.append(target) if len(notfile) > 0: brute.user_gens.append(ListStringGen(notfile)) if len(brute.user_gens) == 0: print('[-] No suitable users were found!') return notfile = [] for target in args.passwords: try: f = open(target, 'r') f.close() brute.password_gens.append(FileStringGen(target)) except: notfile.append(target) if len(notfile) > 0: brute.password_gens.append(ListStringGen(notfile)) if len(brute.password_gens) == 0: print('[-] No suitable passwords were found!') return _, err = asyncio.run(brute.run()) if err is not None: print('Failed to perform bruting! Reason: %s' % err) return if args.silent is False: print('Done!') if __name__ == '__main__': main()asysocks-0.2.18/asysocks/examples/socksportscan.py000066400000000000000000000212141510034457600223710ustar00rootroot00000000000000 import asyncio import ipaddress import logging import copy from asysocks import logger from asysocks.client import SOCKSClient from asysocks.common.comms import SocksQueueComms from asysocks.protocol.socks5 import SOCKS5Method, SOCKS5Nego, SOCKS5NegoReply, SOCKS5Request, SOCKS5Reply, SOCKS5ReplyType, SOCKS5Command, SOCKS5PlainAuth, SOCKS5PlainAuthReply, SOCKS5ServerErrorReply from asysocks.common.clienturl import SocksClientURL class FileTargetPortGen: def __init__(self, filename): self.filename = filename async def run(self, target_q): try: cnt = 0 with open(self.filename, 'r') as f: for line in f: line = line.strip() if line == '': continue if line.find('-') != -1: start, end = line.split('-') for i in range(int(start), int(end)): await target_q.put(i) await asyncio.sleep(0) cnt += 1 else: try: line = int(line) await target_q.put(line) await asyncio.sleep(0) cnt += 1 except: pass return cnt, None except Exception as e: return cnt, e class FileTargetIPGen: """ Parses a file line by line looking for ip address or ipaddres range or hostname """ def __init__(self, filename): self.filename = filename async def run(self, target_q): try: cnt = 0 with open(self.filename, 'r') as f: for line in f: line = line.strip() if line == '': continue try: ipaddress.ip_address(line) await target_q.put(line) await asyncio.sleep(0) cnt += 1 continue except: pass try: for ipaddr in ipaddress.ip_network(line,strict=False): await target_q.put(str(ipaddr)) await asyncio.sleep(0) cnt += 1 continue except: pass await target_q.put(line) await asyncio.sleep(0) cnt += 1 return cnt, None except Exception as e: return cnt, e class ListTargetIPGen: def __init__(self, targets): self.targets = targets async def run(self, target_q): try: cnt = 0 for target in self.targets: try: ipaddress.ip_address(target) await target_q.put(target) await asyncio.sleep(0) cnt += 1 continue except: pass try: for ipaddr in ipaddress.ip_network(target,strict=False): await target_q.put(str(ipaddr)) await asyncio.sleep(0) cnt += 1 continue except: pass await target_q.put(target) await asyncio.sleep(0) cnt += 1 return cnt, None except Exception as e: return cnt, e class ListTargetPortGen: def __init__(self, targets): self.targets = targets self.ports = {} def run(self): try: for target in self.targets: if target.find('-') != -1: start, end = target.split('-') for i in range(int(start), int(end)): self.ports[i] = 1 else: try: target = int(target) self.ports[target] = 1 except: pass return self.ports except Exception as e: return e class SOCKSPortscan: def __init__(self, proxyies): self.proxyies = proxyies self.target_gens = [] self.port_gens = [] self.ports = {} self.worker_cnt = 10 self.max_retries = 1 self.retries_timeout = 1 self.__worker_tasks = [] self.__addr_queue = None self.__target_queue = None self.__result_queue = None self.__total_addrs = 0 self.__total_targets = 0 self.__total_finished = 0 self.__addr_gen_finished_evt = None async def __scanner(self): try: while True: try: temp = await self.__target_queue.get() if temp is None: return ip, port, client = temp retries = self.max_retries while retries != 0: if retries != self.max_retries: await asyncio.sleep(self.retries_timeout) retries -= 1 _, err = await client.handle_queue() if err is not None: if isinstance(err, SOCKS5ServerErrorReply): if err.reply in [SOCKS5ReplyType.TTL_EXPIRED, SOCKS5ReplyType.CONN_REFUSED, SOCKS5ReplyType.HOST_UNREACHABLE, SOCKS5ReplyType.CONN_NOT_ALLOWED]: await self.__result_queue.put((ip, port, False, None)) elif err.reply in [SOCKS5ReplyType.ADDRESS_TYPE_NOT_SUPPORTED]: await self.__result_queue.put((ip, port, None, err)) break continue await self.__result_queue.put((ip, port, True, None)) break if retries == 0: await self.__result_queue.put((ip, port, None, Exception('Max tries reached!'))) except asyncio.CancelledError: return except Exception as e: print(e) pass except asyncio.CancelledError: return except Exception as e: print(e) async def __gen_targets(self): try: for port_gen in self.port_gens: ports = port_gen.run() for port in ports: self.ports[port] = 1 while True: addr = await self.__addr_queue.get() if addr is None: break for port in self.ports: self.__total_targets += 1 proxyies = copy.deepcopy(self.proxyies) proxyies[-1].endpoint_ip = addr proxyies[-1].endpoint_port = port proxyies[-1].timeout = None proxyies[-1].only_open = True in_q = asyncio.Queue() out_q = asyncio.Queue() comms = SocksQueueComms(in_q, out_q) client = SOCKSClient(comms, proxyies) await self.__target_queue.put((addr, port, client)) self.__target_gen_finished_evt.set() except Exception as e: print(e) async def __gen_addrs(self): try: for target_gen in self.target_gens: cnt, err = await target_gen.run(self.__addr_queue) if err is not None: print(err) self.__total_addrs += cnt await self.__addr_queue.put(None) self.__addr_gen_finished_evt.set() except Exception as e: print(e) async def run(self): try: self.__addr_queue = asyncio.Queue() self.__target_queue = asyncio.Queue() self.__result_queue = asyncio.Queue() self.__addr_gen_finished_evt = asyncio.Event() self.__target_gen_finished_evt = asyncio.Event() asyncio.create_task(self.__gen_addrs()) asyncio.create_task(self.__gen_targets()) for _ in range(self.worker_cnt): self.__worker_tasks.append(asyncio.create_task(self.__scanner())) while True: ip, port, status, err = await self.__result_queue.get() self.__total_finished += 1 if status is True: print('%s:%s -> %s' % (ip, port, status)) if self.__target_gen_finished_evt.is_set() is True: if self.__total_finished == self.__total_targets: print('END!') print(self.__total_finished) print(self.__total_targets) break for _ in range(len(self.__worker_tasks)): await self.__target_queue.put(None) for worker in self.__worker_tasks: worker.cancel() except Exception as e: print(e) def main(): import argparse parser = argparse.ArgumentParser(description='SOCKS/HTTP proxy port scanner') parser.add_argument('-p', '--port', action='append', required= True, help='port to scan / port range to scan / port range file. can be stacked') parser.add_argument('--timeout', type = int, default = None, help='Scan retries sleep time') parser.add_argument('-r', '--retries', type = int, default = 1, help='Retries for testing the port') parser.add_argument('-w', '--worker-count', type = int, default = 1, help='Parallelism') parser.add_argument('-v', '--verbose', action='count', default=0) parser.add_argument('-t', '--target', action='append', required=True, help='IP address / IP range (CDIR) / targets file') parser.add_argument('proxies', nargs='*', help='connection string(s) of the target proxy. if you want a chain of proxies') args = parser.parse_args() logger.setLevel(100) if args.verbose >=1: logger.setLevel(logging.DEBUG) elif args.verbose > 2: logger.setLevel(1) proxychain = SocksClientURL.from_urls(args.proxies) scanner = SOCKSPortscan(proxychain) scanner.max_retries = args.retries scanner.retries_timeout = args.timeout scanner.worker_cnt = args.worker_count notfile = [] for target in args.target: try: f = open(target, 'r') f.close() scanner.target_gens.append(FileTargetIPGen(target)) except: notfile.append(target) if len(notfile) > 0: scanner.target_gens.append(ListTargetIPGen(notfile)) if len(scanner.target_gens) == 0: print('[-] No suitable targets were found!') return notfile = [] for target in args.port: try: f = open(target, 'r') f.close() scanner.port_gens.append(FileTargetPortGen(target)) except: notfile.append(target) if len(notfile) > 0: scanner.port_gens.append(ListTargetPortGen(notfile)) if len(scanner.port_gens) == 0: print('[-] No suitable ports were found!') return asyncio.run(scanner.run()) if __name__ == '__main__': main()asysocks-0.2.18/asysocks/examples/socksproxy.py000066400000000000000000000045371510034457600217320ustar00rootroot00000000000000 import asyncio import logging import ssl from asysocks.server import SOCKSServer, srvlogger from asysocks._version import __banner__ async def amain(): try: import argparse parser = argparse.ArgumentParser(description='Universal proxy server (SOCKS4/5 and HTTP proxy') parser.add_argument('--listen-ip', default = '127.0.0.1', help='Listen IP') parser.add_argument('--listen-port', type = int, default = 1080, help='Listen port') parser.add_argument('--cert', help='SSL certificate file') parser.add_argument('--key', help='SSL key file') parser.add_argument('--client-timeout', type=int, default=10, help='How much time to wait ftill the client finishes initial handshake') parser.add_argument('--buffer-size', type=int, default=10240, help='Proxy buffer size') parser.add_argument('-m', '--monitor', action='store_true', help='Monitor mode.') parser.add_argument('-v', '--verbose', action='count', default=0, help='Verbosity') parser.add_argument('-s', '--silent', action='store_true', help = 'dont print banner') args = parser.parse_args() if args.silent is False: print(__banner__) if args.verbose >=1: srvlogger.setLevel(logging.DEBUG) ssl_ctx = None client_timeout = args.client_timeout buffer_size = args.buffer_size supported_protocols = ['SOCKS4','SOCKS5', 'HTTP'] monitor_dispatch_q = None if args.cert is not None: ssl_ctx = ssl.SSLContext() ssl_ctx.load_cert_chain(args.cert, keyfile=args.key) if args.monitor is True: from asysocks.intercepting.server import InterceptServer from asysocks.certmanager import CertManager certmanager = CertManager() log_queue = asyncio.Queue() server = SOCKSServer( args.listen_ip, args.listen_port, ssl_ctx, client_timeout = client_timeout, buffer_size = buffer_size, supported_protocols = supported_protocols ) if args.monitor is True: server = InterceptServer(server, certmanager, log_queue) asyncio.create_task(server.run()) if args.silent is False: print('Proxy server is up and running on %s:%s %s' % (args.listen_ip, args.listen_port, '' if ssl_ctx is None else '(SSL)')) if args.monitor is True: while True: trafficlog = await log_queue.get() print(str(trafficlog)) else: await server.run() except Exception as e: print(e) def main(): asyncio.run(amain()) if __name__ == '__main__': main()asysocks-0.2.18/asysocks/examples/sockssec.py000066400000000000000000000074251510034457600213220ustar00rootroot00000000000000 import asyncio import logging import traceback import ssl from asysocks import logger from asysocks._version import __banner__ from asysocks.security import SocksSecurity def main(): import argparse parser = argparse.ArgumentParser(description='SOCKS5 proxy security tester') parser.add_argument('host', help='') parser.add_argument('port', help='') parser.add_argument('-t', '--timeout', type = int, default = None, help='Timeout') parser.add_argument('-v', '--verbose', action='count', default=0, help='Verbosity') parser.add_argument('--ssl', action='store_true', help='SSL') parser.add_argument('-s', '--silent', action='store_true', help='SSL') parser.add_argument('--verify-host', help='Destination host to test proxy on') parser.add_argument('--verify-port', type = int, help='Destination port to test proxy on') parser.add_argument('--verify-send', help='Data to send to destination to test the proxy') parser.add_argument('--verify-recv', help='Data expected to be recieved from the destination via proxy') parser.add_argument('-u', '--username', help='Username for proxy auth') parser.add_argument('-p', '--password', help='Password for proxy auth') args = parser.parse_args() if args.silent is False: print(__banner__) logger.setLevel(100) if args.verbose >=1: logger.setLevel(logging.DEBUG) elif args.verbose > 2: logger.setLevel(1) socsec = SocksSecurity() socsec.server_ip = args.host socsec.server_port = args.port socsec.server_sslctx = None if args.ssl is False else ssl.create_default_context() socsec.timeout = args.timeout socsec.verify_host = args.verify_host #endpoint ip/hostname to test the connection socsec.verify_port = args.verify_port #endpoint port socsec.verify_send = args.verify_send #data to be sent to the endpoint service upon succsessfull connection socsec.verify_recv = args.verify_recv #data expected from the endpoint service in response to verify_send supported, notsupported, errors = asyncio.run(socsec.socks5_authmethods()) for method, err in errors: print('[AUTHMETHOD][E] %s : %s' % (method.name, err)) for method in supported: print('[AUTHMETHOD][+] %s' % method.name) for method in notsupported: print('[AUTHMETHOD][-] %s' % method.name) res, err = asyncio.run(socsec.socks5_noauth()) if err is not None: print('[NOAUTH][E] Error while determining noauth. Reson: %s' % err) elif res is True: print('[NOAUTH][+] No authentication needed') elif res is False: print('[NOAUTH][-] Authentication needed') res, err = asyncio.run(socsec.socks5_bind(args.username, args.password)) if err is not None: print('[BIND][E] Error while determining BIND support. Reson: %s' % err) elif res is True: print('[BIND][+] Server supports BIND') elif res is False: print('[BIND][-] Server doesnt support BIND') res, err = asyncio.run(socsec.socks5_local(args.username, args.password)) if err is not None: print('[LOCALPORT][E] Error while determining local connect support. Reson: %s' % err) elif res is True: print('[LOCALPORT][+] Server supports connecting to localhost') elif res is False: print('[LOCALPORT][-] Server doesnt support connecting to localhost') if args.verify_host is not None and args.verify_port is not None: if args.username is not None: res, err = asyncio.run(socsec.socks5_login(args.username, args.password)) else: res, err = asyncio.run(socsec.socks5_noauth()) if err is not None: print('[CONNTEST][E] Error while performing connection test! Reason: %s' % err) elif res is True: print('[CONNTEST][+] Server sucsessfully connected to %s:%s' % (args.verify_host, args.verify_port)) elif res is False: print('[CONNTEST][-] Server failed to connect to %s:%s' % (args.verify_host, args.verify_port)) if args.silent is False: print('Done!') if __name__ == '__main__': main()asysocks-0.2.18/asysocks/examples/sockstunnel.py000066400000000000000000000045751510034457600220600ustar00rootroot00000000000000 import logging import asyncio from asysocks import logger from asysocks._version import __banner__ from asysocks.client import SOCKSClient from asysocks.common.clienturl import SocksClientURL from asysocks.common.comms import SocksLitenerComms def main(): import argparse parser = argparse.ArgumentParser(description='Transparent TCP tunnel for SOCKS unaware clients.') parser.add_argument('-l', '--listen-ip', default = '127.0.0.1', help='Listener IP address to bind to') parser.add_argument('-p', '--listen-port', type = int, default = 11111, help='Listener port number to bind to') parser.add_argument('-t', '--timeout', type = int, default = None, help='Endpoint timeout') parser.add_argument('-v', '--verbose', action='count', default=0) parser.add_argument('dst_ip', help='IP address of the desination server') parser.add_argument('dst_port', type = int, help='port number of the desination service') parser.add_argument('proxy_connection_strings', nargs='*', help='connection string(s) decribing the socks proxy server connection properties') args = parser.parse_args() if args.verbose >=1: logger.setLevel(logging.DEBUG) elif args.verbose > 2: logger.setLevel(1) comms = SocksLitenerComms(args.listen_ip, args.listen_port) prev_url = None proxies = [] for con_str in args.proxy_connection_strings[::-1]: url = SocksClientURL.from_url(con_str) if prev_url is None: url.endpoint_ip = args.dst_ip url.endpoint_port = args.dst_port url.endpoint_timeout = args.timeout else: prev_target = prev_url.get_target() url.endpoint_ip = prev_target.server_ip url.endpoint_port = prev_target.server_port url.endpoint_timeout = args.timeout prev_url = url proxies.append(url) proxies = proxies[::-1] targets = [ x.get_target() for x in proxies] credentials = None if args.verbose >=1: print(str(targets)) print(__banner__) tunnel = '' for target in targets: tunnel += "|--->| (%s) %s:%s " % (target.version.name.upper() ,target.server_ip, target.server_port) print(tunnel) layout = """Connection layout CLIENT --->| CLIENT --->|(LISTENER) %s:%s %s |--->| (FINAL DST) %s:%s CLIENT --->| """ % (args.listen_ip, args.listen_port, tunnel , args.dst_ip, args.dst_port) print(layout) client = SOCKSClient(comms, targets, credentials) print('Waiting for incoming connections') asyncio.run(client.run()) if __name__ == '__main__': main()asysocks-0.2.18/asysocks/examples/webdavserver.py000066400000000000000000001511761510034457600222070ustar00rootroot00000000000000#!/usr/bin/env python3 """ WebDAV Server Example This example demonstrates how to create a fully-featured WebDAV server using the HTTPServerHandler class. The server implements the WebDAV protocol (RFC 4918) with support for: - File and directory operations (GET, PUT, DELETE, MKCOL) - WebDAV properties (PROPFIND, PROPPATCH) - File locking (LOCK, UNLOCK) - File copying and moving (COPY, MOVE) - WebDAV-compliant XML responses - Integration with file managers and WebDAV clients Usage: python webdavserver.py [directory_path] [host] [port] Example: python webdavserver.py ./webdav-root 127.0.0.1 8080 """ import asyncio import os import urllib.parse import mimetypes import html import h11 import tempfile import re import uuid import time import json from pathlib import Path from datetime import datetime, timezone from xml.etree import ElementTree as ET from xml.dom import minidom from asysocks.unicomm.common.target import UniTarget, UniProto from asysocks.unicomm.protocol.server.http.httpserver import HTTPServerHandler, HTTPServer class WebDAVLock: """Represents a WebDAV lock on a resource.""" def __init__(self, token, scope, type_lock, owner, timeout=3600, depth="0"): self.token = token self.scope = scope # "exclusive" or "shared" self.type = type_lock # "write" self.owner = owner self.timeout = timeout # seconds self.depth = depth # "0", "1", or "infinity" self.created = time.time() def is_expired(self): return time.time() - self.created > self.timeout def to_xml(self, href): """Convert lock to WebDAV XML format.""" lock_elem = ET.Element("D:activelock", xmlns="DAV:") locktype_elem = ET.SubElement(lock_elem, "D:locktype") ET.SubElement(locktype_elem, f"D:{self.type}") lockscope_elem = ET.SubElement(lock_elem, "D:lockscope") ET.SubElement(lockscope_elem, f"D:{self.scope}") depth_elem = ET.SubElement(lock_elem, "D:depth") depth_elem.text = self.depth owner_elem = ET.SubElement(lock_elem, "D:owner") owner_elem.text = self.owner timeout_elem = ET.SubElement(lock_elem, "D:timeout") timeout_elem.text = f"Second-{self.timeout}" locktoken_elem = ET.SubElement(lock_elem, "D:locktoken") href_elem = ET.SubElement(locktoken_elem, "D:href") href_elem.text = f"opaquelocktoken:{self.token}" lockroot_elem = ET.SubElement(lock_elem, "D:lockroot") lockroot_href = ET.SubElement(lockroot_elem, "D:href") lockroot_href.text = href return lock_elem class WebDAVHandler(HTTPServerHandler): """ WebDAV server handler implementing RFC 4918 WebDAV specification. Features: - Complete WebDAV method support (PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK) - XML property handling with namespace support - File locking mechanism - Directory traversal protection - MIME type detection - WebDAV-compliant error responses """ def __init__(self, webdav_root=None, enable_locks=True, default_lock_timeout=3600, max_depth=100, print_cb=None): """ Initialize the WebDAV handler. Args: webdav_root (str): Root directory to serve WebDAV from enable_locks (bool): Whether to enable WebDAV locking default_lock_timeout (int): Default lock timeout in seconds max_depth (int): Maximum depth for PROPFIND operations print_cb (callable): Logging callback function """ super().__init__() self.webdav_root = webdav_root self.enable_locks = enable_locks self.default_lock_timeout = default_lock_timeout self.max_depth = max_depth self.print_cb = print_cb # Lock storage: path -> WebDAVLock self.locks = {} # WebDAV namespaces self.namespaces = { 'D': 'DAV:', 'xml': 'http://www.w3.org/XML/1998/namespace' } if self.webdav_root: # Resolve to absolute path and ensure it exists self.webdav_root = os.path.abspath(self.webdav_root) if not os.path.exists(self.webdav_root): os.makedirs(self.webdav_root) if not os.path.isdir(self.webdav_root): raise ValueError(f"WebDAV root is not a directory: {self.webdav_root}") async def print(self, msg=''): """Log a message if print callback is available.""" if self.print_cb is None: return await self.print_cb(msg) def _sanitize_path(self, path_request): """ Sanitize the requested path to prevent directory traversal attacks. Args: path_request (str): The requested file or directory path Returns: str or None: Safe absolute path or None if invalid """ if not self.webdav_root: return None if not path_request: return self.webdav_root # Normalize the path request normalized_path = path_request.replace('\\', '/') # Remove dangerous path components path_components = [] for component in normalized_path.split('/'): component = component.strip() if not component or component == '.': continue if component == '..': continue # Skip parent directory references if any(char in component for char in [':', '*', '?', '"', '<', '>', '|']): return None path_components.append(component) # Build the safe path safe_path = self.webdav_root for component in path_components: safe_path = os.path.join(safe_path, component) safe_path = os.path.abspath(safe_path) # Ensure the path is within the webdav root try: common_path = os.path.commonpath([safe_path, self.webdav_root]) if common_path != self.webdav_root: return None except ValueError: return None return safe_path def _get_relative_path(self, absolute_path): """Get relative path from WebDAV root.""" if not self.webdav_root or not absolute_path: return "/" try: rel_path = os.path.relpath(absolute_path, self.webdav_root) if rel_path == ".": return "/" return "/" + rel_path.replace(os.sep, "/") except ValueError: return "/" def _get_mime_type(self, filepath): """Get the MIME type for a file.""" mime_type, _ = mimetypes.guess_type(filepath) return mime_type or 'application/octet-stream' def _format_http_date(self, timestamp=None): """Format timestamp as HTTP date.""" if timestamp is None: timestamp = time.time() dt = datetime.fromtimestamp(timestamp, tz=timezone.utc) return dt.strftime('%a, %d %b %Y %H:%M:%S GMT') def _format_iso_date(self, timestamp=None): """Format timestamp as ISO 8601 date.""" if timestamp is None: timestamp = time.time() dt = datetime.fromtimestamp(timestamp, tz=timezone.utc) return dt.strftime('%Y-%m-%dT%H:%M:%SZ') def _parse_depth_header(self, headers): """Parse the Depth header from request.""" for name, value in headers: if name.lower() == b'depth': depth_str = value.decode('ascii').strip() if depth_str.lower() == 'infinity': return 'infinity' try: depth = int(depth_str) if depth < 0: return '0' return str(depth) except ValueError: return '0' return '1' # Default depth def _clean_expired_locks(self): """Remove expired locks.""" expired_paths = [] for path, lock in self.locks.items(): if lock.is_expired(): expired_paths.append(path) for path in expired_paths: del self.locks[path] def _check_lock_conflicts(self, path, token=None): """Check if there are lock conflicts for a path.""" self._clean_expired_locks() if path not in self.locks: return False lock = self.locks[path] # If we have the lock token, no conflict if token and token == lock.token: return False # If it's a shared lock, allow other shared locks if lock.scope == 'shared': return False # Exclusive lock conflict return True async def _read_request_body(self): """Read the full request body.""" body_parts = [] while True: event = await self._wrapper.next_event() if isinstance(event, h11.Data): if event.data: body_parts.append(event.data) elif isinstance(event, h11.EndOfMessage): break else: break return b''.join(body_parts) def _create_xml_response(self, root_element): """Create a formatted XML response.""" # Add namespace declarations to root root_element.set('xmlns:D', 'DAV:') # Create XML string rough_string = ET.tostring(root_element, encoding='unicode') # Pretty print try: reparsed = minidom.parseString(rough_string) pretty = reparsed.documentElement.toprettyxml(indent=" ") # Remove extra whitespace and fix formatting lines = [line for line in pretty.split('\n') if line.strip()] return '\n'.join(lines) except: return rough_string def _get_file_properties(self, file_path, requested_props=None): """Get WebDAV properties for a file or directory.""" try: stat = os.stat(file_path) is_dir = os.path.isdir(file_path) props = {} # Standard WebDAV properties if not requested_props or 'creationdate' in requested_props: props['creationdate'] = self._format_iso_date(stat.st_ctime) if not requested_props or 'displayname' in requested_props: props['displayname'] = os.path.basename(file_path) or 'Root' if not requested_props or 'getcontentlength' in requested_props: if not is_dir: props['getcontentlength'] = str(stat.st_size) if not requested_props or 'getcontenttype' in requested_props: if not is_dir: props['getcontenttype'] = self._get_mime_type(file_path) if not requested_props or 'getlastmodified' in requested_props: props['getlastmodified'] = self._format_http_date(stat.st_mtime) if not requested_props or 'getetag' in requested_props: # Simple ETag based on mtime and size etag = f'"{stat.st_mtime}-{stat.st_size}"' props['getetag'] = etag if not requested_props or 'resourcetype' in requested_props: if is_dir: props['resourcetype'] = '' else: props['resourcetype'] = '' if not requested_props or 'supportedlock' in requested_props: if self.enable_locks: props['supportedlock'] = ''' ''' else: props['supportedlock'] = '' if not requested_props or 'lockdiscovery' in requested_props: rel_path = self._get_relative_path(file_path) if rel_path in self.locks: lock = self.locks[rel_path] if not lock.is_expired(): lock_xml = self._create_xml_response(lock.to_xml(rel_path)) props['lockdiscovery'] = lock_xml else: props['lockdiscovery'] = '' else: props['lockdiscovery'] = '' return props except OSError: return {} async def do_OPTIONS(self, event): """Handle OPTIONS request - advertise WebDAV capabilities.""" await self.print(f"[WEBDAV] OPTIONS {event.target.decode()}") headers = self.basic_headers() headers.extend([ ("Allow", b"OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, PROPFIND, PROPPATCH, COPY, MOVE, MKCOL, LOCK, UNLOCK"), ("DAV", b"1, 2"), ("MS-Author-Via", b"DAV"), ("Content-Length", b"0") ]) response = h11.Response(status_code=200, headers=headers) await self._wrapper.send(response) await self._wrapper.send(h11.EndOfMessage()) async def do_PROPFIND(self, event): """Handle PROPFIND request - get properties of resources.""" path = urllib.parse.unquote(event.target.decode('utf-8')) await self.print(f"[WEBDAV] PROPFIND {path}") safe_path = self._sanitize_path(path) if not safe_path: await self._serve_error(400, "Invalid path") return if not os.path.exists(safe_path): await self._serve_error(404, "Not Found") return # Parse depth header depth = self._parse_depth_header(event.headers) # Read request body to get requested properties body = await self._read_request_body() requested_props = None if body: try: root = ET.fromstring(body.decode('utf-8')) # Parse requested properties for prop_elem in root.iter(): if prop_elem.tag.endswith('prop'): requested_props = [child.tag.split('}')[-1] for child in prop_elem] break except: pass # Use all properties if parsing fails # Build multistatus response multistatus = ET.Element('D:multistatus') multistatus.set('xmlns:D', 'DAV:') # Add current resource await self._add_propfind_response(multistatus, safe_path, requested_props) # Add child resources if depth > 0 if depth != '0' and os.path.isdir(safe_path): try: for item in os.listdir(safe_path): child_path = os.path.join(safe_path, item) await self._add_propfind_response(multistatus, child_path, requested_props) # Recursive for infinite depth (with protection) if depth == 'infinity' and os.path.isdir(child_path): await self._add_propfind_recursive(multistatus, child_path, requested_props, 1) except PermissionError: pass # Generate response xml_content = '\n' xml_content += self._create_xml_response(multistatus) body = xml_content.encode('utf-8') headers = self.basic_headers() headers.extend([ ("Content-Type", b"application/xml; charset=utf-8"), ("Content-Length", str(len(body)).encode('ascii')) ]) response = h11.Response(status_code=207, headers=headers) # Multi-Status await self._wrapper.send(response) await self._wrapper.send(h11.Data(data=body)) await self._wrapper.send(h11.EndOfMessage()) async def _add_propfind_response(self, multistatus, file_path, requested_props): """Add a single resource response to PROPFIND multistatus.""" response_elem = ET.SubElement(multistatus, 'D:response') # href rel_path = self._get_relative_path(file_path) href_elem = ET.SubElement(response_elem, 'D:href') href_elem.text = rel_path # propstat propstat_elem = ET.SubElement(response_elem, 'D:propstat') prop_elem = ET.SubElement(propstat_elem, 'D:prop') # Get properties props = self._get_file_properties(file_path, requested_props) for prop_name, prop_value in props.items(): if prop_value or prop_value == '': # Include empty properties prop_sub_elem = ET.SubElement(prop_elem, f'D:{prop_name}') if prop_name == 'resourcetype' and prop_value: # Special handling for resourcetype XML try: if prop_value.strip(): temp_elem = ET.fromstring(f'{prop_value}') for child in temp_elem: prop_sub_elem.append(child) except: prop_sub_elem.text = prop_value elif prop_name == 'supportedlock' and prop_value: # Special handling for supportedlock XML try: if prop_value.strip(): temp_elem = ET.fromstring(f'{prop_value}') for child in temp_elem: prop_sub_elem.append(child) except: pass elif prop_name == 'lockdiscovery' and prop_value: # Special handling for lockdiscovery XML try: if prop_value.strip(): temp_elem = ET.fromstring(prop_value) prop_sub_elem.append(temp_elem) except: pass else: prop_sub_elem.text = str(prop_value) # status status_elem = ET.SubElement(propstat_elem, 'D:status') status_elem.text = 'HTTP/1.1 200 OK' async def _add_propfind_recursive(self, multistatus, dir_path, requested_props, current_depth): """Recursively add PROPFIND responses with depth protection.""" if current_depth > self.max_depth: return try: for item in os.listdir(dir_path): child_path = os.path.join(dir_path, item) await self._add_propfind_response(multistatus, child_path, requested_props) if os.path.isdir(child_path): await self._add_propfind_recursive(multistatus, child_path, requested_props, current_depth + 1) except (PermissionError, OSError): pass async def do_PROPPATCH(self, event): """Handle PROPPATCH request - modify properties of resources.""" path = urllib.parse.unquote(event.target.decode('utf-8')) await self.print(f"[WEBDAV] PROPPATCH {path}") # For this implementation, we'll return method not allowed # as we don't support property modification await self._serve_error(405, "Property modification not supported") async def do_MKCOL(self, event): """Handle MKCOL request - create a directory.""" path = urllib.parse.unquote(event.target.decode('utf-8')) await self.print(f"[WEBDAV] MKCOL {path}") safe_path = self._sanitize_path(path) if not safe_path: await self._serve_error(400, "Invalid path") return # Check if resource already exists if os.path.exists(safe_path): await self._serve_error(405, "Resource already exists") return # Check if parent directory exists parent_dir = os.path.dirname(safe_path) if not os.path.exists(parent_dir): await self._serve_error(409, "Parent collection does not exist") return # Check for locks rel_path = self._get_relative_path(safe_path) if self._check_lock_conflicts(rel_path): await self._serve_error(423, "Resource is locked") return try: # Create directory os.makedirs(safe_path) await self.print(f"[WEBDAV] Created directory: {safe_path}") # Success response headers = self.basic_headers() headers.append(("Content-Length", b"0")) response = h11.Response(status_code=201, headers=headers) await self._wrapper.send(response) await self._wrapper.send(h11.EndOfMessage()) except OSError as e: await self._serve_error(500, f"Failed to create directory: {str(e)}") async def do_GET(self, event): """Handle GET request - download files.""" path = urllib.parse.unquote(event.target.decode('utf-8')) await self.print(f"[WEBDAV] GET {path}") safe_path = self._sanitize_path(path) if not safe_path: await self._serve_error(400, "Invalid path") return if not os.path.exists(safe_path): await self._serve_error(404, "Not Found") return if os.path.isdir(safe_path): # Return directory listing as HTML await self._serve_directory_listing(safe_path) else: # Serve file await self._serve_file(safe_path, event.headers) async def do_HEAD(self, event): """Handle HEAD request - get headers without body.""" path = urllib.parse.unquote(event.target.decode('utf-8')) await self.print(f"[WEBDAV] HEAD {path}") safe_path = self._sanitize_path(path) if not safe_path: await self._serve_error(400, "Invalid path") return if not os.path.exists(safe_path): await self._serve_error(404, "Not Found") return try: stat = os.stat(safe_path) mime_type = self._get_mime_type(safe_path) headers = self.basic_headers() headers.extend([ ("Content-Type", mime_type.encode('ascii')), ("Content-Length", str(stat.st_size).encode('ascii')), ("Last-Modified", self._format_http_date(stat.st_mtime).encode('ascii')), ("ETag", f'"{stat.st_mtime}-{stat.st_size}"'.encode('ascii')) ]) response = h11.Response(status_code=200, headers=headers) await self._wrapper.send(response) await self._wrapper.send(h11.EndOfMessage()) except OSError as e: await self._serve_error(500, f"Error accessing file: {str(e)}") async def do_PUT(self, event): """Handle PUT request - upload/create files.""" path = urllib.parse.unquote(event.target.decode('utf-8')) await self.print(f"[WEBDAV] PUT {path}") safe_path = self._sanitize_path(path) if not safe_path: await self._serve_error(400, "Invalid path") return # Check for locks rel_path = self._get_relative_path(safe_path) if self._check_lock_conflicts(rel_path): await self._serve_error(423, "Resource is locked") return # Check if parent directory exists parent_dir = os.path.dirname(safe_path) if not os.path.exists(parent_dir): await self._serve_error(409, "Parent collection does not exist") return is_new_file = not os.path.exists(safe_path) try: # Read request body and write to file with open(safe_path, 'wb') as f: while True: event = await self._wrapper.next_event() if isinstance(event, h11.Data): if event.data: f.write(event.data) elif isinstance(event, h11.EndOfMessage): break else: break await self.print(f"[WEBDAV] {'Created' if is_new_file else 'Updated'} file: {safe_path}") # Success response status_code = 201 if is_new_file else 204 headers = self.basic_headers() headers.append(("Content-Length", b"0")) response = h11.Response(status_code=status_code, headers=headers) await self._wrapper.send(response) await self._wrapper.send(h11.EndOfMessage()) except OSError as e: await self._serve_error(500, f"Failed to write file: {str(e)}") async def do_DELETE(self, event): """Handle DELETE request - delete files and directories.""" path = urllib.parse.unquote(event.target.decode('utf-8')) await self.print(f"[WEBDAV] DELETE {path}") safe_path = self._sanitize_path(path) if not safe_path: await self._serve_error(400, "Invalid path") return if not os.path.exists(safe_path): await self._serve_error(404, "Not Found") return # Check for locks rel_path = self._get_relative_path(safe_path) if self._check_lock_conflicts(rel_path): await self._serve_error(423, "Resource is locked") return try: if os.path.isdir(safe_path): # Remove directory and contents import shutil shutil.rmtree(safe_path) await self.print(f"[WEBDAV] Deleted directory: {safe_path}") else: # Remove file os.remove(safe_path) await self.print(f"[WEBDAV] Deleted file: {safe_path}") # Remove any locks on this resource if rel_path in self.locks: del self.locks[rel_path] # Success response headers = self.basic_headers() headers.append(("Content-Length", b"0")) response = h11.Response(status_code=204, headers=headers) await self._wrapper.send(response) await self._wrapper.send(h11.EndOfMessage()) except OSError as e: await self._serve_error(500, f"Failed to delete: {str(e)}") async def do_COPY(self, event): """Handle COPY request - copy resources.""" path = urllib.parse.unquote(event.target.decode('utf-8')) await self.print(f"[WEBDAV] COPY {path}") # Get destination from headers destination = None overwrite = True for name, value in event.headers: if name.lower() == b'destination': destination = urllib.parse.unquote(value.decode('utf-8')) elif name.lower() == b'overwrite': overwrite = value.decode('ascii').upper() == 'T' if not destination: await self._serve_error(400, "Destination header required") return safe_source = self._sanitize_path(path) safe_dest = self._sanitize_path(destination) if not safe_source or not safe_dest: await self._serve_error(400, "Invalid path") return if not os.path.exists(safe_source): await self._serve_error(404, "Source not found") return # Check if destination exists and overwrite policy dest_exists = os.path.exists(safe_dest) if dest_exists and not overwrite: await self._serve_error(412, "Destination exists and overwrite is false") return # Check for locks on destination rel_dest = self._get_relative_path(safe_dest) if self._check_lock_conflicts(rel_dest): await self._serve_error(423, "Destination is locked") return try: if os.path.isdir(safe_source): # Copy directory import shutil if dest_exists: shutil.rmtree(safe_dest) shutil.copytree(safe_source, safe_dest) await self.print(f"[WEBDAV] Copied directory: {safe_source} -> {safe_dest}") else: # Copy file import shutil # Ensure parent directory exists parent_dir = os.path.dirname(safe_dest) if not os.path.exists(parent_dir): os.makedirs(parent_dir) shutil.copy2(safe_source, safe_dest) await self.print(f"[WEBDAV] Copied file: {safe_source} -> {safe_dest}") # Success response status_code = 204 if dest_exists else 201 headers = self.basic_headers() headers.append(("Content-Length", b"0")) response = h11.Response(status_code=status_code, headers=headers) await self._wrapper.send(response) await self._wrapper.send(h11.EndOfMessage()) except OSError as e: await self._serve_error(500, f"Failed to copy: {str(e)}") async def do_MOVE(self, event): """Handle MOVE request - move/rename resources.""" path = urllib.parse.unquote(event.target.decode('utf-8')) await self.print(f"[WEBDAV] MOVE {path}") # Get destination from headers destination = None overwrite = True for name, value in event.headers: if name.lower() == b'destination': destination = urllib.parse.unquote(value.decode('utf-8')) elif name.lower() == b'overwrite': overwrite = value.decode('ascii').upper() == 'T' if not destination: await self._serve_error(400, "Destination header required") return safe_source = self._sanitize_path(path) safe_dest = self._sanitize_path(destination) if not safe_source or not safe_dest: await self._serve_error(400, "Invalid path") return if not os.path.exists(safe_source): await self._serve_error(404, "Source not found") return # Check if destination exists and overwrite policy dest_exists = os.path.exists(safe_dest) if dest_exists and not overwrite: await self._serve_error(412, "Destination exists and overwrite is false") return # Check for locks on both source and destination rel_source = self._get_relative_path(safe_source) rel_dest = self._get_relative_path(safe_dest) if self._check_lock_conflicts(rel_source) or self._check_lock_conflicts(rel_dest): await self._serve_error(423, "Resource is locked") return try: # Ensure parent directory exists parent_dir = os.path.dirname(safe_dest) if not os.path.exists(parent_dir): os.makedirs(parent_dir) # Move the resource if dest_exists: if os.path.isdir(safe_dest): import shutil shutil.rmtree(safe_dest) else: os.remove(safe_dest) os.rename(safe_source, safe_dest) await self.print(f"[WEBDAV] Moved: {safe_source} -> {safe_dest}") # Move any locks if rel_source in self.locks: self.locks[rel_dest] = self.locks[rel_source] del self.locks[rel_source] # Success response status_code = 204 if dest_exists else 201 headers = self.basic_headers() headers.append(("Content-Length", b"0")) response = h11.Response(status_code=status_code, headers=headers) await self._wrapper.send(response) await self._wrapper.send(h11.EndOfMessage()) except OSError as e: await self._serve_error(500, f"Failed to move: {str(e)}") async def do_LOCK(self, event): """Handle LOCK request - lock resources.""" if not self.enable_locks: await self._serve_error(405, "Locking not supported") return path = urllib.parse.unquote(event.target.decode('utf-8')) await self.print(f"[WEBDAV] LOCK {path}") safe_path = self._sanitize_path(path) if not safe_path: await self._serve_error(400, "Invalid path") return rel_path = self._get_relative_path(safe_path) # Parse lock request body body = await self._read_request_body() if not body: await self._serve_error(400, "Lock request body required") return try: root = ET.fromstring(body.decode('utf-8')) # Extract lock information lock_scope = 'exclusive' # default lock_type = 'write' # default owner = 'unknown' timeout = self.default_lock_timeout for elem in root.iter(): if elem.tag.endswith('exclusive'): lock_scope = 'exclusive' elif elem.tag.endswith('shared'): lock_scope = 'shared' elif elem.tag.endswith('write'): lock_type = 'write' elif elem.tag.endswith('owner'): owner = elem.text or 'unknown' # Parse timeout from headers for name, value in event.headers: if name.lower() == b'timeout': timeout_str = value.decode('ascii') if timeout_str.startswith('Second-'): try: timeout = int(timeout_str[7:]) except ValueError: pass # Check for existing locks if self._check_lock_conflicts(rel_path): await self._serve_error(423, "Resource already locked") return # Create new lock lock_token = str(uuid.uuid4()) lock = WebDAVLock(lock_token, lock_scope, lock_type, owner, timeout) self.locks[rel_path] = lock await self.print(f"[WEBDAV] Created lock {lock_token} on {rel_path}") # Create lock response prop_elem = ET.Element('D:prop') prop_elem.set('xmlns:D', 'DAV:') lockdiscovery_elem = ET.SubElement(prop_elem, 'D:lockdiscovery') lockdiscovery_elem.append(lock.to_xml(rel_path)) xml_content = '\n' xml_content += self._create_xml_response(prop_elem) body = xml_content.encode('utf-8') headers = self.basic_headers() headers.extend([ ("Content-Type", b"application/xml; charset=utf-8"), ("Content-Length", str(len(body)).encode('ascii')), ("Lock-Token", f"".encode('ascii')) ]) response = h11.Response(status_code=200, headers=headers) await self._wrapper.send(response) await self._wrapper.send(h11.Data(data=body)) await self._wrapper.send(h11.EndOfMessage()) except ET.ParseError: await self._serve_error(400, "Invalid XML in lock request") except Exception as e: await self._serve_error(500, f"Lock error: {str(e)}") async def do_UNLOCK(self, event): """Handle UNLOCK request - unlock resources.""" if not self.enable_locks: await self._serve_error(405, "Locking not supported") return path = urllib.parse.unquote(event.target.decode('utf-8')) await self.print(f"[WEBDAV] UNLOCK {path}") safe_path = self._sanitize_path(path) if not safe_path: await self._serve_error(400, "Invalid path") return rel_path = self._get_relative_path(safe_path) # Get lock token from headers lock_token = None for name, value in event.headers: if name.lower() == b'lock-token': token_str = value.decode('ascii') # Parse if token_str.startswith(''): lock_token = token_str[17:-1] break if not lock_token: await self._serve_error(400, "Lock-Token header required") return # Check if lock exists and matches if rel_path not in self.locks: await self._serve_error(409, "Resource not locked") return lock = self.locks[rel_path] if lock.token != lock_token: await self._serve_error(403, "Invalid lock token") return # Remove lock del self.locks[rel_path] await self.print(f"[WEBDAV] Removed lock {lock_token} from {rel_path}") # Success response headers = self.basic_headers() headers.append(("Content-Length", b"0")) response = h11.Response(status_code=204, headers=headers) await self._wrapper.send(response) await self._wrapper.send(h11.EndOfMessage()) async def _serve_file(self, file_path, request_headers): """Serve a file with range support.""" try: file_size = os.path.getsize(file_path) mime_type = self._get_mime_type(file_path) stat = os.stat(file_path) # Check for Range header range_header = None for name, value in request_headers: if name.lower() == b'range': range_header = value.decode('ascii') break start_byte = 0 end_byte = file_size - 1 status_code = 200 # Parse range request if range_header and range_header.startswith('bytes='): try: range_spec = range_header[6:] if '-' in range_spec: start, end = range_spec.split('-', 1) if start: start_byte = int(start) if end: end_byte = int(end) status_code = 206 except (ValueError, IndexError): pass content_length = end_byte - start_byte + 1 headers = self.basic_headers() headers.extend([ ("Content-Type", mime_type.encode('ascii')), ("Content-Length", str(content_length).encode('ascii')), ("Accept-Ranges", b"bytes"), ("Last-Modified", self._format_http_date(stat.st_mtime).encode('ascii')), ("ETag", f'"{stat.st_mtime}-{stat.st_size}"'.encode('ascii')) ]) if status_code == 206: headers.append(("Content-Range", f"bytes {start_byte}-{end_byte}/{file_size}".encode('ascii'))) response = h11.Response(status_code=status_code, headers=headers) await self._wrapper.send(response) # Send file content chunk_size = 64 * 1024 bytes_sent = 0 with open(file_path, 'rb') as f: if start_byte > 0: f.seek(start_byte) while bytes_sent < content_length: read_size = min(chunk_size, content_length - bytes_sent) chunk = f.read(read_size) if not chunk: break await self._wrapper.send(h11.Data(data=chunk)) bytes_sent += len(chunk) await self._wrapper.send(h11.EndOfMessage()) except OSError as e: await self._serve_error(500, f"Error serving file: {str(e)}") async def _serve_directory_listing(self, dir_path): """Serve a simple HTML directory listing.""" try: rel_path = self._get_relative_path(dir_path) html_content = f''' WebDAV Directory: {html.escape(rel_path)}

WebDAV Directory: {html.escape(rel_path)}

''' # Add parent directory link if rel_path != '/': parent_path = '/'.join(rel_path.rstrip('/').split('/')[:-1]) or '/' html_content += f''' ''' # List directory contents for item in sorted(os.listdir(dir_path)): item_path = os.path.join(dir_path, item) item_stat = os.stat(item_path) if os.path.isdir(item_path): item_type = "Directory" item_size = "-" item_url = rel_path.rstrip('/') + '/' + item + '/' else: item_type = "File" item_size = str(item_stat.st_size) item_url = rel_path.rstrip('/') + '/' + item modified = self._format_http_date(item_stat.st_mtime) html_content += f''' ''' html_content += '''
Name Type Size Modified
../ Directory - -
{html.escape(item)} {item_type} {item_size} {modified}

WebDAV Server powered by asysocks

''' body = html_content.encode('utf-8') headers = self.basic_headers() headers.extend([ ("Content-Type", b"text/html; charset=utf-8"), ("Content-Length", str(len(body)).encode('ascii')) ]) response = h11.Response(status_code=200, headers=headers) await self._wrapper.send(response) await self._wrapper.send(h11.Data(data=body)) await self._wrapper.send(h11.EndOfMessage()) except OSError as e: await self._serve_error(500, f"Error listing directory: {str(e)}") async def _serve_error(self, status_code, message): """Serve an error response.""" try: body = f''' WebDAV Error {status_code}

Error {status_code}

{html.escape(message)}


WebDAV Server powered by asysocks

'''.encode('utf-8') headers = self.basic_headers() headers.extend([ ("Content-Type", b"text/html; charset=utf-8"), ("Content-Length", str(len(body)).encode('ascii')) ]) response = h11.Response(status_code=status_code, headers=headers) await self._wrapper.send(response) await self._wrapper.send(h11.Data(data=body)) await self._wrapper.send(h11.EndOfMessage()) except Exception as e: await self.print(f"Error serving error response: {e}") async def run_webdav_server_from_target(target, webdav_root=None, log_callback=None, enable_locks=True, default_lock_timeout=3600): """ Run the WebDAV server from a target. Args: target: Server target configuration webdav_root (str): Root directory for WebDAV operations log_callback: Logging callback function enable_locks (bool): Whether to enable WebDAV locking default_lock_timeout (int): Default lock timeout in seconds """ try: handler_factory = lambda: WebDAVHandler( webdav_root, enable_locks=enable_locks, default_lock_timeout=default_lock_timeout, print_cb=log_callback ) server = HTTPServer(handler_factory, target, log_callback=log_callback) print("WebDAV server starting... Press Ctrl+C to stop") return await server.serve() except Exception as e: return None, e async def run_webdav_server(webdav_root=None, host='127.0.0.1', port=8080, debug=False, enable_locks=True, default_lock_timeout=3600): """ Run the WebDAV server. Args: webdav_root (str): Root directory for WebDAV operations host (str): Host to bind to port (int): Port to bind to debug (bool): Enable debug logging enable_locks (bool): Whether to enable WebDAV locking default_lock_timeout (int): Default lock timeout in seconds """ try: log_callback = None if debug: async def log_callback(msg): print(f"[WEBDAV-SERVER] {msg}") if webdav_root: print(f"Starting WebDAV server on {host}:{port}") print(f"WebDAV root directory: {os.path.abspath(webdav_root)}") else: print(f"Starting WebDAV server on {host}:{port} (no root directory)") if debug: print("Debug mode enabled - verbose logging active") print(f"WebDAV features:") print(f" - File locking: {'Enabled' if enable_locks else 'Disabled'}") if enable_locks: print(f" - Default lock timeout: {default_lock_timeout} seconds") target = UniTarget(host, port, UniProto.SERVER_TCP) server_task, err = await run_webdav_server_from_target( target, webdav_root=webdav_root, log_callback=log_callback, enable_locks=enable_locks, default_lock_timeout=default_lock_timeout ) print(f"Server task: {server_task}") if err is not None: raise err await server_task except KeyboardInterrupt: print("\nWebDAV server stopped by user") except Exception as e: print(f"Server error: {e}") finally: if server_task is not None: server_task.cancel() print("WebDAV server stopped") def main(): """ Main entry point for the WebDAV server. """ import argparse import sys # Create argument parser parser = argparse.ArgumentParser( description='Asysocks WebDAV Server - RFC 4918 compliant WebDAV server', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=''' Examples: %(prog)s # Start server on 127.0.0.1:8080 (no root) %(prog)s /home/user/webdav # Serve WebDAV from directory %(prog)s /home/user/webdav --host 0.0.0.0 # Bind to all interfaces %(prog)s /home/user/webdav --port 9000 # Use custom port %(prog)s /home/user/webdav --debug # Enable debug logging %(prog)s /home/user/webdav --no-locks # Disable file locking %(prog)s --help # Show this help message WebDAV Features: • RFC 4918 compliant WebDAV implementation • File and directory operations (GET, PUT, DELETE, MKCOL) • Property queries and modification (PROPFIND, PROPPATCH) • Resource copying and moving (COPY, MOVE) • File locking and unlocking (LOCK, UNLOCK) • Directory traversal protection • Integration with file managers and WebDAV clients • Range request support for large files Client Compatibility: • Windows Explorer (Map Network Drive) • macOS Finder (Connect to Server) • Linux file managers (Nautilus, Dolphin, etc.) • WebDAV command-line tools (cadaver, davfs2) • Programming libraries (Python requests-webdav, etc.) ''') # Add arguments parser.add_argument( 'directory', nargs='?', help='WebDAV root directory (optional)' ) parser.add_argument( '--host', '-H', default='127.0.0.1', help='Host to bind to (default: 127.0.0.1)' ) parser.add_argument( '--port', '-p', type=int, default=8080, help='Port to bind to (default: 8080)' ) parser.add_argument( '--debug', '-d', action='store_true', help='Enable debug logging (shows detailed server activity)' ) parser.add_argument( '--no-locks', action='store_true', help='Disable WebDAV file locking support' ) parser.add_argument( '--lock-timeout', type=int, default=3600, help='Default lock timeout in seconds (default: 3600)' ) parser.add_argument( '--version', '-v', action='version', version='Asysocks WebDAV Server 1.0.0' ) # Parse arguments args = parser.parse_args() # Validate root directory if provided if args.directory: if not os.path.exists(args.directory): print(f"Creating WebDAV root directory: {args.directory}") try: os.makedirs(args.directory) except OSError as e: print(f"Error: Cannot create directory '{args.directory}': {e}") sys.exit(1) elif not os.path.isdir(args.directory): print(f"Error: '{args.directory}' is not a directory") sys.exit(1) # Validate port range if args.port < 1 or args.port > 65535: print(f"Error: Port must be between 1 and 65535, got {args.port}") sys.exit(1) # Validate lock timeout if args.lock_timeout < 1: print(f"Error: Lock timeout must be at least 1 second, got {args.lock_timeout}") sys.exit(1) # Show startup info print("Asysocks WebDAV Server") print("=" * 50) if args.directory: print(f"WebDAV Root: {os.path.abspath(args.directory)}") else: print("WebDAV Root: None (server-only mode)") print(f"Address: http://{args.host}:{args.port}") if args.debug: print("Debug: Enabled") print(f"File Locking: {'Disabled' if args.no_locks else 'Enabled'}") if not args.no_locks: print(f"Lock Timeout: {args.lock_timeout} seconds") print("=" * 50) print("\nWebDAV URLs:") print(f" Web Browser: http://{args.host}:{args.port}/") print(f" WebDAV URL: http://{args.host}:{args.port}/") print("\nClient Setup Examples:") print(" Windows Explorer: Map Network Drive -> http://server:port/") print(" macOS Finder: Go -> Connect to Server -> http://server:port/") print(" Linux: davfs2, cadaver, or file manager WebDAV support") print("=" * 50) # Run the server try: asyncio.run(run_webdav_server( args.directory, args.host, args.port, args.debug, enable_locks=not args.no_locks, default_lock_timeout=args.lock_timeout )) except KeyboardInterrupt: print("\nWebDAV server stopped by user") except Exception as e: print(f"Failed to start WebDAV server: {e}") sys.exit(1) if __name__ == '__main__': main() asysocks-0.2.18/asysocks/intercepting/000077500000000000000000000000001510034457600200005ustar00rootroot00000000000000asysocks-0.2.18/asysocks/intercepting/__init__.py000066400000000000000000000000001510034457600220770ustar00rootroot00000000000000asysocks-0.2.18/asysocks/intercepting/default_intercept_table.py000066400000000000000000000006151510034457600252240ustar00rootroot00000000000000 import json from asysocks.intercepting.target import InterceptTarget default_table_data = json.loads(""" [ { "dsthost" : ".*", "dstport" : 443, "proto" : "http", "ssl" : true }, { "dsthost" : ".*", "dstport" : 80, "proto" : "http", "ssl" : false } ] """) default_intercept_table = [] for x in default_table_data: default_intercept_table.append(InterceptTarget.from_dict(x))asysocks-0.2.18/asysocks/intercepting/monitors/000077500000000000000000000000001510034457600216525ustar00rootroot00000000000000asysocks-0.2.18/asysocks/intercepting/monitors/__init__.py000066400000000000000000000004461510034457600237670ustar00rootroot00000000000000 from asysocks.intercepting.monitors.base import BaseMonitor from asysocks.intercepting.monitors.sslbase import SSLBaseMonitor from asysocks.intercepting.monitors.rawlogging import RawLoggingMonitor prototable = { 'http' : RawLoggingMonitor, 'rawlog' : RawLoggingMonitor, 'ssllog' : None, }asysocks-0.2.18/asysocks/intercepting/monitors/base.py000066400000000000000000000050501510034457600231360ustar00rootroot00000000000000import asyncio import logging from asysocks.common.trafficlog import TrafficLog srvlogger = logging.getLogger('asysocks.traffic.base') handler = logging.StreamHandler() formatter = logging.Formatter( '%(asctime)s %(name)-12s %(levelname)-8s %(message)s') handler.setFormatter(formatter) srvlogger.addHandler(handler) srvlogger.setLevel(logging.INFO) class BaseMonitor: """ Base class for monitoring traffic. This does not do monitoring just holds all the necessary data that enables monitoring for other classes """ def __init__(self, client_ip, client_port, dst_ip, dst_port, module, session_id): self.client_ip = client_ip self.client_port = client_port self.dst_ip = dst_ip self.dst_hostname = None self.dst_port = dst_port self.module = module self.session_id = session_id self.logline_c2d = '[%s][MONITOR][%s:%s -> %s:%s]' % (self.module, self.client_ip, self.client_port, self.dst_ip, self.dst_port) self.logline_d2c = '[%s][MONITOR][%s:%s -> %s:%s]' % (self.module, self.dst_ip, self.dst_port, self.client_ip, self.client_port) self.c2d_in = asyncio.Queue() self.c2d_out = asyncio.Queue() self.d2c_in = asyncio.Queue() self.d2c_out = asyncio.Queue() def get_trafficlog(self, data, direction, module_name = None): t = TrafficLog() t.module_name = module_name t.srv_module = self.module t.client_ip = self.client_ip t.client_port = self.client_port t.destination_ip = self.dst_ip t.destination_hostname = self.dst_hostname t.destination_port = self.dst_port t.session_id = self.session_id t.direction = direction t.data = data return t def get_dst_hostname(self): if self.dst_hostname is None: return self.dst_ip return self.dst_hostname def set_hostname(self, hostname): #for SNI support on SSL self.dst_hostname = hostname async def __proxy_c2d(self, stop_evt): try: while not stop_evt.is_set(): data = await self.c2d_in.get() await self.c2d_out.put(data) if data == b'': return except Exception as e: srvlogger.exception('__proxy_c2d') finally: stop_evt.set() async def __proxy_d2c(self, stop_evt): try: while not stop_evt.is_set(): data = await self.d2c_in.get() await self.d2c_out.put(data) if data == b'': return except Exception as e: srvlogger.exception('__proxy_d2c') finally: stop_evt.set() async def run(self): """ Only invoke this if no monitoring is required! """ stop_evt = asyncio.Event() asyncio.create_task(self.__proxy_c2d(stop_evt)) asyncio.create_task(self.__proxy_d2c(stop_evt)) await stop_evt.wait()asysocks-0.2.18/asysocks/intercepting/monitors/rawlogging.py000066400000000000000000000030221510034457600243610ustar00rootroot00000000000000import asyncio import logging srvlogger = logging.getLogger('asysocks.traffic.raw') handler = logging.StreamHandler() formatter = logging.Formatter( '%(asctime)s %(name)-12s %(levelname)-8s %(message)s') handler.setFormatter(formatter) srvlogger.addHandler(handler) srvlogger.setLevel(logging.INFO) class RawLoggingMonitor: """ Logs raw socket data, no SSL intercpetion or protocol reconstruction done """ def __init__(self, monitor, log_queue, c2d_in = None, c2d_out = None, d2c_in = None, d2c_out = None): self.monitor = monitor self.log_queue = log_queue self.module_name = 'RAWLOG' async def __justlog_c2d(self, stop_evt): try: while not stop_evt.is_set(): data = await self.monitor.c2d_in.get() await self.log_queue.put(self.monitor.get_trafficlog(data, 'c2d_in', self.module_name)) await self.monitor.c2d_out.put(data) if data == b'': return except Exception as e: srvlogger.exception('__justlog_c2d') finally: stop_evt.set() async def __justlog_d2c(self, stop_evt): try: while not stop_evt.is_set(): data = await self.monitor.d2c_in.get() await self.log_queue.put(self.monitor.get_trafficlog(data, 'd2c_in', self.module_name)) await self.monitor.d2c_out.put(data) if data == b'': return except Exception as e: srvlogger.exception('__justlog_d2c') finally: stop_evt.set() async def run(self): stop_evt = asyncio.Event() asyncio.create_task(self.__justlog_c2d(stop_evt)) asyncio.create_task(self.__justlog_d2c(stop_evt)) await stop_evt.wait() asysocks-0.2.18/asysocks/intercepting/monitors/sslbase.py000066400000000000000000000200151510034457600236560ustar00rootroot00000000000000import asyncio import ssl import logging srvlogger = logging.getLogger('asysocks.traffic.ssl') handler = logging.StreamHandler() formatter = logging.Formatter( '%(asctime)s %(name)-12s %(levelname)-8s %(message)s') handler.setFormatter(formatter) srvlogger.addHandler(handler) srvlogger.setLevel(logging.INFO) class SSLBaseMonitor: def __init__(self, monitor, client_ssl_ctx = None, destination_ssl_ctx = None): self.monitor = monitor self.client_ssl_ctx = client_ssl_ctx self.destination_ssl_ctx = destination_ssl_ctx self.stop_evt = asyncio.Event() self.client_ssl_ok_evt = asyncio.Event() self.destination_ssl_ok_evt = asyncio.Event() self.client_tls_obj = None self.client_tls_in_buff = None self.client_tls_out_buff = None self.destination_tls_obj = None self.destination_tls_in_buff = None self.destination_tls_out_buff = None self.__client_ssl_in_q = asyncio.Queue() self.__destination_ssl_in_q = asyncio.Queue() self.c2d_ssl_task = None self.d2c_ssl_task = None self.dest_task = None self.client_task = None self.c2d_in = asyncio.Queue() self.c2d_out = asyncio.Queue() self.d2c_in = asyncio.Queue() self.d2c_out = asyncio.Queue() def get_trafficlog(self, data, direction, module_name = None): t = self.monitor.get_trafficlog(data, direction, module_name = module_name) t.is_ssl = True return t async def __read_ssl_record(self, raw_in_q, ssl_in_q): try: buffer = b'' length = None while not self.stop_evt.is_set(): if length is None and len(buffer) >= 6: length = int.from_bytes(buffer[3:5], byteorder = 'big', signed = False) if length is not None and len(buffer) >= length + 5: #print('LB raw %s' % len(buffer[:length+5])) await ssl_in_q.put(buffer[:length+5]) buffer = buffer[length+5:] length = None continue data = await raw_in_q.get() if data == b'': await ssl_in_q.put(b'') return buffer+= data except asyncio.CancelledError: return except Exception as e: print('__read_ssl_record %s' % e) await ssl_in_q.put(b'') #finally: # self.stop_evt.set() async def __do_ssl_handshake_cli(self, ssl_ctx, in_q, out_q): try: tls_in_buff = ssl.MemoryBIO() tls_out_buff = ssl.MemoryBIO() tls_obj = ssl_ctx.wrap_bio(tls_in_buff, tls_out_buff, server_side=False, server_hostname = self.monitor.dst_hostname) #print('################## %s' % self.monitor.dst_ip) ctr = 0 while not self.stop_evt.is_set(): ctr += 1 #print('DST Performing handshake!') try: tls_obj.do_handshake() except ssl.SSLWantReadError: #print('DST want %s' % ctr) while True: client_hello = tls_out_buff.read() if client_hello != b'': #print('DST client_hello %s' % len(client_hello)) await out_q.put(client_hello) else: break #print('DST wating server hello %s' % ctr) server_hello = await in_q.get() #print('DST server_hello %s' % len(server_hello)) tls_in_buff.write(server_hello) continue except: raise else: #print('DST handshake ok %s' % ctr) #server_fin = tls_out_buff.read() #print('DST server_fin %s ' % server_fin) #await out_q.put(server_fin) break return tls_in_buff, tls_out_buff, tls_obj, None except Exception as e: print(e) return None, None, None, e async def __do_ssl_handshake_srv(self, ssl_ctx, in_q, out_q): try: tls_in_buff = ssl.MemoryBIO() tls_out_buff = ssl.MemoryBIO() tls_obj = ssl_ctx.wrap_bio(tls_in_buff, tls_out_buff, server_side=True) ctr = 0 while not self.stop_evt.is_set(): ctr += 1 #print('wating client hello %s' % ctr) client_hello = await in_q.get() #print('client_hello %s' % len(client_hello)) tls_in_buff.write(client_hello) #print('Performing handshake!') try: tls_obj.do_handshake() except ssl.SSLWantReadError: #print('want %s' % ctr) while True: server_hello = tls_out_buff.read() if server_hello != b'': #print('server_hello %s' % len(server_hello)) await out_q.put(server_hello) else: break continue except: raise else: #print('handshake ok %s' % ctr) while True: server_fin = tls_out_buff.read() if server_fin != b'': await out_q.put(server_fin) else: break break return tls_in_buff, tls_out_buff, tls_obj, None except Exception as e: #print(e) return None, None, None, e async def __read_dec(self, ssl_in_q, final_out_q, tls_in, tls_obj): try: while True: ssl_data = await ssl_in_q.get() if ssl_data == b'': await final_out_q.put(ssl_data) #print('Connection terminated') return tls_in.write(ssl_data) data_buff = b'' while True: try: data_buff += tls_obj.read() except ssl.SSLWantReadError: break #print('data_buff %s' % data_buff) if data_buff != b'': await final_out_q.put(data_buff) except Exception as e: print(e) async def __client_ssl_endpoint(self): try: self.client_tls_in_buff, self.client_tls_out_buff, self.client_tls_obj, err = await self.__do_ssl_handshake_srv(self.client_ssl_ctx, self.__client_ssl_in_q, self.monitor.d2c_out) if err is not None: raise err print('CLIENT Handshake OK!') self.client_ssl_ok_evt.set() await self.destination_ssl_ok_evt.wait() asyncio.create_task(self.__read_dec(self.__client_ssl_in_q, self.c2d_in, self.client_tls_in_buff, self.client_tls_obj)) while not self.stop_evt.is_set(): client_data_buff = await self.c2d_out.get() if client_data_buff == b'': await self.monitor.c2d_out.put(client_data_buff) break #print('client_data %s' % client_data_buff) self.destination_tls_obj.write(client_data_buff) while True: client_ssl_data = self.destination_tls_out_buff.read() if client_ssl_data == b'': break #print('client_ssl_data %s' % client_ssl_data) await self.monitor.c2d_out.put(client_ssl_data) except Exception as e: print('CLIENT error: %s' % e) return None, None, None, e finally: self.stop_evt.set() self.client_ssl_ok_evt.set() self.destination_ssl_ok_evt.set() await self.monitor.c2d_out.put(b'') self.c2d_ssl_task.cancel() self.d2c_ssl_task.cancel() self.dest_task.cancel() async def __destination_ssl_endpoint(self): try: self.destination_tls_in_buff, self.destination_tls_out_buff, self.destination_tls_obj, err = await self.__do_ssl_handshake_cli(self.client_ssl_ctx, self.__destination_ssl_in_q, self.monitor.c2d_out) if err is not None: raise err self.destination_ssl_ok_evt.set() await self.client_ssl_ok_evt.wait() print('DST Handshake OK!') asyncio.create_task(self.__read_dec(self.__destination_ssl_in_q, self.d2c_in, self.destination_tls_in_buff, self.destination_tls_obj)) while not self.stop_evt.is_set(): destination_data_buff = await self.d2c_out.get() if destination_data_buff == b'': await self.monitor.d2c_out.put(destination_data_buff) break print('destination_data %s' % destination_data_buff) self.client_tls_obj.write(destination_data_buff) while True: destination_ssl_data = self.client_tls_out_buff.read() if destination_ssl_data == b'': break await self.monitor.d2c_out.put(destination_ssl_data) except Exception as e: print(type(e)) print('DST error: %s' % e) return None, None, None, e finally: self.stop_evt.set() self.client_ssl_ok_evt.set() self.destination_ssl_ok_evt.set() await self.monitor.d2c_out.put(b'') self.c2d_ssl_task.cancel() self.d2c_ssl_task.cancel() self.client_task.cancel() async def run(self): self.c2d_ssl_task = asyncio.create_task(self.__read_ssl_record(self.monitor.c2d_in, self.__client_ssl_in_q)) self.d2c_ssl_task = asyncio.create_task(self.__read_ssl_record(self.monitor.d2c_in, self.__destination_ssl_in_q)) self.dest_task = asyncio.create_task(self.__destination_ssl_endpoint()) self.client_task = asyncio.create_task(self.__client_ssl_endpoint()) await self.stop_evt.wait()asysocks-0.2.18/asysocks/intercepting/server.py000066400000000000000000000056631510034457600216720ustar00rootroot00000000000000 import asyncio import logging import json import re import ssl from asysocks.intercepting.monitors import prototable from asysocks.intercepting.monitors.sslbase import SSLBaseMonitor from asysocks.intercepting.default_intercept_table import default_intercept_table logger = logging.getLogger('asysocks.intercept') handler = logging.StreamHandler() formatter = logging.Formatter( '%(asctime)s %(name)-12s %(levelname)-8s %(message)s') handler.setFormatter(formatter) logger.addHandler(handler) logger.setLevel(logging.INFO) class InterceptServer: def __init__(self, server, certmanager, log_queue, intercept_targets = None, protocol_load_table = None): self.server = server self.certmanager = certmanager self.log_queue = log_queue self.intercept_targets = intercept_targets self.protocol_load_table = protocol_load_table self.monitor_dispatch_q = asyncio.Queue() self.server_task = None self.processing_task = None self.setup() def setup(self): if self.protocol_load_table is None: self.protocol_load_table = prototable if self.intercept_targets is None: self.intercept_targets = default_intercept_table async def __lookup_monitor(self, monitor): try: selected_protocol = monitor for entry in self.intercept_targets: is_ssl, proto = entry.get_proto_for_monitor(monitor) if proto is not None: if is_ssl is True: client_ssl_ctx = ssl.create_default_context() client_ssl_ctx.check_hostname = False client_ssl_ctx.verify_mode = ssl.CERT_NONE fake_cert_file, fake_key_file, err = await self.certmanager.get_cert_by_host(monitor.get_dst_hostname(), port = monitor.dst_port) if err is not None: print(err) return monitor, err client_ssl_ctx.load_cert_chain(fake_cert_file, fake_key_file) destination_ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) destination_ssl_ctx.check_hostname = False destination_ssl_ctx.verify_mode = ssl.CERT_NONE monitor = SSLBaseMonitor(monitor, client_ssl_ctx = client_ssl_ctx, destination_ssl_ctx = destination_ssl_ctx) asyncio.create_task(monitor.run()) selected_protocol = self.protocol_load_table[proto](monitor, self.log_queue) break return selected_protocol, None except Exception as e: return None, e async def __process_monitors(self): try: print(2) while True: #each connection will give a monitor class monitor = await self.monitor_dispatch_q.get() print('monitor_in') monitor_protocol, err = await self.__lookup_monitor(monitor) if err is not None: print(err) raise err print(44) asyncio.create_task(monitor_protocol.run()) except Exception as e: logger.exception('__process_monitors') print(e) async def run(self): self.server.monitor_dispatch_q = self.monitor_dispatch_q self.server_task = asyncio.create_task(self.server.run()) await asyncio.sleep(0) await self.__process_monitors()asysocks-0.2.18/asysocks/intercepting/target.py000066400000000000000000000031441510034457600216420ustar00rootroot00000000000000import json import re class InterceptTarget: def __init__(self): self.dsthost = None self.dstport = None self.srchost = None self.srcport = None self.proto = None self.ssl = None def to_dict(self): return { 'dsthost' : self.dsthost.pattern if self.dsthost is not None else None, 'dstport' : self.dstport, 'srchost' : self.srchost.pattern if self.srchost is not None else None, 'srcport' : self.srcport, 'proto' : self.proto, 'ssl' : self.ssl, } @staticmethod def from_dict(d): it = InterceptTarget() it.dsthost = re.compile(d['dsthost']) if d.get('dsthost') is not None else None it.dstport = int(d['dstport']) if d.get('dstport') is not None else None it.srchost = re.compile(d['srchost']) if d.get('srchost') is not None else None it.srcport = int(d['srcport']) if d.get('srcport') is not None else None it.proto = d['proto'] it.ssl = d['ssl'] return it @staticmethod def from_json(jdata): return InterceptTarget.from_dict(json.loads(jdata)) def to_json(self): return json.dumps(self.to_dict()) def get_proto_for_monitor(self, monitor): if self.check_intercept(monitor) is True: return self.ssl, self.proto return None, None def check_intercept(self, monitor): if self.dsthost is not None: if self.dsthost.match(monitor.dst_ip) is None: return False if self.dstport is not None: if self.dstport != monitor.dst_port: return False if self.srchost is not None: if self.srchost.match(monitor.client_ip) is None: return False if self.srcport is not None: if self.srcport != monitor.client_port: return False return Trueasysocks-0.2.18/asysocks/network/000077500000000000000000000000001510034457600167765ustar00rootroot00000000000000asysocks-0.2.18/asysocks/network/__init__.py000066400000000000000000000000001510034457600210750ustar00rootroot00000000000000asysocks-0.2.18/asysocks/network/queue.py000066400000000000000000000025641510034457600205030ustar00rootroot00000000000000 class NetworkQueue: def __init__(self): self.in_queue = None self.out_queue = None self.buffer = b'' self.data_in_evt = None self.pipe_broken_evt = None async def run(self): try: pass except Exception as e: print(e) async def read(self, n = -1): try: if self.pipe_broken_evt.is_set(): return b'' while len(self.buffer) == 0: await self.data_in_evt.wait() if n == -1: temp = self.buffer self.buffer = b'' return temp temp = self.buffer[:n] self.buffer = self.buffer[n:] return temp except Exception as e: print(e) async def readexactly(self, n): try: if self.pipe_broken_evt.is_set(): raise Exception('Pipe broken!') if n < 1: raise Exception('Readexactly must be a positive integer!') while len(self.buffer) >= n: await self.data_in_evt.wait() temp = self.buffer[:n] self.buffer = self.buffer[n:] return temp except Exception as e: print(e) async def readuntil(self, pattern): try: if self.pipe_broken_evt.is_set(): raise Exception('Pipe broken!') while self.buffer.find(pattern) == -1: await self.data_in_evt.wait() end = self.buffer.find(pattern)+len(pattern) temp = self.buffer[:end] self.buffer = self.buffer[end:] return temp except Exception as e: print(e) async def readline(self): return await self.readuntil(b'\n') asysocks-0.2.18/asysocks/network/tcp.py000066400000000000000000000000001510034457600201240ustar00rootroot00000000000000asysocks-0.2.18/asysocks/network/wsnet.py000066400000000000000000000167401510034457600205200ustar00rootroot00000000000000from wsnet.pyodide.client import WSNetworkTCP, WSNetworkUDP import asyncio # This code only works properly in the conditions met in this specific library. # If you want to copy it to your project please make sure that you # NEVER under any circumstances use the reader in multiple separate corutines at the same time # else you gonna have a bad time. # class WSNETNetworkTest: @staticmethod async def open_connection(host, port, wsnet_reuse = False): from asysocks.test.wsnettest import WSNetworkTCPTEST out_queue = asyncio.Queue() in_queue = asyncio.Queue() closed_event = asyncio.Event() client = WSNetworkTCPTEST(host, int(port), in_queue, out_queue, wsnet_reuse) _, err = await client.run() if err is not None: raise err writer = WSNETWriter(out_queue, closed_event) reader = WSNETReader(in_queue, closed_event) await writer.run() await reader.run() return reader, writer class WSNETNetwork: @staticmethod async def open_connection(host, port, wsnet_reuse = False): out_queue = asyncio.Queue() in_queue = asyncio.Queue() closed_event = asyncio.Event() client = WSNetworkTCP(host, int(port), in_queue, out_queue, wsnet_reuse) _, err = await client.run() if err is not None: raise err writer = WSNETWriter(out_queue, closed_event) reader = WSNETReader(in_queue, closed_event) await writer.run() await reader.run() return reader, writer class WSNETWriter: def __init__(self, out_queue, closed_event): self.out_queue = out_queue self.closed_event = closed_event def write(self, data): self.out_queue.put_nowait(data) def close(self): self.out_queue.put_nowait(None) self.closed_event.set() async def drain(self): await asyncio.sleep(0) return async def run(self): return class WSNETReader: def __init__(self, in_queue, closed_event): self.wsnet_reader_type = None self.in_queue = in_queue self.closed_event = closed_event self.buffer = b'' self.data_in_evt = asyncio.Event() self.err = None self.bufferlock = asyncio.Lock() async def __handle_in(self): while True: res, self.err = await self.in_queue.get() if res is None and self.err is None: #this means the connection is closed self.closed_event.set() return if self.err is not None: if res is not None and res != b'': self.buffer += res.data self.data_in_evt.set() self.closed_event.set() return self.buffer += res self.data_in_evt.set() if res == b'': break self.closed_event.set() async def run(self): self.handle_task = asyncio.create_task(self.__handle_in()) await asyncio.sleep(0) #making sure prev line fired async def read(self, n = -1): try: #print('read') if self.closed_event.is_set(): if len(self.buffer) == 0: return b'' if n == -1 or len(self.buffer) < n: temp = self.buffer self.buffer = b'' return temp else: temp = self.buffer[:n] self.buffer = self.buffer[n:] return temp async with self.bufferlock: if n == -1: if len(self.buffer) == 0: self.data_in_evt.clear() await self.data_in_evt.wait() temp = self.buffer self.buffer = b'' else: if len(self.buffer) == 0: self.data_in_evt.clear() await self.data_in_evt.wait() temp = self.buffer[:n] self.buffer = self.buffer[n:] #print('read ret %s' % temp) return temp except Exception as e: #print(e) self.closed_event.set() raise async def readexactly(self, n): try: if self.closed_event.is_set(): if len(self.buffer) == 0 or len(self.buffer) < n: raise Exception('Pipe broken!') else: temp = self.buffer[:n] self.buffer = self.buffer[n:] return temp if n < 1: raise Exception('Readexactly must be a positive integer!') async with self.bufferlock: while len(self.buffer) < n: self.data_in_evt.clear() await self.data_in_evt.wait() #print('self.buffer %s' % self.buffer) temp = self.buffer[:n] self.buffer = self.buffer[n:] #print('readexactly ret %s' % temp) return temp except Exception as e: #print(e) self.closed_event.set() raise async def readuntil(self, separator = b'\n'): try: if self.closed_event.is_set(): pos = self.buffer.find(separator) == -1 if pos == -1: raise Exception('Pipe broken!') else: end = pos+len(separator) temp = self.buffer[:end] self.buffer = self.buffer[end:] return temp async with self.bufferlock: while self.buffer.find(separator) == -1: self.data_in_evt.clear() await self.data_in_evt.wait() end = self.buffer.find(separator)+len(separator) temp = self.buffer[:end] self.buffer = self.buffer[end:] #print('readuntil ret %s' % temp) return temp except Exception as e: #print(e) self.closed_event.set() raise async def readline(self): return await self.readuntil(b'\n') def at_eof(self): return self.closed_event.is_set() and len(self.buffer) == 0 from asysocks.unicomm.common.target import UniTarget async def create_udp_client(target:UniTarget): in_q = asyncio.Queue() out_q = asyncio.Queue() connection = WSNetworkUDP(target.get_ip_or_hostname(), target.port, in_q, out_q, reuse_ws = True) _, err = await connection.run() if err is not None: raise err return WSNETEndpoint(target, in_q, out_q) ### THIS CLASS MUST LOOK SIMILAR TO THE ONE IN asysocks/unicomm/common/connection.py class WSNETEndpoint: def __init__(self, target:UniTarget, in_queue:asyncio.Queue, out_queue:asyncio.Queue): self.target = target self.in_queue = in_queue self.out_queue = out_queue self._closed = False def close(self): if self._closed: return self._closed = True self.out_queue.put_nowait(None) self.in_queue.put_nowait(None) async def write(self, data, addr = None): """Send a datagram to the given address.""" if addr is None: addr = (self.target.get_ip_or_hostname(), self.target.port) return self.send(data, addr) async def read(self, with_addr = False): """Wait for an incoming datagram and return it with the corresponding address. This method is a coroutine. """ while not self._closed: data, addr = await self.receive() if data is None: self.close() break result = (data, addr) if with_addr is True: yield result else: yield data async def read_one(self, with_addr = False): """Wait for an incoming datagram and return it with the corresponding address. This method is a coroutine. """ data, addr = await self.receive() if data is None: self.close() return None if with_addr is True: return (data, addr) return data def send(self, data, addr): """Send a datagram to the given address.""" if self._closed: raise IOError("Enpoint is closed") self.out_queue.put_nowait((data, addr)) async def receive(self): """Wait for an incoming datagram and return it with the corresponding address. This method is a coroutine. """ res = await self.in_queue.get() if res is None: raise IOError("Enpoint is closed") data, addr, err = res if err is not None: raise err if data is None: raise IOError("Enpoint is closed") return data, addr def abort(self): """Close the transport immediately.""" if self._closed: return self.close() async def drain(self): return True @property def address(self): """The endpoint address as a (host, port) tuple.""" raise NotImplementedError() @property def closed(self): """Indicates whether the endpoint is closed or not.""" return self._closedasysocks-0.2.18/asysocks/network/wsnetdirect.py000066400000000000000000000102461510034457600217060ustar00rootroot00000000000000 import asyncio import os import traceback from wsnet.protocol import * import websockets from asysocks.unicomm import logger from asysocks.network.wsnet import WSNETWriter, WSNETReader from asysocks.unicomm.common.proxy import UniProxyProto class WSNetworkDirect: def __init__(self, server_ip, server_port, server_protocol, ip, port, in_q, out_q, reuse = False): self.ws_url = None self.ws = None self.ws_proxy = None self.server_ip = server_ip self.server_port = server_port self.server_protocol = server_protocol self.ip = ip self.port = port self.in_q = in_q self.out_q = out_q self.token = os.urandom(16) self.in_task = None self.out_task = None async def terminate(self): if self.in_task is not None: self.in_task.cancel() if self.out_task is not None: self.out_task.cancel() if self.ws is not None: await self.ws.close() self.ws = None async def __handle_in(self): try: while self.ws.open: try: data = await self.ws.recv() cmd = CMD.from_bytes(bytearray(data)) #print('__handle_in %s' % cmd) if cmd.type == CMDType.OK: logger.debug('Remote end terminated the socket') raise Exception('Remote end terminated the socket') elif cmd.type == CMDType.ERR: logger.debug('Proxy sent error during data transmission. Killing the tunnel.') raise Exception('Proxy sent error during data transmission. Killing the tunnel.') await self.in_q.put((cmd.data, None)) except asyncio.CancelledError: return except Exception as e: traceback.print_exc() await self.in_q.put((None, e)) return except: traceback.print_exc() finally: await self.terminate() async def __handle_out(self): try: while self.ws.open: data = await self.out_q.get() #print('OUT %s' % data) if data is None or data == b'': return cmd = WSNSocketData(self.token, data) await self.ws.send(cmd.to_bytes()) except Exception as e: traceback.print_exc() return finally: try: cmd = WSNOK(self.token) await self.ws.send(cmd.to_bytes()) except: pass await self.terminate() async def connect(self): try: logger.debug('WSNetworkDirect connecting... %s:%s' % (self.ip, self.port)) cmd = WSNConnect(self.token, 'TCP', self.ip, self.port) await self.ws.send(cmd.to_bytes()) data = await self.ws.recv() cmd = CMD.from_bytes(bytearray(data)) logger.debug('WSNetworkDirect connect reply %s' % cmd) if cmd.type == CMDType.CONTINUE: return True, None if cmd.type == CMDType.ERR: raise Exception('Connection failed, proxy sent error. Err: %s' % cmd.reason) raise Exception('Connection failed, expected CONTINUE, got %s' % cmd.type.value) except Exception as e: traceback.print_exc() return False, e async def run(self): try: if self.server_protocol == UniProxyProto.CLIENT_WSNETDIRECT: proto = 'ws' elif self.server_protocol == UniProxyProto.CLIENT_SSL_WSNETDIRECT: proto = 'wss' else: proto = 'ws' if self.server_port is None or self.server_port == '' or self.server_port == 0: self.server_port = 8700 self.ws_url = '%s://%s:%s' % (proto, self.server_ip, self.server_port) logger.debug('WSNetworkDirect connecting to proxy server at %s' % self.ws_url) self.ws = await websockets.connect(self.ws_url) _, err = await self.connect() if err is not None: await self.in_q.put(None) return False, err self.in_task = asyncio.create_task(self.__handle_in()) self.out_task = asyncio.create_task(self.__handle_out()) return True, None except Exception as e: traceback.print_exc() await self.terminate() return False, e @staticmethod async def open_connection(server_ip, server_port, server_protocol, host, port, wsnet_reuse = False): out_queue = asyncio.Queue() in_queue = asyncio.Queue() closed_event = asyncio.Event() client = WSNetworkDirect(server_ip, server_port, server_protocol, host, int(port), in_queue, out_queue, wsnet_reuse) _, err = await client.run() if err is not None: raise err writer = WSNETWriter(out_queue, closed_event) reader = WSNETReader(in_queue, closed_event) await writer.run() await reader.run() return reader, writerasysocks-0.2.18/asysocks/network/wsnetws.py000066400000000000000000000107101510034457600210610ustar00rootroot00000000000000from wsnet.operator.networkproxy import WSNetworkWS from asysocks.common.constants import SocksServerVersion import asyncio import traceback from asysocks import logger # This code only works properly in the conditions met in this specific library. # If you want to copy it to your project please make sure that you # NEVER under any circumstances use the reader in multiple separate corutines at the same time # else you gonna have a bad time. # class WSNETNetworkWS: @staticmethod async def open_connection(target_host, target_port, host, port, proto, agentid = None, timeout = None): try: out_queue = asyncio.Queue() in_queue = asyncio.Queue() closed_event = asyncio.Event() proto = 'ws' if proto == SocksServerVersion.WSNETWSS: proto = 'wss' url = '%s://%s:%s/' % (proto, host, port) logger.debug('WSNETNetworkWS URL: %s' % url) logger.debug('WSNETNetworkWS AGENTID: %s' % agentid) agentid = bytes.fromhex(agentid) client = WSNetworkWS(target_host, target_port, url, in_queue, out_queue, agentid) _, err = await asyncio.wait_for(client.run(), timeout) if err is not None: raise err writer = WSNETWriter(out_queue, closed_event, client) reader = WSNETReader(in_queue, closed_event) await writer.run() await reader.run() return reader, writer except Exception as e: print('W') traceback.print_exc() return None, e class WSNETWriter: def __init__(self, out_queue, closed_event, client): self.out_queue = out_queue self.closed_event = closed_event self.client = client def write(self, data): #print('THIS IS DATA: %s' % data) self.out_queue.put_nowait(data) def close(self): self.out_queue.put_nowait(None) self.closed_event.set() asyncio.create_task(self.client.terminate()) async def drain(self): await asyncio.sleep(0) return async def run(self): return class WSNETReader: def __init__(self, in_queue, closed_event): self.wsnet_reader_type = None self.in_queue = in_queue self.closed_event = closed_event self.buffer = b'' self.data_in_evt = [] self.err = None async def __handle_in(self): try: while True: res, self.err = await self.in_queue.get() if self.err is not None: if res is not None: self.buffer += res if len(self.data_in_evt) != 0: evt = self.data_in_evt.pop() evt.set() self.closed_event.set() return self.buffer += res if len(self.data_in_evt) != 0: evt = self.data_in_evt.pop() evt.set() except asyncio.CancelledError: return except Exception as e: raise e finally: self.closed_event.set() for evt in self.data_in_evt: evt.set() async def run(self): self.handle_task = asyncio.create_task(self.__handle_in()) await asyncio.sleep(0) #making sure prev line fired async def read(self, n = -1): try: #print('read') if self.closed_event.is_set(): data = self.buffer #print('read ret %s' % data) self.buffer = b'' return data if len(self.buffer) == 0: evt = asyncio.Event() self.data_in_evt.append(evt) await evt.wait() temp = self.buffer[:n] self.buffer = self.buffer[n:] #print('read ret %s' % temp) return temp except asyncio.CancelledError: return except Exception as e: #print(e) self.closed_event.set() raise async def readexactly(self, n): try: if self.closed_event.is_set(): raise Exception('Pipe broken!') if n < 1: raise Exception('Readexactly must be a positive integer!') while len(self.buffer) < n: #print('readexactly waiting...') evt = asyncio.Event() self.data_in_evt.append(evt) await evt.wait() #print('self.buffer %s' % self.buffer) temp = self.buffer[:n] self.buffer = self.buffer[n:] #print('readexactly ret %s' % temp) return temp except asyncio.CancelledError: return except Exception as e: #print(e) self.closed_event.set() raise async def readuntil(self, pattern): try: if self.closed_event.is_set(): raise Exception('Pipe broken!') while self.buffer.find(pattern) == -1: evt = asyncio.Event() self.data_in_evt.append(evt) await evt.wait() end = self.buffer.find(pattern)+len(pattern) temp = self.buffer[:end] self.buffer = self.buffer[end:] #print('readuntil ret %s' % temp) return temp except asyncio.CancelledError: return except Exception as e: #print(e) self.closed_event.set() raise async def readline(self): return await self.readuntil(b'\n') asysocks-0.2.18/asysocks/protocol/000077500000000000000000000000001510034457600171465ustar00rootroot00000000000000asysocks-0.2.18/asysocks/protocol/__init__.py000066400000000000000000000000001510034457600212450ustar00rootroot00000000000000asysocks-0.2.18/asysocks/protocol/http.py000066400000000000000000000054351510034457600205060ustar00rootroot00000000000000import asyncio import io class HTTPProxyAuthRequiredException(Exception): pass class HTTPProxyAuthFailed(Exception): pass class HTTPResponse: def __init__(self): self.version = None self.status = None self.reason = None self.headers = {} self.headers_upper = {} self.data = None @staticmethod async def from_streamreader(reader, timeout = None): try: resp = HTTPResponse() #reading headers temp = await asyncio.wait_for(reader.readuntil(b'\r\n\r\n'), timeout = timeout) temp = temp.split(b'\r\n')[:-1] version, status, reason = temp[0].split(b' ', 2) resp.version = version.decode() resp.status = int(status.decode()) resp.reason = reason.decode() for hdr_raw in temp[1:]: if hdr_raw.strip() == b'': continue key_raw, value_raw = hdr_raw.split(b': ', 1) key = key_raw.decode() value = value_raw.strip().decode() resp.headers[key] = value resp.headers_upper[key.upper()] = value if 'CONTENT-LENGTH' in resp.headers_upper: rem_len = int(resp.headers_upper['CONTENT-LENGTH']) resp.data = await asyncio.wait_for(reader.readexactly(rem_len), timeout = timeout) return resp, None except Exception as e: return None, e class HTTPRequest: def __init__(self): self.method = None self.uri = None self.version = None self.headers = {} self.headers_upper = {} self.data = None def __str__(self): t = '%s %s %s\r\n' % (self.method, self.uri, self.version) for x in self.headers: t += '%s: %s\r\n' % (x, self.headers[x]) t += '\r\n' if self.data is not None: t += '' return t def to_bytes(self): t = '%s %s %s\r\n' % (self.method, self.uri, self.version) for x in self.headers: t += '%s: %s\r\n' % (x, self.headers[x]) t += '\r\n' t = t.encode() if self.data is not None: t += self.data return t @staticmethod async def from_streamreader(reader, timeout = None, pre_data = None): try: req = HTTPRequest() #reading headers temp = await asyncio.wait_for(reader.readuntil(b'\r\n\r\n'), timeout = timeout) if pre_data is not None: temp = pre_data + temp temp = temp.split(b'\r\n')[:-1] method, uri, version = temp[0].split(b' ', 2) req.method = method.decode() req.uri = uri.decode() req.version = version.decode() for hdr_raw in temp[1:]: if hdr_raw.strip() == b'': continue key_raw, value_raw = hdr_raw.split(b': ', 1) key = key_raw.decode() value = value_raw.strip().decode() req.headers[key] = value req.headers_upper[key.upper()] = value if 'CONTENT-LENGTH' in req.headers_upper: rem_len = int(req.headers_upper['CONTENT-LENGTH']) req.data = await asyncio.wait_for(reader.readexactly(rem_len), timeout = timeout) return req, None except Exception as e: return None, easysocks-0.2.18/asysocks/protocol/socks4.py000066400000000000000000000063221510034457600207310ustar00rootroot00000000000000 # https://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol import io import os import enum import ipaddress import socket import asyncio SOCKS4_USERID_MAC_LEN = 255 class SOCKS4CDCode(enum.Enum): REQ_CONNECT = 1 REQ_BIND = 2 REP_GRANTED = 90 #request granted REP_FAILED = 91 #request rejected or failed REP_FAILED_NOCONN = 92 #request rejected becasue SOCKS server cannot connect to identd on the client REP_FAILED_USRID_MISMATCH = 93 #request rejected because the client program and identd report different user-ids class SOCKS4Request: def __init__(self): self.VN = 4 self.CD = SOCKS4CDCode.REQ_CONNECT self.DSTPORT = None self.DSTIP = None self.USERID = None @staticmethod def from_bytes(data): return SOCKS4Request.from_buffer(io.BytesIO(data)) @staticmethod async def from_streamreader(reader, timeout = None): try: t = await asyncio.wait_for(reader.readuntil(b'\x00'), timeout = timeout) return SOCKS4Request.from_bytes(t), None except Exception as e: return None, e @staticmethod def from_buffer(buff): o = SOCKS4Request() o.VN = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) o.CD = SOCKS4CDCode(int.from_bytes(buff.read(1), byteorder = 'big', signed = False)) o.DSTPORT = int.from_bytes(buff.read(2), byteorder = 'big', signed = False) o.DSTIP = ipaddress.IPv4Address(buff.read(4)) o.USERID = b'' for _ in range(SOCKS4_USERID_MAC_LEN): x = buff.read(1) if x == 0: break o.USERID += x return o @staticmethod def from_target(target): o = SOCKS4Request() if target.is_bind is True: SOCKS4CDCode.REQ_BIND o.DSTPORT = target.endpoint_port if isinstance(target.endpoint_ip, ipaddress.IPv4Address): o.DSTIP = target.endpoint_ip else: o.DSTIP = ipaddress.ip_address(target.endpoint_ip) o.USERID = target.userid if target.userid is None: o.USERID = os.urandom(4).hex().encode('ascii') return o def to_bytes(self): t = self.VN.to_bytes(1, byteorder = 'big', signed = False) t += self.CD.value.to_bytes(1, byteorder = 'big', signed = False) t += self.DSTPORT.to_bytes(2, byteorder = 'big', signed = False) t += self.DSTIP.packed t += self.USERID t += b'\x00' #trailing for userid return t class SOCKS4Reply: def __init__(self): self.VN = 0 self.CD = None self.DSTPORT = None self.DSTIP = None @staticmethod def from_bytes(data): return SOCKS4Reply.from_buffer(io.BytesIO(data)) @staticmethod async def from_streamreader(reader, timeout = None): try: t = await asyncio.wait_for(reader.readexactly(8), timeout = timeout) return SOCKS4Reply.from_bytes(t), None except Exception as e: return None, e @staticmethod def from_buffer(buff): o = SOCKS4Reply() o.VN = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) o.CD = SOCKS4CDCode(int.from_bytes(buff.read(1), byteorder = 'big', signed = False)) o.DSTPORT = int.from_bytes(buff.read(2), byteorder = 'big', signed = False) o.DSTIP = ipaddress.IPv4Address(buff.read(4)) return o def to_bytes(self): t = self.VN.to_bytes(1, byteorder = 'big', signed = False) t += self.CD.value.to_bytes(1, byteorder = 'big', signed = False) t += self.DSTPORT.to_bytes(2, byteorder = 'big', signed = False) t += self.DSTIP.packed return t asysocks-0.2.18/asysocks/protocol/socks4a.py000066400000000000000000000075111510034457600210730ustar00rootroot00000000000000 # https://www.openssh.com/txt/socks4a.protocol import io import os import enum import ipaddress import socket import asyncio SOCKS4_USERID_MAC_LEN = 255 class SOCKS4ACDCode(enum.Enum): REQ_CONNECT = 1 REQ_BIND = 2 REP_GRANTED = 90 #request granted REP_FAILED = 91 #request rejected or failed REP_FAILED_NOCONN = 92 #request rejected becasue SOCKS server cannot connect to identd on the client REP_FAILED_USRID_MISMATCH = 93 #request rejected because the client program and identd report different user-ids class SOCKS4ARequest: def __init__(self): self.VN = 4 self.CD = SOCKS4ACDCode.REQ_CONNECT self.DSTPORT = None self.DSTIP = None self.USERID = None self.DOMAIN = None @staticmethod def from_bytes(data): return SOCKS4ARequest.from_buffer(io.BytesIO(data)) @staticmethod async def from_streamreader(reader, timeout = None): try: buff = b'' t = await asyncio.wait_for(reader.readexactly(4), timeout = timeout) buff += t t = await asyncio.wait_for(reader.readexactly(4), timeout = timeout) buff += t r = 1 if t[0:3] == b'\x00\x00\x00': r = 2 for _ in range(0,r): t = await asyncio.wait_for(reader.readuntil(b'\x00'), timeout = timeout) buff += t return SOCKS4ARequest.from_bytes(buff), None except Exception as e: return None, e @staticmethod def from_buffer(buff): o = SOCKS4ARequest() o.VN = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) o.CD = SOCKS4ACDCode(int.from_bytes(buff.read(1), byteorder = 'big', signed = False)) o.DSTPORT = int.from_bytes(buff.read(2), byteorder = 'big', signed = False) o.DSTIP = ipaddress.IPv4Address(buff.read(4)) o.USERID = b'' for _ in range(SOCKS4_USERID_MAC_LEN): x = buff.read(1) if x == 0: break o.USERID += x if str(o.DSTIP).startswith('0.0.0.'): for _ in range(SOCKS4_USERID_MAC_LEN): x = buff.read(1) if x == 0: break o.DOMAIN += x return o @staticmethod def from_target(target): o = SOCKS4ARequest() if target.is_bind is True: SOCKS4ACDCode.REQ_BIND o.DSTPORT = target.endpoint_port if isinstance(target.endpoint_ip, ipaddress.IPv4Address): o.DSTIP = target.endpoint_ip else: try: o.DSTIP = ipaddress.ip_address(target.endpoint_ip) except: o.DSTIP = ipaddress.ip_address('0.0.0.3') o.DOMAIN = str(target.endpoint_ip) o.USERID = target.userid if target.userid is None: o.USERID = os.urandom(4).hex().encode('ascii') return o def to_bytes(self): t = self.VN.to_bytes(1, byteorder = 'big', signed = False) t += self.CD.value.to_bytes(1, byteorder = 'big', signed = False) t += self.DSTPORT.to_bytes(2, byteorder = 'big', signed = False) t += self.DSTIP.packed t += self.USERID t += b'\x00' #trailing for userid if self.DOMAIN is not None: t += self.DOMAIN.encode() t += b'\x00' return t class SOCKS4AReply: def __init__(self): self.VN = 0 self.CD = None self.DSTPORT = None self.DSTIP = None @staticmethod def from_bytes(data): return SOCKS4AReply.from_buffer(io.BytesIO(data)) @staticmethod async def from_streamreader(reader, timeout = None): try: t = await asyncio.wait_for(reader.readexactly(8), timeout = timeout) return SOCKS4AReply.from_bytes(t), None except Exception as e: return None, e @staticmethod def from_buffer(buff): o = SOCKS4AReply() o.VN = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) o.CD = SOCKS4ACDCode(int.from_bytes(buff.read(1), byteorder = 'big', signed = False)) o.DSTPORT = int.from_bytes(buff.read(2), byteorder = 'big', signed = False) o.DSTIP = ipaddress.IPv4Address(buff.read(4)) return o def to_bytes(self): t = self.VN.to_bytes(1, byteorder = 'big', signed = False) t += self.CD.value.to_bytes(1, byteorder = 'big', signed = False) t += self.DSTPORT.to_bytes(2, byteorder = 'big', signed = False) t += self.DSTIP.packed return t asysocks-0.2.18/asysocks/protocol/socks5.py000066400000000000000000000476111510034457600207400ustar00rootroot00000000000000#https://www.ietf.org/rfc/rfc1928.txt #https://tools.ietf.org/html/rfc1929 #https://tools.ietf.org/html/rfc1961 import io import enum import ipaddress import socket import asyncio from asysocks.common.constants import SOCKS5Method class SOCKS5ServerState(enum.Enum): NEGOTIATION = 0 NOT_AUTHENTICATED = 1 REQUEST = 3 RELAYING = 4 class SOCKS5Command(enum.Enum): CONNECT = 0x01 BIND = 0x02 UDP_ASSOCIATE = 0x03 class SOCKS5AddressType(enum.Enum): IP_V4 = 0x01 DOMAINNAME = 0x03 IP_V6 = 0x04 class SOCKS5ReplyType(enum.Enum): SUCCEEDED = 0x00 # o X'00' succeeded FAILURE = 0x01 # o X'01' general SOCKS server failure CONN_NOT_ALLOWED = 0x02 # o X'02' connection not allowed by ruleset NETWORK_UNREACHABLE = 0x03 #o X'03' Network unreachable HOST_UNREACHABLE = 0x04 #o X'04' Host unreachable CONN_REFUSED = 0x05 #o X'05' Connection refused TTL_EXPIRED = 0x06 #o X'06' TTL expired COMMAND_NOT_SUPPORTED = 0x07 #o X'07' Command not supported ADDRESS_TYPE_NOT_SUPPORTED = 0x08 #o X'08' Address type not supported #o X'09' to X'FF' unassigned class SOCKS5ServerErrorReply(Exception): def __init__(self, reply): self.reply = reply def __str__(self): return self.reply.name class SOCKS5AuthFailed(Exception): pass class SOCKS5SocketParser: def __init__(self, protocol = socket.SOCK_STREAM): self.protocol = protocol def parse(self, soc, packet_type): return packet_type.from_bytes(self.read_soc(soc, packet_type.size)) def read_soc(self, soc, size): data = b'' while True: temp = soc.recv(4096) if temp == '': break data += temp if len(data) == size: break return data class SOCKS5CommandParser: # the reason we need this class is: SOCKS5 protocol messages doesn't have a type field, # the messages are parsed in context of the session itself. def __init__(self, protocol = socket.SOCK_STREAM): self.protocol = protocol #not used atm def parse(self, buff, session): if session.current_state == SOCKS5ServerState.NEGOTIATION: return SOCKS5Nego.from_buffer(buff) if session.current_state == SOCKS5ServerState.NOT_AUTHENTICATED: if session.mutual_auth_type == SOCKS5Method.PLAIN: return SOCKS5PlainAuth.from_buffer(buff) else: raise Exception('Not implemented!') if session.current_state == SOCKS5ServerState.REQUEST: return SOCKS5Request.from_buffer(buff) @staticmethod async def from_streamreader(reader, session, timeout = None): raise NotImplementedError() #if session.current_state == SOCKS5ServerState.NEGOTIATION: # t = await asyncio.wait_for(SOCKS5Nego.from_streamreader(reader), timeout = timeout) # return t # #if session.current_state == SOCKS5ServerState.NOT_AUTHENTICATED: # if session.mutual_auth_type == SOCKS5Method.PLAIN: # t = await asyncio.wait_for(SOCKS5PlainAuth.from_streamreader(reader), timeout = timeout) # return t # else: # raise Exception('Not implemented!') # #if session.current_state == SOCKS5ServerState.REQUEST: # t = await asyncio.wait_for(SOCKS5Request.from_streamreader(reader), timeout = timeout) # return t class SOCKS5AuthHandler: def __init__(self, authtype, creds = None): self.authtype = authtype self.creds = creds def do_AUTH(self, msg): if self.authtype == SOCKS5Method.PLAIN: if not isinstance(msg, SOCKS5PlainAuth): raise Exception('Wrong message/auth type!') if self.creds is None: return True, SOCKS5PlainCredentials(msg.UNAME, msg.PASSWD) else: if msg.UNAME in self.creds: if msg.PASSWD == self.creds[msg.UNAME]: return True, SOCKS5PlainCredentials(msg.UNAME, msg.PASSWD) return False, SOCKS5PlainCredentials(msg.UNAME, msg.PASSWD) elif self.authtype == SOCKS5Method.GSSAPI: raise Exception('Not implemented! yet') else: raise Exception('Not implemented!') class SOCKS5PlainCredentials: def __init__(self, username, password): self.username = username self.password = password class SOCKS5PlainAuthReply: def __init__(self): self.VER = None self.STATUS = None @staticmethod async def from_streamreader(reader): rep = SOCKS5PlainAuthReply() t = await reader.read(1) rep.VER = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.read(1) rep.STATUS = SOCKS5ReplyType(int.from_bytes(t, byteorder = 'big', signed = False)) return rep @staticmethod def from_bytes(data): return SOCKS5PlainAuthReply.from_buffer(io.BytesIO(data)) @staticmethod def from_buffer(buff): rep = SOCKS5PlainAuthReply() rep.VER = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) rep.STATUS = SOCKS5ReplyType(int.from_bytes(buff.read(1), byteorder = 'big', signed = False)) return rep class SOCKS5PlainAuth: def __init__(self): self.VER = None self.ULEN = None self.UNAME = None self.PLEN = None self.PASSWD = None @staticmethod async def from_streamreader(reader): auth = SOCKS5PlainAuth() t = await reader.read(1) auth.VER = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.read(1) auth.ULEN = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.read(auth.ULEN) auth.UNAME = t.decode() t = await reader.read(1) auth.PLEN = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.read(auth.PLEN) auth.PASSWD = t.decode() return auth @staticmethod def from_bytes(bbuff): return SOCKS5PlainAuth.from_buffer(io.BytesIO(bbuff)) @staticmethod def from_buffer(buff): auth = SOCKS5PlainAuth() auth.VER = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) auth.ULEN = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) auth.UNAME = buff.read(auth.ULEN).decode() auth.PLEN = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) auth.PASSWD = buff.read(auth.PLEN).decode() return auth @staticmethod def construct(username, password): auth = SOCKS5PlainAuth() auth.VER = 1 auth.ULEN = len(username) auth.UNAME = username auth.PLEN = len(password) auth.PASSWD = password return auth def to_bytes(self): t = self.VER.to_bytes(1, byteorder = 'big', signed = False) t += self.ULEN.to_bytes(1, byteorder = 'big', signed = False) t += self.UNAME.encode() t += self.PLEN.to_bytes(1, byteorder = 'big', signed = False) t += self.PASSWD.encode() return t class SOCKS5Nego: def __init__(self): self.VER = 5 self.NMETHODS = None self.METHODS = None @staticmethod async def from_streamreader(reader): nego = SOCKS5Nego() t = await reader.readexactly(1) nego.VER = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.readexactly(1) nego.NMETHODS = int.from_bytes(t, byteorder = 'big', signed = False) nego.METHODS = [] for _ in range(nego.NMETHODS): t = await reader.readexactly(1) nego.METHODS.append(SOCKS5Method(int.from_bytes(t, byteorder = 'big', signed = False))) return nego @staticmethod def from_bytes(bbuff): return SOCKS5Nego.from_buffer(io.BytesIO(bbuff)) @staticmethod def from_buffer(buff): nego = SOCKS5Nego() nego.VER = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) nego.NMETHODS = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) nego.METHODS = [] for _ in range(nego.NMETHODS): nego.METHODS.append(SOCKS5Method(int.from_bytes(buff.read(1), byteorder = 'big', signed = False))) return nego @staticmethod def from_methods(methods): if not isinstance(methods, list): methods = [methods] nego = SOCKS5Nego() nego.NMETHODS = len(methods) nego.METHODS = methods return nego def to_bytes(self): t = self.VER.to_bytes(1, byteorder = 'big', signed = False) t += self.NMETHODS.to_bytes(1, byteorder = 'big', signed = False) for method in self.METHODS: t += method.value.to_bytes(1, byteorder = 'big', signed = False) return t class SOCKS5NegoReply: def __init__(self): self.VER = 5 self.METHOD = None def __repr__(self): t = '== SOCKS5NegoReply ==\r\n' t += 'VER: %s\r\n' % self.VER t += 'METHOD: %s\r\n' % self.METHOD return t @staticmethod def from_socket(soc): data = b'' total_size = 2 while True: temp = soc.recv(1024) if temp == b'': break data += temp if len(data) >= total_size: break return SOCKS5NegoReply.from_bytes(data) @staticmethod async def from_streamreader(reader): rep = SOCKS5NegoReply() t = await reader.readexactly(1) rep.VER = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.readexactly(1) rep.METHOD = SOCKS5Method(int.from_bytes(t, byteorder = 'big', signed = False)) return rep @staticmethod def from_bytes(bbuff): return SOCKS5NegoReply.from_buffer(io.BytesIO(bbuff)) @staticmethod def from_buffer(buff): rep = SOCKS5NegoReply() rep.VER = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) rep.METHOD = SOCKS5Method(int.from_bytes(buff.read(1), byteorder = 'big', signed = False)) return rep @staticmethod def construct(method): rep = SOCKS5NegoReply() rep.VER = 5 rep.METHOD = method return rep @staticmethod def construct_auth(method, ver = 1): rep = SOCKS5NegoReply() rep.VER = ver rep.METHOD = method return rep def to_bytes(self): t = self.VER.to_bytes(1, byteorder = 'big', signed = False) t += self.METHOD.value.to_bytes(1, byteorder = 'big', signed = False) return t class SOCKS5Request: def __init__(self): self.VER = 5 self.CMD = SOCKS5Command.CONNECT self.RSV = 0 self.ATYP = None self.DST_ADDR = None self.DST_PORT = None @staticmethod async def from_streamreader(reader): req = SOCKS5Request() t = await reader.readexactly(1) req.VER = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.readexactly(1) req.CMD = SOCKS5Command(int.from_bytes(t, byteorder = 'big', signed = False)) t = await reader.readexactly(1) req.RSV = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.readexactly(1) req.ATYP = SOCKS5AddressType(int.from_bytes(t, byteorder = 'big', signed = False)) if req.ATYP == SOCKS5AddressType.IP_V4: t = await reader.readexactly(4) req.DST_ADDR = ipaddress.IPv4Address(t) elif req.ATYP == SOCKS5AddressType.IP_V6: t = await reader.readexactly(16) req.DST_ADDR = ipaddress.IPv6Address(t) elif req.ATYP == SOCKS5AddressType.DOMAINNAME: t = await reader.readexactly(1) length = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.readexactly(length) req.DST_ADDR = t.decode() t = await reader.readexactly(2) req.DST_PORT = int.from_bytes(t, byteorder = 'big', signed = False) return req @staticmethod def from_bytes(bbuff): return SOCKS5Request.from_buffer(io.BytesIO(bbuff)) @staticmethod def from_buffer(buff): req = SOCKS5Request() req.VER = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) req.CMD = SOCKS5Command(int.from_bytes(buff.read(1), byteorder = 'big', signed = False)) req.RSV = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) req.ATYP = SOCKS5AddressType(int.from_bytes(buff.read(1), byteorder = 'big', signed = False)) if req.ATYP == SOCKS5AddressType.IP_V4: req.DST_ADDR = ipaddress.IPv4Address(buff.read(4)) elif req.ATYP == SOCKS5AddressType.IP_V6: req.DST_ADDR = ipaddress.IPv6Address(buff.read(16)) elif req.ATYP == SOCKS5AddressType.DOMAINNAME: length = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) req.DST_ADDR = buff.read(length).decode() req.DST_PORT = int.from_bytes(buff.read(2), byteorder = 'big', signed = False) return req @staticmethod def from_target(target): req = SOCKS5Request() req.CMD = SOCKS5Command.CONNECT if target.is_bind is True: req.CMD = SOCKS5Command.BIND if isinstance(target.endpoint_ip, ipaddress.IPv4Address): req.ATYP = SOCKS5AddressType.IP_V4 req.DST_ADDR = target.endpoint_ip elif isinstance(target.endpoint_ip, ipaddress.IPv6Address): req.ATYP = SOCKS5AddressType.IP_V6 req.DST_ADDR = target.endpoint_ip elif isinstance(target.endpoint_ip, str): req.ATYP = SOCKS5AddressType.DOMAINNAME req.DST_ADDR = target.endpoint_ip req.DST_PORT = target.endpoint_port return req @staticmethod def construct(cmd, address, port): req = SOCKS5Request() req.VER = 5 req.CMD = cmd req.RSV = 0 if isinstance(address, ipaddress.IPv4Address): req.ATYP = SOCKS5AddressType.IP_V4 req.DST_ADDR = address elif isinstance(address, ipaddress.IPv6Address): req.ATYP = SOCKS5AddressType.IP_V6 req.DST_ADDR = address elif isinstance(address, str): req.ATYP = SOCKS5AddressType.DOMAINNAME req.DST_ADDR = address req.DST_PORT = port return req def to_bytes(self): t = self.VER.to_bytes(1, byteorder = 'big', signed = False) t += self.CMD.value.to_bytes(1, byteorder = 'big', signed = False) t += self.RSV.to_bytes(1, byteorder = 'big', signed = False) t += self.ATYP.value.to_bytes(1, byteorder = 'big', signed = False) if self.ATYP == SOCKS5AddressType.DOMAINNAME: t += len(self.DST_ADDR).to_bytes(1, byteorder = 'big', signed = False) t += self.DST_ADDR.encode() else: t += self.DST_ADDR.packed t += self.DST_PORT.to_bytes(2, byteorder = 'big', signed = False) return t class SOCKS5Reply: def __init__(self): self.VER = 5 self.REP = None self.RSV = 0 self.ATYP = None self.BIND_ADDR= None self.BIND_PORT= None @staticmethod def from_socket(soc): data = b'' total_size = 1024 while True: temp = soc.recv(1024) if temp == b'': break data += temp if len(data) > 4: rt = SOCKS5AddressType(data[3]) if rt == SOCKS5AddressType.IP_V4: total_size = 4 + 2 + 4 if rt == SOCKS5AddressType.IP_V6: total_size = 4 + 2 + 16 if rt == SOCKS5AddressType.DOMAINNAME: total_size = 4 + 2 + data[4] if len(data) >= total_size: break return SOCKS5Reply.from_bytes(data) @staticmethod async def from_streamreader(reader): rep = SOCKS5Reply() t = await reader.readexactly(1) rep.VER = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.readexactly(1) rep.REP = SOCKS5ReplyType(int.from_bytes(t, byteorder = 'big', signed = False)) t = await reader.readexactly(1) rep.RSV = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.readexactly(1) rep.ATYP = SOCKS5AddressType(int.from_bytes(t, byteorder = 'big', signed = False)) if rep.ATYP == SOCKS5AddressType.IP_V4: t = await reader.readexactly(4) rep.BIND_ADDR = ipaddress.IPv4Address(t) elif rep.ATYP == SOCKS5AddressType.IP_V6: t = await reader.readexactly(16) rep.BIND_ADDR = ipaddress.IPv6Address(t) elif rep.ATYP == SOCKS5AddressType.DOMAINNAME: t = await reader.readexactly(1) length = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.readexactly(length) rep.BIND_ADDR = t.decode() t = await reader.readexactly(2) rep.BIND_PORT = int.from_bytes(t, byteorder = 'big', signed = False) return rep @staticmethod def from_bytes(bbuff): return SOCKS5Reply.from_buffer(io.BytesIO(bbuff)) @staticmethod def from_buffer(buff): rep = SOCKS5Reply() rep.VER = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) rep.REP = SOCKS5ReplyType(int.from_bytes(buff.read(1), byteorder = 'big', signed = False)) rep.RSV = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) rep.ATYP = SOCKS5AddressType(int.from_bytes(buff.read(1), byteorder = 'big', signed = False)) if rep.ATYP == SOCKS5AddressType.IP_V4: rep.BIND_ADDR = ipaddress.IPv4Address(buff.read(4)) elif rep.ATYP == SOCKS5AddressType.IP_V6: rep.BIND_ADDR = ipaddress.IPv6Address(buff.read(16)) elif rep.ATYP == SOCKS5AddressType.DOMAINNAME: length = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) rep.BIND_ADDR = buff.read(length).decode() rep.BIND_PORT = int.from_bytes(buff.read(2), byteorder = 'big', signed = False) return rep @staticmethod def construct(reply, address, port): rep = SOCKS5Reply() rep.VER = 5 rep.REP = reply rep.RSV = 0 if isinstance(address, ipaddress.IPv4Address): rep.ATYP = SOCKS5AddressType.IP_V4 rep.DST_ADDR = address elif isinstance(address, ipaddress.IPv6Address): rep.ATYP = SOCKS5AddressType.IP_V6 rep.DST_ADDR = address elif isinstance(address, str): rep.ATYP = SOCKS5AddressType.DOMAINNAME rep.DST_ADDR = address rep.DST_PORT = port return rep def to_bytes(self): t = self.VER.to_bytes(1, byteorder = 'big', signed = False) t += self.REP.value.to_bytes(1, byteorder = 'big', signed = False) t += self.RSV.to_bytes(1, byteorder = 'big', signed = False) t += self.ATYP.value.to_bytes(1, byteorder = 'big', signed = False) if self.ATYP == SOCKS5AddressType.DOMAINNAME: t += len(self.DST_ADDR).to_bytes(1, byteorder = 'big', signed = False) t += self.DST_ADDR.encode() else: t += self.DST_ADDR.packed t += self.DST_PORT.to_bytes(2, byteorder = 'big', signed = False) return t def __repr__(self): t = '== SOCKS5Reply ==\r\n' t += 'REP: %s\r\n' % repr(self.REP) t += 'ATYP: %s\r\n' % repr(self.ATYP) t += 'BIND_ADDR: %s\r\n' % repr(self.BIND_ADDR) t += 'BIND_PORT: %s\r\n' % repr(self.BIND_PORT) return t class SOCKS5UDP: def __init__(self): self.RSV = None self.FRAG = None self.ATYP = None self.DST_ADDR = None self.DST_PORT = None self.DATA = None @staticmethod async def from_streamreader(reader): rep = SOCKS5UDP() t = await reader.readexactly(2) rep.RSV = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.readexactly(1) rep.FRAG = SOCKS5ReplyType(int.from_bytes(t, byteorder = 'big', signed = False)) t = await reader.readexactly(1) rep.ATYP = SOCKS5AddressType(int.from_bytes(t, byteorder = 'big', signed = False)) if rep.ATYP == SOCKS5AddressType.IP_V4: t = await reader.readexactly(4) rep.DST_ADDR = ipaddress.IPv4Address(t) elif rep.ATYP == SOCKS5AddressType.IP_V6: t = await reader.readexactly(16) rep.DST_ADDR = ipaddress.IPv6Address(t) elif rep.ATYP == SOCKS5AddressType.DOMAINNAME: t = await reader.readexactly(1) length = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.readexactly(length) rep.DST_ADDR = t.decode() t = await reader.readexactly(2) rep.DST_PORT = int.from_bytes(t, byteorder = 'big', signed = False) return rep @staticmethod def from_bytes(bbuff): return SOCKS5UDP.from_buffer(io.BytesIO(bbuff)) @staticmethod def from_buffer(buff): rep = SOCKS5UDP() rep.RSV = int.from_bytes(buff.read(2), byteorder = 'big', signed = False) rep.FRAG = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) rep.ATYP = SOCKS5AddressType(buff.read(1), byteorder = 'big', signed = False) if rep.ATYP == SOCKS5AddressType.IP_V4: rep.DST_ADDR = ipaddress.IPv4Address(buff.read(4)) elif rep.ATYP == SOCKS5AddressType.IP_V6: rep.DST_ADDR = ipaddress.IPv6Address(buff.read(16)) elif rep.ATYP == SOCKS5AddressType.DOMAINNAME: length = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) rep.DST_ADDR = buff.read(length).decode() rep.DST_PORT = int.from_bytes(buff.read(2), byteorder = 'big', signed = False) #be careful, not data length is defined in the RFC!! rep.DATA = buff.read() @staticmethod def construct(address, port, data, frag = 0): req = SOCKS5Request() req.RSV = 0 req.FRAG = frag if isinstance(address, ipaddress.IPv4Address): req.ATYP = SOCKS5AddressType.IP_V4 req.DST_ADDR = address elif isinstance(address, ipaddress.IPv6Address): req.ATYP = SOCKS5AddressType.IP_V6 req.DST_ADDR = address elif isinstance(address, str): req.ATYP = SOCKS5AddressType.DOMAINNAME req.DST_ADDR = address req.DST_PORT = port req.DATA = data return req def to_bytes(self): t = self.RSV.to_bytes(2, byteorder = 'big', signed = False) t += self.FRAG.value.to_bytes(1, byteorder = 'big', signed = False) t += self.ATYP.value.to_bytes(1, byteorder = 'big', signed = False) if self.ATYP == SOCKS5AddressType.DOMAINNAME: t += len(self.DST_ADDR).to_bytes(1, byteorder = 'big', signed = False) t += self.DST_ADDR.encode() else: t += self.DST_ADDR.packed t += self.DST_PORT.to_bytes(2, byteorder = 'big', signed = False) t += self.DATA return tasysocks-0.2.18/asysocks/security.py000066400000000000000000000310301510034457600175230ustar00rootroot00000000000000 import asyncio import ipaddress import random from asysocks import logger from asysocks.common.constants import SocksServerVersion, SocksCommsMode from asysocks.protocol.socks4 import SOCKS4Request, SOCKS4Reply, SOCKS4CDCode from asysocks.protocol.socks5 import SOCKS5Method, SOCKS5Nego, SOCKS5NegoReply, SOCKS5Request, SOCKS5Reply, SOCKS5ReplyType, SOCKS5Command, SOCKS5PlainAuth, SOCKS5PlainAuthReply, SOCKS5ServerErrorReply from asysocks.common.target import SocksTarget from asysocks.common.credentials import SocksCredential from asysocks.common.comms import SocksQueueComms from asysocks.client import SOCKSClient class SocksSecurity: def __init__(self): self.server_ip = None self.server_port = None self.server_sslctx = None self.timeout = None self.verify_host = None #endpoint ip/hostname to test the connection self.verify_port = None #endpoint port self.verify_send = None #data to be sent to the endpoint service upon succsessfull connection self.verify_recv = None #data expected from the endpoint service in response to verify_send def socks4_noauth(self): pass async def socks5_authmethods(self, methods = [SOCKS5Method.NOAUTH, SOCKS5Method.PLAIN, SOCKS5Method.GSSAPI], timeout = None): """ Checks which auth methods are supported by the server """ supported = [] notsupported = [] errors = [] for method in methods: remote_reader = None remote_writer = None try: logger.debug('method %s' % method.name) try: remote_reader, remote_writer = await asyncio.wait_for( asyncio.open_connection( self.server_ip, self.server_port, ssl = self.server_sslctx, ), timeout = self.timeout ) logger.debug('Connected to socks server!') except Exception as e: logger.debug('Failed to connect to SOCKS server!') return None, None, e nego = SOCKS5Nego.from_methods([method]) logger.debug('[SOCKS5] Sending negotiation command to server @ %s:%d' % remote_writer.get_extra_info('peername')) remote_writer.write(nego.to_bytes()) await asyncio.wait_for( remote_writer.drain(), timeout = timeout ) rep_nego = await asyncio.wait_for( SOCKS5NegoReply.from_streamreader(remote_reader), timeout = timeout ) logger.debug( '[SOCKS5] Got negotiation reply from from %s! Server choosen auth type: %s' % (remote_writer.get_extra_info('peername'), rep_nego.METHOD.name) ) if rep_nego.METHOD == method: supported.append(method) else: notsupported.append(method) except Exception as e: logger.debug('Failed proto test for method %s. Reason: %s' % (method.name, e)) errors.append((method, e)) continue finally: if remote_writer is not None: remote_writer.close() return supported, notsupported, errors async def socks5_login(self, username, password, timeout = None): """ Tries to open connection to a remote endpoint using the socks proxy server using plaintext auth method with username and password """ try: remote_reader = None remote_writer = None try: remote_reader, remote_writer = await asyncio.wait_for( asyncio.open_connection( self.server_ip, self.server_port, ssl = self.server_sslctx, ), timeout = self.timeout ) logger.debug('Connected to socks server!') except Exception as e: logger.debug('Failed to connect to SOCKS server!') return None, e logger.debug('[SOCKS5] invoked') methods = [SOCKS5Method.PLAIN] nego = SOCKS5Nego.from_methods(methods) logger.debug('[SOCKS5] Sending negotiation command to server @ %s:%d' % remote_writer.get_extra_info('peername')) remote_writer.write(nego.to_bytes()) await asyncio.wait_for( remote_writer.drain(), timeout = timeout ) rep_nego = await asyncio.wait_for( SOCKS5NegoReply.from_streamreader(remote_reader), timeout = timeout ) logger.debug( '[SOCKS5] Got negotiation reply from from %s! Server choosen auth type: %s' % (remote_writer.get_extra_info('peername'), rep_nego.METHOD.name) ) if rep_nego.METHOD != SOCKS5Method.PLAIN: return False, None auth = SOCKS5PlainAuth.construct(username, password) remote_writer.write(auth.to_bytes()) await asyncio.wait_for( remote_writer.drain(), timeout = timeout ) rep_data = await asyncio.wait_for( remote_reader.read(2), timeout = timeout ) if rep_data == b'': return False, None rep_nego = SOCKS5PlainAuthReply.from_bytes(rep_data) if rep_nego.STATUS != SOCKS5ReplyType.SUCCEEDED: return False, None if self.verify_host is None: return True, None logger.debug('[SOCKS5] Opening channel to %s:%s' % (self.verify_host, self.verify_port)) logger.debug('[SOCKS5] Sending connect request to SOCKS server @ %s:%d' % remote_writer.get_extra_info('peername')) try: target = ipaddress.ip_address(self.verify_host) except: target = self.verify_host remote_writer.write( SOCKS5Request.construct( SOCKS5Command.CONNECT, target, int(self.verify_port) ).to_bytes() ) await asyncio.wait_for( remote_writer.drain(), timeout=timeout ) rep = await asyncio.wait_for( SOCKS5Reply.from_streamreader(remote_reader), timeout=timeout ) if rep.REP != SOCKS5ReplyType.SUCCEEDED: #logger.info('Failed to connect to proxy %s! Server replied: %s' % (self.proxy_writer.get_extra_info('peername'), repr(rep.REP))) return False, rep.REP.name logger.debug('[SOCKS5] Server @ %s:%d successfully set up the connection to the endpoint! ' % remote_writer.get_extra_info('peername')) if self.verify_send is None: return True, None remote_writer.write(self.verify_send) await asyncio.wait_for( remote_writer.drain(), timeout=timeout ) data = await remote_reader.read(4096) if data.find(self.verify_recv) == -1: return False, None return True, None except Exception as e: return None, e finally: if remote_writer is not None: remote_writer.close() async def socks5_noauth(self, timeout = None): """ Tries to open connection to a remote endpoint using the socks proxy server without authentication """ try: remote_reader = None remote_writer = None try: remote_reader, remote_writer = await asyncio.wait_for( asyncio.open_connection( self.server_ip, self.server_port, ssl = self.server_sslctx, ), timeout = self.timeout ) logger.debug('Connected to socks server!') except Exception as e: logger.debug('Failed to connect to SOCKS server!') return None, e logger.debug('[SOCKS5] invoked') methods = [SOCKS5Method.NOAUTH] nego = SOCKS5Nego.from_methods(methods) logger.debug('[SOCKS5] Sending negotiation command to server @ %s:%d' % remote_writer.get_extra_info('peername')) remote_writer.write(nego.to_bytes()) await asyncio.wait_for( remote_writer.drain(), timeout = timeout ) rep_nego = await asyncio.wait_for( SOCKS5NegoReply.from_streamreader(remote_reader), timeout = timeout ) logger.debug( '[SOCKS5] Got negotiation reply from from %s! Server choosen auth type: %s' % (remote_writer.get_extra_info('peername'), rep_nego.METHOD.name) ) if rep_nego.METHOD != SOCKS5Method.NOAUTH: return False, None if self.verify_host is None: return True, None logger.debug('[SOCKS5] Opening channel to %s:%s' % (self.verify_host, self.verify_port)) logger.debug('[SOCKS5] Sending connect request to SOCKS server @ %s:%d' % remote_writer.get_extra_info('peername')) try: target = ipaddress.ip_address(self.verify_host) except: target = self.verify_host remote_writer.write( SOCKS5Request.construct( SOCKS5Command.CONNECT, target, int(self.verify_port) ).to_bytes() ) await asyncio.wait_for( remote_writer.drain(), timeout=timeout ) rep = await asyncio.wait_for( SOCKS5Reply.from_streamreader(remote_reader), timeout=timeout ) if rep.REP != SOCKS5ReplyType.SUCCEEDED: #logger.info('Failed to connect to proxy %s! Server replied: %s' % (self.proxy_writer.get_extra_info('peername'), repr(rep.REP))) return False, rep.REP.name logger.debug('[SOCKS5] Server @ %s:%d successfully set up the connection to the endpoint! ' % remote_writer.get_extra_info('peername')) if self.verify_send is None: return True, None remote_writer.write(self.verify_send) await asyncio.wait_for( remote_writer.drain(), timeout=timeout ) data = await remote_reader.read(4096) if data.find(self.verify_recv) == -1: return False, None return True, None except Exception as e: return None, e finally: if remote_writer is not None: remote_writer.close() async def socks5_bind(self, username = None, password = None, timeout = None): """ Tests if port binding is supported by the socks server """ try: target = SocksTarget() target.version = SocksServerVersion.SOCKS5 target.server_ip = self.server_ip target.server_port = self.server_port target.is_bind = True target.timeout = 10 target.buffer_size = 4096 target.ssl_ctx = self.server_sslctx target.endpoint_ip = '0.0.0.0' target.endpoint_port = random.randint(35000, 50000) target.endpoint_timeout = None target.only_bind = True credential = SocksCredential() credential.username = username credential.password = password in_q = asyncio.Queue() out_q = asyncio.Queue() comms = SocksQueueComms(in_q, out_q) client = SOCKSClient(comms, target) res, err = await client.handle_queue() if err is None: return True, None if isinstance(err, SOCKS5ServerErrorReply): if err.reply == SOCKS5ReplyType.COMMAND_NOT_SUPPORTED: return False, None return res, err except Exception as e: return None, e async def socks5_local(self, username = None, password = None, timeout = None): """ Tests if port binding is supported by the socks server """ try: target = SocksTarget() target.version = SocksServerVersion.SOCKS5 target.server_ip = self.server_ip target.server_port = self.server_port target.is_bind = False target.timeout = 10 target.buffer_size = 4096 target.ssl_ctx = self.server_sslctx target.endpoint_ip = self.server_ip target.endpoint_port = self.verify_port if self.verify_port is not None else 22 target.endpoint_timeout = None target.only_open = True credential = SocksCredential() credential.username = username credential.password = password target.credential = credential in_q = asyncio.Queue() out_q = asyncio.Queue() comms = SocksQueueComms(in_q, out_q) client = SOCKSClient(comms, target) res, err = await client.handle_queue() if err is None: return True, None if isinstance(err, SOCKS5ServerErrorReply): if err.reply in [SOCKS5ReplyType.FAILURE, SOCKS5ReplyType.CONN_NOT_ALLOWED]: return False, None return res, err except Exception as e: return None, e async def socks5_test_port(self, username = None, password = None, timeout = None, retries = -1): """ Tests if port binding is supported by the socks server """ try: while retries != 0: retries -= 1 target = SocksTarget() target.version = SocksServerVersion.SOCKS5 target.server_ip = self.server_ip target.server_port = self.server_port target.is_bind = False target.timeout = 10 target.buffer_size = 4096 target.ssl_ctx = self.server_sslctx target.endpoint_ip = self.verify_host target.endpoint_port = self.verify_port #random.randint(35000, 50000) target.endpoint_timeout = None target.only_open = True credential = SocksCredential() credential.username = username credential.password = password target.credential = credential in_q = asyncio.Queue() out_q = asyncio.Queue() comms = SocksQueueComms(in_q, out_q) channel_open_evt = asyncio.Event() client = SOCKSClient(comms, target, bind_evt = channel_open_evt) #client_task = asyncio.create_task(client.run()) res, err = await client.handle_queue() if err is not None: if isinstance(err, SOCKS5ServerErrorReply): if err.reply in [SOCKS5ReplyType.TTL_EXPIRED, SOCKS5ReplyType.CONN_REFUSED, SOCKS5ReplyType.HOST_UNREACHABLE, SOCKS5ReplyType.CONN_NOT_ALLOWED]: return False, err elif err.reply in [SOCKS5ReplyType.ADDRESS_TYPE_NOT_SUPPORTED]: return None, err continue return True, None return None, Exception('Max retries reached!') except Exception as e: return None, e asysocks-0.2.18/asysocks/server.py000066400000000000000000000431461510034457600171750ustar00rootroot00000000000000 import logging import asyncio import socket import copy import ipaddress from urllib.parse import urlparse import ssl from asysocks.common.constants import SocksServerVersion, SocksCommsMode from asysocks.protocol.http import HTTPRequest from asysocks.protocol.socks4 import SOCKS4Request, SOCKS4Reply, SOCKS4CDCode from asysocks.protocol.socks5 import SOCKS5Command, SOCKS5AddressType, SOCKS5Method, SOCKS5Nego, SOCKS5NegoReply, SOCKS5Request, SOCKS5Reply, SOCKS5ReplyType, SOCKS5PlainAuth, SOCKS5PlainAuthReply, SOCKS5ServerErrorReply, SOCKS5AuthFailed from asysocks.intercepting.monitors.base import BaseMonitor srvlogger = logging.getLogger('asysocks.proxyserver') handler = logging.StreamHandler() formatter = logging.Formatter( '%(asctime)s %(name)-12s %(levelname)-8s %(message)s') handler.setFormatter(formatter) srvlogger.addHandler(handler) srvlogger.setLevel(logging.INFO) class SOCKSServer: def __init__(self, listen_ip, listen_port, ssl_ctx = None, client_timeout = 10, buffer_size = 10240, supported_protocols = ['SOCKS4', 'SOCKS5', 'HTTP'], monitor_dispatch_q = None): self.listen_ip = listen_ip self.listen_port = listen_port self.ssl_ctx = ssl_ctx self.client_timeout = client_timeout self.buffer_size = buffer_size self.supported_protocols = supported_protocols self.monitor_dispatch_q = monitor_dispatch_q self.session_id = 0 def get_session_id(self): t = self.session_id self.session_id += 1 return t async def __proxy(self, reader_a, writer_b, stop_evt, src_module, monitor_in_q = None, monitor_out_q = None): async def pi(reader, in_q, buffer_size): try: while not stop_evt.is_set(): data = await reader_a.read(buffer_size) if data == b'' or data is None: await in_q.put(data) return await in_q.put(data) except: return finally: stop_evt.set() try: if monitor_in_q is not None: asyncio.create_task(pi(reader_a, monitor_in_q, self.buffer_size)) while not stop_evt.is_set(): data = await monitor_out_q.get() #print('data_modded %s' % data) if data == b'' or data is None: return writer_b.write(data) await writer_b.drain() else: while not stop_evt.is_set(): data = await reader_a.read(self.buffer_size) if data == b'' or data is None: return writer_b.write(data) await writer_b.drain() except asyncio.CancelledError: return except Exception as e: srvlogger.debug('[%s][TCPPROXY] Connection ended. Reason: %s' % (src_module, e)) finally: writer_b.close() stop_evt.set() async def handle_socks4(self, init_cmd, reader, writer): try: if init_cmd.CD == SOCKS4CDCode.REQ_CONNECT: srvlogger.debug('[SOCKS4] Client wants to connect to: %s:%s' % (str(init_cmd.DSTIP), init_cmd.DSTPORT)) try: dst_reader, dst_writer = await asyncio.open_connection(str(init_cmd.DSTIP), init_cmd.DSTPORT) except Exception as e: srvlogger.debug('[SOCKS4] Could not connect to: %s:%s Reason: %s' % (str(init_cmd.DSTIP), init_cmd.DSTPORT, e)) reply = SOCKS4Reply() reply.CD = SOCKS4CDCode.REP_FAILED reply.DSTPORT = init_cmd.DSTPORT reply.DSTIP = init_cmd.DSTIP writer.write(reply.to_bytes()) await writer.drain() return else: srvlogger.debug('[SOCKS4] Sucsessfully connected to: %s:%s Starting TCP proxy' % (str(init_cmd.DSTIP), init_cmd.DSTPORT)) reply = SOCKS4Reply() reply.CD = SOCKS4CDCode.REP_GRANTED reply.DSTPORT = init_cmd.DSTPORT reply.DSTIP = init_cmd.DSTIP writer.write(reply.to_bytes()) await writer.drain() stop_evt = asyncio.Event() c2d_in = None c2d_out = None d2c_in = None d2c_out = None if self.monitor_dispatch_q is not None: rem_ip, rem_port = dst_writer.get_extra_info('peername') client_ip, client_port = writer.get_extra_info('peername') monitor = BaseMonitor(client_ip, client_port, rem_ip, rem_port, 'SOCKS4', self.get_session_id()) c2d_in = monitor.c2d_in c2d_out = monitor.c2d_out d2c_in = monitor.d2c_in d2c_out = monitor.d2c_out await self.monitor_dispatch_q.put(monitor) task_1 = asyncio.create_task(self.__proxy(reader, dst_writer, stop_evt, 'SOCKS4', c2d_in, c2d_out)) task_2 = asyncio.create_task(self.__proxy(dst_reader, writer, stop_evt, 'SOCKS4', d2c_in, d2c_out)) await stop_evt.wait() srvlogger.debug('[SOCKS4] Connection ended %s:%s' % (str(init_cmd.DSTIP), init_cmd.DSTPORT)) task_1.cancel() task_2.cancel() return elif init_cmd.CD == SOCKS4CDCode.REQ_BIND: async def __handle_remote(dst_reader, dst_writer): try: rem_ip, rem_port = dst_writer.get_extra_info('peername') reply = SOCKS4Reply() reply.CD = SOCKS4CDCode.REP_GRANTED reply.DSTPORT = rem_port reply.DSTIP = ipaddress.IPv4Address(rem_ip) writer.write(reply.to_bytes()) await writer.drain() stop_evt = asyncio.Event() task_1 = asyncio.create_task(self.__proxy(reader, dst_writer, stop_evt, 'SOCKS4')) task_2 = asyncio.create_task(self.__proxy(dst_reader, writer, stop_evt, 'SOCKS4')) await stop_evt.wait() print('Connection terminated') task_1.cancel() task_2.cancel() except Exception as e: print('__handle_remote %s' % e) try: serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serversocket.bind((str(init_cmd.DSTIP), 0)) serversocket.listen(1) serversocket_ip, serversocket_port = serversocket.getsockname() reply = SOCKS4Reply() reply.CD = SOCKS4CDCode.REP_GRANTED reply.DSTPORT = serversocket_port reply.DSTIP = ipaddress.IPv4Address(serversocket_ip) writer.write(reply.to_bytes()) await writer.drain() await asyncio.start_server(__handle_remote, sock=serversocket, backlog = 1) #TODO: this will constantly server bc there is no way to terminate this FIX!!! except Exception as e: reply = SOCKS4Reply() reply.CD = SOCKS4CDCode.REP_FAILED reply.DSTPORT = init_cmd.DSTPORT reply.DSTIP = init_cmd.DSTIP writer.write(reply.to_bytes()) await writer.drain() return else: raise Exception('Unknown client request! %s' % init_cmd.CD) except Exception as e: import traceback traceback.print_exc() print('handle_socks4 %s' % e) async def handle_socks5(self, init_cmd, reader, writer): try: #TODO: currently no auth is supported, add auth functionality! srvlogger.debug('[SOCKS5] Authentication supported by the client %s' % (','.join([x.name for x in init_cmd.METHODS]))) if SOCKS5Method.NOAUTH not in init_cmd.METHODS: reply = SOCKS5NegoReply.construct(SOCKS5Method.NOTACCEPTABLE) writer.write(reply.to_bytes()) await writer.drain() return reply = SOCKS5NegoReply.construct(SOCKS5Method.NOAUTH) writer.write(reply.to_bytes()) await writer.drain() req = await asyncio.wait_for(SOCKS5Request.from_streamreader(reader), timeout = self.client_timeout) if req.CMD == SOCKS5Command.CONNECT: srvlogger.debug('[SOCKS5] Client wants to connect to: %s:%s' % (str(req.DST_ADDR), req.DST_PORT)) try: dst_reader, dst_writer = await asyncio.open_connection(str(req.DST_ADDR), req.DST_PORT) except Exception as e: srvlogger.debug('[SOCKS5] Could not connect to: %s:%s Reason: %s' % (str(req.DST_ADDR), req.DST_PORT, e)) reply = SOCKS5Reply.construct(SOCKS5ReplyType.FAILURE,req.DST_ADDR, req.DST_PORT) #TODO: support more error types to let the client know what exscatly went wrong writer.write(reply.to_bytes()) await writer.drain() return else: srvlogger.debug('[SOCKS5] Sucsessfully connected to: %s:%s Starting TCP proxy' % (str(req.DST_ADDR), req.DST_PORT)) reply = SOCKS5Reply.construct(SOCKS5ReplyType.SUCCEEDED, req.DST_ADDR, req.DST_PORT) writer.write(reply.to_bytes()) await writer.drain() stop_evt = asyncio.Event() c2d_in = None c2d_out = None d2c_in = None d2c_out = None if self.monitor_dispatch_q is not None: rem_ip, rem_port = dst_writer.get_extra_info('peername') client_ip, client_port = writer.get_extra_info('peername') monitor = BaseMonitor(client_ip, client_port, rem_ip, rem_port, 'SOCKS5', self.get_session_id()) if req.ATYP == SOCKS5AddressType.DOMAINNAME: monitor.set_hostname(str(req.DST_ADDR)) await self.monitor_dispatch_q.put(monitor) c2d_in = monitor.c2d_in c2d_out = monitor.c2d_out d2c_in = monitor.d2c_in d2c_out = monitor.d2c_out task_1 = asyncio.create_task(self.__proxy(reader, dst_writer, stop_evt, 'SOCKS5', c2d_in, c2d_out)) task_2 = asyncio.create_task(self.__proxy(dst_reader, writer, stop_evt, 'SOCKS5', d2c_in, d2c_out)) await stop_evt.wait() srvlogger.debug('[SOCKS5] Connection ended %s:%s' % (str(req.DST_ADDR), req.DST_PORT)) task_1.cancel() task_2.cancel() return elif req.CMD == SOCKS5Command.BIND: srvlogger.debug('[SOCKS5] Client wants to BIND to: %s:%s' % (str(req.DST_ADDR), req.DST_PORT)) async def __handle_remote(dst_reader, dst_writer): try: rem_ip, rem_port = dst_writer.get_extra_info('peername') srvlogger.debug('[SOCKS5] Client BIND server get a connection from: %s:%s Notifying client...' % (str(rem_ip), rem_port)) reply = SOCKS5Reply.construct(SOCKS5ReplyType.SUCCEEDED , ipaddress.ip_address(rem_ip), rem_port) writer.write(reply.to_bytes()) await writer.drain() srvlogger.debug('[SOCKS5] Client BIND starting TCP proxy %s:%s' % (str(rem_ip), rem_port)) stop_evt = asyncio.Event() task_1 = asyncio.create_task(self.__proxy(reader, dst_writer, stop_evt, 'SOCKS5')) task_2 = asyncio.create_task(self.__proxy(dst_reader, writer, stop_evt, 'SOCKS5')) await stop_evt.wait() srvlogger.debug('[SOCKS5] Client BIND connection ended %s:%s' % (str(rem_ip), rem_port)) task_1.cancel() task_2.cancel() except Exception as e: srvlogger.exception('[SOCKS5] Client BIND error') try: if req.ATYP == SOCKS5AddressType.IP_V4: serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serversocket.bind((str(req.DST_ADDR), req.DST_PORT)) elif req.ATYP == SOCKS5AddressType.IP_V6: serversocket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) serversocket.bind((str(req.DST_ADDR), req.DST_PORT)) elif req.ATYP == SOCKS5AddressType.DOMAINNAME: #not sure abt this... serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serversocket.bind((str(req.DST_ADDR), req.DST_PORT)) serversocket.listen(1) serversocket_ip, serversocket_port = serversocket.getsockname() reply = SOCKS5Reply.construct(SOCKS5ReplyType.SUCCEEDED , ipaddress.ip_address(serversocket_ip), serversocket_port) writer.write(reply.to_bytes()) await writer.drain() await asyncio.start_server(__handle_remote, sock=serversocket, backlog = 1) #TODO: this will constantly server bc there is no way to terminate this FIX!!! except Exception as e: srvlogger.debug('[SOCKS5] Error! Could not bind to: %s:%s Reason: %s' % (str(req.DST_ADDR), req.DST_PORT, e)) reply = SOCKS5Reply.construct(SOCKS5ReplyType.FAILURE , req.DST_ADDR, req.DST_PORT) writer.write(reply.to_bytes()) await writer.drain() return elif req.CMD == SOCKS5Command.UDP_ASSOCIATE: srvlogger.debug('[SOCKS5] Error! Client requested UDP Associate, but it is not implemented!') raise Exception('UDP assoc requested, but is not implemented!') else: raise Exception('Client requested something that was not understood. %s' % req.CMD) except Exception as e: srvlogger.exception('[SOCKS5] Generic error: %s' % (e)) #print('handle_socks5 %s' % e) async def handle_http_connect(self, init_cmd, reader, writer): try: #TODO: there is not authentication implemented currently, fix! #TODO: not intercepting SSL if init_cmd.method.upper() != 'CONNECT': raise Exception('Unknown request type! %s' % init_cmd.method) #URI should be in HOST:PORT format host, port = init_cmd.uri.split(':') port = int(port) srvlogger.debug('[HTTPCONNECT] Client wants to connect to: %s:%s' % (host, port)) try: dst_reader, dst_writer = await asyncio.open_connection(host, port) except Exception as e: srvlogger.debug('[HTTPCONNECT] Could not connect to %s:%s. Reason: %s' % (host, port, e)) fail = b'HTTP/1.1 500 Error\r\n\r\n' writer.write(fail) await writer.drain() return else: srvlogger.debug('[HTTPCONNECT] Sucsessfully connected to %s:%s. Starting tcp proxy.' % (host, port)) ok = b'HTTP/1.1 200 OK\r\n\r\n' writer.write(ok) await writer.drain() stop_evt = asyncio.Event() c2d_in = None c2d_out = None d2c_in = None d2c_out = None if self.monitor_dispatch_q is not None: rem_ip, rem_port = dst_writer.get_extra_info('peername') client_ip, client_port = writer.get_extra_info('peername') monitor = BaseMonitor(client_ip, client_port, rem_ip, rem_port, 'HTTPCONNECT', self.get_session_id()) monitor.set_hostname(host) await self.monitor_dispatch_q.put(monitor) c2d_in = monitor.c2d_in c2d_out = monitor.c2d_out d2c_in = monitor.d2c_in d2c_out = monitor.d2c_out task_1 = asyncio.create_task(self.__proxy(reader, dst_writer, stop_evt, 'HTTPCONNECT', c2d_in, c2d_out)) task_2 = asyncio.create_task(self.__proxy(dst_reader, writer, stop_evt, 'HTTPCONNECT', d2c_in, d2c_out)) await stop_evt.wait() srvlogger.debug('[HTTPCONNECT] Connection ended @ %s:%s.' % (host, port)) task_1.cancel() task_2.cancel() return except Exception as e: srvlogger.exception('[HTTP] Generic error: %s' % (e)) async def handle_http_generic(self, init_cmd, reader, writer): try: #TODO: there is not authentication implemented currently, fix! o = urlparse(init_cmd.uri) host = o.hostname port = o.port if port is None or port == '': port = 80 if o.scheme.lower() == 'http' else 443 try: dst_reader, dst_writer = await asyncio.open_connection(host, port) except Exception as e: srvlogger.debug('[HTTP] Could not connect to %s:%s. Reason: %s' % (host, port, e)) fail = b'HTTP/1.1 500 Error\r\n\r\n' writer.write(fail) await writer.drain() return c_cmd = copy.deepcopy(init_cmd) c_cmd.uri = o.path + o.query data = None monitor = None if self.monitor_dispatch_q is not None: rem_ip, rem_port = dst_writer.get_extra_info('peername') client_ip, client_port = writer.get_extra_info('peername') monitor = BaseMonitor(client_ip, client_port, rem_ip, rem_port, 'HTTPCONNECT', self.get_session_id()) monitor.set_hostname(host) await self.monitor_dispatch_q.put(monitor) await monitor.c2d_in.put(c_cmd.to_bytes()) data = await monitor.c2d_out.get() else: data = c_cmd.to_bytes() dst_writer.write(data) await dst_writer.drain() data = None reply, err = await HTTPRequest.from_streamreader(dst_reader, timeout = self.client_timeout) if err is not None: raise err if monitor is not None: await monitor.d2c_in.put(reply.to_bytes()) data = await monitor.d2c_out.get() else: data = reply.to_bytes() writer.write(data) await writer.drain() except Exception as e: srvlogger.exception('[HTTP] Generic error: %s' % (e)) async def handle_client(self, reader, writer): #checking what protocol the client uses try: try: temp = await asyncio.wait_for(reader.readexactly(1), timeout = self.client_timeout) except asyncio.exceptions.IncompleteReadError as err: srvlogger.debug('Client terminated the socket before socks/http proxy handshake') return if temp == b'\x04': if 'SOCKS4' not in self.supported_protocols: raise Exception('Client tried to use SOCKS4, but it is disabled on the server') temp2 = await asyncio.wait_for(reader.readexactly(7), timeout = self.client_timeout) rest = await asyncio.wait_for(reader.readuntil(b'\x00'), timeout = self.client_timeout) init_cmd = SOCKS4Request.from_bytes(temp + temp2+ rest) await self.handle_socks4(init_cmd, reader, writer) return elif temp == b'\x05': #socks5 if 'SOCKS5' not in self.supported_protocols: raise Exception('Client tried to use SOCKS5, but it is disabled on the server') nmethods = await asyncio.wait_for(reader.readexactly(1), timeout = self.client_timeout) t_nmethods = int.from_bytes(nmethods, byteorder = 'big', signed = False) methods = await asyncio.wait_for(reader.readexactly(t_nmethods), timeout = self.client_timeout) init_cmd = SOCKS5Nego.from_bytes(temp + nmethods + methods) await self.handle_socks5(init_cmd, reader, writer) return elif temp in [b'\x43', b'\x63']: if 'HTTP' not in self.supported_protocols: raise Exception('Client tried to use HTTP proxy, but it is disabled on the server') #HTTP CONNECT init_cmd, err = await HTTPRequest.from_streamreader(reader, timeout = self.client_timeout, pre_data = temp) if err is not None: raise err await self.handle_http_connect(init_cmd, reader, writer) return elif temp in [b'g', b'G', b'p', b'P', b'o', b'O']: if 'HTTP' not in self.supported_protocols: raise Exception('Client tried to use HTTP proxy, but it is disabled on the server') init_cmd, err = await HTTPRequest.from_streamreader(reader, timeout = self.client_timeout, pre_data = temp) if err is not None: raise err await self.handle_http_generic(init_cmd, reader, writer) else: raise Exception('Unknwon protocol used by the client! %s' % temp) except Exception as e: srvlogger.exception('handle_client') async def run(self): server = await asyncio.start_server(self.handle_client, self.listen_ip, self.listen_port, ssl=self.ssl_ctx) await server.wait_closed() asysocks-0.2.18/asysocks/test/000077500000000000000000000000001510034457600162645ustar00rootroot00000000000000asysocks-0.2.18/asysocks/test/__init__.py000066400000000000000000000000001510034457600203630ustar00rootroot00000000000000asysocks-0.2.18/asysocks/test/cert.pem000066400000000000000000000033711510034457600177300ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIE+TCCAuGgAwIBAgIUHtJZFSjIM473I5dzDYWxAQJlv6MwDQYJKoZIhvcNAQEL BQAwDDEKMAgGA1UEAwwBKjAeFw0yMDA5MzAyMDI1MDJaFw0yMTA5MzAyMDI1MDJa MAwxCjAIBgNVBAMMASowggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCh 0Js0kM2GUT1OMUNhD5TKaQ4XOEM4ggobrnw2mFUFL6Dr4PtIrtYbh/bt5oM2jmUA L2PmtoI8sGzPZTASfGTBGVQW9+Iy5d8p2XriOiTyR2/PBIWYW1GCqsI4fpCoWTkl BKSUPt0broMpxjUByVe0rmfGReMXypCMgB4dZo5k6UyhakG7bsWOzAgrKI5Cc3k+ Sm3CD4TcP68+DJzHax5nBUQJPyyDx0eN3CGSt5rvF4b4qlZD3nYCl1SeYuxBNtQg xOFeNQRGiARdyNK8oz0rfJIlK7VFGJA3fbx/49UOFr+N/RGNTiubx8QHy09fvMsF Dtfb9MHRAjsEcS09llGa27kq5ifwZ13ebhPyxY8W4MqtALORVdXNUgO+JsSKsabH lCqj5TPt7B7YhhUMSW2+pS4tZ0AhAfPsNAyAT2y2TFhkQZr+7AcMl6hKsH6ZWY02 E9rQpRy5DNrKnYjibTZGEHjROA/1BuBSVQobrrxthAa60Na7eiFuql8OZXipi5Hj sHLrlEirxZ/YWYFWaLRQjaRbdEY9v2k8jQCXEnfM0HW+8agLk3F58L7s7bFKiId1 zzD91qJSLjsNkXG05a5/EduTa5htsOpI4kSLu5suWygtJBY9LfadAuNRBpRMlFMM xzxr+By7ClCplNk0vcLbvvAymrcrdkcIHMRkA6zLiwIDAQABo1MwUTAdBgNVHQ4E FgQULvP39U3tjidwV9AdhgxNFOndTrMwHwYDVR0jBBgwFoAULvP39U3tjidwV9Ad hgxNFOndTrMwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAc/Jg dZS5Z8ohOQLMg8jMaaQnXkLPlTkhEKVvjuRfw26GTfypcFi/oV346sTBG8FcTnjZ eHcw/Nt5IjWAQewDraq9q96FloqeR1wJIX1nziXo6H83NV1lHZd2aVPAmZbAAItg yh57pOXebS7n00aSkSPTdN7PWfpFpTkKIghKQbOXH0JXhsFoTEoebfiFb9DWkQC4 ESjJJywD9ZwpFVBFLGJqCMlO7jGIt6GHvQGuI20diTwqgyOdDFRqb9fYOv4LNVwJ uRGdwgYeGek7KLT8nHm/gBfDt2XU1BsHQZ7vI0LuXBvDjL0xubXymJPWI9ddjClH rmkEkU5xb2YxFv/ZJTHv/zW91R7lEiNYkZHlK+WmW98UUqHtwEQ9ENAkh0jMDF3Q PhMwgiWL4bSIidYG4SqjdeRkQtQgMKuF4P1Y3O+6gQTwsTCzAuDPw30dz1sqK6lZ uxxdUi47TxMLl9plnYUypLrRxtx6YjiyYgwBUuyezRBobEO8ZLPKVUIkDRPc0BcP M+XesOid5OeCziMwkwfsAcgj/dfyYjgiXhkEiM1ZVgNZtw3SCpKz8Ha5BXTl34LN PMpoUAJIeKdIJOwSU7hQPo3mCgstyskCRGzE5fUBDKDfo59Rujyjz+g2WgI0e6uX tNE9MdFeQShLc5WDeaCraeuxJGB4NWDKBeDlXcw= -----END CERTIFICATE----- asysocks-0.2.18/asysocks/test/key.pem000066400000000000000000000065261510034457600175700ustar00rootroot00000000000000-----BEGIN ENCRYPTED PRIVATE KEY----- MIIJnDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIak4biJGmyLECAggA MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECCsg7mEnZ812BIIJSMarKFiiMFW4 nv6+BDqyGOXf9xYxo11Vw5NSTQ6206Mt0kmoX+uP75oY16BuwRPVDdeuceND+vDN 4Xak8e96mdr+Avog2f+yjO2vShkN5xsUgQUwr4S+wUr7qmAYsZ5yfzsqhWGGR13W K0BVtpjY1q/ISuIP+v4n16YEA4h7eIc5Hx9UqCVllzpTY8wMpfdCwALzw7eL7N6A MaV2pepzh35wSazJdNgXBLy+Z0HCEpLShsZM2Um/6oQtGHT7TjV74MAd1MdE0xTB vOcJTMBLaUVH3wsvM1bQWE4izx7ldhduH7DpzIfWt9Aqapgmn8yqzxiGiXw6kdgI mpEMzBDN1+Djnv9M8ODP6sX9CxLmo28D5b070lPiOmy9Y88/CHsB1rE4IiYlzVsO 1GVCajvwHdAmyMJttF4e0dzZ1M+r7zBvYOxVzXZUt/FRyULevFs92Fwq+bp0zjkV YLycajpKG9k081Vmv9jfZEPddB0h60HWzCetv28QLN52VW3a30r7uVTj6Zfkbnz2 8iSNrUIbxKTIQghDFsJC+zvv4XaDoHM/abH0/JJIxkEBEVRmtaUOyfsxPzdyeQxd z7eMDkRG/IuIWuccSah9lkDThKt+bcFhK5/keceEAcO3cvgPh7V996EyoFIx667A Gus0bARsx4JsrAGnNjL/yyym1h0A8Bo52tD3pdNM2VPIBtzpTw+JYWFxbSUUR/rt jZnJdeuUo/8ly2AnggcTR77ndyCGuYv8wMuKMSHjcg5nqmyMeOj+lnCN8/H5ezuK 4rGqtiaPyIklk3DGOBE5P9mA4+ZzUoOJYW4tqhfY9kpSnXFEScmbmBHFLZTYoAhV BhKZEDZ8Ka9F+4+IHS+LnLVLafy2k7oxLETpS4w4696iehQGVEwFxvNmrbH0qmsk a6OtdPKkNjaAIncNtK6dH5yOxlH5DjuLhAlOvZx48172CuXL9+z37gd93OftQZ8r fV4FN3cWeLUJDPXagjVSVpuE3x2xQtAkCIJNIy1EZM9TvwI1XTDKUrq10UpzBVLc cIlyi/AGMgFG795ZacXu4buwbbulGyat4lJtA/PcZuLZTKFKYILRR3otGXEkoNHS kndQEHffDSbBOVVPc/eMuAG50RbdynuCYo96oBvo9hXui2AvHyRL98n1tpVMMcVZ +yosHOJ36HpPZw8iO76I+DbkixrZ9Mrk8M05MkAlYQNUPxfV4y8B21zaAthJc+AO bpDb1v3rH4v5/nYQHVz+RirzhstXk4fTzIbx9GJlX4JDKvjqCuaYYNz8z9D9raVd EuaC8t/JnOhHayveAx/VE2C4IIURXQgmzexQy2hxOL9ibHqHnx6pkYh50mJHgFYb kafHfTR9MY3+isa+zPK212YjzZx608bpnIipLIqMu0+uTIksqoDu1AnbVaQE89CK mht06TyudLPRw6PvZQQiWjBQQKOneDuykLkR/3ZaUpuO2tbw45bqOIvmRa8DflOW jrGeiz3cxP2e76Hkxb7laZP+1hGgofd9Pk5ad5oCOm/+ut6Nm7tYkbam7+jcrPOL 0wGDsNXZFM6dCEmvUhET16oP2WtimmzVuqkVakDeuaF7dnV7BRIKAG4EhEU33uFJ ZSFUtKTeYgSoto8P+rJB+pXnj5RZa/AHavkvpRnepA90MIIKYB79697b6GxqD86M r5DZWybjUu2AseyYOnl2fyp0JIZNUFnCw8VYU9v/kms06QsOwOSTsvURyW0JGUsP 43gWc0nGgrc/Q5oUnKmnySBr5P3AuJNhKh3JEetYR8mIAYHkeqW0cg3e/BRSIMsy 0mETQJaALP0NxjJY4Cspwp2AmBRs0nLZLKQxyMCWbFZLsS7gN7ASNVe+MBTKhWzj vSlHtyes7UAU6VHXxrrUn3sFn0W7Law0iPxn139OVH5nilIn58s+7ypYKlyl8aFS RU4rdWU9H4Qe+L0kxP9CzsaLhuEyRp5WseLADVa4h81wzkx7XQzqk2+At9G5qcp9 vSIxVN/7+ov9YsvvWDU0G4wlMtClFH5mfuKnPqIyUoMpgujOBFS0V/3SR3HXHGiu a0U56eX7xapwLEQ9ZtK+QW3qWXvCmuFvuMtiwlN50WUMutjETccM/FwfrveIeaqk lSrdzDgn/hq4GDZPwSTJDiivUWuLNHv8e2g4J8R0lz1N9RylMQZ8x/XskiiveXWs R6w+JVdct0/9AORjla6atjx0Vk9ONR9j+niOglR3/oOJQBmsuZtBSRgknygdkRyw FtQ7dUMPPFWrjIvWOd6f6mc0J+VlB7Xk2c5EQSynqBzVkvuiADJPIVE/huXW9Jj6 7yQGAQMdNUqvhcscXPMTocOnwfb1MeNFTj1pwwIfDvyD08qlkeK2Wbcc6ZcYTZQH OjFLm6OU1EaZbhZm32L1Hoj4F4WvOapF9veNJfpZdzDn+Y1Lpkhmz9D6u8BsfxT0 wfryJSq7phwPQEQzu2AQ/ZLcMulhv6tDZ+eZWzpSvtIHzWKbq5JNg9SFa7Xrxh65 Uv+XV69nnYWq6p0O6aSlmmA5xAe8iYTlgOmhO+RCz5TRLeAYCTA+l4of46yG/ufu eFBef43cVsRlFCppqLMIppEy5+zXdfOE9OU1GxUGgM0wILhFiW0fhv7QooJlEfhm 4sFaO/Nxcs4bvr73W2CO6Vhj/WW90NR5/uH8tHGsmif1fr1ZyKCTeRmJKvHs++0k +mBmVpZv3SDG04fZH4NGUKjWpdfDb/88b8zgJQG6XzrwZ0XiMc3bjhz3r05/Bmcf Doji5a8NQUbur07pbWMbvzWGI49pQ3z7SLwWT7RutOZHKD/59aWNwp0xpGqPeY+U KqDYjhTGkylk/GaWK/cFosgJqUsnHisv3vIrw5qRjdeNXuIKDnpsrEv5zot5GAI9 VJ+cum7do1kiYR7/MC1BX6GdEpE1VlKNKw0aW3JxDqyJvmFTiaWCX8N6TzpyXKoz L5stveUCrC3YOeCFHiZ4TT6KtXB+14Q+0rceNhzocHFyYwBrL+vK5azmqTItcbtu v7h/5/LThkeX7KJdh42Qo4E7sO8YVqwqK9f59sRgq2Beo+XflJaQC43/KEdccObH 7POC+clBPfzJj5a/pSHSQL/uaS1Snem7JqupPznYdO4Yo9o0zRcvXrQCyHdbfBms dsWOVO6iqo+TB3RqLHsfgg== -----END ENCRYPTED PRIVATE KEY----- asysocks-0.2.18/asysocks/test/listener_test.py000066400000000000000000000015501510034457600215230ustar00rootroot00000000000000 import ipaddress import logging import asyncio from asysocks.common.target import SocksTarget from asysocks.common.comms import SocksLitenerComms from asysocks.common.constants import * from asysocks.client import SOCKSClient from asysocks import logger logging.basicConfig(level=2) logger.setLevel(logging.DEBUG) async def main(): target = SocksTarget() target.version = SocksServerVersion.SOCKS4 target.server_ip = '127.0.0.1' target.server_port = 9050 target.is_bind = False target.proto = SocksProtocol.TCP target.timeout = 10 target.buffer_size = 4096 target.endpoint_ip = ipaddress.ip_address('216.239.32.117') target.endpoint_port = 80 comms = SocksLitenerComms('127.0.0.1', 9999) client = SOCKSClient(comms, target) await client.run() print('DONE!') if __name__ == '__main__': asyncio.run(main())asysocks-0.2.18/asysocks/test/queue_test.py000066400000000000000000000022601510034457600210210ustar00rootroot00000000000000 import ipaddress import logging import asyncio from asysocks.common.target import SocksTarget from asysocks.common.comms import SocksQueueComms from asysocks.common.constants import * from asysocks.client import SOCKSClient from asysocks import logger logging.basicConfig(level=2) logger.setLevel(logging.DEBUG) async def read_q(in_queue): while True: data = await in_queue.get() print(data) async def main(): in_queue = asyncio.Queue() out_queue = asyncio.Queue() await out_queue.put(b'GET / HTTP/1.1\r\nHost: google.com\r\n\r\n') target = SocksTarget() #target.version = SocksServerVersion.SOCKS4 target.version = SocksServerVersion.SOCKS5 target.server_ip = '127.0.0.1' target.server_port = 9050 target.is_bind = False target.proto = SocksProtocol.TCP target.timeout = 10 target.buffer_size = 4096 target.endpoint_ip = ipaddress.ip_address('216.239.32.117') target.endpoint_port = 80 comms = SocksQueueComms(out_queue, in_queue) t = asyncio.create_task(read_q(in_queue)) client = SOCKSClient(comms, target) await client.run() print('DONE!') if __name__ == '__main__': asyncio.run(main())asysocks-0.2.18/asysocks/test/ssl_test.py000066400000000000000000000062751510034457600205100ustar00rootroot00000000000000 import asyncio import logging import ssl from asysocks.server import SOCKSServer, srvlogger, ProxyMonitorSSL from asysocks._version import __banner__ async def amain(): try: import argparse parser = argparse.ArgumentParser(description='Universal proxy server (SOCKS4/5 and HTTP proxy') parser.add_argument('--listen-ip', default = '127.0.0.1', help='Listen IP') parser.add_argument('--listen-port', type = int, default = 1080, help='Listen port') parser.add_argument('--cert', help='SSL certificate file') parser.add_argument('--key', help='SSL key file') parser.add_argument('--client-timeout', type=int, default=10, help='How much time to wait ftill the client finishes initial handshake') parser.add_argument('--buffer-size', type=int, default=10240, help='Proxy buffer size') parser.add_argument('-m', '--monitor', action='store_true', help='Monitor mode.') parser.add_argument('-v', '--verbose', action='count', default=0, help='Verbosity') parser.add_argument('-s', '--silent', action='store_true', help = 'dont print banner') args = parser.parse_args() #### #### fake_cert = 'asysocks/test/cert.pem' fake_key = 'asysocks/test/key.pem' fake_pass = 'testcert' #### if args.silent is False: print(__banner__) if args.verbose >=1: srvlogger.setLevel(logging.DEBUG) ssl_ctx = None client_timeout = args.client_timeout buffer_size = args.buffer_size supported_protocols = ['SOCKS4','SOCKS5', 'HTTP'] monitor_dispatch_q = None if args.cert is not None: ssl_ctx = ssl.SSLContext() ssl_ctx.load_cert_chain(args.cert, keyfile=args.key) if args.monitor is True: monitor_dispatch_q = asyncio.Queue() server = SOCKSServer( args.listen_ip, args.listen_port, ssl_ctx, client_timeout = client_timeout, buffer_size = buffer_size, supported_protocols = supported_protocols, monitor_dispatch_q=monitor_dispatch_q ) if args.silent is False: print('Proxy server is up and running on %s:%s %s' % (args.listen_ip, args.listen_port, '' if ssl_ctx is None else '(SSL)')) if monitor_dispatch_q is None: await server.run() else: server_task = asyncio.create_task(server.run()) while True: #each connection will give a monitor class monitor = await monitor_dispatch_q.get() if monitor.dst_port == 443: client_ssl_ctx = ssl.create_default_context() client_ssl_ctx.check_hostname = False client_ssl_ctx.verify_mode = ssl.CERT_NONE print(client_ssl_ctx.verify_mode) client_ssl_ctx.load_cert_chain(fake_cert, fake_key, password=fake_pass) destination_ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) destination_ssl_ctx.check_hostname = False destination_ssl_ctx.verify_mode = ssl.CERT_NONE destination_ssl_ctx.set_ciphers('DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-GCM-SHA256:TLS_AES_128_GCM_SHA256') ssl_monitor = ProxyMonitorSSL(monitor, client_ssl_ctx = client_ssl_ctx, destination_ssl_ctx = destination_ssl_ctx) asyncio.create_task(ssl_monitor.intercept()) else: asyncio.create_task(monitor.just_log()) server_task.cancel() except Exception as e: print(e) def main(): asyncio.run(amain()) if __name__ == '__main__': main()asysocks-0.2.18/asysocks/test/test_url_param.py000066400000000000000000000007141510034457600216610ustar00rootroot00000000000000import logging from asysocks.common.clienturl import SocksClientURL from asysocks import logger logging.basicConfig(level=2) logger.setLevel(logging.DEBUG) def main(): url_1 = 'smb://10.10.10.2/?timeout=10&dc=10.10.10.2&proxytype=socks5&proxyserver=127.0.0.1&proxyuser=admin&proxypass=alma&dc=10.10.10.2&dns=8.8.8.8' cu = SocksClientURL.from_params(url_1) target = cu.get_target() print(repr(target)) if __name__ == '__main__': main()asysocks-0.2.18/asysocks/test/wsnettest.py000066400000000000000000000057511510034457600207060ustar00rootroot00000000000000 import asyncio import os import traceback from wsnet.protocol import * import websockets class WSNetworkTCPTEST: def __init__(self, ip, port, in_q, out_q, reuse_ws = False): self.ws_url = None self.ws = None self.ws_proxy = None self.ip = ip self.port = port self.in_q = in_q self.out_q = out_q self.reuse_ws = reuse_ws self.token = os.urandom(16) self.in_task = None self.out_task = None self.__internal_in_q = None async def terminate(self): if self.in_task is not None: self.in_task.cancel() if self.out_task is not None: self.out_task.cancel() if self.ws is not None: await self.ws.close() self.ws = None async def __handle_in(self): try: while self.ws.open: try: data = await self.ws.recv() cmd = CMD.from_bytes(bytearray(data)) #print('__handle_in %s' % cmd) if cmd.type == CMDType.OK: print('Remote end terminated the socket') raise Exception('Remote end terminated the socket') elif cmd.type == CMDType.ERR: print('Proxy sent error during data transmission. Killing the tunnel.') raise Exception('Proxy sent error during data transmission. Killing the tunnel.') await self.in_q.put((cmd.data, None)) except asyncio.CancelledError: return except Exception as e: traceback.print_exc() await self.in_q.put((None, e)) return except: traceback.print_exc() finally: await self.terminate() async def __handle_out(self): try: while self.ws.open: data = await self.out_q.get() #print('OUT %s' % data) if data is None or data == b'': return cmd = WSNSocketData(self.token, data) await self.ws.send(cmd.to_bytes()) except Exception as e: traceback.print_exc() return finally: try: cmd = WSNOK(self.token) await self.ws.send(cmd.to_bytes()) except: pass await self.terminate() async def connect(self): try: print('WSNETTEST connecting... %s:%s' % (self.ip, self.port)) cmd = WSNConnect(self.token, 'TCP', self.ip, self.port) await self.ws.send(cmd.to_bytes()) data = await self.ws.recv() cmd = CMD.from_bytes(bytearray(data)) print('WSNETTEST connect reply %s' % cmd) if cmd.type == CMDType.CONTINUE: return True, None if cmd.type == CMDType.ERR: raise Exception('Connection failed, proxy sent error. Err: %s' % cmd.reason) raise Exception('Connection failed, expected CONTINUE, got %s' % cmd.type.value) except Exception as e: traceback.print_exc() return False, e async def run(self): try: self.__internal_in_q = asyncio.Queue() self.ws_url = 'ws://127.0.0.1:8700' self.ws = await websockets.connect(self.ws_url) _, err = await self.connect() if err is not None: await self.in_q.put(None) return False, err self.in_task = asyncio.create_task(self.__handle_in()) self.out_task = asyncio.create_task(self.__handle_out()) return True, None except Exception as e: traceback.print_exc() await self.terminate() return False, easysocks-0.2.18/asysocks/unicomm/000077500000000000000000000000001510034457600167545ustar00rootroot00000000000000asysocks-0.2.18/asysocks/unicomm/__init__.py000066400000000000000000000004161510034457600210660ustar00rootroot00000000000000import logging logger = logging.getLogger(__name__) handler = logging.StreamHandler() formatter = logging.Formatter( '%(asctime)s %(name)-12s %(levelname)-8s %(message)s') handler.setFormatter(formatter) logger.addHandler(handler) logger.setLevel(logging.INFO) asysocks-0.2.18/asysocks/unicomm/client.py000066400000000000000000000435401510034457600206120ustar00rootroot00000000000000import asyncio import copy import random import base64 import ipaddress from asysocks.unicomm.protocol.http import HTTPProxyAuthRequiredException, HTTPResponse, HTTPProxyAuthFailed from asysocks.unicomm.protocol.socks4a import SOCKS4ARequest, SOCKS4AReply, SOCKS4ACDCode from asysocks.unicomm.protocol.socks5 import SOCKS5Method, SOCKS5Nego, SOCKS5NegoReply, SOCKS5PlainAuth, \ SOCKS5AuthFailed, SOCKS5PlainAuthReply, SOCKS5ReplyType, SOCKS5Request, SOCKS5Reply, \ SOCKS5ServerErrorReply from asysocks.unicomm.common.target import UniTarget, UniProto from asysocks.unicomm.common.proxy import UniProxyProto, UniProxyTarget from asysocks.unicomm.common.packetizers import Packetizer from asysocks.unicomm.common.packetizers.ssl import PacketizerSSL from asysocks.unicomm.common.connection import UniConnection from asysocks.unicomm import logger class UniClientFactory: def __init__(self, target:UniTarget): self.target = target def get_target(self): return copy.deepcopy(self.target) def get_newtarget(self, ip_or_hostname:str, port:int = None): newtarget = self.get_target() if port is not None: newtarget.port = port try: newtarget.ip = ipaddress.ip_address(ip_or_hostname) except: newtarget.hostname = ip_or_hostname return newtarget def get_client(self, packetizer:Packetizer = None): if packetizer is None: packetizer = Packetizer() return UniClient(self.get_target(), packetizer) class ProxyChainError(Exception): def __init__(self, innerexception, message="Something failed setting up the proxy chain! See innerexception for more details"): self.innerexception = innerexception self.message = message super().__init__(self.message) class UniClient: def __init__(self, target:UniTarget, packetizer:Packetizer): self.target = target self.packetizer = packetizer self.bind_progress_evt = asyncio.Event() self.http_auth_ctx = None async def run_http(self, proxy:UniProxyTarget, remote_reader:asyncio.StreamReader, remote_writer:asyncio.StreamWriter, timeout:int = None, http_auth_ctx:str = None): try: logger.debug('[HTTP] Opening channel to %s:%s' % (proxy.endpoint_ip, proxy.endpoint_port)) connect_cmd = 'CONNECT %s:%s HTTP/1.1\r\nHost: %s:%s\r\n' % (proxy.endpoint_ip, proxy.endpoint_port, proxy.endpoint_ip, proxy.endpoint_port) connect_cmd = connect_cmd.encode() if http_auth_ctx is None: remote_writer.write(connect_cmd + b'\r\n') await asyncio.wait_for(remote_writer.drain(), timeout = timeout) resp, err = await HTTPResponse.from_streamreader(remote_reader, timeout=timeout) if err is not None: raise err if resp.status == 200: logger.debug('[HTTP] Server succsessfully connected!') return True, http_auth_ctx, None elif resp.status == 407: logger.debug('[HTTP] Server proxy auth required!') if proxy.credential is None: raise Exception('HTTP proxy auth required but no credential set!') auth_type = resp.headers_upper.get('PROXY-AUTHENTICATE', None) if auth_type is None: raise Exception('HTTP proxy requires authentication, but requested auth type could not be determined') auth_type, _ = auth_type.split(' ', 1) logger.debug('HTTP proxy requires %s auth' % auth_type) http_auth_ctx = auth_type.upper() return False, http_auth_ctx, HTTPProxyAuthRequiredException() elif http_auth_ctx == 'BASIC': auth_data = base64.b64encode(('%s:%s' % (proxy.credential.username, proxy.credential.password) ).encode() ) auth_connect = connect_cmd + b'Proxy-Authorization: Basic ' + auth_data + b'\r\n' remote_writer.write(auth_connect + b'\r\n') await asyncio.wait_for(remote_writer.drain(), timeout = timeout) resp, err = await HTTPResponse.from_streamreader(remote_reader, timeout=timeout) if err is not None: raise err if resp.status == 200: logger.debug('[HTTP] Server proxy auth succsess!') return True, http_auth_ctx, None else: raise HTTPProxyAuthFailed() #raise Exception('Proxy auth failed!') else: raise Exception('HTTP proxy requires %s authentication, but it\'s not implemented' % http_auth_ctx) except Exception as e: logger.debug('run_http') return False, None, e async def run_socks4a(self, proxy:UniProxyTarget, remote_reader:asyncio.StreamReader, remote_writer:asyncio.StreamWriter, timeout:int = None): """ Does the intial "handshake" instructing the remote server to set up the connection to the endpoint """ try: logger.debug('[SOCKS4A %s:%s] Opening channel to %s:%s (target)' % (proxy.server_ip, proxy.server_port, proxy.endpoint_ip, proxy.endpoint_port)) req = SOCKS4ARequest.from_target(proxy) if proxy.credential is not None and proxy.credential.username is not None: req.USERID = proxy.credential.username remote_writer.write(req.to_bytes()) await asyncio.wait_for(remote_writer.drain(), timeout = timeout) rep, err = await asyncio.wait_for(SOCKS4AReply.from_streamreader(remote_reader), timeout = timeout) if err is not None: raise err if rep is None: raise Exception('Socks server failed to reply to CONNECT request!') if rep.CD != SOCKS4ACDCode.REP_GRANTED: raise Exception('Socks server returned error on CONNECT! %s' % rep.CD.value) logger.debug('[SOCKS4A] Channel opened') return True, None except Exception as e: logger.debug('run_socks4a') return False, e async def run_socks5(self, proxy:UniProxyTarget, remote_reader:asyncio.StreamReader, remote_writer:asyncio.StreamWriter, timeout:int = None): """ Does the intial "handshake" instructing the remote server to set up the connection to the endpoint """ sname = proxy.get_sname() tname = proxy.get_tname() methods = [SOCKS5Method.NOAUTH] if proxy.credential is not None and proxy.credential.username is not None and proxy.credential.password is not None: methods.append(SOCKS5Method.PLAIN) #methods = [SOCKS5Method.PLAIN] try: nego = SOCKS5Nego.from_methods(methods) logger.debug('[SOCKS5 %s][SETUP] Sending negotiation command to server' % sname) remote_writer.write(nego.to_bytes()) await asyncio.wait_for( remote_writer.drain(), timeout = timeout ) rep_nego = await asyncio.wait_for( SOCKS5NegoReply.from_streamreader(remote_reader), timeout = timeout ) logger.debug( '[SOCKS5 %s] Got negotiation reply! Server choosen auth type: %s' % (sname, rep_nego.METHOD.name) ) if rep_nego.METHOD == SOCKS5Method.PLAIN: if proxy.credential is None or proxy.credential.username is None or proxy.credential.password is None: raise Exception('SOCKS5 %s] server requires PLAIN authentication, but no credentials were supplied!' % sname) logger.debug('[SOCKS5 %s]Preforming plaintext auth' % sname) remote_writer.write( SOCKS5PlainAuth.construct( proxy.credential.username, proxy.credential.password ).to_bytes() ) await asyncio.wait_for( remote_writer.drain(), timeout=None ) rep_data = await asyncio.wait_for( remote_reader.read(2), timeout = timeout ) if rep_data == b'': raise SOCKS5AuthFailed() #raise Exception('Plaintext auth failed! Bad username or password') rep_nego = SOCKS5PlainAuthReply.from_bytes(rep_data) if rep_nego.STATUS != SOCKS5ReplyType.SUCCEEDED: raise SOCKS5AuthFailed() #raise Exception('Plaintext auth failed! Bad username or password') elif rep_nego.METHOD == SOCKS5Method.GSSAPI: raise Exception('[SOCKS5 %s] server requires GSSAPI authentication, but it\'s not supported at the moment' % sname) logger.debug('[SOCKS5 %s] Opening channel to %s' % (sname, tname)) if proxy.only_auth is True: return True, None remote_writer.write( SOCKS5Request.from_target( proxy ).to_bytes() ) await asyncio.wait_for( remote_writer.drain(), timeout=timeout ) rep = await asyncio.wait_for( SOCKS5Reply.from_streamreader(remote_reader), timeout=timeout ) if rep.REP != SOCKS5ReplyType.SUCCEEDED: logger.info('[SOCKS5 %s] remote end failed to connect to proxy! Reson: %s' % (sname, rep.REP.name)) raise SOCKS5ServerErrorReply(rep.REP) if proxy.protocol.name.startswith('SERVER') is False: logger.debug('[SOCKS5 %s] Successfully set up the connection to the endpoint %s ' % (sname,tname)) return True, None logger.debug('[SOCKS5 %s] BIND first set completed, port %s available!' % (sname , rep.BIND_PORT)) #bind in progress, waiting for a second reply to notify us that the remote endpoint connected back. self.bind_port = rep.BIND_PORT self.bind_progress_evt.set() #notifying that the bind port is now available on the socks server to be used if proxy.only_bind is True: return True, None rep = await asyncio.wait_for( SOCKS5Reply.from_streamreader(remote_reader), timeout=timeout ) if rep.REP != SOCKS5ReplyType.SUCCEEDED: logger.info('[SOCKS5 %s] remote end failed to connect to proxy! Reson: %s' % (sname, rep.REP.name)) raise SOCKS5ServerErrorReply(rep.REP) return True, None except Exception as e: logger.debug('[SOCKS5 %s] Error in run_socks5 %s' % (sname, e)) return False, e async def create_link(self): remote_writer = None remote_reader = None try: logger.debug('[handle_client] Client connected!') if len(self.target.proxies) > 1: logger.debug('Start chaining...') for _ in range(3, 0 , -1): #this is for HTTP auth... if remote_writer is not None: remote_writer.close() try: proxy = copy.deepcopy(self.target.proxies[0]) if len(self.target.proxies) == 1: proxy.endpoint_ip = self.target.get_ip_or_hostname() proxy.endpoint_port = self.target.port if proxy.protocol == UniProxyProto.CLIENT_WSNET: from asysocks.network.wsnet import WSNETNetwork remote_reader, remote_writer = await WSNETNetwork.open_connection( proxy.endpoint_ip, proxy.endpoint_port, proxy.wsnet_reuse, ) elif proxy.protocol == UniProxyProto.CLIENT_WSNETTEST: from asysocks.network.wsnet import WSNETNetworkTest remote_reader, remote_writer = await WSNETNetworkTest.open_connection( proxy.endpoint_ip, proxy.endpoint_port, proxy.wsnet_reuse, ) elif proxy.protocol in [UniProxyProto.CLIENT_WSNETDIRECT, UniProxyProto.CLIENT_SSL_WSNETDIRECT]: from asysocks.network.wsnetdirect import WSNetworkDirect remote_reader, remote_writer = await WSNetworkDirect.open_connection( proxy.server_ip, proxy.server_port, proxy.protocol, proxy.endpoint_ip, proxy.endpoint_port, proxy.wsnet_reuse, ) elif proxy.protocol in [UniProxyProto.CLIENT_WSNETWS, UniProxyProto.CLIENT_SSL_WSNETWS]: from asysocks.network.wsnetws import WSNETNetworkWS remote_reader, remote_writer = await WSNETNetworkWS.open_connection( proxy.endpoint_ip, proxy.endpoint_port, proxy.server_ip, proxy.server_port, proxy.protocol, proxy.agentid, proxy.timeout, ) if remote_reader is None: raise remote_writer elif proxy.protocol == UniProxyProto.CLIENT_CUSTOM: proxyconnection, err = proxy.customproxyfactory() if err is not None: raise err remote_reader, remote_writer, err = await proxyconnection.connect( proxy.endpoint_ip, proxy.endpoint_port, proxy.protocol, ) if err is not None: raise err else: remote_reader, remote_writer = await asyncio.wait_for( asyncio.open_connection( proxy.server_ip, proxy.server_port, ssl=proxy.ssl_ctx, ), timeout = proxy.timeout ) logger.debug('Connected to socks server!') except: logger.debug('Failed to connect to SOCKS server!') raise try: for i, proxy in enumerate(self.target.proxies): proxy = copy.deepcopy(proxy) if i == len(self.target.proxies)-1: proxy.endpoint_ip = self.target.get_ip_or_hostname() proxy.endpoint_port = self.target.port if proxy.protocol in [UniProxyProto.CLIENT_SOCKS4, UniProxyProto.CLIENT_SSL_SOCKS4]: try: x = await asyncio.wait_for(self.run_socks4a(proxy, remote_reader, remote_writer), timeout=self.target.proxies[-1].timeout) except asyncio.TimeoutError: raise Exception('Proxy Connection establishment timeout') _, err = x if err is not None: if len(self.target.proxies) > 1 and i != len(self.target.proxies)-1: raise ProxyChainError(err) raise err continue elif proxy.protocol in [UniProxyProto.CLIENT_SOCKS5_TCP, UniProxyProto.CLIENT_SSL_SOCKS5_TCP]: try: x = await asyncio.wait_for(self.run_socks5(proxy, remote_reader, remote_writer), timeout=self.target.proxies[-1].timeout) except asyncio.TimeoutError: raise Exception('Proxy Connection establishment timeout') _, err = x if err is not None: if len(self.target.proxies) > 1 and i != len(self.target.proxies)-1: raise ProxyChainError(err) raise err continue elif proxy.protocol in [UniProxyProto.CLIENT_HTTP, UniProxyProto.CLIENT_SSL_HTTP]: try: x = await asyncio.wait_for(self.run_http(proxy, remote_reader, remote_writer, http_auth_ctx = self.http_auth_ctx), timeout=self.target.proxies[-1].timeout) except asyncio.TimeoutError: raise Exception('Proxy Connection establishment timeout') _, self.http_auth_ctx, err = x if err is not None: if len(self.target.proxies) > 1 and i != len(self.target.proxies)-1: raise ProxyChainError(err) raise err continue elif proxy.protocol in [UniProxyProto.CLIENT_WSNET, UniProxyProto.CLIENT_WSNETWS, UniProxyProto.CLIENT_SSL_WSNETWS, UniProxyProto.CLIENT_WSNETTEST, UniProxyProto.CLIENT_CUSTOM, UniProxyProto.CLIENT_WSNETDIRECT, UniProxyProto.CLIENT_SSL_WSNETDIRECT]: if i != 0: raise Exception("WSNET only supported as the first proxy in chain!") continue else: raise Exception('Unknown SOCKS version! "%s"' % proxy.protocol) else: # no need to do more iterations because of HTTP at this point break except HTTPProxyAuthRequiredException: continue except: raise return remote_reader, remote_writer except Exception as e: logger.debug('[handle_client] Exception: %s' % e) if remote_writer is not None: remote_writer.close() raise async def open_privileged_connection(self): for attempt in range(10): try: # Attempt to use a lower port, incrementing if unsuccessful local_addr = ('0.0.0.0', random.randint(10, 1023)) reader, writer = await asyncio.wait_for(asyncio.open_connection(self.target.get_ip_or_hostname(), self.target.port, local_addr=local_addr), timeout=self.target.timeout) return reader, writer, None except PermissionError as e: return None, None, e except OSError as e: if e.errno == 98: # Port already in use (Linux specific error code, adjust for your OS if necessary) continue # Try again with a different port return None, None, e except Exception as e: return None, None, e return None, None, Exception("Failed to connect after 10 attempts") async def connect(self): packetizer = copy.deepcopy(self.packetizer) # THIS PART IS ONLY FOR UDP if self.target.protocol == UniProto.CLIENT_UDP: if len(self.target.proxies) > 0: if len(self.target.proxies) > 1: raise Exception('Proxy chaining not supported for UDP!') proxy = self.target.proxies[0] if proxy.protocol != UniProxyProto.CLIENT_WSNET: raise Exception('Only WSNET proxy supported for UDP!') from asysocks.network.wsnet import create_udp_client endpoint = await create_udp_client(self.target) return endpoint else: from asysocks.unicomm.common.connection import DatagramEndpointProtocol, Endpoint loop = asyncio.get_event_loop() endpoint = Endpoint(self.target) protocol_factory = lambda: DatagramEndpointProtocol(endpoint) reader, writer = await loop.create_datagram_endpoint( protocol_factory, local_addr = self.target.get_sockaddr(), remote_addr = self.target.get_peeraddr(), ) return endpoint # the rest is for TCP if len(self.target.proxies) > 0: reader, writer = await asyncio.wait_for(self.create_link(), timeout=self.target.timeout) if self.target.protocol == UniProto.CLIENT_SSL_TCP: ssl_ctx = self.target.get_ssl_context() packetizer = PacketizerSSL(ssl_ctx, packetizer) await asyncio.wait_for(packetizer.do_handshake(reader, writer), timeout = self.target.timeout) else: if self.target.protocol not in [UniProto.CLIENT_SSL_TCP, UniProto.CLIENT_TCP]: raise Exception('Unsupported protocol "%s"' % self.target.protocol) if self.target.use_privileged_source_port is True: # client should use a privileged source port reader, writer, err = await self.open_privileged_connection() if err is not None: raise err else: reader, writer = await asyncio.wait_for(asyncio.open_connection(self.target.get_ip_or_hostname(), self.target.port), timeout = self.target.timeout) if self.target.protocol == UniProto.CLIENT_SSL_TCP: ssl_ctx = self.target.get_ssl_context() packetizer = PacketizerSSL(ssl_ctx, packetizer) await asyncio.wait_for(packetizer.do_handshake(reader, writer), timeout = self.target.timeout) return UniConnection(reader, writer, packetizer) async def amain(): import logging logger.setLevel(logging.DEBUG) target = UniTarget() target.hostname = 'google.com' target.port = 443 target.protocol = UniProto.CLIENT_SSL_TCP target.timeout = 10 target.ssl_ctx = None packetizer = Packetizer() client = UniClient(target, packetizer) connection = await client.connect() await connection.write(b'GET / HTTP/1.1\r\nHost: google.com\r\n\r\n') async for data in connection.read(): print(data) def main(): asyncio.run(amain()) if __name__ == '__main__': main()asysocks-0.2.18/asysocks/unicomm/common/000077500000000000000000000000001510034457600202445ustar00rootroot00000000000000asysocks-0.2.18/asysocks/unicomm/common/__init__.py000066400000000000000000000000001510034457600223430ustar00rootroot00000000000000asysocks-0.2.18/asysocks/unicomm/common/connection.py000066400000000000000000000230531510034457600227600ustar00rootroot00000000000000import ssl import copy import asyncio import traceback import ipaddress from asysocks.unicomm.common.target import UniTarget from asysocks.unicomm.common.packetizers import Packetizer, StreamPacketizer from asysocks.unicomm.common.packetizers.ssl import PacketizerSSL from asysocks.unicomm.common.transport import UNITransport class UniConnection: def __init__(self, reader:asyncio.StreamReader, writer:asyncio.StreamWriter, packetizer:Packetizer, peer_ip:str = None, peer_port:int = None): self.reader = reader self.writer = writer self.packetizer = packetizer self.peer_ip = peer_ip #for connection types where reader/writer does not have get_extra_info self.peer_port = peer_port self.packetizer_task = None self.closing = False self.closed_evt = asyncio.Event() self.read_lock:asyncio.Lock = asyncio.Lock() self.read_resume:asyncio.Event = asyncio.Event() self.read_resume.set() async def __aenter__(self): return self async def __aexit__(self, exc_type, exc, tb): await self.close() def get_extra_info(self, name, default=None): if hasattr(self.writer, 'get_extra_info'): return self.writer.get_extra_info(name, default) if name == 'peername' and self.peer_ip is not None: return (self.peer_ip, self.peer_port) return default def get_peer_certificate(self): return self.packetizer.get_peer_certificate() def change_packetizer(self, packetizer): rem_data = self.packetizer.flush_buffer() if isinstance(self.packetizer, PacketizerSSL): self.packetizer.packetizer = packetizer else: self.packetizer = packetizer def packetizer_control(self, *args, **kw): return self.packetizer.packetizer_control(*args, **kw) async def wrap_ssl(self, ssl_ctx = None, packetizer = None): if packetizer is None: packetizer = self.packetizer if ssl_ctx is None: ssl_ctx = ssl.create_default_context() ssl_ctx.check_hostname = False ssl_ctx.verify_mode = ssl.CERT_NONE self.packetizer = PacketizerSSL(ssl_ctx, packetizer) await self.packetizer.do_handshake(self.reader, self.writer) async def close(self): self.closing = True if self.writer is not None: self.writer.close() self.closed_evt.set() async def drain(self): return async def write(self, data): async for packet in self.packetizer.data_out(data): self.writer.write(packet) await self.writer.drain() async def read_one(self): async for packet in self.read(): return packet async def read(self): try: data = None while self.closing is False: async for result in self.packetizer.data_in(data): if result is None: break yield result data = await self.reader.read(self.packetizer.buffer_size) if data == b'': break #flush the buffer, test if there is any data left data = None async for result in self.packetizer.data_in(data): if result is None: break yield result except Exception as e: yield None async def stream(self): if not isinstance(self.packetizer, StreamPacketizer): raise Exception('This function onaly available when StreamPacketizer is used!') while True: data = await self.reader.read(self.packetizer.buffer_size) await self.packetizer.data_in(data) if data == b'': break ### PROTOCOL INTERFACE async def pause_reading(self): self.read_resume.clear() async with self.read_lock: await self.read_resume.wait() async def __transport_reader(self, protocol:asyncio.Protocol): err = None try: data = None while True: async with self.read_lock: async for result in self.packetizer.data_in(data): if result is None: protocol.eof_received() break protocol.data_received(result) data = await self.reader.read(self.packetizer.buffer_size) if data == b'': protocol.eof_received() break except Exception as e: traceback.print_exc() err = e finally: protocol.connection_lost(err) async def get_transport(self, protocol:asyncio.Protocol): transport = UNITransport(self, protocol) protocol.connection_made(transport) x = asyncio.create_task(self.__transport_reader(protocol)) return transport class UniUDPConnection: def __init__(self, socket, data, addr): self.socket = socket self.data = data self.addr = addr """Provide high-level UDP endpoints for asyncio. Example: async def main(): # Create a local UDP enpoint local = await open_local_endpoint('localhost', 8888) # Create a remote UDP enpoint, pointing to the first one remote = await open_remote_endpoint(*local.address) # The remote endpoint sends a datagram remote.send(b'Hey Hey, My My') # The local endpoint receives the datagram, along with the address data, address = await local.receive() # This prints: Got 'Hey Hey, My My' from 127.0.0.1 port 8888 print(f"Got {data!r} from {address[0]} port {address[1]}") TAKEN FROM: https://gist.github.com/vxgmichel/e47bff34b68adb3cf6bd4845c4bed448 License: MIT """ # Imports import asyncio import warnings # Datagram protocol class DatagramEndpointProtocol(asyncio.DatagramProtocol): """Datagram protocol for the endpoint high-level interface.""" def __init__(self, endpoint): self._endpoint = endpoint # Protocol methods def connection_made(self, transport): self._endpoint._transport = transport def connection_lost(self, exc): assert exc is None if self._endpoint._write_ready_future is not None: self._endpoint._write_ready_future.set_result(None) self._endpoint.close() # Datagram protocol methods def datagram_received(self, data, addr): self._endpoint.feed_datagram(data, addr) def error_received(self, exc): msg = 'Endpoint received an error: {!r}' warnings.warn(msg.format(exc)) # Workflow control def pause_writing(self): assert self._endpoint._write_ready_future is None loop = self._endpoint._transport._loop self._endpoint._write_ready_future = loop.create_future() def resume_writing(self): assert self._endpoint._write_ready_future is not None self._endpoint._write_ready_future.set_result(None) self._endpoint._write_ready_future = None # Enpoint classes class Endpoint: """High-level interface for UDP enpoints. Can either be local or remote. It is initialized with an optional queue size for the incoming datagrams. """ def __init__(self, target:UniTarget, queue_size=None): self.target = target if queue_size is None: queue_size = 0 self._queue = asyncio.Queue(queue_size) self._closed = False self._transport = None self._write_ready_future = None # Protocol callbacks def feed_datagram(self, data, addr): try: self._queue.put_nowait((data, addr)) except asyncio.QueueFull: warnings.warn('Endpoint queue is full') def close(self): # Manage flag if self._closed: return self._closed = True # Wake up if self._queue.empty(): self.feed_datagram(None, None) # Close transport if self._transport: self._transport.close() # User methods async def write(self, data, addr = None): """Send a datagram to the given address.""" if addr is None: addr = (self.target.get_ip_or_hostname(), self.target.port) return self.send(data, addr) async def read(self, with_addr = False): """Wait for an incoming datagram and return it with the corresponding address. This method is a coroutine. """ while not self._closed: data, addr = await self.receive() if data is None: self.close() break result = (data, addr) if with_addr is True: yield result else: yield data async def read_one(self, with_addr = False): """Wait for an incoming datagram and return it with the corresponding address. This method is a coroutine. """ data, addr = await self.receive() if data is None: self.close() return None if with_addr is True: return (data, addr) return data def send(self, data, addr): """Send a datagram to the given address.""" if self._closed: raise IOError("Enpoint is closed") if asyncio.iscoroutine(self._transport.sendto) is True: # absolutetly not a good idea to do this, but it works x = asyncio.create_task(self._transport.sendto(data, addr)) else: self._transport.sendto(data, addr) async def receive(self): """Wait for an incoming datagram and return it with the corresponding address. This method is a coroutine. """ if self._queue.empty() and self._closed: raise IOError("Enpoint is closed") data, addr = await self._queue.get() if data is None: raise IOError("Enpoint is closed") return data, addr def abort(self): """Close the transport immediately.""" if self._closed: raise IOError("Enpoint is closed") self._transport.abort() self.close() async def drain(self): """Drain the transport buffer below the low-water mark.""" if self._write_ready_future is not None: await self._write_ready_future # Properties @property def address(self): """The endpoint address as a (host, port) tuple.""" return self._transport.get_extra_info("socket").getsockname() @property def closed(self): """Indicates whether the endpoint is closed or not.""" return self._closed class LocalEndpoint(Endpoint): """High-level interface for UDP local enpoints. It is initialized with an optional queue size for the incoming datagrams. """ pass class RemoteEndpoint(Endpoint): """High-level interface for UDP remote enpoints. It is initialized with an optional queue size for the incoming datagrams. """ def send(self, data): """Send a datagram to the remote host.""" super().send(data, None) async def receive(self): """ Wait for an incoming datagram from the remote host. This method is a coroutine. """ data, addr = await super().receive() return data asysocks-0.2.18/asysocks/unicomm/common/packetizers/000077500000000000000000000000001510034457600225705ustar00rootroot00000000000000asysocks-0.2.18/asysocks/unicomm/common/packetizers/__init__.py000066400000000000000000000052421510034457600247040ustar00rootroot00000000000000import asyncio class Packetizer: def __init__(self, buffer_size = 65535): self.buffer_size = buffer_size def packetizer_control(self, *args, **kw): return None def flush_buffer(self): return b'' def set_buffersize(self, buffer_size:int): self.buffer_size = buffer_size async def data_out(self, data): yield data async def data_in(self, data): yield data class StreamPacketizer: def __init__(self, buffer_size = 65535): self.buffer_size = buffer_size self.buffer = b'' self.stream_ended = asyncio.Event() self.__read_lock = asyncio.Lock() self.data_incoming_evt = asyncio.Event() async def terminate(self): pass def packetizer_control(self, *args, **kw): return None def flush_buffer(self): return b'' def set_buffersize(self, buffer_size:int): self.buffer_size = buffer_size async def readuntil(self, end = b'\n'): if self.stream_ended.is_set(): raise Exception('Stream ended!') if self.__read_lock.locked(): raise Exception('A read operation is already in progress!') async with self.__read_lock: while self.buffer.find(end) == -1: await self.data_incoming_evt.wait() self.data_incoming_evt.clear() if self.stream_ended.is_set(): break m = self.buffer.find(end) + len(end) res = self.buffer[:m] self.buffer = self.buffer[m:] return res async def readexactly(self, n): if self.stream_ended.is_set(): raise Exception('Stream ended!') if n < 0: raise Exception('N must be >0') if n == 0: return b'' if self.__read_lock.locked(): raise Exception('A read operation is already in progress!') async with self.__read_lock: while len(self.buffer) < n: await self.data_incoming_evt.wait() self.data_incoming_evt.clear() if self.stream_ended.is_set(): break res = self.buffer[:n] self.buffer = self.buffer[n:] return res async def read(self, n = -1): if self.stream_ended.is_set(): raise Exception('Stream ended!') if self.__read_lock.locked(): raise Exception('A read operation is already in progress!') async with self.__read_lock: while len(self.buffer) == 0: await self.data_incoming_evt.wait() self.data_incoming_evt.clear() if self.stream_ended.is_set(): break if n == -1: n = len(self.buffer) res = self.buffer[:n] self.buffer = self.buffer[n:] return res async def data_out(self, data): yield data async def data_in(self, data): if self.stream_ended.is_set() and data != b'': raise Exception('More data incoming after stream has ended!') if data == b'': self.stream_ended.set() self.data_incoming_evt.set() else: self.buffer += data self.data_incoming_evt.set() asysocks-0.2.18/asysocks/unicomm/common/packetizers/ssl.py000066400000000000000000000074411510034457600237510ustar00rootroot00000000000000import ssl from asysocks.unicomm.common.packetizers import Packetizer class PacketizerSSL(Packetizer): def __init__(self, ssl_ctx, packetizer:Packetizer): Packetizer.__init__(self, 16384) self.ssl_ctx:ssl.SSLContext = ssl_ctx self.packetizer = packetizer self.tls_in_buff:ssl.MemoryBIO = None self.tls_out_buff:ssl.MemoryBIO = None self.tls_obj:ssl.SSLObject = None self.__plaintext_buffer = [] self.__deepdebug = False def flush_buffer(self): return self.packetizer.flush_buffer() def set_buffersize(self, buffer_size:int): self.packetizer.set_buffersize(buffer_size) def get_peer_certificate(self): return self.tls_obj.getpeercert(binary_form=True) def packetizer_control(self, *args, **kwargs): self.packetizer.packetizer_control(*args, **kwargs) async def do_handshake(self, reader, writer, server_side=False): #print('do_handshake') self.tls_in_buff = ssl.MemoryBIO() self.tls_out_buff = ssl.MemoryBIO() self.tls_obj = self.ssl_ctx.wrap_bio(self.tls_in_buff, self.tls_out_buff, server_side=server_side) # , server_hostname = self.monitor.dst_hostname ctr = 0 while True: if self.__deepdebug is True: print('DST start %s' % ctr) # setting a maximum number of iterations to avoid infinite loops if ctr > 20: raise Exception('Handshake took too many iterations') ctr += 1 try: self.tls_obj.do_handshake() except ssl.SSLWantReadError: if self.__deepdebug is True: print('DST want %s' % ctr) # again, we need to limit the number of iterations for _ in range(10): client_hello = self.tls_out_buff.read() if client_hello != b'': if self.__deepdebug is True: print('DST client_hello %s' % len(client_hello)) writer.write(client_hello) await writer.drain() else: break else: raise Exception('Handshake took too many iterations 2') if self.__deepdebug is True: print('DST wating server hello %s' % ctr) server_hello = await reader.read(self.buffer_size) if server_hello is None or server_hello == b'': raise Exception('Connection closed') if ctr == 1 and server_hello[0] != 0x16: if self.__deepdebug is True: print('Server did not send a valid TLS handshake packet') raise Exception('Server did not send a valid TLS handshake packet') if self.__deepdebug is True: print('DST server_hello %s' % len(server_hello)) self.tls_in_buff.write(server_hello) continue except Exception as e: raise e else: if self.__deepdebug is True: print('DST handshake ok %s' % ctr) server_fin = self.tls_out_buff.read() if self.__deepdebug is True: print('DST server_fin %s ' % server_fin) if server_fin != b'': writer.write(server_fin) await writer.drain() break if self.__deepdebug is True: print('Handshake done') async def data_out(self, data:bytes): outdata = b'' async for packetraw in self.packetizer.data_out(data): outdata += packetraw self.tls_obj.write(outdata) while True: raw = self.tls_out_buff.read() if raw != b'': yield raw continue break async def data_in(self, encdata:bytes): while self.__plaintext_buffer: yield self.__plaintext_buffer.pop(0) if encdata is None: yield None self.tls_in_buff.write(encdata) data = b'' was_data = False while True: try: # super weird, sometimes the read() call returns an empty byte string, # sometimes it raises an ssl.SSLWantReadError exception chunk = self.tls_obj.read() if chunk == b'': break data += chunk was_data = True except ssl.SSLWantReadError: break if was_data is True: async for packet in self.packetizer.data_in(data): self.__plaintext_buffer.append(packet) while self.__plaintext_buffer: yield self.__plaintext_buffer.pop(0)asysocks-0.2.18/asysocks/unicomm/common/proxy.py000066400000000000000000000177731510034457600220160ustar00rootroot00000000000000import os import ssl import enum from typing import List from urllib.parse import urlparse, parse_qs import copy def stru(x): return str(x).upper() class UniProxyProto(enum.Enum): CLIENT_SOCKS4 = 1 CLIENT_SSL_SOCKS4 = 2 CLIENT_SOCKS5_TCP = 3 CLIENT_SOCKS5_UDP = 4 CLIENT_SSL_SOCKS5_TCP = 5 CLIENT_SSL_SOCKS5_UDP = 6 CLIENT_HTTP = 7 CLIENT_SSL_HTTP = 8 CLIENT_WSNET = 9 CLIENT_CUSTOM = 998 CLIENT_WSNETTEST = 999 CLIENT_WSNETDIRECT = 17 CLIENT_SSL_WSNETDIRECT = 18 CLIENT_WSNETWS = 10 CLIENT_SSL_WSNETWS = 11 SERVER_WSNET = 12 SERVER_SOCKS5_TCP = 13 SERVER_SOCKS5_UDP = 14 SERVER_SSL_SOCKS5_TCP = 15 SERVER_SSL_SOCKS5_UDP = 16 proxyshort_to_type = { 'SOCKS4' : UniProxyProto.CLIENT_SOCKS4, 'SOCKS4S' : UniProxyProto.CLIENT_SSL_SOCKS4, 'SOCKS5' : UniProxyProto.CLIENT_SOCKS5_TCP, 'SOCKS5U' : UniProxyProto.CLIENT_SOCKS5_UDP, 'SOCKS5S' : UniProxyProto.CLIENT_SSL_SOCKS5_TCP, 'HTTP' : UniProxyProto.CLIENT_HTTP, 'HTTPS' : UniProxyProto.CLIENT_SSL_HTTP, 'WSNET' : UniProxyProto.CLIENT_WSNET, 'WSNETTEST' : UniProxyProto.CLIENT_WSNETTEST, 'WSNETWS' : UniProxyProto.CLIENT_WSNETWS, 'WSNETWSS' : UniProxyProto.CLIENT_SSL_WSNETWS, 'WSNETDIRECT': UniProxyProto.CLIENT_WSNETDIRECT, 'WSNETDIRECTSSL': UniProxyProto.CLIENT_SSL_WSNETDIRECT, 'CUSTOM': UniProxyProto.CLIENT_CUSTOM, } proxyshort_protocol_defport = { UniProxyProto.CLIENT_SOCKS4 : 1080, UniProxyProto.CLIENT_SSL_SOCKS4 : 1080, UniProxyProto.CLIENT_SOCKS5_TCP: 1080, UniProxyProto.CLIENT_SOCKS5_UDP: 1080, UniProxyProto.CLIENT_SSL_SOCKS5_TCP: 1080, UniProxyProto.CLIENT_HTTP : 8080, UniProxyProto.CLIENT_SSL_HTTP :8443, UniProxyProto.CLIENT_WSNET: 8700, UniProxyProto.CLIENT_WSNETTEST: 8700, UniProxyProto.CLIENT_WSNETWS:8700, UniProxyProto.CLIENT_SSL_WSNETWS:8765, UniProxyProto.CLIENT_WSNETDIRECT:8700, } def urlparam_proto(x): return proxyshort_to_type[x.upper()] uniproxytarget_urlparams_param2var = { 'type' : ('protocol', [stru, urlparam_proto]), 'host' : ('server_ip', [str]), 'server' : ('server_ip', [str]), 'port' : ('server_port', [int]), 'bind' : ('is_bind', [bool]), 'timeout': ('timeout', [int]), 'etimeout' : ('endpoint_timeout', [int]), 'bsize' : ('buffer_size', [int]), 'user' : ('username', [str]), 'pass' : ('password', [str]), #'authtype' : ('authtype', [SOCKS5Method]), 'userid' : ('userid', [str]), 'agentid' : ('agentid', [str]), } class UniProxyTarget: def __init__(self): self.server_ip:str = None self.server_port:int = None self.agentid:str = None self.protocol:UniProxyProto = None self.timeout:int = 10 self.ssl_ctx:ssl.SSLContext = None self.credential = None self.endpoint_ip:str = None self.endpoint_port:int = None self.wsnet_reuse:bool = False self.userid = os.urandom(4).hex().encode('ascii') self.customproxyfactory = None self.only_open = False #These params used for security testing only! self.only_auth = False #These params used for security testing only! self.only_bind = False #These params used for security testing only! def get_sname(self): return '%s:%s' % (self.server_ip, self.server_port) def get_tname(self): return '%s:%s' % (self.endpoint_ip, self.endpoint_port) def __repr__(self): return str(self.__dict__) def __str__(self): t = '==== UniProxyTarget ====\r\n' for k in self.__dict__: t += '%s: %s\r\n' % (k, self.__dict__[k]) return t def __deepcopy__(self, memo=None): proxy = UniProxyTarget() proxy.server_ip = copy.deepcopy(self.server_ip) proxy.server_port = copy.deepcopy(self.server_port) proxy.agentid = copy.deepcopy(self.agentid) proxy.protocol = copy.deepcopy(self.protocol) proxy.timeout = copy.deepcopy(self.timeout) proxy.ssl_ctx = copy.deepcopy(self.ssl_ctx) #this will definitely fail!!! proxy.credential = copy.deepcopy(self.credential) proxy.endpoint_ip = copy.deepcopy(self.endpoint_ip) proxy.endpoint_port = copy.deepcopy(self.endpoint_port) proxy.wsnet_reuse = copy.deepcopy(self.wsnet_reuse) proxy.userid = copy.deepcopy(self.userid) self.only_open = copy.deepcopy(self.only_open) self.only_auth = copy.deepcopy(self.only_auth) self.only_bind = copy.deepcopy(self.only_bind) proxy.customproxyfactory = self.customproxyfactory return proxy @staticmethod def from_url_params(query, hostname, endpoint_port = None): lastproxy = UniProxyTarget() lastproxy.endpoint_ip = hostname lastproxy.endpoint_port = int(endpoint_port) if query is not None: proxycounts = [0] proxynums = {'0' : None} for k in query: if k.startswith('proxy'): try: int(k[5]) if k[5] not in proxynums: proxynums[k[5]] = None proxycounts.append(int(k[5])) except Exception as e: #print(e) pass #print(proxynums) #print(proxycounts) if len(proxycounts) != len(proxynums): raise Exception('proxyies are not in sequential order! ERROR!') proxycounts.sort() firstiter = True prevproxy = lastproxy for i in proxycounts[::-1]: pdata = UniProxyTarget() if firstiter is True: firstiter = False pdata = lastproxy else: pdata.endpoint_ip = prevproxy.server_ip pdata.endpoint_port = prevproxy.server_port prevproxy = pdata startstring = 'proxy%s' % i if i == 0: startstring = 'proxy' for k in query: if k.startswith(startstring): startpos = 6 if i == 0: startpos = 5 if k[startpos:] in uniproxytarget_urlparams_param2var: data = query[k][0] for c in uniproxytarget_urlparams_param2var[k[startpos:]][1]: #print(c) data = c(data) setattr( pdata, uniproxytarget_urlparams_param2var[k[startpos:]][0], data ) proxynums[str(i)] = pdata #if len(proxynums) > 1: # for k in proxynums: # if proxynums[k].version in sockssslversions: # raise Exception('SSL in proxy chaining not supported! That would be a lot of work...') #else: # if proxynums['0'].version in sockssslversions: # proxynums['0'].ssl_ctx = ssl.create_default_context() #for k in proxynums: # proxynums[k].sanity_check() targets = [] for i in proxycounts: targets.append(proxynums[str(i)]) return targets @staticmethod def from_url(url_str, endpoint_port = None): """ """ url = urlparse(url_str) endpoint_ip = url.hostname if url.port: endpoint_port = int(url.port) else: endpoint_port = int(endpoint_port) query = None if url.query is not None: query = parse_qs(url.query) return UniProxyTarget.from_url_params(query, endpoint_ip, endpoint_port) @staticmethod def from_url_full(url_str, endpoint_ip, endpoint_port): """socks5+password://TEST\\Administrator:Password!1@127.0.0.1""" result = [] if isinstance(url_str, list): for url in url_str[::-1]: res = UniProxyTarget.from_url_full(url, endpoint_ip, endpoint_port) result += res endpoint_ip = res[0].server_ip endpoint_port = res[0].server_port return result[::-1] url = urlparse(url_str) proto, auth = url.scheme.upper().split('+') protocol = proxyshort_to_type[proto] query = None if url.query is not None: query = parse_qs(url.query) params = {} for k in query: if k in uniproxytarget_urlparams_param2var: data = query[k][0] for c in uniproxytarget_urlparams_param2var[k][1]: data = c(data) params[k] = data credential = None if url.username is not None or url.password is not None: credential = (url.username, url.password) pt = UniProxyTarget() pt.server_ip = url.hostname pt.server_port = url.port if url.port is not None else proxyshort_protocol_defport[protocol] pt.protocol= protocol pt.endpoint_ip = endpoint_ip pt.endpoint_port = endpoint_port pt.credential = credential pt.agentid = params.get('agentid') pt.timeout = params.get('timeout', 10) return [pt] if __name__ == '__main__': a = "socks5+password://TEST\\Administrator:Password!1@127.0.0.1" b = "socks5+password://TEST\\Administrator:Password!1@127.0.0.1" c = "socks5+password://TEST\\Administrator:Password!1@127.0.0.1" res = UniProxyTarget.from_url_full([a,b,c], None, None) for r in res: print(r)asysocks-0.2.18/asysocks/unicomm/common/scanner/000077500000000000000000000000001510034457600216755ustar00rootroot00000000000000asysocks-0.2.18/asysocks/unicomm/common/scanner/__init__.py000066400000000000000000000000001510034457600237740ustar00rootroot00000000000000asysocks-0.2.18/asysocks/unicomm/common/scanner/common.py000066400000000000000000000076131510034457600235460ustar00rootroot00000000000000import enum import os import datetime import traceback import json class ScannerResultType(enum.Enum): STARTED = 'STARTED' ERROR = 'ERROR' DATA = 'DATA' PROGRESS = 'PROGRESS' FINISHED = 'FINISHED' INFO = 'INFO' TARGETDONE = 'TARGETDONE' class ScannerResult: def __init__(self, type, resid, data = None, **kwargs): self.type = type self.resid = str(resid) self.data = data self.kwargs = kwargs def get_extra_info(self, key): if key in self.kwargs: return self.kwargs[key] return None def to_line(self, separator = '\t'): raise NotImplementedError() class ScannerStarted(ScannerResult): def __init__(self, scannername): ScannerResult.__init__( self, ScannerResultType.STARTED, scannername ) def to_line(self, separator = '\t'): return separator.join(['STARTED', '%s' % self.type]) class ScannerFinished(ScannerResult): def __init__(self, scannername): ScannerResult.__init__( self, ScannerResultType.FINISHED, scannername ) def to_line(self, separator = '\t'): return separator.join(['FINISHED','%s' % self.type]) class ScannerError(ScannerResult): def __init__(self, resid, error): ScannerResult.__init__( self, ScannerResultType.ERROR, resid, error ) self.traceback = None if isinstance(error, Exception) is True: self.traceback = error.__traceback__ self.data = repr(error) def to_line(self, separator = '\t'): return separator.join(['ERROR', str(self.resid), str(self.data)]) #.replace('\r','').replace('\n', '').replace('\t', ' ') def to_traceback(self): if self.traceback is not None: return ''.join(traceback.format_tb(self.traceback)) class ScannerData(ScannerResult): def __init__(self, resid, data, **kwargs): ScannerResult.__init__( self, ScannerResultType.DATA, resid, data=data, **kwargs ) def get_name(self): try: return self.data.get_name() except: return str(type(self.data).__name__) def get_fname(self): try: return self.data.get_fname() except: return 'scandata_%s_%s.bin' % (os.urandom(4).hex(), datetime.datetime.now().strftime("%Y%m%d_%H%M%S")) def get_fdata(self): try: return self.data.get_fdata() except: return None def __flatten_data(self): try: return self.data.to_line() except: return str(self.data) def to_line(self, separator = '\t'): res = '' for line in self.__flatten_data().split('\n'): line = line.strip() res += separator.join([str(self.resid), line])# + '\r\n' return res def to_dict(self): try: return { 'resid' : self.resid, 'restype' : str(type(self.data).__name__), 'data' : self.data.to_dict() } except: return { 'resid' : self.resid, 'restype' : str(type(self.data).__name__), 'data' : self.__flatten_data() } def to_json(self): return json.dumps(self.to_dict()) class ScannerProgress(ScannerResult): def __init__(self, scannername, total, current): ScannerResult.__init__( self, ScannerResultType.PROGRESS, scannername ) self.total = total self.current = current def to_percent(self): if self.total is None or self.total == 0: return 'x' return (self.current/self.total)*100 def to_line(self, separator = '\t'): return separator.join(['PROGRESS', '%s\\%s (%s%%)' % (self.total, self.current, self.to_percent())]) class ScannerInfo(ScannerResult): def __init__(self, resid, info): ScannerResult.__init__( self, ScannerResultType.INFO, resid, info ) def to_line(self, separator = '\t'): res = '' for line in str(self.data).split('\n'): line = line.strip() res += separator.join(['INFO', str(self.resid), line]) + '\r\n' return res class ScannerTargetDone(ScannerResult): def __init__(self, resid, target): ScannerResult.__init__( self, ScannerResultType.TARGETDONE, resid ) self.target = target def to_line(self, separator = '\t'): return separator.join(['TARGETDONE', str(self.resid), str(self.target)])asysocks-0.2.18/asysocks/unicomm/common/scanner/scanner.py000066400000000000000000000141431510034457600237030ustar00rootroot00000000000000import asyncio import traceback from tqdm import tqdm import datetime import sys from pathlib import Path from asysocks.unicomm.common.scanner.common import * class UniScannerExecutor: def __init__(self): pass async def run(self, targetid, target, out_queue): await out_queue.put(ScannerData(targetid, 'alma')) class UniScanner: def __init__(self, name, executors, target_generators, worker_count = 100, host_timeout = 5): self.name = name self.target_generators = target_generators self.worker_count = worker_count self.host_timeout = host_timeout if host_timeout > 0 else None self.__workers = [] self.__targetgen = None self.__targetqueue = None self.out_queue = asyncio.Queue() self.__total_items = 0 self.__finished_items = 0 self.scantime = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") self.executors = executors if isinstance(self.executors, list) is False: self.executors = [self.executors] if isinstance(self.target_generators, list) is False: self.target_generators = [self.target_generators] async def worker(self): while not asyncio.current_task().cancelled(): x = await self.__targetqueue.get() if x is None: return targetid, target = x for executor in self.executors: try: await asyncio.wait_for(executor.run(targetid, target, self.out_queue), self.host_timeout) except asyncio.CancelledError: return except Exception as e: await self.out_queue.put(ScannerError(target, e)) self.__finished_items += 1 await self.out_queue.put(ScannerProgress(self.name, self.__total_items, self.__finished_items)) await self.out_queue.put(ScannerTargetDone(targetid, target)) async def stop(self): for worker in self.__workers: worker.cancel() if self.__targetgen is not None: self.__targetgen.cancel() async def targets(self): try: for generator in self.target_generators: async for targetid, target in generator.run(): await self.__targetqueue.put((targetid, target)) for _ in range(len(self.__workers)): await self.__targetqueue.put(None) except asyncio.CancelledError: pass async def scan(self): try: for generator in self.target_generators: self.__total_items += generator.get_total() self.__targetqueue = asyncio.Queue(self.worker_count) for _ in range(self.worker_count): self.__workers.append(asyncio.create_task(self.worker())) self.__targetgen = asyncio.create_task(self.targets()) yield ScannerStarted(self.name) gather_coro = asyncio.gather(*self.__workers) while gather_coro.done() is False or self.out_queue.qsize() > 0: try: result = await asyncio.wait_for(self.out_queue.get(), timeout = 1) yield result except asyncio.TimeoutError: continue except: break except Exception as e: yield ScannerError('!SCANNER!', e) finally: await self.stop() yield ScannerFinished(self.name) async def __flush_perionically(self, fhandles): try: while asyncio.current_task().cancelled() is False: await asyncio.sleep(4) for k in fhandles: try: fhandles[k].flush() except: pass except: pass async def scan_and_process(self, progress = True, out_file = None, include_errors = False): fhandles = {} flush_task = asyncio.create_task(self.__flush_perionically(fhandles)) try: def update_pbar(pbar, rtype): if len(pbar) == 0: return if rtype == ScannerResultType.ERROR: pbar['errors'].update() elif rtype == ScannerResultType.PROGRESS: pbar['targets'].update() elif rtype == ScannerResultType.FINISHED: for k in pbar: pbar[k].refresh() elif rtype == ScannerResultType.DATA: pbar['results'].update() pbar = {} if out_file is None: out_file = Path.cwd() #os.getcwd() else: if isinstance(out_file, str) is True: out_file = Path(out_file) if out_file.exists() is False: out_file.mkdir(parents = True, exist_ok = True) if progress is True: for generator in self.target_generators: self.__total_items += generator.get_total() pbar['targets'] = tqdm(desc='Targets ', unit='', position=0, total=self.__total_items) pbar['results'] = tqdm(desc='Results ', unit='', position=1) pbar['errors'] = tqdm(desc='Errors ', unit='', position=2) async for result in self.scan(): update_pbar(pbar, result.type) if result.type == ScannerResultType.ERROR and include_errors is True: if 'error' not in fhandles: if out_file is None: fhandles['error'] = sys.stderr else: fhandles['error'] = open(os.path.join(out_file.absolute(), '%s_error_%s.tsv' % (str(self.name), self.scantime)), 'w', newline = '') fhandles['error'].write(result.to_line()+'\r\n') elif result.type == ScannerResultType.DATA: filedata = result.get_fdata() if filedata is not None: with open(result.get_fname() ,'wb') as f: f.write(filedata) continue if filedata is None: rtype = result.get_name() if rtype not in fhandles: if out_file is None: fhandles[rtype] = sys.stdout else: fhandles[rtype] = open(os.path.join(out_file.absolute(), '%s_%s%s' %(rtype, self.scantime,'.tsv')), 'w', newline = '') fhandles[rtype].write(result.to_line()+'\r\n') except Exception as e: print('SCANNER CRITICAL ERROR %s' % str(e)) finally: flush_task.cancel() await self.stop() for k in fhandles: try: fhandles[k].close() except: pass async def amain(): try: executor = UniScannerExecutor() from asysocks.unicomm.common.scanner.targetgen import UniTargetGen from aiosmb.commons.connection.factory import SMBConnectionFactory connectionfactory = SMBConnectionFactory.from_url('smb2+kerberos-password://TEST.corp\\Administrator:Passw0rd!1@win2019ad.test.corp/') scanner = UniScanner('TEST', [executor]) scanner.target_generators.append(UniTargetGen.from_list(['10.10.10.2', '10.10.10.3'])) async for x in scanner.scan(): print(x) if x.type == ScannerResultType.ERROR: print(x.data) except Exception as e: traceback.print_exc() def main(): asyncio.run(amain()) if __name__ == '__main__': main() asysocks-0.2.18/asysocks/unicomm/common/scanner/targetgen.py000066400000000000000000000140031510034457600242250ustar00rootroot00000000000000 import asyncio import ipaddress import uuid class UniCredentialGen: def __init__(self, domain=None, max_attempts=None, sleep_time=300, username_is_password=False): self.max_attempts = max_attempts self.sleep_time = sleep_time self.usernames = {} self.passwords = {} self.credentials = [] # fixed user,password pairs self.username_is_password = username_is_password self.domain = domain if self.max_attempts is None: self.max_attempts = -1 def set_domain(self, domain): self.domain=domain def add_credential(self, username, password): if password is None: password = '' self.credentials.append((username, password)) def add_credential_tuple(self, t): username, password = t.split(':') self.add_credential(username, password) def add_credential_file(self, fname): with open(fname, 'r') as f: for line in f: line=line.strip() self.add_credential_tuple(line) def add_users_file(self, fname): with open(fname, 'r') as f: for line in f: line=line.strip() self.add_username(line) def add_passwords_file(self, fname): with open(fname, 'r') as f: for line in f: line=line.strip() self.add_password(line) def add_username(self, username): self.usernames[username] = 1 def add_password(self, password): if password is None: password = '' self.passwords[password] = 1 def get_total(self): return len(self.credentials) + (len(self.usernames) * len(self.passwords)) async def get_password_candidates(self, username): current_attempt = 0 for password in self.passwords: uid = str(uuid.uuid4()) yield (uid, (self.domain, username, password)) current_attempt += 1 if self.max_attempts != -1: if current_attempt >= self.max_attempts: #print('Sleeping for %s seconds' % self.sleep_time) await asyncio.sleep(self.sleep_time) #print('Waking up') current_attempt = 0 async def run(self): if self.username_is_password is True: for username in self.usernames: uid = str(uuid.uuid4()) yield (uid, (self.domain, username, username)) return tasks = [] for username in self.usernames: tasks.append(self.get_password_candidates(username)) while len(tasks) > 0: for task in tasks: try: yield await task.__anext__() except StopAsyncIteration: tasks.remove(task) continue except: continue for username, password in self.credentials: uid = str(uuid.uuid4()) yield (uid, (self.domain, username, password)) class UniTargetGen: def __init__(self): self.targets = [] self.excludes = [] @staticmethod def from_list(tl): targetgen = UniTargetGen() targetgen.add_list(tl) return targetgen def add_list(self, targetlist): for t in targetlist: try: self.add_ip(t) except: try: self.add_file(t) except: self.add_hostname(t) def add_ip(self, ip): uids = [] try: ipaddress.ip_address(ip) uid = str(uuid.uuid4()) uids.append(uid) self.targets.append((uid, ip)) except: for t in ipaddress.ip_network(ip,strict=False): if(str(t).endswith('.0') or str(t).endswith('.255')): continue uid = str(uuid.uuid4()) uids.append(uid) self.targets.append((uid, str(t))) return uids def add_file(self, fname): uids = [] with open(fname, 'r') as f: for line in f: line=line.strip() try: uids += self.add_ip(line) except: uids += self.add_hostname(line) return uids def add_hostname(self, hosntame): uid = str(uuid.uuid4()) self.targets.append((uid, str(hosntame))) return [uid] def get_total(self): return len(self.targets) def check_excludes(self, target): starget = str(target).lower() for exclude in self.excludes: try: ipn = ipaddress.ip_network(exclude) if ipaddress.ip_address(starget) in ipn: return False except: pass if starget == str(exclude).lower(): return False return True async def run(self): for tid, target in self.targets: if self.check_excludes(target) is False: continue yield (tid, target) class UniTargetPortGen: def __init__(self): self.targets = [] self.ports = {} @staticmethod def from_list(tl): targetgen = UniTargetGen() targetgen.add_list(tl) return targetgen def add_port_list(self, portranges): def calc_range(x): if x.find('-') != -1: start,end = x.split('-') for i in range(int(start), int(end)): self.ports[int(i)] = None else: self.ports[int(x)] = None for prange in portranges: if prange.find(',') != -1: for port in prange.split(','): calc_range(port) else: calc_range(prange) def add_list(self, targetlist): for t in targetlist: try: self.add_ip(t) except: try: self.add_file(t) except: self.add_hostname(t) def add_ip(self, ip): uids = [] try: ipaddress.ip_address(ip) uid = str(uuid.uuid4()) uids.append(uid) self.targets.append((uid, ip)) except: for t in ipaddress.ip_network(ip,strict=False): if(str(t).endswith('.0') or str(t).endswith('.255')): continue uid = str(uuid.uuid4()) uids.append(uid) self.targets.append((uid, str(t))) return uids def add_file(self, fname): uids = [] with open(fname, 'r') as f: for line in f: line=line.strip() try: uids += self.add_ip(line) except: uids += self.add_hostname(line) return uids def add_hostname(self, hosntame): uid = str(uuid.uuid4()) self.targets.append((uid, str(hosntame))) return [uid] def get_total(self): return len(self.targets)*len(self.ports) async def run(self): for tid, target in self.targets: for port in self.ports: yield (tid, (target,port)) class UniGenericGen: def __init__(self): self.targets = [] @staticmethod def from_list(tl): targetgen = UniGenericGen() targetgen.add_list(tl) return targetgen def add_list(self, targetlist): for t in targetlist: uid = str(uuid.uuid4()) self.targets.append((uid, t)) def get_total(self): return len(self.targets) async def run(self): for tid, target in self.targets: yield (tid, target)asysocks-0.2.18/asysocks/unicomm/common/target.py000066400000000000000000000156241510034457600221140ustar00rootroot00000000000000from os import stat import ssl import copy import enum import ipaddress from typing import Callable, Dict, List from urllib.parse import urlparse, parse_qs from asysocks.unicomm.common.unissl import UniSSL from asysocks.unicomm.common.proxy import UniProxyProto, UniProxyTarget from asysocks.unicomm.utils.paramprocessor import str_one, int_one, bool_one class UniProto(enum.Enum): CLIENT_TCP = 1 CLIENT_SSL_TCP = 2 CLIENT_UDP = 3 CLIENT_DTLS = 4 CLIENT_QUIC = 5 SERVER_TCP = 6 SERVER_SSL_TCP = 7 SERVER_UDP = 8 unitarget_url_params = { 'dc' : str_one, 'timeout' : int_one, 'serverip' : str_one, 'dns' : str_one, 'privport' : bool_one, } class UniTarget: def __init__(self, ip:str, port:int, protocol:UniProto, timeout:int=5, ssl_ctx:UniSSL=None, hostname:str = None, dc_ip:str = None, domain:str = None, proxies:List[UniProxyTarget] = None, dns:str = None, use_privileged_source_port:bool = False, srcip:str='', srcport:int = 0): self.hostname = hostname self.port = port self.protocol = protocol self.timeout = timeout self.ssl_ctx = ssl_ctx self.dc_ip = dc_ip self.domain = domain self.dns = dns self.srcip = srcip self.srcport = srcport self.use_privileged_source_port = use_privileged_source_port self.proxies:List[UniProxyTarget] = proxies if proxies is None: self.proxies = [] try: ipaddress.ip_address(ip) self.ip = ip except: if ip is not None: self.hostname = ip self.ip = None try: ipaddress.ip_address(hostname) self.ip = hostname except: if hostname is not None: self.hostname = hostname if ip is None and hostname is None: raise Exception('Both IP and Hostname can\'t be none!') self.__update_proxy() def __update_proxy(self): if len(self.proxies) == 0: return self.proxies[-1].endpoint_ip = self.get_hostname_or_ip() self.proxies[-1].endpoint_port = self.port @staticmethod def get_help(helptype:str = 'url'): if helptype == 'url': return 'Connection related parameters -query params-:\n' + \ '\ttimeout - int - connection timeout in seconds\n' + \ '\tdc - str - domain controller IP address (KERBEROS)\n' + \ '\tserverip - str - server IP address (if different from what your DNS resolver would say)\n' + \ '\tdns - str - DNS server IP address (not used)\n' + \ '\tsslverify - bool - SSL/TLS verify server certificate\n' + \ '\tsslcert - str - SSL/TLS certificate path\n' + \ '\tsslkey - str - SSL/TLS key path\n' + \ '\tsslpassword - str - SSL/TLS key password\n' +\ '\t=== PROXY PARAMETERS ===\n' + \ '\tEach proxy needs at least "proxytype", "proxyhost" and "proxyport" defined!\n' +\ '\tProxy parameters can be repeated to add multiple proxies, just change the prefix to "proxy1" "proxy2" ...\n'+\ '\tproxytype - str - Available PROXY protocols: http, socks4, socks5\n\n' + \ '\tproxyusername - str - proxy username\n' + \ '\tproxypassword - str - proxy password\n' + \ '\tproxytimeout - int - proxy connection timeout in seconds\n' + \ '\tproxyhost - str - proxy server IP address\n' + \ '\tproxyport - int - proxy port\n' + \ '\tproxyssl - bool - proxy SSL/TLS\n' + \ '\tproxysslverify - bool - proxy SSL/TLS verification\n' + \ '\tproxysslcert - str - proxy SSL/TLS certificate path\n' + \ '\tproxysslkey - str - proxy SSL/TLS key path\n' + \ '\tproxysslkey_password - str - proxy SSL/TLS key password\n' def get_kerberos_target(self, dc_ip = None, hostname=None, domain=None): if dc_ip is None and self.dc_ip is None: raise Exception('DC IP must be provided for kerberos target!') if dc_ip is None: dc_ip = self.dc_ip return UniTarget( dc_ip, 88, UniProto.CLIENT_TCP, timeout = self.timeout, ssl_ctx = None, hostname = hostname, dc_ip = dc_ip, domain = domain, proxies=copy.deepcopy(self.proxies) ) def get_newtarget(self, ip, port, hostname = None): return UniTarget(ip, port, self.protocol, self.timeout, ssl_ctx = self.ssl_ctx, hostname = hostname, dc_ip = self.dc_ip, domain = self.domain, proxies=copy.deepcopy(self.proxies)) def get_ssl_context(self, protocol = None): if self.ssl_ctx is None: is_server = True if self.protocol in [UniProto.SERVER_TCP, UniProto.SERVER_SSL_TCP, UniProto.SERVER_UDP] else False self.ssl_ctx = UniSSL.get_noverify_context(is_server) return self.ssl_ctx.get_ssl_context(protocol) def get_hostname(self): return self.hostname def set_hostname_or_ip(self, ip): try: self.ip = str(ipaddress.ip_address(ip)) except: self.hostname = ip self.ip = ip def get_ip_or_hostname(self): if self.ip is not None: return self.ip return self.hostname def get_hostname_or_ip(self): if self.hostname is not None: return self.hostname return self.ip def get_sockaddr(self): if self.srcip == '' or self.srcip is None: return None return (self.srcip, self.srcport) def get_peeraddr(self): return (self.get_ip_or_hostname(), self.port) @staticmethod def from_url(connection_url, protocol:UniProto, port:int = None, extraparams:Dict[str, Callable] = {}): url_e = urlparse(connection_url) domain = None if url_e.username is not None: if url_e.username.find('\\') != -1: domain, username = url_e.username.split('\\') if domain == '.': domain = None else: domain = None username = url_e.username ip = url_e.hostname if url_e.port is None and port is None: raise Exception('Port must be provided!') if url_e.port and port is None: port = url_e.port params = dict.fromkeys(unitarget_url_params.keys(),None) extra = dict.fromkeys(extraparams.keys(),None) proxy_present = False if url_e.query is not None: query = parse_qs(url_e.query) for k in query: if k.startswith('proxy') is True: proxy_present = True if k in unitarget_url_params: params[k] = unitarget_url_params[k](query[k]) if k in extraparams: extra[k] = extraparams[k](query[k]) hostname = None if ip is not None: try: ipaddress.ip_address(ip) except: hostname = ip ip = None if params['serverip'] is not None: ip = params['serverip'] proxies = None if proxy_present is True: proxies = UniProxyTarget.from_url(connection_url, endpoint_port=port) timeout = params['timeout'] if params['timeout'] is not None else 5 sslctx = UniSSL.from_url(connection_url) return UniTarget( ip, port, protocol, timeout = timeout, hostname = hostname, ssl_ctx=sslctx, dc_ip = params['dc'], domain=domain, proxies=proxies, dns=params['dns'], use_privileged_source_port=params['privport'] ), extra def get_preproxy(self): if len(self.proxies) > 1: lproxy = self.proxies[-1] lproto=UniProto.CLIENT_TCP return UniTarget(lproxy.server_ip, lproxy.server_port, protocol = lproto, proxies=self.proxies[:-1]) return self def __str__(self): t = '==== UniTarget ====\r\n' for k in self.__dict__: t += '%s: %s\r\n' % (k, self.__dict__[k]) return tasysocks-0.2.18/asysocks/unicomm/common/transport.py000066400000000000000000000105401510034457600226520ustar00rootroot00000000000000from asyncio.transports import Transport import asyncio class UNITransport(Transport): """Base class for WSNET transport.""" def __init__(self, connection, extra=None): Transport.__init__(self) self.write_buffer_size = 65535 self.read_buffer_size = 65535 self.comms = connection self._write_buffer_limits = (0, 0) self._protocol = None self._isclosing = False def get_extra_info(self, name, default=None): """Get optional transport information.""" print('Extrainfo', name, default) return self.comms.get_extra_info(name, default) def is_closing(self): """Return True if the transport is closing or closed.""" #print('is_closing') return self._isclosing def close(self): """Close the transport. Buffered data will be flushed asynchronously. No more data will be received. After all buffered data is flushed, the protocol's connection_lost() method will (eventually) be called with None as its argument. """ #print('close') self._isclosing = True x = asyncio.create_task(self.comms.close()) def set_protocol(self, protocol): """Set a new protocol.""" #print('set_protocol', protocol) self.comms.protocol = protocol def get_protocol(self): """Return the current protocol.""" return self.comms.protocol def is_reading(self): #print('is_reading') """Return True if the transport is receiving.""" return self.comms.read_resume.is_set() def pause_reading(self): """Pause the receiving end. No data will be passed to the protocol's data_received() method until resume_reading() is called. """ #print('pause_reading') x = asyncio.create_task(self.comms.pause_reading()) def resume_reading(self): """Resume the receiving end. Data received will once again be passed to the protocol's data_received() method. """ #print('resume_reading') self.comms.read_resume.set() def set_write_buffer_limits(self, high=None, low=None): """Set the high- and low-water limits for write flow control. These two values control when to call the protocol's pause_writing() and resume_writing() methods. If specified, the low-water limit must be less than or equal to the high-water limit. Neither value can be negative. The defaults are implementation-specific. If only the high-water limit is given, the low-water limit defaults to an implementation-specific value less than or equal to the high-water limit. Setting high to zero forces low to zero as well, and causes pause_writing() to be called whenever the buffer becomes non-empty. Setting low to zero causes resume_writing() to be called only once the buffer is empty. Use of zero for either limit is generally sub-optimal as it reduces opportunities for doing I/O and computation concurrently. """ #print('set_write_buffer_limits') self._write_buffer_limits = (low, high) def get_write_buffer_size(self): #print('get_write_buffer_size') """Return the current size of the write buffer.""" return self.write_buffer_size def get_write_buffer_limits(self): #print('get_write_buffer_limits') """Get the high and low watermarks for write flow control. Return a tuple (low, high) where low and high are positive number of bytes.""" return self._write_buffer_limits def write(self, data): #print('write', data) """Write some data bytes to the transport. This does not block; it buffers the data and arranges for it to be sent out asynchronously. """ asyncio.run_coroutine_threadsafe(self.comms.write(data), asyncio.get_event_loop()) def writelines(self, list_of_data): #print('writelines', list_of_data) """Write a list (or any iterable) of data bytes to the transport. The default implementation concatenates the arguments and calls write() on the result. """ data = b''.join(list_of_data) self.write(data) def write_eof(self): #print('write_eof') """Close the write end after flushing buffered data. (This is like typing ^D into a UNIX program reading from stdin.) Data may still be received. """ self.write(b'') def can_write_eof(self): #print('can_write_eof') """Return True if this transport supports write_eof(), False if not.""" return True def abort(self): #print('abort') """Close the transport immediately. Buffered data will be lost. No more data will be received. The protocol's connection_lost() method will (eventually) be called with None as its argument. """ self.close()asysocks-0.2.18/asysocks/unicomm/common/unissl.py000066400000000000000000000150441510034457600221370ustar00rootroot00000000000000import os import ssl from urllib.parse import urlparse, parse_qs from asysocks.unicomm.utils.paramprocessor import str_one, int_one, bool_one from contextlib import contextmanager from tempfile import NamedTemporaryFile unissl_url_param2var = { 'ca' : ('cacert', [str]), 'cert' : ('certfile', [str]), 'key' : ('keyfile', [str]), 'password' : ('password', [str]), 'verify' : ('verify', [bool]), } class UniSSL: """This class was necessary to be able to create duplicates of ssl contexts, which is not possible with the ssl.SSLContext class.""" def __init__(self, certfile:str = None, keyfile:str = None, cacert:str = None, password:str = None, verify:bool = False): self.protocol = ssl.PROTOCOL_TLS self.cacert:str = cacert self.keyfile:str = keyfile self.certfile:str = certfile self.password:str = password self.verify:bool = verify self.__cacertfilename = None self.__keyfilename = None self.__certfilename = None self.__startup() def __startup(self): if self.keyfile is not None: if isinstance(self.keyfile, str): # is the keyfile a p12 or pfx file? if self.keyfile.endswith('.pfx') or self.keyfile.endswith('.p12'): self.pfx_to_pem(self.keyfile, self.password) else: #is the keyfile an actual file? try: with open(self.keyfile, 'rb') as f: pass self.__keyfilename = self.keyfile except: # is the keyfile a string? self.__keyfilename = 'key_%s.pem' % os.urandom(4).hex() with open(self.__keyfilename, 'w', newline='') as f: f.write(self.keyfile) else: # is the keyfile a bytes object? self.__keyfilename = 'key_%s.pem' % os.urandom(4).hex() with open(self.__keyfilename, 'wb') as f: f.write(self.keyfile) if self.certfile is not None: if isinstance(self.certfile, str): # is the certfile a p12 or pfx file? if self.certfile.endswith('.pfx') or self.certfile.endswith('.p12'): self.pfx_to_pem(self.certfile, self.password) else: #is the certfile an actual file? try: with open(self.certfile, 'rb') as f: pass self.__certfilename = self.certfile except: # is the certfile a string? self.__certfilename = 'cert_%s.pem' % os.urandom(4).hex() with open(self.__certfilename, 'w', newline='') as f: f.write(self.certfile) else: # is the certfile a bytes object? self.__certfilename = 'key_%s.pem' % os.urandom(4).hex() with open(self.__certfilename, 'wb') as f: f.write(self.certfile) if self.cacert is not None: if isinstance(self.cacert, str): try: with open(self.cacert, 'rb') as f: pass self.__cacertfilename = self.cacert except: self.__cacertfilename = 'cacert_%s.pem' % os.urandom(4).hex() with open(self.__cacertfilename, 'w', newline='') as f: f.write(self.cacert) else: self.__cacertfilename = 'cacert_%s.pem' % os.urandom(4).hex() with open(self.__cacertfilename, 'wb') as f: f.write(self.cacert) def pfx_to_pem(self, pfx_path, pfx_password): #https://gist.github.com/erikbern/756b1d8df2d1487497d29b90e81f8068 from pathlib import Path from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption from cryptography.hazmat.primitives.serialization.pkcs12 import load_key_and_certificates ''' Decrypts the .pfx file to be used with requests. ''' pfx = Path(pfx_path).read_bytes() if isinstance(pfx_password, str): pfx_password = pfx_password.encode('utf-8') private_key, main_cert, add_certs = load_key_and_certificates(pfx, pfx_password, None) suffix = '%s.pem' % os.urandom(4).hex() self.__keyfilename = 'key_%s' % suffix self.__certfilename = 'cert_%s' % suffix with open(self.__keyfilename, 'wb') as f: f.write(private_key.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption())) with open(self.__certfilename, 'wb') as f: f.write(main_cert.public_bytes(Encoding.PEM)) if len(add_certs) > 0: self.__cacertfilename = 'cacert_%s' % suffix with open(self.__cacertfilename, 'wb') as f: for ca in add_certs: f.write(ca.public_bytes(Encoding.PEM)) @staticmethod def get_noverify_context(is_server=False, hostname = 'localhost'): """Returns a generic SSL context that does not verify the certificate.""" if is_server is True: from asysocks.unicomm.utils.genselfsigned import generate_selfsigned_cert server_certfile, server_keyfile = generate_selfsigned_cert(hostname) return UniSSL(server_certfile, server_keyfile, None, verify=False) return UniSSL(verify=False) def get_ssl_context(self, protocol = ssl.PROTOCOL_TLS_CLIENT): try: self.__startup() ssl_ctx = ssl.SSLContext(protocol) if self.__certfilename is not None: ssl_ctx.load_cert_chain(certfile=self.__certfilename, keyfile=self.__keyfilename, password=self.password) if self.verify is False: ssl_ctx.check_hostname = False ssl_ctx.verify_mode = ssl.CERT_NONE else: if self.cacert is not None: ssl_ctx.load_verify_locations(cafile=self.__cacertfilename) else: ssl_ctx.load_default_certs(purpose=ssl.Purpose.SERVER_AUTH) return ssl_ctx finally: self.__cleanup() @staticmethod def from_url(url): url_e = urlparse(url) params = {} if url_e.query is not None: query = parse_qs(url_e.query) for k in query: if k.startswith('ssl') is False: continue params[k] = query[k] return UniSSL.from_urlparams(params) @staticmethod def from_urlparams(params): finalparams = {} for k in params: if k.startswith('ssl_') is True: pname = k[4:] elif k.startswith('ssl') is True: pname = k[3:] else: continue if pname in unissl_url_param2var: finalparams[unissl_url_param2var[pname][0]] = unissl_url_param2var[pname][1][0](params[k][0]) if len(finalparams) == 0: return None return UniSSL(**finalparams) def __cleanup(self): if self.__certfilename != self.certfile: try: os.remove(self.__certfilename) except: pass if self.__keyfilename != self.keyfile: try: os.remove(self.__keyfilename) except: pass if self.__cacertfilename != self.cacert: try: os.remove(self.__cacertfilename) except: pass def __str__(self): return 'UniSSL(certfile=%s, keyfile=%s, cacert=%s, verify=%s, protocol=%s, password=%s)' % (self.certfile, self.keyfile, self.cacert, self.verify, self.protocol, self.password) def __del__(self): self.__cleanup() def main(): url = 'https://www.google.com/?ssl_cert=cert.pem&ssl_key=key.pem&ssl_cacert=cacert.pem&ssl_verify=False' ssl = UniSSL.from_url(url) print(ssl) if __name__ == '__main__': main() asysocks-0.2.18/asysocks/unicomm/protocol/000077500000000000000000000000001510034457600206155ustar00rootroot00000000000000asysocks-0.2.18/asysocks/unicomm/protocol/__init__.py000066400000000000000000000000001510034457600227140ustar00rootroot00000000000000asysocks-0.2.18/asysocks/unicomm/protocol/client/000077500000000000000000000000001510034457600220735ustar00rootroot00000000000000asysocks-0.2.18/asysocks/unicomm/protocol/client/__init__.py000066400000000000000000000001251510034457600242020ustar00rootroot00000000000000import logging logger = logging.getLogger('asysocks.client') logger.propagate = Trueasysocks-0.2.18/asysocks/unicomm/protocol/client/http/000077500000000000000000000000001510034457600230525ustar00rootroot00000000000000asysocks-0.2.18/asysocks/unicomm/protocol/client/http/__init__.py000066400000000000000000000001321510034457600251570ustar00rootroot00000000000000import logging logger = logging.getLogger('asysocks.client.http') logger.propagate = Trueasysocks-0.2.18/asysocks/unicomm/protocol/client/http/auth.py000066400000000000000000000176011510034457600243720ustar00rootroot00000000000000import base64 import copy from hashlib import sha256 from asysocks.unicomm.protocol.client.http import logger from asysocks.unicomm.common.target import UniProto from asysocks.unicomm.protocol.client.http.requestmgr import RequestManager from asyauth.common.winapi.constants import ISC_REQ from asyauth.common.credentials.ntlm import NTLMCredential from asyauth.common.credentials.kerberos import KerberosCredential from asyauth.common.credentials.spnego import SPNEGOCredential from asyauth.common.credentials.credssp import CREDSSPCredential from asyauth.common.credentials import UniCredential class HTTPAuthManager: def __init__(self, session, credential): self.session = session self.status = 'start' self.authobj = credential.build_context() self.credential = credential async def authenticate(self, requestmanager): pass @staticmethod def from_credential(session, credential, auth_type:str = 'auto'): auth_type = auth_type.lower() if isinstance(credential, (UniCredential)): credential = credential.build_context() if auth_type == 'auto': if isinstance(credential, (SPNEGOCredential)) is True: return HTTPAuthManagerNegotiate(session, credential) elif isinstance(credential, (NTLMCredential, KerberosCredential)) is True: credential = SPNEGOCredential([credential]) return HTTPAuthManagerNegotiate(session, credential) else: raise Exception('No supported auth type found!') elif auth_type == 'spnego': if isinstance(credential, (SPNEGOCredential)) is True: return HTTPAuthManagerNegotiate(session, credential) elif isinstance(credential, (NTLMCredential, KerberosCredential)) is True: credential = SPNEGOCredential([credential]) return HTTPAuthManagerNegotiate(session, credential) elif hasattr(credential, 'credential') is True and isinstance(credential.credential, (NTLMCredential, KerberosCredential)) is True: credential = SPNEGOCredential([copy.deepcopy(credential.credential)]) return HTTPAuthManagerNegotiate(session, credential) else: raise Exception('Credential type %s cannot be used for SPNEGO authentication!' % credential.__class__.__name__) elif auth_type == 'credssp': if isinstance(credential, (CREDSSPCredential)) is True: return HTTPAuthManagerCredSSP(session, credential) elif isinstance(credential, (NTLMCredential, KerberosCredential)) is True: credential = CREDSSPCredential([credential]) return HTTPAuthManagerCredSSP(session, credential) elif hasattr(credential, 'credential') is True and isinstance(credential.credential, (NTLMCredential, KerberosCredential)) is True: credential = CREDSSPCredential([copy.deepcopy(credential.credential)]) return HTTPAuthManagerNegotiate(session, credential) else: raise Exception('Credential type %s cannot be used for CREDSSP authentication!' % credential.__class__.__name__) else: raise Exception('Unsupported auth type! Authtype: %s Credential: %s' % (auth_type, credential.__class__.__name__)) class HTTPAuthManagerCredSSP(HTTPAuthManager): def __init__(self, session, credential:CREDSSPCredential): HTTPAuthManager.__init__(self, session, credential) self.auth_steps = False self.cb_data = None async def authenticate(self, requestmanager): self.status = 'progress' authreply = None domain = 'UNKNOWN' for cred in self.credential.credentials: domain = cred.domain break while True: flags = ISC_REQ.CONNECTION|ISC_REQ.CONFIDENTIALITY|ISC_REQ.INTEGRITY target_spn = 'HTTP/%s@%s' % (requestmanager.transport.target.get_hostname_or_ip(), domain) authdata, to_continue, err = await self.authobj.authenticate(authreply, spn=target_spn, flags=flags) #, cb_data = self.cb_data if err is not None: raise err if to_continue is False and response.status == 200: return response auth_header = 'CredSSP '+ base64.b64encode(authdata).decode() newheaders = [] newheaders.append(('Authorization', auth_header)) for entry in requestmanager.headers: newheaders.append(entry) async with RequestManager(self.session, requestmanager.url, requestmanager.req_type, headers=newheaders, data=requestmanager.data, need_length=requestmanager.need_length, transport=requestmanager.transport) as response: if to_continue is False and response.status != 200: raise Exception('Authentication failed! %s' % response.status) await response.read() #consuming all data is mandatory here! if response.transport.connection is not None and response.transport.can_reuse is True: self.transport = response.transport response_auth_header = response.getheaders('www-authenticate') if response_auth_header is None or len(response_auth_header) == 0: if to_continue is False and response.status == 200: return response raise Exception('Authentication failed! No www-authenticate header found!') for rauthh in response_auth_header: if rauthh.startswith('CredSSP'): response_auth_header = rauthh break else: if to_continue is False and response.status == 200: return response raise Exception('Authentication failed! No CredSSP header found!') response_header_data_b64 = response_auth_header.replace('CredSSP ', '').strip().encode() if response_header_data_b64 == b'': if to_continue is False and response.status == 200: return response raise Exception('Authentication failed! Empty CredSSP header found!') authreply = base64.b64decode(response_header_data_b64) if to_continue is False and response.status == 200: return response class HTTPAuthManagerNegotiate(HTTPAuthManager): def __init__(self, session, credential:SPNEGOCredential): HTTPAuthManager.__init__(self, session, credential) self.auth_steps = False self.cb_data = None async def authenticate(self, requestmanager): self.status = 'progress' authreply = None domain = 'UNKNOWN' for cred in self.credential.credentials: domain = cred.domain break while True: flags = ISC_REQ.CONNECTION|ISC_REQ.CONFIDENTIALITY|ISC_REQ.INTEGRITY|ISC_REQ.USE_DCE_STYLE|ISC_REQ.MUTUAL_AUTH if requestmanager.transport.target.protocol == UniProto.CLIENT_SSL_TCP: if self.cb_data is None: certdata = requestmanager.transport.connection.get_peer_certificate() if certdata is not None: self.cb_data = b'tls-server-end-point:' + sha256(certdata).digest() # flags = ISC_REQ.CONNECTION target_spn = 'HTTP/%s@%s' % (requestmanager.transport.target.get_hostname_or_ip(), domain) authdata, to_continue, err = await self.authobj.authenticate(authreply, spn=target_spn, flags=flags, cb_data = self.cb_data) #, if err is not None: raise err if to_continue is False and response.status == 200: logger.debug('Authentication successful!') return response auth_header = 'Negotiate '+ base64.b64encode(authdata).decode() newheaders = [] newheaders.append(('Authorization', auth_header)) for entry in requestmanager.headers: newheaders.append(entry) logger.debug('Updated Headers: %s' % newheaders) async with RequestManager(self.session, requestmanager.url, requestmanager.req_type, headers=newheaders, data=requestmanager.data, need_length=requestmanager.need_length, transport=requestmanager.transport) as response: logger.debug('Response Status: %s' % response.status ) logger.debug('Response Headers: %s' % response.headers) if to_continue is False and response.status != 200: raise Exception('Authentication failed! %s' % response.status) await response.read() #consuming all data is mandatory here! if response.transport.connection is not None and response.transport.can_reuse is True: self.transport = response.transport authreply = response.getheaders('www-authenticate')[0].replace('Negotiate ', '').strip().encode() authreply = base64.b64decode(authreply) if to_continue is False and response.status == 200: logger.debug('Authentication successful!') return response asysocks-0.2.18/asysocks/unicomm/protocol/client/http/client.py000066400000000000000000000123571510034457600247120ustar00rootroot00000000000000 import re import copy import asyncio import http.cookiejar from urllib.parse import urlparse, urljoin from typing import List, Tuple, Any, Dict from asyauth.common.credentials import UniCredential from asysocks.unicomm.protocol.client.http.requestmgr import RequestManager from asysocks.unicomm.protocol.client.http.auth import HTTPAuthManager from asysocks.unicomm.protocol.client.http.commons.messages import MockHTTPRequest, HTTPResponse from asysocks.unicomm.protocol.client.http.commons.factory import HTTPConnectionFactory from asysocks.unicomm.protocol.client.http.transport import HTTPClientTransport from asysocks.unicomm.common.proxy import UniProxyTarget http_url_pattern = re.compile(r'^https?:\/\/') class ClientSession: def __init__(self, url:str=None, static_headers:Dict[str, str]={}, credential:UniCredential=None, proxies:List[UniProxyTarget] = None, auth_type='auto', ssl_ctx=None, force_sinle_connection:bool=False): self.url = url self.cookiejar:http.cookiejar.CookieJar = http.cookiejar.CookieJar() self.factory:HTTPConnectionFactory = None self.authmanager:HTTPAuthManager = None self.ssl_ctx = ssl_ctx self.static_headers = static_headers self.force_sinle_connection = force_sinle_connection self.auth_type = auth_type self.proxies = proxies if self.url is not None: self.factory = HTTPConnectionFactory.from_url(url, self.proxies) if credential is not None: self.authmanager = HTTPAuthManager.from_credential(self, credential, auth_type=self.auth_type) self.connections = [] self.session_closed_evt = asyncio.Event() async def __aenter__(self): return self async def __aexit__(self, exc_type, exc, tb): await self.close() async def close(self): if self.session_closed_evt.is_set() is True: return self.session_closed_evt.set() for conn in self.connections: await conn.disconnect() self.connections = [] def process_url(self, url:str): if bool(http_url_pattern.match(url)) is True: parsed_url = urlparse(url) #if self.url is not None: # temp = f"{parsed_url.scheme}://{parsed_url.netloc}/" # if temp != self.url: # raise Exception('URL was already specified!') self.url = url self.factory = HTTPConnectionFactory.from_url(url, self.proxies) if self.factory.get_credential() is not None: self.authmanager = HTTPAuthManager.from_credential(self, self.factory.get_credential(), self.auth_type) target = parsed_url.path if target == '': target = '/' if target.startswith('/') is False: target = '/' + target if parsed_url.query != '': target += parsed_url.query else: target = url if target == '': target = '/' if target.startswith('/') is False: target = '/' + target original_url = urljoin(self.url, target) return target, original_url async def get_transport(self) -> HTTPClientTransport: if self.factory is None: raise Exception('No URL was specified!') if self.force_sinle_connection is True and len(self.connections) > 0: return self.connections[0] conn = HTTPClientTransport(self.factory.get_target()) await conn.connect() self.connections.append(conn) return conn def apply_cookies(self, url:str, headers:List[Tuple[str,str]]) -> List[Tuple[str,str]]: req = MockHTTPRequest(url) self.cookiejar.add_cookie_header(req) for entry in req._headers: headers.append((entry, req._headers[entry])) return headers def apply_headers(self, url, headers:List[Tuple[str,str]]) -> List[Tuple[str,str]]: final_headers = copy.deepcopy(headers) entry_exists = lambda entry, lst: any(x[0] == entry for x in lst) for entry in self.static_headers: if not entry_exists(entry, final_headers): final_headers.append((entry, self.static_headers[entry])) self.apply_cookies(url, final_headers) return final_headers def get(self, url: str, *, allow_redirects: bool = True, headers=[], **kwargs: Any) -> RequestManager: return RequestManager(self, url, 'GET', allow_redirects=allow_redirects, headers=headers, **kwargs) def post(self, url: str, *, data: bytes = None, allow_redirects: bool = True, headers=[], **kwargs: Any) -> RequestManager: return RequestManager(self, url, 'POST', data=data, allow_redirects=allow_redirects, headers=headers, need_length=True, **kwargs) async def amain(): #target = UniTarget('www.google.com', 443, protocol=UniProto.CLIENT_SSL_TCP) #conn = HTTPClientTransport(target) #await conn.connect() #resp = await conn.get(headers=[('Connection', 'keep-alive')]) #print(resp.headers) #await asyncio.sleep(10) #x = await resp.text() #await asyncio.sleep(100) async with ClientSession() as session: async with session.get('http://www.google.com/') as resp: print(resp.status) print(await resp.text()) async with session.get('http://www.google.com/') as resp: print(resp.status) #print(await resp.text()) #async with ClientSession() as session: # resp = await session.get('http://www.google.com/') # print(resp.status) # print(await resp.text()) #session = ClientSession('http://www.google.com/') #resp = await session.get('/') #print(resp.headers) ##print(await resp.text()) #print() #print(session.cookiejar) #resp = await session.get('/') #print(resp.headers) ##print(await resp.text()) def main(): asyncio.run(amain()) if __name__ == '__main__': main() asysocks-0.2.18/asysocks/unicomm/protocol/client/http/commons/000077500000000000000000000000001510034457600245255ustar00rootroot00000000000000asysocks-0.2.18/asysocks/unicomm/protocol/client/http/commons/__init__.py000066400000000000000000000000001510034457600266240ustar00rootroot00000000000000asysocks-0.2.18/asysocks/unicomm/protocol/client/http/commons/factory.py000066400000000000000000000025421510034457600265510ustar00rootroot00000000000000 import copy import enum from asyauth.common.credentials import UniCredential from asysocks.unicomm.protocol.client.http.commons.target import HTTPTarget class HTTPConnectionFactory: def __init__(self, credential:UniCredential = None, target:HTTPTarget = None ): self.credential = credential self.target = target @staticmethod def from_url(connection_url, proxies = None): target = HTTPTarget.from_url(connection_url) credential = UniCredential.from_url(connection_url) if credential.secret is None: credential = None if target.domain is None and credential is not None: target.domain = credential.domain if proxies is not None: target.proxies = proxies return HTTPConnectionFactory(credential, target) def get_credential(self) -> UniCredential: """ Creates a credential object :return: Credential object :rtype: :class:`UniCredential` """ return copy.deepcopy(self.credential) def get_target(self) -> HTTPTarget: """ Creates a target object :return: Target object :rtype: :class:`HTTPTarget` """ return copy.deepcopy(self.target) def __str__(self): t = '==== HTTPConnectionFactory ====\r\n' for k in self.__dict__: val = self.__dict__[k] if isinstance(val, enum.IntFlag): val = val elif isinstance(val, enum.Enum): val = val.name t += '%s: %s\r\n' % (k, str(val)) return tasysocks-0.2.18/asysocks/unicomm/protocol/client/http/commons/messages.py000066400000000000000000000067471510034457600267240ustar00rootroot00000000000000import h11 import json import codecs from typing import List, Tuple, Any, Union, Dict def is_supported_encoding(enc): try: codecs.lookup(enc) return True except LookupError: return False class MockHTTPRequest: """Do NOT use this class directly! It is only used to make the code compatible with python's cookiejar""" def __init__(self, url): self._url = url self._headers = {} def get_header(self, name, default=None): return self._headers.get(name, default) def get_type(self): return self._url.split(':')[0] def get_host(self): return self._url.split('/')[2] def get_full_url(self): return self._url def get_origin_req_host(self, origin=False): return self.get_host() @property def unverifiable(self): return False @property def type(self): return self.get_type() def has_header(self, name): return self.get_header(name, False) def add_unredirected_header(self, name, value): self._headers[name] = value class HTTPResponse: def __init__(self): self.status:int = None self.http_version:str = None self.reason:str = None self.headers:Dict[str, List[str]] = {} self.h11:h11.Response = None self.data_iter = None self.transport = None self.url = None self.__all_data_consumed = False async def __aenter__(self): return self async def __aexit__(self, exc_type, exc, tb): await self.cleanup() async def cleanup(self): if self.data_iter is not None and self.__all_data_consumed is False: async for event in self.data_iter(): if type(event) is h11.EndOfMessage: break if type(event) is h11.ConnectionClosed: break continue if self.transport is not None: await self.transport.disconnect_if_needed() @staticmethod def from_h11_header(rh:h11.Response, data_iter, transport, fullurl): resp = HTTPResponse() resp.url = fullurl resp.h11 = rh resp.status = rh.status_code resp.http_version = rh.http_version.decode() resp.reason = rh.reason.decode() resp.headers = {} resp.transport = transport for entry in rh.headers: name = entry[0].decode() value = entry[1].decode() if name == 'set-cookie': name = 'Set-Cookie' if name not in resp.headers: resp.headers[name] = [] resp.headers[name].append(value) resp.data_iter = data_iter return resp def info(self): return self def getheaders(self, name): return self.headers.get(name) def get_all(self, name, default): return self.headers.get(name, default) async def stream_data(self): async for event in self.data_iter(): if type(event) is h11.Data: yield event.data if type(event) is h11.EndOfMessage: self.__all_data_consumed = True break async def read(self): data = b'' async for chunk in self.stream_data(): data += chunk return data async def text(self): codec = 'utf-8' if 'content-type' in self.headers: for entry in self.headers['content-type']: if 'charset=' in entry: codec = entry.split('charset=')[1] break if is_supported_encoding(codec) is False: codec = 'utf-8' data = b'' async for chunk in self.stream_data(): data += chunk return data.decode(codec) async def json(self): return json.loads(await self.text()) def __str__(self): t = 'HTTPResponse\r\n' t += 'HTTP/%s %s %s\r\n' % (self.http_version, self.status, self.reason) for k in self.headers: if isinstance(self.headers[k], list): for v in self.headers[k]: t += '%s: %s\r\n' % (k, v) else: t += '%s: %s\r\n' % (k, self.headers[k]) return tasysocks-0.2.18/asysocks/unicomm/protocol/client/http/commons/target.py000066400000000000000000000113651510034457600263730ustar00rootroot00000000000000from os import stat from sqlite3 import connect from asysocks.unicomm.common.target import UniTarget, UniProto, unitarget_url_params from urllib.parse import urlparse, parse_qs, urljoin, urlencode, urlunparse from asysocks.unicomm.utils.paramprocessor import str_one, int_one, bool_one class HTTPTarget(UniTarget): def __init__(self, ip, port = 80, protocol = UniProto.CLIENT_TCP, path = None, query = None, proxies = None, timeout = 10, dns:str=None, dc_ip:str = None, domain:str = None, hostname:str = None, ssl_ctx = None): UniTarget.__init__(self, ip, port, protocol, timeout, hostname = hostname, ssl_ctx= ssl_ctx, proxies = proxies, domain = domain, dc_ip = dc_ip, dns=dns) self.path = path self.query = query def to_target_string(self): return 'HTTP/%s@%s' % (self.get_hostname_or_ip(), self.domain) #HTTP/WIN2019AD.test.corp @ TEST.CORP def get_host(self): if self.protocol == UniProto.CLIENT_SSL_TCP: proto = 'https' elif self.protocol == UniProto.CLIENT_TCP: proto = 'http' if proto == 'http' and self.port == 80: return '%s://%s' % (proto, self.get_hostname_or_ip()) elif proto == 'https' and self.port == 443: return '%s://%s' % (proto, self.get_hostname_or_ip()) return '%s://%s:%s' % (proto, self.get_hostname_or_ip(), self.port) def get_url(self): url = urljoin(self.get_host(), self.path) if self.query is not None and len(self.query) > 0: parsed_url = urlparse(url) new_parsed_url = parsed_url._replace(query=self.query) url = urlunparse(new_parsed_url) return url def is_ssl(self): return self.protocol == UniProto.CLIENT_SSL_TCP @staticmethod def from_url(connection_url): url_e = urlparse(connection_url) schemes = [] for item in url_e.scheme.upper().split('+'): schemes.append(item.replace('-','_')) if schemes[0] == 'HTTP': protocol = UniProto.CLIENT_TCP port = 80 elif schemes[0] == 'HTTPS': protocol = UniProto.CLIENT_SSL_TCP port = 443 else: raise Exception('Unknown protocol! %s' % schemes[0]) if url_e.port: port = url_e.port if port is None: raise Exception('Port must be provided!') path = None if url_e.path not in ['/', '', None]: path = url_e.path unitarget, extraparams = UniTarget.from_url(connection_url, protocol, port, {}) #removing query params params_to_remove = {'dc': 1, 'dns': 1, 'dcip': 1} params_to_remove.update(unitarget_url_params) params_to_remove.update(extraparams) parsed_query = parse_qs(url_e.query) for param in parsed_query: if param.startswith('proxy') is True: params_to_remove[param] = 1 for param in params_to_remove: parsed_query.pop(param, None) new_query = urlencode(parsed_query, doseq=True) target = HTTPTarget( unitarget.ip, port = unitarget.port, protocol = unitarget.protocol, path = path, query=new_query, proxies = unitarget.proxies, timeout = unitarget.timeout, dns = unitarget.dns, dc_ip = unitarget.dc_ip, domain = unitarget.domain, hostname = unitarget.hostname, ssl_ctx = unitarget.ssl_ctx, ) return target def __str__(self): t = '==== HTTPTarget ====\r\n' for k in self.__dict__: t += '%s: %s\r\n' % (k, self.__dict__[k]) return t if __name__ == '__main__': test = [ #please create random URLs for testing 'http://test.corp:80', 'https://test.corp:80', 'http://test.corp:443', 'https://test.corp:443', 'http://test.corp:80/', 'https://test.corp:80/', 'http://test.corp:443/', 'https://test.corp:443/', 'http://test.corp:80/path', 'https://test.corp:80/path', 'http://test.corp:443/path', 'https://test.corp:443/path', 'http://test.corp:80/path?query', 'https://test.corp:80/path?query', 'http://test.corp:443/path?query', 'https://test.corp:443/path?query', 'http://test.corp:80/path?query=1', 'https://test.corp:80/path?query=1', 'http://test.corp:443/path?query=1', 'https://test.corp:443/path?query=1', 'http://test.corp:80/path?query=1&query2=2', 'https://test.corp:80/path?query=1&query2=2', 'http://test.corp:443/path?query=1&query2=2', 'https://test.corp:443/path?query=1&query2=2', 'http://test.corp:80/path?query=1&query2=2&query3=3', 'https://test.corp:80/path?query=1&query2=2&query3=3', 'http://test.corp:443/path?query=1&query2=2&query3=3', 'https://test.corp:443/path?query=1&query2=2&query3=3', 'http://test.corp:80/path?query=1&query2=2&query3=3&query4=4', 'https://test.corp:80/path?query=1&query2=2&query3=3&query4=4', 'http+ntlm-password://test.corp:443/path?query=1&query2=2&query3=3&query4', 'http+ntlm-password://test.corp:443/path?query=1&query2=2&query3=3&proxy=1' ] for url in test: res = HTTPTarget.from_url(url) print(res) print('Original: %s' % url) print('Reconstr: %s' % res.get_url()) print(res.get_url() == url) print('------------------')asysocks-0.2.18/asysocks/unicomm/protocol/client/http/requestmgr.py000066400000000000000000000026631510034457600256310ustar00rootroot00000000000000 from typing import List, Tuple, Any, Dict from asysocks.unicomm.protocol.client.http.commons.messages import MockHTTPRequest, HTTPResponse from asysocks.unicomm.protocol.client.http.transport import HTTPClientTransport class RequestManager: def __init__(self, session, url:str, req_type:str, headers:List[Tuple[str,str]] = [], data:bytes=None, need_length:bool=False, transport:HTTPClientTransport = None, **kwargs): self.session = session self.url = url self.req_type = req_type self.headers = headers self.data = data self.need_length = need_length self.transport = transport self.__response = None async def __aenter__(self) -> HTTPResponse: try: target, fullurl = self.session.process_url(self.url) if self.transport is None: self.transport = await self.session.get_transport() if self.session.authmanager is not None and self.session.authmanager.status == 'start': return await self.session.authmanager.authenticate(self) else: headers = self.session.apply_headers(fullurl, self.headers) self.__response = await self.transport.request(fullurl, self.req_type, target, headers=headers, data=self.data, need_length=self.need_length) self.session.cookiejar.extract_cookies(self.__response, MockHTTPRequest(fullurl)) return self.__response except Exception as e: raise e async def __aexit__(self, exc_type, exc, tb): if self.__response is not None: await self.__response.cleanup()asysocks-0.2.18/asysocks/unicomm/protocol/client/http/transport.py000066400000000000000000000107151510034457600254640ustar00rootroot00000000000000import h11 import asyncio import traceback from typing import List, Tuple, Any, Union, Dict from asysocks.unicomm.client import UniClient from asysocks.unicomm.common.target import UniTarget, UniProto from asysocks.unicomm.common.packetizers import Packetizer from asysocks.unicomm.protocol.client.http.commons.messages import HTTPResponse from asysocks.unicomm.protocol.client.http.commons.target import HTTPTarget class HTTPClientTransport: def __init__(self, target:HTTPTarget, request_connection_type='keep-alive'): self.target = target self.connection_closed_evt = None self.connection = None self.httpconn = None self.request_connection_type = request_connection_type self.response_connection_type = request_connection_type self.__disconnect_requested = False async def __next_event(self): try: while self.connection_closed_evt.is_set() is False: event = self.httpconn.next_event() if event is h11.NEED_DATA: data = await self.connection.read_one() self.httpconn.receive_data(data) if event is h11.ConnectionClosed: await self.disconnect() break yield event await self.disconnect_if_needed() #if self.__disconnect_requested is True: # await self.disconnect_if_needed() except Exception as e: await self.disconnect() traceback.print_exc() async def __send(self, event): data = self.httpconn.send(event) if type(data) is h11.ConnectionClosed: self.__disconnect_requested = True return if data == b'': # the client requests the connection to be closed, but we must wait for the server to send the response # this is done correctly in asyncio but the proxies are not asyncio friendly return #print('write: %s' % data) await self.connection.write(data) @property def supports_keepalive(self): return self.response_connection_type == self.request_connection_type == 'keep-alive' @property def can_reuse(self): return self.supports_keepalive is True and self.connection_closed_evt.is_set() is False async def connect(self): self.httpconn = h11.Connection(our_role=h11.CLIENT) self.connection_closed_evt = asyncio.Event() packetizer = Packetizer() client = UniClient(self.target, packetizer) self.connection = await client.connect() async def disconnect_if_needed(self): if self.can_reuse is False: await self.disconnect() async def disconnect(self): self.__disconnect_requested = True self.connection_closed_evt.set() if self.connection is not None: await self.connection.close() def __correct_headers(self, headers:List[Tuple[str,str]], data:bytes=b'', need_length:bool=False): has_host = False has_connection = False has_length = False for entry in headers: if entry[0].lower() == 'host': has_host = True elif entry[0].lower() == 'connection': has_connection = True self.request_connection_type = entry[1].lower() elif entry[0].lower() == 'content-length': has_length = True if has_host is False: headers.append(('Host', self.target.get_hostname_or_ip())) if has_connection is False: headers.append(("Connection", self.request_connection_type)) if need_length is True and has_length is False: if data is None: data = b'' headers.append(("Content-Length", str(len(data)))) return headers async def __read_header(self, fullurl): async for event in self.__next_event(): if type(event) is h11.Response: return HTTPResponse.from_h11_header(event, self.__next_event, self, fullurl) if type(event) is h11.EndOfMessage: raise Exception('Response ended without server sending headers!') if type(event) is h11.ConnectionClosed: raise Exception('Server terminated the connection!') async def request(self, fullurl, req_type:str, target:str = '/', headers:List[Tuple[str,str]] = [], data:bytes=None, need_length:bool=False): if self.httpconn.states[h11.CLIENT] == self.httpconn.states[h11.SERVER] == h11.DONE: self.httpconn.start_next_cycle() request_header = self.__correct_headers(headers, data, need_length) request_event = h11.Request( method=req_type, target=target, headers=request_header, ) await self.__send(request_event) if data is not None: if type(data) is str: data = data.encode() await self.__send(h11.Data(data=data)) await self.__send(h11.EndOfMessage()) response = await self.__read_header(fullurl) for header in response.headers: if header.lower() == 'connection': self.response_connection_type = response.headers[header][0].lower() return responseasysocks-0.2.18/asysocks/unicomm/protocol/http.py000066400000000000000000000054351510034457600221550ustar00rootroot00000000000000import asyncio import io class HTTPProxyAuthRequiredException(Exception): pass class HTTPProxyAuthFailed(Exception): pass class HTTPResponse: def __init__(self): self.version = None self.status = None self.reason = None self.headers = {} self.headers_upper = {} self.data = None @staticmethod async def from_streamreader(reader, timeout = None): try: resp = HTTPResponse() #reading headers temp = await asyncio.wait_for(reader.readuntil(b'\r\n\r\n'), timeout = timeout) temp = temp.split(b'\r\n')[:-1] version, status, reason = temp[0].split(b' ', 2) resp.version = version.decode() resp.status = int(status.decode()) resp.reason = reason.decode() for hdr_raw in temp[1:]: if hdr_raw.strip() == b'': continue key_raw, value_raw = hdr_raw.split(b': ', 1) key = key_raw.decode() value = value_raw.strip().decode() resp.headers[key] = value resp.headers_upper[key.upper()] = value if 'CONTENT-LENGTH' in resp.headers_upper: rem_len = int(resp.headers_upper['CONTENT-LENGTH']) resp.data = await asyncio.wait_for(reader.readexactly(rem_len), timeout = timeout) return resp, None except Exception as e: return None, e class HTTPRequest: def __init__(self): self.method = None self.uri = None self.version = None self.headers = {} self.headers_upper = {} self.data = None def __str__(self): t = '%s %s %s\r\n' % (self.method, self.uri, self.version) for x in self.headers: t += '%s: %s\r\n' % (x, self.headers[x]) t += '\r\n' if self.data is not None: t += '' return t def to_bytes(self): t = '%s %s %s\r\n' % (self.method, self.uri, self.version) for x in self.headers: t += '%s: %s\r\n' % (x, self.headers[x]) t += '\r\n' t = t.encode() if self.data is not None: t += self.data return t @staticmethod async def from_streamreader(reader, timeout = None, pre_data = None): try: req = HTTPRequest() #reading headers temp = await asyncio.wait_for(reader.readuntil(b'\r\n\r\n'), timeout = timeout) if pre_data is not None: temp = pre_data + temp temp = temp.split(b'\r\n')[:-1] method, uri, version = temp[0].split(b' ', 2) req.method = method.decode() req.uri = uri.decode() req.version = version.decode() for hdr_raw in temp[1:]: if hdr_raw.strip() == b'': continue key_raw, value_raw = hdr_raw.split(b': ', 1) key = key_raw.decode() value = value_raw.strip().decode() req.headers[key] = value req.headers_upper[key.upper()] = value if 'CONTENT-LENGTH' in req.headers_upper: rem_len = int(req.headers_upper['CONTENT-LENGTH']) req.data = await asyncio.wait_for(reader.readexactly(rem_len), timeout = timeout) return req, None except Exception as e: return None, easysocks-0.2.18/asysocks/unicomm/protocol/quic.py000066400000000000000000000061761510034457600221420ustar00rootroot00000000000000import enum import asyncio import ssl from asysocks.unicomm import logger from aioquic.asyncio.client import connect from aioquic.asyncio.protocol import QuicConnectionProtocol from aioquic.quic.configuration import QuicConfiguration from aioquic.quic.events import ConnectionTerminated, QuicEvent, StreamDataReceived from typing import Optional, cast class QuicTransportClient(QuicConnectionProtocol): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._ack_waiter: Optional[asyncio.Future[None]] = None self.in_queue: asyncio.Queue = None self.disconnected_evt = None self.stream_id = None def connection_made(self, transport: asyncio.BaseTransport) -> None: print('Connected!') return super().connection_made(transport) async def send(self, data:bytes) -> None: if self.stream_id is None: self.stream_id = self._quic.get_next_available_stream_id() #logger.debug(f"Stream ID: {self.stream_id}") #print('Sending -> %s' % data) end_stream = False self._quic.send_stream_data(self.stream_id, data, end_stream) self.transmit() def quic_event_received(self, event: QuicEvent) -> None: if isinstance(event, StreamDataReceived): self.in_queue.put_nowait( (event.data, None) ) if isinstance(event, ConnectionTerminated): self.in_queue.put_nowait( (None, None) ) class QUICSocket: """ Generic asynchronous TCP socket class, nothing SMB related. Creates the connection and channels incoming/outgoing bytes via asynchonous queues. """ def __init__(self, socket = None, target = None): self.settings = target self.socket = socket #for future, if we want a custom soscket self.out_queue = asyncio.Queue() self.in_queue = asyncio.Queue() self.disconnected_evt = asyncio.Event() async def disconnect(self): """ Disconnects from the socket. Stops the reader and writer streams. """ if self.disconnected.is_set(): return self.disconnected.set() async def run_inner(self): try: async with connect(self.settings.hostname, self.settings.port, configuration=self.configuration, create_protocol=QuicTransportClient) as client: client = cast(QuicTransportClient, client) client.in_queue = self.in_queue client.disconnected_evt = self.disconnected_evt while not self.disconnected.is_set(): data = await self.out_queue.get() await client.send(data) except asyncio.CancelledError: #the SMB connection is terminating return except Exception as e: logger.exception('[QUICSocket] handle_outgoing') await self.disconnect() #async def connect(self, settings): #not needed parameter because we have networkselector now... async def connect(self): """ Main function to be called, connects to the target specified in settings, and starts reading/writing. """ #self.settings = settings try: self.configuration = QuicConfiguration( alpn_protocols=['smb'], is_client=True, max_datagram_frame_size=65536, verify_mode = ssl.CERT_NONE, ) self.client = asyncio.create_task(self.run_inner()) return True, None except Exception as e: logger.exception('[QUICSocket] main') return False, e asysocks-0.2.18/asysocks/unicomm/protocol/relay/000077500000000000000000000000001510034457600217315ustar00rootroot00000000000000asysocks-0.2.18/asysocks/unicomm/protocol/relay/__init__.py000066400000000000000000000000001510034457600240300ustar00rootroot00000000000000asysocks-0.2.18/asysocks/unicomm/protocol/relay/http.py000066400000000000000000000143211510034457600232630ustar00rootroot00000000000000import asyncio import copy import traceback import base64 from asysocks.unicomm.common.target import UniTarget, UniProto from asysocks.unicomm.common.proxy import UniProxyProto, UniProxyTarget from asysocks.unicomm.common.packetizers import Packetizer, StreamPacketizer from asysocks.unicomm.common.packetizers.ssl import PacketizerSSL from asysocks.unicomm.server import UniServer, UniConnection class HTTPConnectionSettings(RunningServerSettings): def __init__(self, server_settings: ServerSettings, gssapi: SPNEGORelay, log_q: asyncio.Queue = None): RunningServerSettings.__init__(self, server_settings, gssapi, log_q) class HTTPServerConnection: def __init__(self, settings:HTTPConnectionSettings, reader:asyncio.StreamReader, writer:asyncio.StreamWriter, client_ip:str, client_port:int): self.client_ip = client_ip self.client_port = client_port self.settings = settings self.gssapi = settings.gssapi self.ntlm = self.gssapi.authentication_contexts['NTLMSSP - Microsoft NTLM Security Support Provider'] self.reader = reader self.writer = writer self.unauthorized_status = 401 self.unauthorized_reason = 'Unauthorized' self.authorization_key = 'WWW-Authenticate' if self.settings.server_settings.isproxy is True: self.unauthorized_status = 407 self.unauthorized_reason = 'Proxy Authentication Required' self.authorization_key = 'Proxy-Authenticate' async def log_async(self, level, msg): if self.settings.log_q is not None: src = 'HTTPCON-%s:%s' % (self.client_ip, self.client_port) await self.settings.log_q.put((src, level, msg)) else: logger.log(level, msg) async def run(self): try: while True: req, err = await HTTPRequest.from_streamreader(self.reader, timeout = 10) if err is not None: raise err if 'AUTHORIZATION' not in req.headers_upper: response = HTTPResponse() response.version = 'HTTP/1.1' response.status = self.unauthorized_status response.reason = self.unauthorized_reason response.headers = { 'Server': 'ngix', 'Connection': 'close', self.authorization_key: 'NTLM', 'Content-Length': '0' } await self.log_async(1, "Sending response: %s" % response.to_bytes()) self.writer.write(response.to_bytes()) await self.writer.drain() self.writer.close() return authdata_raw = req.headers_upper['AUTHORIZATION'] if authdata_raw.startswith('NTLM') is False: await self.log_async(1, 'Authdata doesnt seem to be NTLM! %s' % authdata) return authdata = base64.b64decode(authdata_raw[5:]) res, to_continue, err = await self.ntlm.authenticate_relay_server(authdata) if err is not None: raise err if to_continue is True: if res is not None: response = HTTPResponse() response.version = 'HTTP/1.1' response.status = self.unauthorized_status response.reason = self.unauthorized_reason response.headers = { 'Server': 'ngix', 'Connection': 'keep-alive', self.authorization_key: 'NTLM %s' % base64.b64encode(res).decode(), 'Content-Length': '0' } await self.log_async(1, "Sending response: %s" % response.to_bytes()) self.writer.write(response.to_bytes()) await self.writer.drain() continue else: response = HTTPResponse() response.version = 'HTTP/1.1' response.status = 404 response.reason = 'Not Found' response.headers = { 'Server': 'ngix', 'Connection': 'keep-alive', 'Content-Length': '0' } self.writer.write(response.to_bytes()) await self.writer.drain() return except Exception as e: await self.log_async(1, "Client connection error! %s" % e) class HTTPServerSocket(ServerBase): """ """ def __init__(self, settings:HTTPConnectionSettings, server_queue: asyncio.Queue, log_q: asyncio.Queue): ServerBase.__init__(self, settings, server_queue, log_q, log_source_name = 'HTTP-TCP') self.ip = settings.server_settings.ip self.port = settings.server_settings.port async def handle_incoming(self, client): """ Reads data bytes from the socket and dispatches it to the incoming queue """ try: while not self.shutdown_evt.is_set(): data = await client.reader.read(65535) await client.in_queue.put((data, None)) except Exception as e: await client.in_queue.put((None, e)) return async def handle_outgoing(self, client): """ Reads data bytes from the outgoing queue and dispatches it to the socket """ try: while not self.shutdown_evt.is_set(): data = await client.out_queue.get() client.writer.write(data) await client.writer.drain() except asyncio.CancelledError: #the SMB connection is terminating return except Exception as e: await self.log_async(99, '[TCP] handle_outgoing %s' % str(e)) async def handle_client(self, connection:UniConnection): out_task = None in_task = None try: raddr = '' rport = 0 #raddr, rport = writer.get_extra_info('peername') #await self.log_async(20, '[TCP] Client connected from %s:%s' % (raddr, rport)) connection_settings = copy.deepcopy(self.settings) connection_settings.log_q = self.log_q connection_settings.gssapi.setup(self.log_q) server = HTTPServerConnection(connection_settings, connection, raddr, rport) await self.server_queue.put(('http', server)) await server.run() await self.log_async(10, 'Connection terminated, closing client! %s:%s' % (raddr, rport)) except Exception as e: traceback.print_exc() await self.log_async(99, '[TCP] handle_client %s' % str(e)) finally: if out_task is not None: out_task.cancel() if in_task is not None: in_task.cancel() #async def listen(self): # try: # proxies = [] # target = UniTarget(self.ip, self.port, UniProto.SERVER_TCP, proxies=proxies) # packetizer = StreamPacketizer() # server = UniServer(target, packetizer) # await self.log_async(10, 'TCP Server in listening state') # async for connection in server.serve(): # print('Client connected!') # asyncio.create_task(self.handle_client(connection)) # # await self.log_async(10, 'TCP server terminated') # except Exception as e: # await self.log_async(99, '[TCP] listen %s' % str(e)) async def run(self): await self.listen()asysocks-0.2.18/asysocks/unicomm/protocol/server/000077500000000000000000000000001510034457600221235ustar00rootroot00000000000000asysocks-0.2.18/asysocks/unicomm/protocol/server/__init__.py000066400000000000000000000000001510034457600242220ustar00rootroot00000000000000asysocks-0.2.18/asysocks/unicomm/protocol/server/http/000077500000000000000000000000001510034457600231025ustar00rootroot00000000000000asysocks-0.2.18/asysocks/unicomm/protocol/server/http/__init__.py000066400000000000000000000000001510034457600252010ustar00rootroot00000000000000asysocks-0.2.18/asysocks/unicomm/protocol/server/http/httprelay.py000066400000000000000000000173711510034457600255010ustar00rootroot00000000000000 import base64 import traceback from typing import List from asysocks.unicomm.protocol.server.http.httpserver import HTTPServerHandler # SPNEGO is not fully implemented yet, will be useful for kerberos relaying import h11 async def dummy_log(msg:str): print(msg) class HTTPRelayHandler(HTTPServerHandler): def __init__(self, relay_handler, *args, is_proxy:bool = False, authtype:str='NTLM', authbody:bytes=None, successbody:bytes=None, log_callback=None, **kwargs): super().__init__(*args, **kwargs) self.webdav_url = '' self.relay_handler = relay_handler self.gssapi = relay_handler() self.authtype = authtype.upper() if authtype not in ['NTLM', 'SPNEGO']: raise Exception('Unsupported auth type %s' % authtype) if is_proxy is True: self.unauthorized_reason = 'Proxy Authentication Required' self.authorization_key = 'Proxy-Authenticate' self.client_auth_key = 'Proxy-Authorization' self.unauthorized_status = 407 else: self.unauthorized_reason = 'Unauthorized' self.authorization_key = 'WWW-Authenticate' self.client_auth_key = 'Authorization' self.unauthorized_status = 401 if isinstance(authbody, str): authbody = authbody.encode() if isinstance(successbody, str): successbody = successbody.encode() self.authbody = authbody self.successbody = successbody self.log_callback = log_callback def get_auth_header(self, request): client_auth_key = self.client_auth_key.lower().encode('ascii') for header in request.headers: if header[0] == client_auth_key: return header[1] async def debug(self, *args): msg = [str(x) for x in args] msg = ' '.join(msg) if self.log_callback is not None: await self.log_callback(msg) async def initial_response(self): await self.debug('Initial response') try: status_code = 401 headers = self.basic_headers() headers.append(("Connection", 'keep-alive')) if self.authbody is not None: headers.append(("Content-Type", 'text/html')) headers.append(("Content-Length", str(len(self.authbody)))) headers.append((self.authorization_key, self.authtype)) res = h11.Response(status_code=status_code, headers=headers) await self._wrapper.send(res) if self.authbody is not None: await self._wrapper.send(h11.Data(data=self.authbody)) await self._wrapper.send(h11.EndOfMessage()) except Exception as e: traceback.print_exc() async def send_auth_progress(self, auth_data:bytes): await self.debug('send_auth_progress %s' % auth_data) status_code = 401 headers = self.basic_headers() headers.append((self.authorization_key, auth_data)) res = h11.Response(status_code=status_code, headers=headers) await self._wrapper.send(res) await self._wrapper.send(h11.EndOfMessage()) async def send_success(self, auth_data:bytes = None, extra_headers:List[tuple] = []): await self.debug('send_success %s' % auth_data) status_code = 200 headers = self.basic_headers() headers.extend(extra_headers) headers.append(("Content-Type", 'text/html')) if self.successbody is not None: headers.append(("Content-Length", str(len(self.successbody)))) if auth_data is not None: headers.append((self.authorization_key, auth_data)) res = h11.Response(status_code=status_code, headers=headers) await self._wrapper.send(res) if self.successbody is not None: await self._wrapper.send(h11.Data(data=self.successbody)) await self._wrapper.send(h11.EndOfMessage()) async def handle_options(self, request): extra_headers = [ ('Access-Control-Allow-Methods', 'OPTIONS, PROPFIND, GET, PUT, DELETE, MKCOL, LOCK, UNLOCK'), ('Access-Control-Allow-Origin', '*'), ('Access-Control-Allow-Headers', 'Content-Type, Authorization') ] await self.send_success(extra_headers=extra_headers) async def handle_propfind(self, request): status_code = 207 request_target = request.target.decode() request_file = request_target.split('/')[-1] getcontentlength = 0 creationdate = '2016-11-12T22:00:22Z' content = f"""{self.webdav_url}{creationdate}{request_file}{getcontentlength}Mon, 20 Mar 2017 00:00:22 GMT0HTTP/1.1 200 OK""" headers = self.basic_headers() headers.append(("Content-Type", 'text/xml')) headers.append(("Content-Length", str(len(content)))) res = h11.Response(status_code=status_code, headers=headers) await self._wrapper.send(res) await self._wrapper.send(h11.Data(data=content.encode())) await self._wrapper.send(h11.EndOfMessage()) async def handle_head(self, request): headers = self.basic_headers() headers.append(("Content-Type", 'text/xml')) res = h11.Response(status_code=200, headers=headers) await self._wrapper.send(res) await self._wrapper.send(h11.EndOfMessage()) async def _process_request(self, wrapper, request): authtype = self.authtype.encode() authobj = None # setting connection info self.gssapi.set_connection_info(wrapper.stream) #UniConnection if self.authtype == 'NTLM': authobj = self.gssapi.get_ntlm() elif self.authtype == 'SPNEGO': authobj = self.gssapi try: self._wrapper = wrapper auth_data = self.get_auth_header(request) await self.debug('auth_data %s' % auth_data) if auth_data is None: if request.method == b'OPTIONS': await self.handle_options(request) return elif request.method == b'PROPFIND': await self.handle_propfind(request) return elif request.method == b'HEAD': await self.handle_head(request) return await self.initial_response() return else: if auth_data.startswith(authtype) is True: auth_data = base64.b64decode(auth_data[len(authtype)+1:]) res, to_continue, err = await authobj.authenticate_relay_server(auth_data) if err is not None: raise err if to_continue is True and res is not None: await self.send_auth_progress(b'%s %s' % (authtype, base64.b64encode(res))) return res_data = None if res is not None: res_data = b'%s %s' % (authtype, base64.b64encode(res)) await self.send_success(res_data) else: raise Exception('Authdata doesnt seem to be NTLM! %s' % auth_data) except Exception as e: traceback.print_exc() raise e asysocks-0.2.18/asysocks/unicomm/protocol/server/http/httpserver.py000066400000000000000000000242041510034457600256640ustar00rootroot00000000000000 from asysocks.unicomm.common.target import UniTarget, UniProto from asysocks.unicomm.common.connection import UniConnection from asysocks.unicomm.common.packetizers import StreamPacketizer, Packetizer from asysocks.unicomm.server import UniServer import asyncio import traceback from itertools import count import datetime import email.utils import h11 class AsysocksHTTPWrapper: _next_id = count() def __init__(self, client_id, stream:UniConnection, log_callback=None): self.log_callback = log_callback self.client_id = client_id self.MAX_RECV = 2**16 self.stream = stream self.conn = h11.Connection(h11.SERVER) # Our Server: header self.ident = " ".join( [f"h11-example-asysocks-server/{h11.__version__}", h11.PRODUCT_ID] ).encode("ascii") # A unique id for this connection, to include in debugging output # (useful for understanding what's going on if there are multiple # simultaneous clients). self._obj_id = next(AsysocksHTTPWrapper._next_id) async def debug(self, *args): msg = [str(x) for x in args] msg = ' '.join(msg) if self.log_callback is not None: await self.log_callback(msg) async def send(self, event): # The code below doesn't send ConnectionClosed, so we don't bother # handling it here either -- it would require that we do something # appropriate when 'data' is None. assert type(event) is not h11.ConnectionClosed data = self.conn.send(event) try: await self.stream.write(data) except BaseException: # If send_all raises an exception (especially trio.Cancelled), # we have no choice but to give it up. self.conn.send_failed() raise async def _read_from_peer(self): if self.conn.they_are_waiting_for_100_continue: await self.debug("Sending 100 Continue") go_ahead = h11.InformationalResponse( status_code=100, headers=self.basic_headers() ) await self.send(go_ahead) try: await self.debug('[%s] Reading from peer...' % self.client_id) #data = await self.stream.packetizer.read(self.MAX_RECV) data = await self.stream.read_one() await self.debug('[%s] DATA: %s' % (self.client_id, data)) except Exception as exc: await self.debug('Error reading from peer:', exc) # They've stopped listening. Not much we can do about it here. data = b"" self.conn.receive_data(data) async def next_event(self): while True: event = self.conn.next_event() await self.debug('[%s] Event: %s' % (self.client_id, event)) if event is h11.NEED_DATA: await self._read_from_peer() continue return event async def shutdown_and_clean_up(self): try: await self.stream.close() except Exception as exc: return #maybe read some more? def basic_headers(self): # HTTP requires these headers in all responses (client would do # something different here) return [ ("Date", self.format_date_time().encode("ascii")), ("Server", self.ident), ] def format_date_time(self, dt=None): """Generate a RFC 7231 / RFC 9110 IMF-fixdate string""" if dt is None: dt = datetime.datetime.now(datetime.timezone.utc) return email.utils.format_datetime(dt, usegmt=True) class HTTPServerHandler: def __init__(self): self._wrapper:AsysocksHTTPWrapper = None self.ident = " ".join( [f"h11-example-asysocks-server/{h11.__version__}", h11.PRODUCT_ID] ).encode("ascii") @staticmethod def format_date_time(dt=None): """Generate a RFC 7231 / RFC 9110 IMF-fixdate string""" if dt is None: dt = datetime.datetime.now(datetime.timezone.utc) return email.utils.format_datetime(dt, usegmt=True) def basic_headers(self): # HTTP requires these headers in all responses (client would do # something different here) return [ ("Date", HTTPServerHandler.format_date_time().encode("ascii")), ("Server", self.ident), ] async def _process_request(self, wrapper:AsysocksHTTPWrapper, request:h11.Event): self._wrapper = wrapper method = request.method.decode("ascii") func = getattr(self, f"do_{method}", None) if func is None: response = h11.Response(status_code=405, headers=wrapper.basic_headers()) response.body = b"Method Not Allowed" return await wrapper.send(response) response = await func(request) await wrapper.send(response) async def do_GET(self, event): body = "Hello, world!".encode("ascii") status_code = 200 headers = self.basic_headers() headers.append(("Content-Type", 'text/html')) headers.append(("Content-Length", str(len(body)))) res = h11.Response(status_code=status_code, headers=headers) await self._wrapper.send(res) await self._wrapper.send(h11.Data(data=body)) await self._wrapper.send(h11.EndOfMessage()) class HTTPServer: def __init__(self, client_handler:HTTPServerHandler, target:UniTarget, ssl_ctx = None, log_callback=None): self.log_callback = log_callback self.target = target self.client_handler = client_handler self.ssl_ctx = ssl_ctx self.clients = [] self.id_counter = 0 self.__main_task = None self.started_evt = asyncio.Event() async def debug(self, *args): msg = [str(x) for x in args] msg = ' '.join(msg) if self.log_callback is not None: await self.log_callback(msg) async def __aenter__(self): self.__main_task = asyncio.create_task(self.serve()) await asyncio.sleep(1) return self async def __aexit__(self, exc_type, exc, tb): await self.terminate() async def terminate(self): for client in self.clients: await client.terminate() self.clients = [] async def __handle_connection(self, connection): try: #x = asyncio.create_task(connection.stream()) client_id = self.id_counter self.id_counter += 1 wrapper = AsysocksHTTPWrapper(client_id, connection) handler = self.client_handler() await self.debug('Server: New client connected with id %s' % client_id) while True: if wrapper.conn.states == {h11.CLIENT: h11.CLOSED, h11.SERVER: h11.CLOSED}: break if wrapper.conn.states[h11.CLIENT] == h11.MUST_CLOSE: break if wrapper.conn.states[h11.SERVER] == h11.MUST_CLOSE: break if wrapper.conn.states == {h11.CLIENT: h11.DONE, h11.SERVER: h11.DONE}: wrapper.conn.start_next_cycle() continue if not (wrapper.conn.states == {h11.CLIENT: h11.IDLE, h11.SERVER: h11.IDLE}): if not (wrapper.conn.states == {h11.CLIENT: h11.SEND_BODY, h11.SERVER: h11.DONE}): await self.debug('[%s] Server: Connection state not idle' % client_id) await self.debug(wrapper.conn.states) break try: await self.debug("Server main loop waiting for request") event = await wrapper.next_event() if event is None: break await self.debug("Server main loop got event:", event) if type(event) is h11.Request: try: await handler._process_request(wrapper, event) except Exception as exc: break continue if type(event) is h11.ConnectionClosed: break await self.debug('[%s] Server: unknown event type %s' % (client_id, type(event))) except Exception as exc: await self.debug(f"Error during response handler: {exc!r}") break except Exception as e: traceback.print_exc() finally: await connection.close() async def serve(self): try: packetizer = Packetizer() server = UniServer(self.target, packetizer) return await server.serve_callback(self.__handle_connection) except Exception as e: traceback.print_exc() return None, e async def relaytest(): from asyauth.protocols.spnego.relay.native import spnegorelay_ntlm_factory from asyauth.protocols.ntlm.relay.native import NTLMRelaySettings, ntlmrelay_factory from asysocks.unicomm.protocol.server.http.httprelay import HTTPRelayHandler async def read_q(queue): while True: item = await queue.get() print(item) async def dummy_log(msg:str): print(msg) def ntlm_settings_factory(): ntlm_settings = NTLMRelaySettings() ntlm_settings.log_callback = dummy_log ntlm_settings.timeout = 5 #ntlm_settings.force_signdisable = False #ntlm_settings.dropmic = False #ntlm_settings.dropmic2 = False #ntlm_settings.modify_negotiate_cb = None #ntlm_settings.modify_challenge_cb = None #ntlm_settings.modify_authenticate_cb = None return ntlm_settings target = UniTarget('0.0.0.0', 80, UniProto.SERVER_TCP) auth_relay_queue = asyncio.Queue() x = asyncio.create_task(read_q(auth_relay_queue)) relay_handler = lambda: spnegorelay_ntlm_factory(auth_relay_queue, lambda: ntlmrelay_factory(ntlm_settings_factory)) server = HTTPServer(lambda: HTTPRelayHandler(relay_handler), target, log_callback=dummy_log) await server.serve() print('done') if __name__ == '__main__': asyncio.run(relaytest())asysocks-0.2.18/asysocks/unicomm/protocol/server/udp.py000066400000000000000000000007721510034457600232730ustar00rootroot00000000000000import asyncio from asyncio import DatagramProtocol, Protocol, StreamReader, StreamWriter class UDPProtocol(Protocol): def __init__(self, in_queue:asyncio.Queue): self.in_queue = in_queue self.socket = None def connection_made(self, transport): self.socket = transport.get_extra_info('socket') self.transport = transport def datagram_received(self, data, addr): self.in_queue.put_nowait((self.socket, data, addr)) def connection_lost(self, exc): self.in_queue.put_nowait((None, exc)) asysocks-0.2.18/asysocks/unicomm/protocol/server/websocket/000077500000000000000000000000001510034457600241115ustar00rootroot00000000000000asysocks-0.2.18/asysocks/unicomm/protocol/server/websocket/__init__.py000066400000000000000000000000001510034457600262100ustar00rootroot00000000000000asysocks-0.2.18/asysocks/unicomm/protocol/server/websocket/server.py000066400000000000000000000236521510034457600260010ustar00rootroot00000000000000 from asysocks.unicomm.common.target import UniTarget, UniProto from asysocks.unicomm.common.packetizers import StreamPacketizer from asysocks.unicomm.server import UniServer from hashlib import sha1 from base64 import b64encode import asyncio import traceback import struct # https://github.com/Pithikos/python-websocket-server/ ''' +-+-+-+-+-------+-+-------------+-------------------------------+ 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | Payload Data continued ... | +---------------------------------------------------------------+ ''' FIN = 0x80 OPCODE = 0x0f MASKED = 0x80 PAYLOAD_LEN = 0x7f PAYLOAD_LEN_EXT16 = 0x7e PAYLOAD_LEN_EXT64 = 0x7f OPCODE_CONTINUATION = 0x0 OPCODE_TEXT = 0x1 OPCODE_BINARY = 0x2 OPCODE_CLOSE_CONN = 0x8 OPCODE_PING = 0x9 OPCODE_PONG = 0xA CLOSE_STATUS_NORMAL = 1000 DEFAULT_CLOSE_REASON = bytes('', encoding='utf-8') class WebSocketServer: def __init__(self, client_handler, host, port, ssl_ctx = None, proxies = None): self.client_handler = client_handler self.host = host self.port = port self.ssl_ctx = ssl_ctx self.proxies = proxies self.clients = [] self.id_counter = 0 self.__main_task = None self.started_evt = asyncio.Event() async def __aenter__(self): self.__main_task = asyncio.create_task(self.serve()) await asyncio.sleep(1) return self async def __aexit__(self, exc_type, exc, tb): await self.terminate() async def terminate(self): for client in self.clients: await client.terminate() self.clients = [] async def __handle_connection(self, connection): client_id = self.id_counter self.id_counter += 1 #print('Server: New client connected with id %s' % client_id) client = WebSocketClientHandler(client_id, self, connection, self.client_handler) self.clients.append(client) await client.run() self.clients.remove(client) async def serve(self): try: target = UniTarget(self.host, self.port, UniProto.SERVER_TCP, proxies=self.proxies) packetizer = StreamPacketizer() server = UniServer(target, packetizer) async for connection in server.serve(): x = asyncio.create_task(self.__handle_connection(connection)) except Exception as e: traceback.print_exc() class WebSocketClientHandler: def __init__(self, client_id, server, connection, client_handler): self.client_id = client_id self.server = server self.connection = connection self.client_handler = client_handler self.incoming_buffer = asyncio.Queue() @staticmethod def make_handshake_response(key): return \ 'HTTP/1.1 101 Switching Protocols\r\n'\ 'Upgrade: websocket\r\n' \ 'Connection: Upgrade\r\n' \ 'Sec-WebSocket-Accept: %s\r\n' \ '\r\n' % WebSocketClientHandler.calculate_response_key(key) @staticmethod def calculate_response_key(key): GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' hash = sha1(key.encode() + GUID.encode()) response_key = b64encode(hash.digest()).strip() return response_key.decode('ASCII') async def __read_http_headers(self): headers = {} # first line should be HTTP GET http_raw = await self.connection.packetizer.readuntil(b'\r\n') http_get = http_raw.decode().strip() if http_get.upper().startswith('GET') is False: raise Exception('Invalid HTTP request: %s' % http_get) # remaining should be headers while True: http_raw = await self.connection.packetizer.readuntil(b'\r\n') header = http_raw.decode().strip() if not header: break head, value = header.split(':', 1) headers[head.lower().strip()] = value.strip() return headers async def handshake(self): try: headers = await self.__read_http_headers() if 'upgrade' not in headers: raise Exception('Client tried to connect but was missing an upgrade header') if headers['upgrade'].lower() != 'websocket': raise Exception('Client tried to connect but was missing a key') if 'sec-websocket-key' not in headers: raise Exception('Client tried to connect but was missing a key') key = headers['sec-websocket-key'] response = WebSocketClientHandler.make_handshake_response(key) await self.connection.write(response.encode()) except Exception as e: traceback.print_exc() async def __recv_internal(self): try: while True: b1, b2 = 0, 0 res = await self.connection.packetizer.readexactly(2) b1 = res[0] b2 = res[1] fin = b1 & FIN opcode = b1 & OPCODE masked = b2 & MASKED payload_length = b2 & PAYLOAD_LEN if opcode == OPCODE_CLOSE_CONN: raise Exception("Client asked to close connection.") if not masked: raise Exception("Client must always be masked.") if opcode == OPCODE_CONTINUATION: raise Exception("Continuation frames are not supported.") if payload_length in [126, 127]: payload_length_raw = await self.connection.packetizer.readexactly(2 if payload_length == 126 else 8) payload_length = int.from_bytes(payload_length_raw, byteorder='big') masks = await self.connection.packetizer.readexactly(4) message_bytes = bytearray() raw = await self.connection.packetizer.readexactly(payload_length) for c in raw: c ^= masks[len(message_bytes) % 4] message_bytes.append(c) yield opcode, bytes(message_bytes) except Exception as e: #traceback.print_exc() yield None, None async def send_pong(self, data): if isinstance(data, str) is True: data = data.encode('utf-8') await self.__send_internal(OPCODE_PONG, data) async def send_ping(self, data): if isinstance(data, str) is True: data = data.encode('utf-8') await self.__send_internal(OPCODE_PING, data) async def start_recv(self): async for opcode, data in self.__recv_internal(): if opcode is None: break #print('Received opcode: %s' % opcode) #print('Received data: %s' % data) if opcode == OPCODE_TEXT: self.incoming_buffer.put_nowait(data.decode('utf-8')) elif opcode == OPCODE_BINARY: self.incoming_buffer.put_nowait(data) elif opcode == OPCODE_PING: await self.send_pong(data) elif opcode == OPCODE_PONG: print('Received pong') elif opcode == OPCODE_CLOSE_CONN: break else: print('Unknown opcode %s' % opcode) #print('Client disconnected') async def __send_internal(self, opcode, data:bytes): header = bytearray() payload_length = len(data) # Normal payload if payload_length <= 125: header.append(FIN | opcode) header.append(payload_length) # Extended payload elif payload_length >= 126 and payload_length <= 65535: header.append(FIN | opcode) header.append(PAYLOAD_LEN_EXT16) header.extend(struct.pack(">H", payload_length)) # Huge extended payload elif payload_length < 18446744073709551616: header.append(FIN | opcode) header.append(PAYLOAD_LEN_EXT64) header.extend(struct.pack(">Q", payload_length)) else: raise Exception("Message is too big. Consider breaking it into chunks.") await self.connection.write(header + data) async def send(self, data): if isinstance(data, str) is True: opcode = OPCODE_TEXT await self.__send_internal(opcode, data.encode('utf-8')) else: opcode = OPCODE_BINARY await self.__send_internal(opcode, data) async def run(self): x = asyncio.create_task(self.connection.stream()) await self.handshake() x = asyncio.create_task(self.start_recv()) conn = WebSocketClientConnection(self) await self.client_handler(conn) class WebSocketClientConnection: def __init__(self, clienthandler): self.clienthandler = clienthandler def __aiter__(self): return self async def __anext__(self): return await self.clienthandler.incoming_buffer.get() async def recv(self): return await self.clienthandler.incoming_buffer.get() async def send(self, data): await self.clienthandler.send(data) async def ping(self, data): await self.clienthandler.send_ping(data) serve = WebSocketServerasysocks-0.2.18/asysocks/unicomm/protocol/server/websocket/test.py000066400000000000000000000006511510034457600254440ustar00rootroot00000000000000import asyncio from asysocks.unicomm.protocol.server.websocket.server import serve async def echo(websocket): await websocket.ping("LLLLLLLLLLLLLLLLLLLLLLLLLLLLLL") async for message in websocket: print(f"Received: {message}") await websocket.send(message) async def server(): async with serve(echo, "localhost", 8765): await asyncio.Future() # run forever asyncio.run(server())asysocks-0.2.18/asysocks/unicomm/protocol/server/websocket/test_client.py000066400000000000000000000005721510034457600270040ustar00rootroot00000000000000import asyncio from websockets.sync.client import connect async def client(): i = 0 with connect("ws://localhost:8765") as websocket: while True: websocket.send(b"Hello world!" + b'A'*i*100) message = websocket.recv() print(f"Received: {message}") await asyncio.sleep(1) i += 1 asyncio.run(client())asysocks-0.2.18/asysocks/unicomm/protocol/socks4.py000066400000000000000000000062101510034457600223740ustar00rootroot00000000000000 # https://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol import io import os import enum import ipaddress import socket import asyncio SOCKS4_USERID_MAC_LEN = 255 class SOCKS4CDCode(enum.Enum): REQ_CONNECT = 1 REQ_BIND = 2 REP_GRANTED = 90 #request granted REP_FAILED = 91 #request rejected or failed REP_FAILED_NOCONN = 92 #request rejected becasue SOCKS server cannot connect to identd on the client REP_FAILED_USRID_MISMATCH = 93 #request rejected because the client program and identd report different user-ids class SOCKS4Request: def __init__(self): self.VN = 4 self.CD = SOCKS4CDCode.REQ_CONNECT self.DSTPORT = None self.DSTIP = None self.USERID = None @staticmethod def from_bytes(data): return SOCKS4Request.from_buffer(io.BytesIO(data)) @staticmethod async def from_streamreader(reader, timeout = None): try: t = await asyncio.wait_for(reader.readuntil(b'\x00'), timeout = timeout) return SOCKS4Request.from_bytes(t), None except Exception as e: return None, e @staticmethod def from_buffer(buff): o = SOCKS4Request() o.VN = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) o.CD = SOCKS4CDCode(int.from_bytes(buff.read(1), byteorder = 'big', signed = False)) o.DSTPORT = int.from_bytes(buff.read(2), byteorder = 'big', signed = False) o.DSTIP = ipaddress.IPv4Address(buff.read(4)) o.USERID = b'' for _ in range(SOCKS4_USERID_MAC_LEN): x = buff.read(1) if x == 0: break o.USERID += x return o @staticmethod def from_target(target): o = SOCKS4Request() if target.protocol.name.startswith('SERVER') is True: SOCKS4CDCode.REQ_BIND o.DSTPORT = target.endpoint_port o.DSTIP = ipaddress.ip_address(target.endpoint_ip) o.USERID = target.userid if target.userid is None: o.USERID = os.urandom(4).hex().encode('ascii') return o def to_bytes(self): t = self.VN.to_bytes(1, byteorder = 'big', signed = False) t += self.CD.value.to_bytes(1, byteorder = 'big', signed = False) t += self.DSTPORT.to_bytes(2, byteorder = 'big', signed = False) t += self.DSTIP.packed t += self.USERID t += b'\x00' #trailing for userid return t class SOCKS4Reply: def __init__(self): self.VN = 0 self.CD = None self.DSTPORT = None self.DSTIP = None @staticmethod def from_bytes(data): return SOCKS4Reply.from_buffer(io.BytesIO(data)) @staticmethod async def from_streamreader(reader, timeout = None): try: t = await asyncio.wait_for(reader.readexactly(8), timeout = timeout) return SOCKS4Reply.from_bytes(t), None except Exception as e: return None, e @staticmethod def from_buffer(buff): o = SOCKS4Reply() o.VN = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) o.CD = SOCKS4CDCode(int.from_bytes(buff.read(1), byteorder = 'big', signed = False)) o.DSTPORT = int.from_bytes(buff.read(2), byteorder = 'big', signed = False) o.DSTIP = ipaddress.IPv4Address(buff.read(4)) return o def to_bytes(self): t = self.VN.to_bytes(1, byteorder = 'big', signed = False) t += self.CD.value.to_bytes(1, byteorder = 'big', signed = False) t += self.DSTPORT.to_bytes(2, byteorder = 'big', signed = False) t += self.DSTIP.packed return t asysocks-0.2.18/asysocks/unicomm/protocol/socks4a.py000066400000000000000000000074301510034457600225420ustar00rootroot00000000000000 # https://www.openssh.com/txt/socks4a.protocol import io import os import enum import ipaddress import socket import asyncio SOCKS4_USERID_MAC_LEN = 255 class SOCKS4ACDCode(enum.Enum): REQ_CONNECT = 1 REQ_BIND = 2 REP_GRANTED = 90 #request granted REP_FAILED = 91 #request rejected or failed REP_FAILED_NOCONN = 92 #request rejected becasue SOCKS server cannot connect to identd on the client REP_FAILED_USRID_MISMATCH = 93 #request rejected because the client program and identd report different user-ids class SOCKS4ARequest: def __init__(self): self.VN = 4 self.CD = SOCKS4ACDCode.REQ_CONNECT self.DSTPORT = None self.DSTIP = None self.USERID = None self.DOMAIN = None @staticmethod def from_bytes(data): return SOCKS4ARequest.from_buffer(io.BytesIO(data)) @staticmethod async def from_streamreader(reader, timeout = None): try: buff = b'' t = await asyncio.wait_for(reader.readexactly(4), timeout = timeout) buff += t t = await asyncio.wait_for(reader.readexactly(4), timeout = timeout) buff += t r = 1 if t[0:3] == b'\x00\x00\x00': r = 2 for _ in range(0,r): t = await asyncio.wait_for(reader.readuntil(b'\x00'), timeout = timeout) buff += t return SOCKS4ARequest.from_bytes(buff), None except Exception as e: return None, e @staticmethod def from_buffer(buff): o = SOCKS4ARequest() o.VN = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) o.CD = SOCKS4ACDCode(int.from_bytes(buff.read(1), byteorder = 'big', signed = False)) o.DSTPORT = int.from_bytes(buff.read(2), byteorder = 'big', signed = False) o.DSTIP = ipaddress.IPv4Address(buff.read(4)) o.USERID = b'' for _ in range(SOCKS4_USERID_MAC_LEN): x = buff.read(1) if x == 0: break o.USERID += x if str(o.DSTIP).startswith('0.0.0.'): for _ in range(SOCKS4_USERID_MAC_LEN): x = buff.read(1) if x == 0: break o.DOMAIN += x return o @staticmethod def from_target(target): o = SOCKS4ARequest() if target.protocol.name.startswith('SERVER') is True: SOCKS4ACDCode.REQ_BIND o.DSTPORT = target.endpoint_port try: endpoint_ip = ipaddress.ip_address(target.endpoint_ip) o.DSTIP = endpoint_ip except: o.DSTIP = ipaddress.ip_address('0.0.0.3') o.DOMAIN = str(target.endpoint_ip) o.USERID = target.userid if target.userid is None: o.USERID = os.urandom(4).hex().encode('ascii') return o def to_bytes(self): t = self.VN.to_bytes(1, byteorder = 'big', signed = False) t += self.CD.value.to_bytes(1, byteorder = 'big', signed = False) t += self.DSTPORT.to_bytes(2, byteorder = 'big', signed = False) t += self.DSTIP.packed t += self.USERID t += b'\x00' #trailing for userid if self.DOMAIN is not None: t += self.DOMAIN.encode() t += b'\x00' return t class SOCKS4AReply: def __init__(self): self.VN = 0 self.CD = None self.DSTPORT = None self.DSTIP = None @staticmethod def from_bytes(data): return SOCKS4AReply.from_buffer(io.BytesIO(data)) @staticmethod async def from_streamreader(reader, timeout = None): try: t = await asyncio.wait_for(reader.readexactly(8), timeout = timeout) return SOCKS4AReply.from_bytes(t), None except Exception as e: return None, e @staticmethod def from_buffer(buff): o = SOCKS4AReply() o.VN = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) o.CD = SOCKS4ACDCode(int.from_bytes(buff.read(1), byteorder = 'big', signed = False)) o.DSTPORT = int.from_bytes(buff.read(2), byteorder = 'big', signed = False) o.DSTIP = ipaddress.IPv4Address(buff.read(4)) return o def to_bytes(self): t = self.VN.to_bytes(1, byteorder = 'big', signed = False) t += self.CD.value.to_bytes(1, byteorder = 'big', signed = False) t += self.DSTPORT.to_bytes(2, byteorder = 'big', signed = False) t += self.DSTIP.packed return t asysocks-0.2.18/asysocks/unicomm/protocol/socks5.py000066400000000000000000000476101510034457600224060ustar00rootroot00000000000000#https://www.ietf.org/rfc/rfc1928.txt #https://tools.ietf.org/html/rfc1929 #https://tools.ietf.org/html/rfc1961 import io import enum import ipaddress import socket import asyncio from asysocks.common.constants import SOCKS5Method class SOCKS5ServerState(enum.Enum): NEGOTIATION = 0 NOT_AUTHENTICATED = 1 REQUEST = 3 RELAYING = 4 class SOCKS5Command(enum.Enum): CONNECT = 0x01 BIND = 0x02 UDP_ASSOCIATE = 0x03 class SOCKS5AddressType(enum.Enum): IP_V4 = 0x01 DOMAINNAME = 0x03 IP_V6 = 0x04 class SOCKS5ReplyType(enum.Enum): SUCCEEDED = 0x00 # o X'00' succeeded FAILURE = 0x01 # o X'01' general SOCKS server failure CONN_NOT_ALLOWED = 0x02 # o X'02' connection not allowed by ruleset NETWORK_UNREACHABLE = 0x03 #o X'03' Network unreachable HOST_UNREACHABLE = 0x04 #o X'04' Host unreachable CONN_REFUSED = 0x05 #o X'05' Connection refused TTL_EXPIRED = 0x06 #o X'06' TTL expired COMMAND_NOT_SUPPORTED = 0x07 #o X'07' Command not supported ADDRESS_TYPE_NOT_SUPPORTED = 0x08 #o X'08' Address type not supported #o X'09' to X'FF' unassigned class SOCKS5ServerErrorReply(Exception): def __init__(self, reply): self.reply = reply def __str__(self): return self.reply.name class SOCKS5AuthFailed(Exception): pass class SOCKS5SocketParser: def __init__(self, protocol = socket.SOCK_STREAM): self.protocol = protocol def parse(self, soc, packet_type): return packet_type.from_bytes(self.read_soc(soc, packet_type.size)) def read_soc(self, soc, size): data = b'' while True: temp = soc.recv(4096) if temp == '': break data += temp if len(data) == size: break return data class SOCKS5CommandParser: # the reason we need this class is: SOCKS5 protocol messages doesn't have a type field, # the messages are parsed in context of the session itself. def __init__(self, protocol = socket.SOCK_STREAM): self.protocol = protocol #not used atm def parse(self, buff, session): if session.current_state == SOCKS5ServerState.NEGOTIATION: return SOCKS5Nego.from_buffer(buff) if session.current_state == SOCKS5ServerState.NOT_AUTHENTICATED: if session.mutual_auth_type == SOCKS5Method.PLAIN: return SOCKS5PlainAuth.from_buffer(buff) else: raise Exception('Not implemented!') if session.current_state == SOCKS5ServerState.REQUEST: return SOCKS5Request.from_buffer(buff) @staticmethod async def from_streamreader(reader, session, timeout = None): raise NotImplementedError() #if session.current_state == SOCKS5ServerState.NEGOTIATION: # t = await asyncio.wait_for(SOCKS5Nego.from_streamreader(reader), timeout = timeout) # return t # #if session.current_state == SOCKS5ServerState.NOT_AUTHENTICATED: # if session.mutual_auth_type == SOCKS5Method.PLAIN: # t = await asyncio.wait_for(SOCKS5PlainAuth.from_streamreader(reader), timeout = timeout) # return t # else: # raise Exception('Not implemented!') # #if session.current_state == SOCKS5ServerState.REQUEST: # t = await asyncio.wait_for(SOCKS5Request.from_streamreader(reader), timeout = timeout) # return t class SOCKS5AuthHandler: def __init__(self, authtype, creds = None): self.authtype = authtype self.creds = creds def do_AUTH(self, msg): if self.authtype == SOCKS5Method.PLAIN: if not isinstance(msg, SOCKS5PlainAuth): raise Exception('Wrong message/auth type!') if self.creds is None: return True, SOCKS5PlainCredentials(msg.UNAME, msg.PASSWD) else: if msg.UNAME in self.creds: if msg.PASSWD == self.creds[msg.UNAME]: return True, SOCKS5PlainCredentials(msg.UNAME, msg.PASSWD) return False, SOCKS5PlainCredentials(msg.UNAME, msg.PASSWD) elif self.authtype == SOCKS5Method.GSSAPI: raise Exception('Not implemented! yet') else: raise Exception('Not implemented!') class SOCKS5PlainCredentials: def __init__(self, username, password): self.username = username self.password = password class SOCKS5PlainAuthReply: def __init__(self): self.VER = None self.STATUS = None @staticmethod async def from_streamreader(reader): rep = SOCKS5PlainAuthReply() t = await reader.read(1) rep.VER = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.read(1) rep.STATUS = SOCKS5ReplyType(int.from_bytes(t, byteorder = 'big', signed = False)) return rep @staticmethod def from_bytes(data): return SOCKS5PlainAuthReply.from_buffer(io.BytesIO(data)) @staticmethod def from_buffer(buff): rep = SOCKS5PlainAuthReply() rep.VER = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) rep.STATUS = SOCKS5ReplyType(int.from_bytes(buff.read(1), byteorder = 'big', signed = False)) return rep class SOCKS5PlainAuth: def __init__(self): self.VER = None self.ULEN = None self.UNAME = None self.PLEN = None self.PASSWD = None @staticmethod async def from_streamreader(reader): auth = SOCKS5PlainAuth() t = await reader.read(1) auth.VER = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.read(1) auth.ULEN = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.read(auth.ULEN) auth.UNAME = t.decode() t = await reader.read(1) auth.PLEN = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.read(auth.PLEN) auth.PASSWD = t.decode() return auth @staticmethod def from_bytes(bbuff): return SOCKS5PlainAuth.from_buffer(io.BytesIO(bbuff)) @staticmethod def from_buffer(buff): auth = SOCKS5PlainAuth() auth.VER = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) auth.ULEN = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) auth.UNAME = buff.read(auth.ULEN).decode() auth.PLEN = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) auth.PASSWD = buff.read(auth.PLEN).decode() return auth @staticmethod def construct(username, password): auth = SOCKS5PlainAuth() auth.VER = 1 auth.ULEN = len(username) auth.UNAME = username auth.PLEN = len(password) auth.PASSWD = password return auth def to_bytes(self): t = self.VER.to_bytes(1, byteorder = 'big', signed = False) t += self.ULEN.to_bytes(1, byteorder = 'big', signed = False) t += self.UNAME.encode() t += self.PLEN.to_bytes(1, byteorder = 'big', signed = False) t += self.PASSWD.encode() return t class SOCKS5Nego: def __init__(self): self.VER = 5 self.NMETHODS = None self.METHODS = None @staticmethod async def from_streamreader(reader): nego = SOCKS5Nego() t = await reader.readexactly(1) nego.VER = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.readexactly(1) nego.NMETHODS = int.from_bytes(t, byteorder = 'big', signed = False) nego.METHODS = [] for _ in range(nego.NMETHODS): t = await reader.readexactly(1) nego.METHODS.append(SOCKS5Method(int.from_bytes(t, byteorder = 'big', signed = False))) return nego @staticmethod def from_bytes(bbuff): return SOCKS5Nego.from_buffer(io.BytesIO(bbuff)) @staticmethod def from_buffer(buff): nego = SOCKS5Nego() nego.VER = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) nego.NMETHODS = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) nego.METHODS = [] for _ in range(nego.NMETHODS): nego.METHODS.append(SOCKS5Method(int.from_bytes(buff.read(1), byteorder = 'big', signed = False))) return nego @staticmethod def from_methods(methods): if not isinstance(methods, list): methods = [methods] nego = SOCKS5Nego() nego.NMETHODS = len(methods) nego.METHODS = methods return nego def to_bytes(self): t = self.VER.to_bytes(1, byteorder = 'big', signed = False) t += self.NMETHODS.to_bytes(1, byteorder = 'big', signed = False) for method in self.METHODS: t += method.value.to_bytes(1, byteorder = 'big', signed = False) return t class SOCKS5NegoReply: def __init__(self): self.VER = 5 self.METHOD = None def __repr__(self): t = '== SOCKS5NegoReply ==\r\n' t += 'VER: %s\r\n' % self.VER t += 'METHOD: %s\r\n' % self.METHOD return t @staticmethod def from_socket(soc): data = b'' total_size = 2 while True: temp = soc.recv(1024) if temp == b'': break data += temp if len(data) >= total_size: break return SOCKS5NegoReply.from_bytes(data) @staticmethod async def from_streamreader(reader): rep = SOCKS5NegoReply() t = await reader.readexactly(1) rep.VER = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.readexactly(1) rep.METHOD = SOCKS5Method(int.from_bytes(t, byteorder = 'big', signed = False)) return rep @staticmethod def from_bytes(bbuff): return SOCKS5NegoReply.from_buffer(io.BytesIO(bbuff)) @staticmethod def from_buffer(buff): rep = SOCKS5NegoReply() rep.VER = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) rep.METHOD = SOCKS5Method(int.from_bytes(buff.read(1), byteorder = 'big', signed = False)) return rep @staticmethod def construct(method): rep = SOCKS5NegoReply() rep.VER = 5 rep.METHOD = method return rep @staticmethod def construct_auth(method, ver = 1): rep = SOCKS5NegoReply() rep.VER = ver rep.METHOD = method return rep def to_bytes(self): t = self.VER.to_bytes(1, byteorder = 'big', signed = False) t += self.METHOD.value.to_bytes(1, byteorder = 'big', signed = False) return t class SOCKS5Request: def __init__(self): self.VER = 5 self.CMD = SOCKS5Command.CONNECT self.RSV = 0 self.ATYP = None self.DST_ADDR = None self.DST_PORT = None @staticmethod async def from_streamreader(reader): req = SOCKS5Request() t = await reader.readexactly(1) req.VER = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.readexactly(1) req.CMD = SOCKS5Command(int.from_bytes(t, byteorder = 'big', signed = False)) t = await reader.readexactly(1) req.RSV = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.readexactly(1) req.ATYP = SOCKS5AddressType(int.from_bytes(t, byteorder = 'big', signed = False)) if req.ATYP == SOCKS5AddressType.IP_V4: t = await reader.readexactly(4) req.DST_ADDR = ipaddress.IPv4Address(t) elif req.ATYP == SOCKS5AddressType.IP_V6: t = await reader.readexactly(16) req.DST_ADDR = ipaddress.IPv6Address(t) elif req.ATYP == SOCKS5AddressType.DOMAINNAME: t = await reader.readexactly(1) length = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.readexactly(length) req.DST_ADDR = t.decode() t = await reader.readexactly(2) req.DST_PORT = int.from_bytes(t, byteorder = 'big', signed = False) return req @staticmethod def from_bytes(bbuff): return SOCKS5Request.from_buffer(io.BytesIO(bbuff)) @staticmethod def from_buffer(buff): req = SOCKS5Request() req.VER = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) req.CMD = SOCKS5Command(int.from_bytes(buff.read(1), byteorder = 'big', signed = False)) req.RSV = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) req.ATYP = SOCKS5AddressType(int.from_bytes(buff.read(1), byteorder = 'big', signed = False)) if req.ATYP == SOCKS5AddressType.IP_V4: req.DST_ADDR = ipaddress.IPv4Address(buff.read(4)) elif req.ATYP == SOCKS5AddressType.IP_V6: req.DST_ADDR = ipaddress.IPv6Address(buff.read(16)) elif req.ATYP == SOCKS5AddressType.DOMAINNAME: length = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) req.DST_ADDR = buff.read(length).decode() req.DST_PORT = int.from_bytes(buff.read(2), byteorder = 'big', signed = False) return req @staticmethod def from_target(target): req = SOCKS5Request() req.CMD = SOCKS5Command.CONNECT if target.protocol.name.startswith('SERVER') is True: req.CMD = SOCKS5Command.BIND try: endpoint_ip = ipaddress.ip_address(target.endpoint_ip) if endpoint_ip.version == 4: req.ATYP = SOCKS5AddressType.IP_V4 req.DST_ADDR = endpoint_ip elif endpoint_ip.version == 6: req.ATYP = SOCKS5AddressType.IP_V6 req.DST_ADDR = endpoint_ip except: req.ATYP = SOCKS5AddressType.DOMAINNAME req.DST_ADDR = target.endpoint_ip req.DST_PORT = target.endpoint_port return req @staticmethod def construct(cmd, address, port): req = SOCKS5Request() req.VER = 5 req.CMD = cmd req.RSV = 0 try: endpoint_ip = ipaddress.ip_address(address) if endpoint_ip.version == 4: req.ATYP = SOCKS5AddressType.IP_V4 req.DST_ADDR = endpoint_ip elif endpoint_ip.version == 6: req.ATYP = SOCKS5AddressType.IP_V6 req.DST_ADDR = endpoint_ip except: req.ATYP = SOCKS5AddressType.DOMAINNAME req.DST_ADDR = address req.DST_PORT = port return req def to_bytes(self): t = self.VER.to_bytes(1, byteorder = 'big', signed = False) t += self.CMD.value.to_bytes(1, byteorder = 'big', signed = False) t += self.RSV.to_bytes(1, byteorder = 'big', signed = False) t += self.ATYP.value.to_bytes(1, byteorder = 'big', signed = False) if self.ATYP == SOCKS5AddressType.DOMAINNAME: t += len(self.DST_ADDR).to_bytes(1, byteorder = 'big', signed = False) t += self.DST_ADDR.encode() else: t += self.DST_ADDR.packed t += self.DST_PORT.to_bytes(2, byteorder = 'big', signed = False) return t class SOCKS5Reply: def __init__(self): self.VER = 5 self.REP = None self.RSV = 0 self.ATYP = None self.BIND_ADDR= None self.BIND_PORT= None @staticmethod def from_socket(soc): data = b'' total_size = 1024 while True: temp = soc.recv(1024) if temp == b'': break data += temp if len(data) > 4: rt = SOCKS5AddressType(data[3]) if rt == SOCKS5AddressType.IP_V4: total_size = 4 + 2 + 4 if rt == SOCKS5AddressType.IP_V6: total_size = 4 + 2 + 16 if rt == SOCKS5AddressType.DOMAINNAME: total_size = 4 + 2 + data[4] if len(data) >= total_size: break return SOCKS5Reply.from_bytes(data) @staticmethod async def from_streamreader(reader): rep = SOCKS5Reply() t = await reader.readexactly(1) rep.VER = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.readexactly(1) rep.REP = SOCKS5ReplyType(int.from_bytes(t, byteorder = 'big', signed = False)) t = await reader.readexactly(1) rep.RSV = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.readexactly(1) rep.ATYP = SOCKS5AddressType(int.from_bytes(t, byteorder = 'big', signed = False)) if rep.ATYP == SOCKS5AddressType.IP_V4: t = await reader.readexactly(4) rep.BIND_ADDR = ipaddress.IPv4Address(t) elif rep.ATYP == SOCKS5AddressType.IP_V6: t = await reader.readexactly(16) rep.BIND_ADDR = ipaddress.IPv6Address(t) elif rep.ATYP == SOCKS5AddressType.DOMAINNAME: t = await reader.readexactly(1) length = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.readexactly(length) rep.BIND_ADDR = t.decode() t = await reader.readexactly(2) rep.BIND_PORT = int.from_bytes(t, byteorder = 'big', signed = False) return rep @staticmethod def from_bytes(bbuff): return SOCKS5Reply.from_buffer(io.BytesIO(bbuff)) @staticmethod def from_buffer(buff): rep = SOCKS5Reply() rep.VER = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) rep.REP = SOCKS5ReplyType(int.from_bytes(buff.read(1), byteorder = 'big', signed = False)) rep.RSV = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) rep.ATYP = SOCKS5AddressType(int.from_bytes(buff.read(1), byteorder = 'big', signed = False)) if rep.ATYP == SOCKS5AddressType.IP_V4: rep.BIND_ADDR = ipaddress.IPv4Address(buff.read(4)) elif rep.ATYP == SOCKS5AddressType.IP_V6: rep.BIND_ADDR = ipaddress.IPv6Address(buff.read(16)) elif rep.ATYP == SOCKS5AddressType.DOMAINNAME: length = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) rep.BIND_ADDR = buff.read(length).decode() rep.BIND_PORT = int.from_bytes(buff.read(2), byteorder = 'big', signed = False) return rep @staticmethod def construct(reply, address, port): rep = SOCKS5Reply() rep.VER = 5 rep.REP = reply rep.RSV = 0 if isinstance(address, ipaddress.IPv4Address): rep.ATYP = SOCKS5AddressType.IP_V4 rep.DST_ADDR = address elif isinstance(address, ipaddress.IPv6Address): rep.ATYP = SOCKS5AddressType.IP_V6 rep.DST_ADDR = address elif isinstance(address, str): rep.ATYP = SOCKS5AddressType.DOMAINNAME rep.DST_ADDR = address rep.DST_PORT = port return rep def to_bytes(self): t = self.VER.to_bytes(1, byteorder = 'big', signed = False) t += self.REP.value.to_bytes(1, byteorder = 'big', signed = False) t += self.RSV.to_bytes(1, byteorder = 'big', signed = False) t += self.ATYP.value.to_bytes(1, byteorder = 'big', signed = False) if self.ATYP == SOCKS5AddressType.DOMAINNAME: t += len(self.DST_ADDR).to_bytes(1, byteorder = 'big', signed = False) t += self.DST_ADDR.encode() else: t += self.DST_ADDR.packed t += self.DST_PORT.to_bytes(2, byteorder = 'big', signed = False) return t def __repr__(self): t = '== SOCKS5Reply ==\r\n' t += 'REP: %s\r\n' % repr(self.REP) t += 'ATYP: %s\r\n' % repr(self.ATYP) t += 'BIND_ADDR: %s\r\n' % repr(self.BIND_ADDR) t += 'BIND_PORT: %s\r\n' % repr(self.BIND_PORT) return t class SOCKS5UDP: def __init__(self): self.RSV = None self.FRAG = None self.ATYP = None self.DST_ADDR = None self.DST_PORT = None self.DATA = None @staticmethod async def from_streamreader(reader): rep = SOCKS5UDP() t = await reader.readexactly(2) rep.RSV = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.readexactly(1) rep.FRAG = SOCKS5ReplyType(int.from_bytes(t, byteorder = 'big', signed = False)) t = await reader.readexactly(1) rep.ATYP = SOCKS5AddressType(int.from_bytes(t, byteorder = 'big', signed = False)) if rep.ATYP == SOCKS5AddressType.IP_V4: t = await reader.readexactly(4) rep.DST_ADDR = ipaddress.IPv4Address(t) elif rep.ATYP == SOCKS5AddressType.IP_V6: t = await reader.readexactly(16) rep.DST_ADDR = ipaddress.IPv6Address(t) elif rep.ATYP == SOCKS5AddressType.DOMAINNAME: t = await reader.readexactly(1) length = int.from_bytes(t, byteorder = 'big', signed = False) t = await reader.readexactly(length) rep.DST_ADDR = t.decode() t = await reader.readexactly(2) rep.DST_PORT = int.from_bytes(t, byteorder = 'big', signed = False) return rep @staticmethod def from_bytes(bbuff): return SOCKS5UDP.from_buffer(io.BytesIO(bbuff)) @staticmethod def from_buffer(buff): rep = SOCKS5UDP() rep.RSV = int.from_bytes(buff.read(2), byteorder = 'big', signed = False) rep.FRAG = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) rep.ATYP = SOCKS5AddressType(buff.read(1), byteorder = 'big', signed = False) if rep.ATYP == SOCKS5AddressType.IP_V4: rep.DST_ADDR = ipaddress.IPv4Address(buff.read(4)) elif rep.ATYP == SOCKS5AddressType.IP_V6: rep.DST_ADDR = ipaddress.IPv6Address(buff.read(16)) elif rep.ATYP == SOCKS5AddressType.DOMAINNAME: length = int.from_bytes(buff.read(1), byteorder = 'big', signed = False) rep.DST_ADDR = buff.read(length).decode() rep.DST_PORT = int.from_bytes(buff.read(2), byteorder = 'big', signed = False) #be careful, not data length is defined in the RFC!! rep.DATA = buff.read() @staticmethod def construct(address, port, data, frag = 0): req = SOCKS5Request() req.RSV = 0 req.FRAG = frag if isinstance(address, ipaddress.IPv4Address): req.ATYP = SOCKS5AddressType.IP_V4 req.DST_ADDR = address elif isinstance(address, ipaddress.IPv6Address): req.ATYP = SOCKS5AddressType.IP_V6 req.DST_ADDR = address elif isinstance(address, str): req.ATYP = SOCKS5AddressType.DOMAINNAME req.DST_ADDR = address req.DST_PORT = port req.DATA = data return req def to_bytes(self): t = self.RSV.to_bytes(2, byteorder = 'big', signed = False) t += self.FRAG.value.to_bytes(1, byteorder = 'big', signed = False) t += self.ATYP.value.to_bytes(1, byteorder = 'big', signed = False) if self.ATYP == SOCKS5AddressType.DOMAINNAME: t += len(self.DST_ADDR).to_bytes(1, byteorder = 'big', signed = False) t += self.DST_ADDR.encode() else: t += self.DST_ADDR.packed t += self.DST_PORT.to_bytes(2, byteorder = 'big', signed = False) t += self.DATA return tasysocks-0.2.18/asysocks/unicomm/server.py000066400000000000000000000510701510034457600206370ustar00rootroot00000000000000# this is experimental! import asyncio import copy import ssl import platform import traceback from typing import Callable from asysocks.unicomm.protocol.socks5 import SOCKS5Method, SOCKS5Nego, SOCKS5NegoReply, SOCKS5PlainAuth, \ SOCKS5AuthFailed, SOCKS5PlainAuthReply, SOCKS5ReplyType, SOCKS5Request, SOCKS5Reply, \ SOCKS5ServerErrorReply from asysocks.unicomm.common.target import UniTarget, UniProto from asysocks.unicomm.common.proxy import UniProxyProto, UniProxyTarget from asysocks.unicomm.common.packetizers import Packetizer, StreamPacketizer from asysocks.unicomm.common.packetizers.ssl import PacketizerSSL from asysocks.unicomm.common.connection import UniConnection, UniUDPConnection from asysocks.unicomm import logger from asysocks.unicomm.client import UniClient from asysocks.unicomm.protocol.server.udp import UDPProtocol import ipaddress class ProxyChainError(Exception): def __init__(self, innerexception, message="Something failed setting up the proxy chain! See innerexception for more details"): self.innerexception = innerexception self.message = message super().__init__(self.message) class UniServer: def __init__(self, target:UniTarget, packetizer:Packetizer, bindtype:int = None): self.target = target self.bindtype = bindtype self.packetizer = packetizer self.bind_progress_evt = asyncio.Event() self.connection_queue = asyncio.Queue() self.udpprotocol = None self.udptransport = None self.udpsocket = None def get_interface_from_ip(self): try: import netifaces except: print('netifaces not found! Install it with "pip install netifaces" ') return [] interfaces = [] for interface in netifaces.interfaces(): addresses = netifaces.ifaddresses(interface) # Check for IPv4 addresses if netifaces.AF_INET in addresses: for addr_info in addresses[netifaces.AF_INET]: if addr_info['addr'] == self.target.ip or self.target.ip in ['','0.0.0.0', '::', '::0']: interfaces.append(interface) # Check for IPv6 addresses if netifaces.AF_INET6 in addresses: for addr_info in addresses[netifaces.AF_INET6]: if addr_info['addr'] == self.target.ip or self.target.ip in ['','0.0.0.0', '::', '::0']: interfaces.append(interface) return interfaces def get_ips_from_interface(self, interface:str, ip_version:int = 4): try: import netifaces except: print('netifaces not found! Install it with "pip install netifaces" ') return [] ips = [] if ip_version == 4: addresses = netifaces.ifaddresses(interface) if netifaces.AF_INET in addresses: for addr_info in addresses[netifaces.AF_INET]: ips.append(addr_info['addr']) elif ip_version == 6: addresses = netifaces.ifaddresses(interface) if netifaces.AF_INET6 in addresses: for addr_info in addresses[netifaces.AF_INET6]: ips.append(addr_info['addr']) return ips async def run_socks5(self, proxy:UniProxyTarget, connection:UniConnection, timeout:int = None): """ Does the intial "handshake" instructing the remote server to set up the connection to the endpoint """ stream_task = asyncio.create_task(connection.stream()) sname = proxy.get_sname() tname = proxy.get_tname() methods = [SOCKS5Method.NOAUTH] if proxy.credential is not None and proxy.credential.username is not None and proxy.credential.password is not None: methods.append(SOCKS5Method.PLAIN) #methods = [SOCKS5Method.PLAIN] try: nego = SOCKS5Nego.from_methods(methods) logger.debug('[SOCKS5 %s][SETUP] Sending negotiation command to server' % sname) await connection.write(nego.to_bytes()) rep_nego = await asyncio.wait_for( SOCKS5NegoReply.from_streamreader(connection.packetizer), timeout = timeout ) logger.debug( '[SOCKS5 %s] Got negotiation reply! Server choosen auth type: %s' % (sname, rep_nego.METHOD.name) ) if rep_nego.METHOD == SOCKS5Method.PLAIN: if proxy.credential is None or proxy.credential.username is None or proxy.credential.password is None: raise Exception('SOCKS5 %s] server requires PLAIN authentication, but no credentials were supplied!' % sname) logger.debug('[SOCKS5 %s]Preforming plaintext auth' % sname) await connection.write( SOCKS5PlainAuth.construct( proxy.credential.username, proxy.credential.password ).to_bytes() ) rep_data = await asyncio.wait_for( connection.packetizer.readexactly(2), timeout = timeout ) if rep_data == b'': raise SOCKS5AuthFailed() #raise Exception('Plaintext auth failed! Bad username or password') rep_nego = SOCKS5PlainAuthReply.from_bytes(rep_data) if rep_nego.STATUS != SOCKS5ReplyType.SUCCEEDED: raise SOCKS5AuthFailed() #raise Exception('Plaintext auth failed! Bad username or password') elif rep_nego.METHOD == SOCKS5Method.GSSAPI: raise Exception('[SOCKS5 %s] server requires GSSAPI authentication, but it\'s not supported at the moment' % sname) logger.debug('[SOCKS5 %s] Opening channel to %s' % (sname, tname)) await connection.write( SOCKS5Request.from_target( proxy ).to_bytes() ) rep = await asyncio.wait_for( SOCKS5Reply.from_streamreader(connection.packetizer), timeout=timeout ) if rep.REP != SOCKS5ReplyType.SUCCEEDED: logger.info('[SOCKS5 %s] remote end failed to connect to proxy! Reson: %s' % (sname, rep.REP.name)) raise SOCKS5ServerErrorReply(rep.REP) if proxy.protocol.name.startswith('SERVER') is False: logger.debug('[SOCKS5 %s] Successfully set up the connection to the endpoint %s ' % (sname,tname)) return True, None logger.debug('[SOCKS5 %s] BIND first set completed, port %s available!' % (sname , rep.BIND_PORT)) #bind in progress, waiting for a second reply to notify us that the remote endpoint connected back. self.bind_port = rep.BIND_PORT self.bind_progress_evt.set() #notifying that the bind port is now available on the socks server to be used if proxy.only_bind is True: return True, None rep = await asyncio.wait_for( SOCKS5Reply.from_streamreader(connection.packetizer), timeout=timeout ) if rep.REP != SOCKS5ReplyType.SUCCEEDED: logger.info('[SOCKS5 %s] remote end failed to connect to proxy! Reson: %s' % (sname, rep.REP.name)) raise SOCKS5ServerErrorReply(rep.REP) return True, None except Exception as e: logger.debug('[SOCKS5 %s] Error in run_socks5 %s' % (sname, e)) return False, e finally: if stream_task is not None: stream_task.cancel() async def create_link(self): remote_writer = None remote_reader = None try: if len(self.target.proxies) > 1: logger.debug('Start chaining...') if len(self.target.proxies) == 1: if self.target.proxies[0].protocol == UniProxyProto.SERVER_WSNET: from wsnet.pyodide.tcpserver import WSNetworkTCPServer if self.target.protocol == UniProto.SERVER_TCP: return await WSNetworkTCPServer(self.__handle_connection, self.target.get_ip_or_hostname(), self.target.port, bindtype = 1, reuse_ws = True).run() elif self.target.protocol == UniProto.SERVER_SSL_TCP: return await WSNetworkTCPServer(self.__handle_connection_ssl, self.target.get_ip_or_hostname(), self.target.port, bindtype = 1, reuse_ws = True).run() else: raise Exception('WSNET unimplemented proto %s' % self.target.protocol) elif self.target.proxies[0].protocol in [UniProxyProto.SERVER_SOCKS5_TCP, UniProxyProto.CLIENT_SSL_SOCKS5_TCP, UniProxyProto.SERVER_SOCKS5_UDP, UniProxyProto.SERVER_SSL_SOCKS5_UDP]: #### SOCKS TCP BIND only allows one (1) connection at a time!!!!!! This is a limitation in the RFC try: ttarget = UniTarget(self.target.proxies[0].server_ip, self.target.proxies[0].server_port, protocol=UniProto.CLIENT_TCP) client = UniClient(ttarget, StreamPacketizer()) connection = await client.connect() _, err = await asyncio.wait_for(self.run_socks5(self.target.proxies[0], connection), timeout=self.target.proxies[-1].timeout) if err is not None: raise err if self.target.protocol == UniProto.SERVER_TCP: connection.change_packetizer(copy.deepcopy(self.packetizer)) await self.connection_queue.put(connection) return connection, None elif self.target.protocol == UniProto.SERVER_SSL_TCP: ssl_ctx = self.target.get_ssl_context(ssl.PROTOCOL_TLS_SERVER) packetizer = PacketizerSSL(ssl_ctx, packetizer) await packetizer.do_handshake(connection.packetizer, connection, server_side=True) connection = UniConnection(connection.packetizer, connection, packetizer) await self.connection_queue.put(connection) return connection, None except asyncio.TimeoutError: raise Exception('Proxy Connection establishment timeout') else: raise Exception("Unsupported proxy protocol %s" % self.target.proxies[0].protocol) else: raise NotImplementedError() client_target = self.target.get_preproxy() client = UniClient(client_target, Packetizer()) connection = await client.connect() raise Exception('Should not be here!') except Exception as e: logger.exception('[create_link]') if remote_writer is not None: remote_writer.close() raise async def __handle_connection(self, reader, writer): packetizer = copy.deepcopy(self.packetizer) connection = UniConnection(reader, writer, packetizer) await self.connection_queue.put(connection) #await connection.closed_evt.wait() async def __handle_connection_ssl(self, reader, writer): ssl_ctx = None try: packetizer = copy.deepcopy(self.packetizer) ssl_ctx = self.target.get_ssl_context(ssl.PROTOCOL_TLS_SERVER) packetizer = PacketizerSSL(ssl_ctx, packetizer) await packetizer.do_handshake(reader, writer, server_side=True) connection = UniConnection(reader, writer, packetizer) await self.connection_queue.put(connection) #await connection.closed_evt.wait() finally: if ssl_ctx is not None: del ssl_ctx async def start_generic_udp_server(self): try: in_queue = asyncio.Queue() if platform.system() == 'Emscripten': from wsnet.pyodide.udpserver import WSNetworkUDPServer protofactory = lambda: UDPProtocol(in_queue) wsnetserver = WSNetworkUDPServer(protofactory, self.target.get_ip_or_hostname(), self.target.port, bindtype = 1, reuse_ws = True) servertransport, serverproto, err = await wsnetserver.run() if err is not None: raise err return servertransport, serverproto, wsnetserver.writer, None import socket sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((self.target.get_ip_or_hostname(), self.target.port)) sock.setblocking(False) protofactory = lambda: UDPProtocol(in_queue) servertransport, serverproto = await asyncio.get_event_loop().create_datagram_endpoint(protofactory, sock=sock) return servertransport, serverproto, sock, None except Exception as e: return None, None, None, e async def start_llmnr_server(self): try: in_queue = asyncio.Queue() if platform.system() == 'Emscripten': from wsnet.pyodide.udpserver import WSNetworkUDPServer protofactory = lambda: UDPProtocol(in_queue) wsnetserver = WSNetworkUDPServer(protofactory, self.target.hostname, 5355, bindtype = 2, reuse_ws = False) servertransport, serverproto, err = await wsnetserver.run() if err is not None: raise err return servertransport, serverproto, wsnetserver.writer, None import socket import struct sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255) sock.bind(('', 5355)) llmnr_addr = "224.0.0.252" llmnr_addr6 = "FF02:0:0:0:0:0:1:3" # IPv4 for ip in self.get_ips_from_interface(self.target.hostname, 4): mreq = socket.inet_aton(llmnr_addr) + socket.inet_aton(ip) sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) # IPv6 try: if_index = socket.if_nametoindex(self.target.hostname) mreq6 = socket.inet_pton(socket.AF_INET6, llmnr_addr6) + struct.pack('@I', if_index) sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq6) except Exception as e: pass sock.setblocking(False) protofactory = lambda: UDPProtocol(in_queue) servertransport, serverproto = await asyncio.get_event_loop().create_datagram_endpoint(protofactory, sock=sock) return servertransport, serverproto, sock, None except Exception as e: return None, None, None, e async def start_mdns_server(self): try: in_queue = asyncio.Queue() if platform.system() == 'Emscripten': from wsnet.pyodide.udpserver import WSNetworkUDPServer protofactory = lambda: UDPProtocol(in_queue) wsnetserver = WSNetworkUDPServer(protofactory, self.target.hostname, 5353, bindtype = 4, reuse_ws = False) servertransport, serverproto, err = await wsnetserver.run() if err is not None: raise err return servertransport, serverproto, wsnetserver.writer, None import socket import struct sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255) mdns_addr = "224.0.0.251" mdns_addr6 = "FF02::FB" sock.bind(('', 5353)) # IPv4 for ip in self.get_ips_from_interface(self.target.hostname, 4): mreq = socket.inet_aton(mdns_addr) + socket.inet_aton(ip) sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) # IPv6 try: if_index = socket.if_nametoindex(self.target.hostname) mreq6 = socket.inet_pton(socket.AF_INET6, mdns_addr6) + struct.pack('@I', if_index) sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq6) except Exception as e: pass sock.setblocking(False) protofactory = lambda: UDPProtocol(in_queue) servertransport, serverproto = await asyncio.get_event_loop().create_datagram_endpoint(protofactory, sock=sock) return servertransport, serverproto, sock, None except Exception as e: return None, None, None, e async def start_nbtns_server(self): try: in_queue = asyncio.Queue() if platform.system() == 'Emscripten': from wsnet.pyodide.udpserver import WSNetworkUDPServer protofactory = lambda: UDPProtocol(in_queue) wsnetserver = WSNetworkUDPServer(protofactory, self.target.hostname, 137, bindtype = 3, reuse_ws = False) servertransport, serverproto, err = await wsnetserver.run() if err is not None: raise err return servertransport, serverproto, wsnetserver.writer, None import socket sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) for ip in self.get_ips_from_interface(self.target.hostname, 4): sock.bind((ip, 137)) break sock.setblocking(False) protofactory = lambda: UDPProtocol(in_queue) servertransport, serverproto = await asyncio.get_event_loop().create_datagram_endpoint(protofactory, sock=sock) return servertransport, serverproto, sock, None except Exception as e: return None, None, None, e async def sendto(self, data, addr): if self.udpsocket is None: raise Exception('UDP Server not started!') if asyncio.iscoroutine(self.udpsocket.sendto) is True: await self.udpsocket.sendto(data, addr) else: self.udpsocket.sendto(data, addr) async def start_udp_server(self): if self.udpsocket is not None: return True, None try: if self.bindtype == 1: self.udptransport, self.udpprotocol, self.udpsocket, err = await self.start_generic_udp_server() if err is not None: raise err elif self.bindtype == 2: self.udptransport, self.udpprotocol, self.udpsocket, err = await self.start_llmnr_server() if err is not None: raise err elif self.bindtype == 3: self.udptransport, self.udpprotocol, self.udpsocket, err = await self.start_nbtns_server() if err is not None: raise err elif self.bindtype == 4: self.udptransport, self.udpprotocol, self.udpsocket, err = await self.start_mdns_server() if err is not None: raise err else: raise Exception('Unsupported bindtype %s' % self.bindtype) return True, None except Exception as e: return None, e async def serve(self): server = None serverobj = None try: if self.target.protocol == UniProto.SERVER_UDP: _, err = await self.start_udp_server() if err is not None: raise err while True: socket, data, addr = await self.udpprotocol.in_queue.get() yield UniUDPConnection(socket, data, addr) else: if len(self.target.proxies) > 0: server, err = await self.create_link() if err is not None: raise err while not server.closed_evt.is_set(): connection = await self.connection_queue.get() yield connection else: if self.target.protocol in [UniProto.SERVER_TCP, UniProto.SERVER_SSL_TCP]: if self.target.protocol == UniProto.SERVER_TCP: server = await asyncio.start_server(self.__handle_connection, self.target.get_ip_or_hostname(), self.target.port) elif self.target.protocol == UniProto.SERVER_SSL_TCP: server = await asyncio.start_server(self.__handle_connection_ssl, self.target.get_ip_or_hostname(), self.target.port) while server.is_serving(): connection = await self.connection_queue.get() yield connection else: raise Exception('Unknown protocol "%s"' % self.target.protocol) finally: if server is not None: try: server.close() except: pass if self.udpprotocol is not None: try: self.udpprotocol.close() except: pass if self.udpsocket is not None: try: self.udpsocket.close() except: pass async def udp_dispatcher(self, connection_callback:Callable[[UniUDPConnection], None]): try: while True: socket, data, addr = await self.udpprotocol.in_queue.get() x = asyncio.create_task(connection_callback(UniUDPConnection(socket, data, addr))) finally: if self.udptransport is not None: try: self.udptransport.close() except Exception as e: pass if self.udpprotocol is not None: try: self.udpprotocol.close() except Exception as e: pass if self.udpsocket is not None: try: self.udpsocket.close() except Exception as e: pass async def tcp_dispatcher(self, server, connection_callback:Callable[[UniConnection], None]): try: while not server.closed_evt.is_set(): connection = await self.connection_queue.get() x = asyncio.create_task(connection_callback(connection)) finally: if server is not None: try: server.close() except: pass async def tcp_dispatcher_builtin(self, server, connection_callback:Callable[[UniConnection], None]): try: while server.is_serving(): connection = await self.connection_queue.get() x = asyncio.create_task(connection_callback(connection)) finally: if server is not None: try: server.close() except: pass async def serve_callback(self, connection_callback:Callable[[UniConnection], None]): server = None try: if self.target.protocol == UniProto.SERVER_UDP: _, err = await self.start_udp_server() if err is not None: raise err dispatcher_task = asyncio.create_task(self.udp_dispatcher(connection_callback)) else: if len(self.target.proxies) > 0: server, err = await self.create_link() if err is not None: raise err dispatcher_task = asyncio.create_task(self.tcp_dispatcher(server, connection_callback)) else: if self.target.protocol in [UniProto.SERVER_TCP, UniProto.SERVER_SSL_TCP]: if self.target.protocol == UniProto.SERVER_TCP: server = await asyncio.start_server(self.__handle_connection, self.target.get_ip_or_hostname(), self.target.port) elif self.target.protocol == UniProto.SERVER_SSL_TCP: server = await asyncio.start_server(self.__handle_connection_ssl, self.target.get_ip_or_hostname(), self.target.port) dispatcher_task = asyncio.create_task(self.tcp_dispatcher_builtin(server, connection_callback)) else: raise Exception('Unknown protocol "%s"' % self.target.protocol) return dispatcher_task, None except Exception as e: return None, e async def amain(): import logging logger.setLevel(logging.DEBUG) proxy = UniProxyTarget() proxy.server_ip = '127.0.0.1' proxy.server_port = 1080 proxy.protocol = UniProxyProto.SERVER_SOCKS5_TCP target = UniTarget('0.0.0.0', 9999, UniProto.SERVER_TCP, proxies=[proxy]) packetizer = Packetizer() server = UniServer(target, packetizer) async for connection in server.serve(): print('Client connected!') async for data in connection.read(): print(data) def main(): asyncio.run(amain()) if __name__ == '__main__': main()asysocks-0.2.18/asysocks/unicomm/utils/000077500000000000000000000000001510034457600201145ustar00rootroot00000000000000asysocks-0.2.18/asysocks/unicomm/utils/__init__.py000066400000000000000000000000001510034457600222130ustar00rootroot00000000000000asysocks-0.2.18/asysocks/unicomm/utils/genselfsigned.py000066400000000000000000000071571510034457600233150ustar00rootroot00000000000000# Copyright 2018 Simon Davy # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # WARNING: the code in the gist generates self-signed certs, for the purposes of testing in development. # Do not use these certs in production, or You Will Have A Bad Time. # # Caveat emptor # from datetime import datetime, timedelta import ipaddress def generate_selfsigned_cert(hostname, ip_addresses=None, key=None): """Generates self signed certificate for a hostname, and optional IP addresses.""" from cryptography import x509 from cryptography.x509.oid import NameOID from cryptography.hazmat.primitives import hashes from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa # Generate our key if key is None: key = rsa.generate_private_key( public_exponent=65537, key_size=2048, backend=default_backend(), ) name = x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, hostname) ]) # best practice seem to be to include the hostname in the SAN, which *SHOULD* mean COMMON_NAME is ignored. alt_names = [x509.DNSName(hostname)] # allow addressing by IP, for when you don't have real DNS (common in most testing scenarios if ip_addresses: for addr in ip_addresses: # openssl wants DNSnames for ips... alt_names.append(x509.DNSName(addr)) # ... whereas golang's crypto/tls is stricter, and needs IPAddresses # note: older versions of cryptography do not understand ip_address objects alt_names.append(x509.IPAddress(ipaddress.ip_address(addr))) san = x509.SubjectAlternativeName(alt_names) # path_len=0 means this cert can only sign itself, not other certs. basic_contraints = x509.BasicConstraints(ca=True, path_length=0) now = datetime.utcnow() cert = ( x509.CertificateBuilder() .subject_name(name) .issuer_name(name) .public_key(key.public_key()) .serial_number(1000) .not_valid_before(now) .not_valid_after(now + timedelta(days=10*365)) .add_extension(basic_contraints, False) .add_extension(san, False) .sign(key, hashes.SHA256(), default_backend()) ) cert_pem = cert.public_bytes(encoding=serialization.Encoding.PEM) key_pem = key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption(), ) return cert_pem, key_pemasysocks-0.2.18/asysocks/unicomm/utils/paramprocessor.py000066400000000000000000000001611510034457600235240ustar00rootroot00000000000000 def str_one(x): return str(x[0]) def int_one(x): return int(x[0]) def bool_one(x): return bool(int_one(x)) asysocks-0.2.18/asysocks/utils/000077500000000000000000000000001510034457600164455ustar00rootroot00000000000000asysocks-0.2.18/asysocks/utils/__init__.py000066400000000000000000000000001510034457600205440ustar00rootroot00000000000000asysocks-0.2.18/asysocks/utils/mbedtlswrapper.py000066400000000000000000000143611510034457600220570ustar00rootroot00000000000000import asyncio import platform import traceback import logging from mbedtls import tls from asn1crypto.x509 import Certificate class MBEDTLSWrapper: def __init__(self): self.comms = None self.transport_in = None self.transport_out = None self.ssl_ctx = None self.ssl_obj = None self.transport = None self.handshake_done_evt = None self.handshake_timeout = 10 self.__ssl_in_q = None self.stop_evt = None self.server_certificate = None self.__enc_task = None self.__dec_task = None self.__process_task = None async def disconnect(self): try: if self.__enc_task is not None: self.__enc_task.cancel() if self.__dec_task is not None: self.__dec_task.cancel() if self.__process_task is not None: self.__process_task.cancel() if self.transport is not None: self.transport_out.put_nowait(b'') await self.transport.disconnect() except Exception as e: pass async def get_server_pubkey(self): try: if self.server_certificate is None: return None cert = Certificate.load(self.server_certificate) return cert['tbs_certificate']['subject_public_key_info']['public_key'].dump()[5:] #why? except Exception as e: return e def get_server_cert(self): if self.server_certificate is not None: return self.server_certificate async def setup(self): try: self.handshake_done_evt = asyncio.Event() self.stop_evt = asyncio.Event() self.__ssl_in_q = asyncio.Queue() self.ssl_obj = self.ssl_ctx.wrap_buffers(None) _, err = await self.__do_handshake() if err is not None: raise err self.server_certificate = self.ssl_ctx.getpeercert(binary_form=True) print(self.server_certificate) self.__enc_task = asyncio.create_task(self.__encrypt_ssl_rec()) self.__dec_task = asyncio.create_task(self.__decrypt_ssl_rec()) self.__process_task = asyncio.create_task(self.__read_ssl_record()) return True, None except Exception as e: traceback.print_exc() return None, e async def __encrypt_ssl_rec(self): try: while True: try: raw_data = await self.comms.in_queue.get() if raw_data == b'': await self.comms.in_queue.put(None) return self.ssl_obj.write(raw_data) while True: try: enc_data = self.ssl_obj.peek_outgoing(16384) if enc_data == b'': break await self.transport_out.put(enc_data) self.ssl_obj.consume_outgoing(len(enc_data)) except Exception as e: raise e except Exception as e: raise e except asyncio.CancelledError: return except Exception as e: traceback.print_exc() print('__encrypt_ssl_rec %s' % e) logging.debug('__encrypt_ssl_rec %s' % e) async def __decrypt_ssl_rec(self): """ Decrypting the encrypted SSL records from the internal queue and putting them in the queue for the uppr layers to use """ try: while not self.stop_evt.is_set(): ssl_data = await self.__ssl_in_q.get() if ssl_data == b'': await self.comms.out_queue.put((ssl_data, None)) #print('Connection terminated') return self.ssl_obj.receive_from_network(ssl_data) data_buff = b'' while not self.stop_evt.is_set(): try: data_read = self.ssl_obj.read(16384) if data_read == b'': break data_buff += data_read except Exception as e: raise e #print('data_buff %s' % data_buff) if data_buff != b'': await self.comms.out_queue.put((data_buff, None)) except asyncio.CancelledError: return except Exception as e: traceback.print_exc() logging.debug('__decrypt_ssl_rec %s' % e) async def __read_ssl_record(self): try: """ buffering the incoming SSL (encrypted) recodrs and putting them in an internal queue one record at a time """ buffer = b'' length = None while not self.stop_evt.is_set(): if length is None and len(buffer) >= 6: length = int.from_bytes(buffer[3:5], byteorder = 'big', signed = False) if length is not None and len(buffer) >= length + 5: #print('LB raw %s' % len(buffer[:length+5])) await self.__ssl_in_q.put(buffer[:length+5]) buffer = buffer[length+5:] length = None continue data, err = await self.transport_in.get() if err is not None: raise err if data == b'': await self.__ssl_in_q.put(b'') return buffer+= data except asyncio.CancelledError: return except Exception as e: traceback.print_exc() logging.debug('__read_ssl_record %s' % e) await self.__ssl_in_q.put(b'') #finally: # self.stop_evt.set() async def __do_handshake(self): try: ctr = 0 while not self.stop_evt.is_set() and self.ssl_obj.context._state is not tls.HandshakeStep.HANDSHAKE_OVER: ctr += 1 if ctr == 1000: raise Exception('Handshake broke :(') print('DST Performing handshake!') try: self.ssl_obj.do_handshake() data = self.ssl_obj.peek_outgoing(1024) if len(data) == 0: continue await self.transport_out.put(data) self.ssl_obj.consume_outgoing(len(data)) except tls.WantReadError: print('WantReadError %s' % ctr) data = self.ssl_obj.peek_outgoing(1024) if len(data) == 0: continue await self.transport_out.put(data) self.ssl_obj.consume_outgoing(len(data)) continue except tls.WantWriteError: print('WantWriteError %s' % ctr) server_hello, err = await self.transport_in.get() if err is not None: raise err if server_hello == b'': raise Exception('Server closed the connection!') self.ssl_obj.receive_from_network(server_hello) continue except: raise self.handshake_done_evt.set() return True, None except Exception as e: traceback.print_exc() logging.debug('__do_handshake %s' % e) return None, e @staticmethod async def from_commsettings(comms, transport_in, transport_out): try: tunnel = MBEDTLSWrapper() tunnel.transport_in = transport_in tunnel.transport_out = transport_out tunnel.comms = comms tunnel.handshake_timeout = comms.handshake_timeout tunnel.ssl_ctx = tls.ClientContext(tls.TLSConfiguration( validate_certificates=False, )) _, err = await tunnel.setup() if err is not None: raise err return tunnel, None except Exception as e: traceback.print_exc() return None, easysocks-0.2.18/asysocks/utils/sslwrapper.py000066400000000000000000000143311510034457600212230ustar00rootroot00000000000000import asyncio import traceback import logging import ssl from asn1crypto.x509 import Certificate class SSLWrapper: def __init__(self): self.comms = None self.transport_in = None self.transport_out = None self.ssl_ctx = None self.transport = None self.ssl_in_buff = None self.ssl_out_buff = None self.ssl_obj = None self.handshake_done_evt = None self.handshake_timeout = 10 self.__ssl_in_q = None self.stop_evt = None self.server_certificate = None self.__enc_task = None self.__dec_task = None self.__process_task = None async def disconnect(self): try: if self.__enc_task is not None: self.__enc_task.cancel() if self.__dec_task is not None: self.__dec_task.cancel() if self.__process_task is not None: self.__process_task.cancel() if self.transport is not None: self.transport_out.put_nowait(b'') await self.transport.disconnect() except Exception as e: pass async def get_server_pubkey(self): try: if self.server_certificate is None: return None cert = Certificate.load(self.server_certificate) return cert['tbs_certificate']['subject_public_key_info']['public_key'].dump()[5:] #why? except Exception as e: return e def get_server_cert(self): if self.server_certificate is not None: return self.server_certificate async def setup(self): try: self.handshake_done_evt = asyncio.Event() self.stop_evt = asyncio.Event() self.__ssl_in_q = asyncio.Queue() self.ssl_in_buff = ssl.MemoryBIO() self.ssl_out_buff = ssl.MemoryBIO() self.ssl_obj = self.ssl_ctx.wrap_bio(self.ssl_in_buff, self.ssl_out_buff, server_side=False, server_hostname = None) _, err = await self.__do_handshake() if err is not None: raise err self.server_certificate = self.ssl_obj.getpeercert(binary_form=True) self.__enc_task = asyncio.create_task(self.__encrypt_ssl_rec()) self.__dec_task = asyncio.create_task(self.__decrypt_ssl_rec()) self.__process_task = asyncio.create_task(self.__read_ssl_record()) return True, None except Exception as e: return None, e async def __encrypt_ssl_rec(self): try: while True: try: raw_data = await self.comms.in_queue.get() if raw_data == b'': await self.comms.in_queue.put(None) return self.ssl_obj.write(raw_data) while True: try: enc_data = self.ssl_out_buff.read() if enc_data == b'': break await self.transport_out.put(enc_data) except Exception as e: raise e except Exception as e: raise e except asyncio.CancelledError: return except Exception as e: logging.debug('__encrypt_ssl_rec %s' % e) async def __decrypt_ssl_rec(self): """ Decrypting the encrypted SSL records from the internal queue and putting them in the queue for the uppr layers to use """ try: while not self.stop_evt.is_set(): ssl_data = await self.__ssl_in_q.get() if ssl_data == b'': await self.comms.out_queue.put((ssl_data, None)) #print('Connection terminated') return self.ssl_in_buff.write(ssl_data) data_buff = b'' while not self.stop_evt.is_set(): try: data_buff += self.ssl_obj.read() except ssl.SSLWantReadError: break except Exception as e: raise e #print('data_buff %s' % data_buff) if data_buff != b'': await self.comms.out_queue.put((data_buff, None)) except asyncio.CancelledError: return except Exception as e: logging.debug('__decrypt_ssl_rec %s' % e) async def __read_ssl_record(self): try: """ buffering the incoming SSL (encrypted) recodrs and putting them in an internal queue one record at a time """ buffer = b'' length = None while not self.stop_evt.is_set(): if length is None and len(buffer) >= 6: length = int.from_bytes(buffer[3:5], byteorder = 'big', signed = False) if length is not None and len(buffer) >= length + 5: #print('LB raw %s' % len(buffer[:length+5])) await self.__ssl_in_q.put(buffer[:length+5]) buffer = buffer[length+5:] length = None continue data, err = await self.transport_in.get() if err is not None: raise err if data == b'': await self.__ssl_in_q.put(b'') return buffer+= data except asyncio.CancelledError: return except Exception as e: logging.debug('__read_ssl_record %s' % e) await self.__ssl_in_q.put(b'') #finally: # self.stop_evt.set() async def __do_handshake(self): try: ctr = 0 while not self.stop_evt.is_set(): ctr += 1 #print('DST Performing handshake!') try: self.ssl_obj.do_handshake() except ssl.SSLWantReadError: #print('DST want %s' % ctr) while True: client_hello = self.ssl_out_buff.read() if client_hello != b'': #print('DST client_hello %s' % len(client_hello)) await self.transport_out.put(client_hello) else: break #print('DST wating server hello %s' % ctr) server_hello, err = await self.transport_in.get() if err is not None: raise err if server_hello == b'': raise Exception('Server closed the connection!') #print('DST server_hello %s' % len(server_hello)) self.ssl_in_buff.write(server_hello) continue except: raise else: #print('DST handshake ok %s' % ctr) #server_fin = tls_out_buff.read() #print('DST server_fin %s ' % server_fin) #await out_q.put(server_fin) break self.handshake_done_evt.set() return True, None except Exception as e: logging.debug('__do_handshake %s' % e) return None, e @staticmethod async def from_commsettings(comms, transport_in, transport_out): try: tunnel = SSLWrapper() tunnel.transport_in = transport_in tunnel.transport_out = transport_out tunnel.comms = comms tunnel.handshake_timeout = comms.handshake_timeout if comms.ssl_ctx is not None: tunnel.ssl_ctx = comms.ssl_ctx #elif comms.ssl_cert is not None: # raise NotImplementedError() else: tunnel.ssl_ctx = ssl.create_default_context() tunnel.ssl_ctx.check_hostname = False tunnel.ssl_ctx.verify_mode = ssl.CERT_NONE _, err = await tunnel.setup() if err is not None: raise err return tunnel, None except Exception as e: return None, easysocks-0.2.18/builder/000077500000000000000000000000001510034457600150745ustar00rootroot00000000000000asysocks-0.2.18/builder/pyinstaller/000077500000000000000000000000001510034457600174425ustar00rootroot00000000000000asysocks-0.2.18/builder/pyinstaller/build.bat000066400000000000000000000033601510034457600212330ustar00rootroot00000000000000@echo off set projectname=asysocks set hiddenimports= --hidden-import cryptography --hidden-import cffi --hidden-import cryptography.hazmat.backends.openssl --hidden-import cryptography.hazmat.bindings._openssl --hidden-import unicrypto --hidden-import unicrypto.backends.pycryptodome.DES --hidden-import unicrypto.backends.pycryptodome.TDES --hidden-import unicrypto.backends.pycryptodome.AES --hidden-import unicrypto.backends.pycryptodome.RC4 --hidden-import unicrypto.backends.pure.DES --hidden-import unicrypto.backends.pure.TDES --hidden-import unicrypto.backends.pure.AES --hidden-import unicrypto.backends.pure.RC4 --hidden-import unicrypto.backends.cryptography.DES --hidden-import unicrypto.backends.cryptography.TDES --hidden-import unicrypto.backends.cryptography.AES --hidden-import unicrypto.backends.cryptography.RC4 --hidden-import unicrypto.backends.pycryptodomex.DES --hidden-import unicrypto.backends.pycryptodomex.TDES --hidden-import unicrypto.backends.pycryptodomex.AES --hidden-import unicrypto.backends.pycryptodomex.RC4 set root=%~dp0 set repo=%root%..\..\%projectname% IF NOT DEFINED __BUILDALL_VENV__ (GOTO :CREATEVENV) GOTO :BUILD :CREATEVENV python -m venv %root%\env CALL %root%\env\Scripts\activate.bat pip install pyinstaller GOTO :BUILD :BUILD cd %repo%\..\ pip install . cd %repo%\examples pyinstaller -F socksbrute.py -n asysocks-brute %hiddenimports% pyinstaller -F socksportscan.py -n asysocks-portscan %hiddenimports% pyinstaller -F socksproxy.py -n asysocks-proxy %hiddenimports% pyinstaller -F sockssec.py -n asysocks-sec %hiddenimports% pyinstaller -F sockstunnel.py -n asysocks-tunnel %hiddenimports% copy %repo%\examples\dist\*.exe %root% GOTO :CLEANUP :CLEANUP IF NOT DEFINED __BUILDALL_VENV__ (deactivate) cd %root% EXIT /Basysocks-0.2.18/pyproject.toml000066400000000000000000000001301510034457600163540ustar00rootroot00000000000000[build-system] requires = ["setuptools>=61.0.0"] build-backend = "setuptools.build_meta"asysocks-0.2.18/setup.py000066400000000000000000000031341510034457600151610ustar00rootroot00000000000000from setuptools import setup, find_packages import re VERSIONFILE="asysocks/_version.py" verstrline = open(VERSIONFILE, "rt").read() VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]" mo = re.search(VSRE, verstrline, re.M) if mo: verstr = mo.group(1) else: raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,)) setup( # Application name: name="asysocks", # Version number (initial): version=verstr, # Application author details: author="Tamas Jos", author_email="info@skelsecprojects.com", # Packages packages=find_packages(), # Include additional files into the package include_package_data=True, # Details url="https://github.com/skelsec/asysocks", zip_safe = True, # # license="LICENSE.txt", description="", long_description="", # long_description=open("README.txt").read(), python_requires='>=3.6', classifiers=[ "Programming Language :: Python :: 3.6", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ], install_requires=[ 'asn1crypto', 'cryptography', 'h11>=0.14.0', #'netifaces>=0.10.4', #for the server modules, but I dont want to force it on everyone ], entry_points={ 'console_scripts': [ 'asysocks-tunnel = asysocks.examples.sockstunnel:main', 'asysocks-sec = asysocks.examples.sockssec:main', 'asysocks-brute = asysocks.examples.socksbrute:main', 'asysocks-portscan = asysocks.examples.socksportscan:main', 'asysocks-proxy = asysocks.examples.socksproxy:main', 'asysocks-fileserver = asysocks.examples.fileserver:main', 'asysocks-webdav = asysocks.examples.webdavserver:main', ], } )