pax_global_header 0000666 0000000 0000000 00000000064 15100344576 0014517 g ustar 00root root 0000000 0000000 52 comment=c454dbbc8acfacd34d3bb606444d6dec25401a22
asysocks-0.2.18/ 0000775 0000000 0000000 00000000000 15100344576 0013446 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/.github/ 0000775 0000000 0000000 00000000000 15100344576 0015006 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/.github/workflows/ 0000775 0000000 0000000 00000000000 15100344576 0017043 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/.github/workflows/python-windows-exe.yml 0000664 0000000 0000000 00000006536 15100344576 0023370 0 ustar 00root root 0000000 0000000 name: 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/.gitignore 0000664 0000000 0000000 00000003414 15100344576 0015440 0 ustar 00root root 0000000 0000000 # 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/
*.exe asysocks-0.2.18/LICENSE 0000664 0000000 0000000 00000002065 15100344576 0014456 0 ustar 00root root 0000000 0000000 MIT 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/Makefile 0000664 0000000 0000000 00000000563 15100344576 0015112 0 ustar 00root root 0000000 0000000 clean:
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 install asysocks-0.2.18/Manifest.in 0000664 0000000 0000000 00000000042 15100344576 0015540 0 ustar 00root root 0000000 0000000 include LICENSE
include README.md
asysocks-0.2.18/README.md 0000664 0000000 0000000 00000006176 15100344576 0014737 0 ustar 00root root 0000000 0000000  [](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/ 0000775 0000000 0000000 00000000000 15100344576 0015305 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/__init__.py 0000664 0000000 0000000 00000000546 15100344576 0017423 0 ustar 00root root 0000000 0000000 #!/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 = False asysocks-0.2.18/asysocks/_version.py 0000664 0000000 0000000 00000000202 15100344576 0017475 0 ustar 00root root 0000000 0000000 __version__ = "0.2.18"
__banner__ = \
"""
# asysocks %s
# Author: Tamas Jos @skelsec (info@skelsecprojects.com)
""" % __version__ asysocks-0.2.18/asysocks/authentication/ 0000775 0000000 0000000 00000000000 15100344576 0020324 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/authentication/__init__.py 0000664 0000000 0000000 00000000000 15100344576 0022423 0 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/certmanager.py 0000664 0000000 0000000 00000022647 15100344576 0020162 0 ustar 00root root 0000000 0000000
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.py 0000664 0000000 0000000 00000060767 15100344576 0017155 0 ustar 00root root 0000000 0000000
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/ 0000775 0000000 0000000 00000000000 15100344576 0016575 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/common/__init__.py 0000664 0000000 0000000 00000000000 15100344576 0020674 0 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/common/aiowrappers.py 0000664 0000000 0000000 00000003434 15100344576 0021507 0 ustar 00root root 0000000 0000000 import 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.py 0000664 0000000 0000000 00000016702 15100344576 0021156 0 ustar 00root root 0000000 0000000
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.py 0000664 0000000 0000000 00000001266 15100344576 0020272 0 ustar 00root root 0000000 0000000
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_port asysocks-0.2.18/asysocks/common/constants.py 0000664 0000000 0000000 00000001102 15100344576 0021155 0 ustar 00root root 0000000 0000000 import 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 = 0xFF asysocks-0.2.18/asysocks/common/credentials.py 0000664 0000000 0000000 00000000134 15100344576 0021442 0 ustar 00root root 0000000 0000000
class SocksCredential:
def __init__(self):
self.username = None
self.password = None asysocks-0.2.18/asysocks/common/target.py 0000664 0000000 0000000 00000002131 15100344576 0020432 0 ustar 00root root 0000000 0000000
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.py 0000664 0000000 0000000 00000001441 15100344576 0021267 0 ustar 00root root 0000000 0000000
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/ 0000775 0000000 0000000 00000000000 15100344576 0017123 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/examples/__init__.py 0000664 0000000 0000000 00000000000 15100344576 0021222 0 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/examples/fileserver.py 0000664 0000000 0000000 00000233522 15100344576 0021652 0 ustar 00root root 0000000 0000000 #!/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
📍 Current Location: ''' + breadcrumbs + '''
'''
# 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
💡 Drag and drop files or click to browse. Multiple files supported. Large files streamed efficiently.
📂 Directory Contents
📋 Name
📁 Type
📏 Size
🕒 Actions
'''
# 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'''
📁 {escaped_dirname}
Directory
-
Browse
'''
# 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'''
{icon} {escaped_filename}
File
{size_str}
Download
'''
total_items = len(directories) + len(files)
html_content += f'''
📊 Directory Statistics
{len(directories)}
📁 Directories
{total_items}
📋 Total Items
💾 Current path: {escaped_display_path}
💻 Command Line Upload Examples
Use these commands to upload files from your terminal or scripts.
🌐 cURL
📥 wget
⚡ PowerShell
🐍 Python
📋 Copy
# 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.
📋 Copy
# 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.
📋 Copy
# 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+.
📋 Copy
#!/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):
'''
for file_info in uploaded_files:
size_str = self._format_file_size(file_info['size'])
html_content += f' 📄 {html.escape(file_info["filename"])} ({size_str}) \n'
html_content += '''
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.py 0000664 0000000 0000000 00000021242 15100344576 0021662 0 ustar 00root root 0000000 0000000
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.py 0000664 0000000 0000000 00000021214 15100344576 0022371 0 ustar 00root root 0000000 0000000
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.py 0000664 0000000 0000000 00000004537 15100344576 0021732 0 ustar 00root root 0000000 0000000
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.py 0000664 0000000 0000000 00000007425 15100344576 0021322 0 ustar 00root root 0000000 0000000
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.py 0000664 0000000 0000000 00000004575 15100344576 0022060 0 ustar 00root root 0000000 0000000
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.py 0000664 0000000 0000000 00000151176 15100344576 0022207 0 ustar 00root root 0000000 0000000 #!/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)}
Name
Type
Size
Modified
'''
# Add parent directory link
if rel_path != '/':
parent_path = '/'.join(rel_path.rstrip('/').split('/')[:-1]) or '/'
html_content += f'''
../
Directory
-
-
'''
# 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.escape(item)}
{item_type}
{item_size}
{modified}
'''
html_content += '''
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/ 0000775 0000000 0000000 00000000000 15100344576 0020000 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/intercepting/__init__.py 0000664 0000000 0000000 00000000000 15100344576 0022077 0 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/intercepting/default_intercept_table.py 0000664 0000000 0000000 00000000615 15100344576 0025224 0 ustar 00root root 0000000 0000000
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/ 0000775 0000000 0000000 00000000000 15100344576 0021652 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/intercepting/monitors/__init__.py 0000664 0000000 0000000 00000000446 15100344576 0023767 0 ustar 00root root 0000000 0000000
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.py 0000664 0000000 0000000 00000005050 15100344576 0023136 0 ustar 00root root 0000000 0000000 import 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.py 0000664 0000000 0000000 00000003022 15100344576 0024361 0 ustar 00root root 0000000 0000000 import 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.py 0000664 0000000 0000000 00000020015 15100344576 0023656 0 ustar 00root root 0000000 0000000 import 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.py 0000664 0000000 0000000 00000005663 15100344576 0021672 0 ustar 00root root 0000000 0000000
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.py 0000664 0000000 0000000 00000003144 15100344576 0021642 0 ustar 00root root 0000000 0000000 import 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 True asysocks-0.2.18/asysocks/network/ 0000775 0000000 0000000 00000000000 15100344576 0016776 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/network/__init__.py 0000664 0000000 0000000 00000000000 15100344576 0021075 0 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/network/queue.py 0000664 0000000 0000000 00000002564 15100344576 0020503 0 ustar 00root root 0000000 0000000
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.py 0000664 0000000 0000000 00000000000 15100344576 0020124 0 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/network/wsnet.py 0000664 0000000 0000000 00000016740 15100344576 0020520 0 ustar 00root root 0000000 0000000 from 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._closed asysocks-0.2.18/asysocks/network/wsnetdirect.py 0000664 0000000 0000000 00000010246 15100344576 0021706 0 ustar 00root root 0000000 0000000
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, writer asysocks-0.2.18/asysocks/network/wsnetws.py 0000664 0000000 0000000 00000010710 15100344576 0021061 0 ustar 00root root 0000000 0000000 from 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/ 0000775 0000000 0000000 00000000000 15100344576 0017146 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/protocol/__init__.py 0000664 0000000 0000000 00000000000 15100344576 0021245 0 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/protocol/http.py 0000664 0000000 0000000 00000005435 15100344576 0020506 0 ustar 00root root 0000000 0000000 import 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, e asysocks-0.2.18/asysocks/protocol/socks4.py 0000664 0000000 0000000 00000006322 15100344576 0020731 0 ustar 00root root 0000000 0000000
# 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.py 0000664 0000000 0000000 00000007511 15100344576 0021073 0 ustar 00root root 0000000 0000000
# 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.py 0000664 0000000 0000000 00000047611 15100344576 0020740 0 ustar 00root root 0000000 0000000 #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 t asysocks-0.2.18/asysocks/security.py 0000664 0000000 0000000 00000031030 15100344576 0017523 0 ustar 00root root 0000000 0000000
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.py 0000664 0000000 0000000 00000043146 15100344576 0017175 0 ustar 00root root 0000000 0000000
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/ 0000775 0000000 0000000 00000000000 15100344576 0016264 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/test/__init__.py 0000664 0000000 0000000 00000000000 15100344576 0020363 0 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/test/cert.pem 0000664 0000000 0000000 00000003371 15100344576 0017730 0 ustar 00root root 0000000 0000000 -----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.pem 0000664 0000000 0000000 00000006526 15100344576 0017570 0 ustar 00root root 0000000 0000000 -----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.py 0000664 0000000 0000000 00000001550 15100344576 0021523 0 ustar 00root root 0000000 0000000
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.py 0000664 0000000 0000000 00000002260 15100344576 0021021 0 ustar 00root root 0000000 0000000
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.py 0000664 0000000 0000000 00000006275 15100344576 0020510 0 ustar 00root root 0000000 0000000
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.py 0000664 0000000 0000000 00000000714 15100344576 0021661 0 ustar 00root root 0000000 0000000 import 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.py 0000664 0000000 0000000 00000005751 15100344576 0020706 0 ustar 00root root 0000000 0000000
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, e asysocks-0.2.18/asysocks/unicomm/ 0000775 0000000 0000000 00000000000 15100344576 0016754 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/unicomm/__init__.py 0000664 0000000 0000000 00000000416 15100344576 0021066 0 ustar 00root root 0000000 0000000 import 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.py 0000664 0000000 0000000 00000043540 15100344576 0020612 0 ustar 00root root 0000000 0000000 import 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/ 0000775 0000000 0000000 00000000000 15100344576 0020244 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/unicomm/common/__init__.py 0000664 0000000 0000000 00000000000 15100344576 0022343 0 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/unicomm/common/connection.py 0000664 0000000 0000000 00000023053 15100344576 0022760 0 ustar 00root root 0000000 0000000 import 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/ 0000775 0000000 0000000 00000000000 15100344576 0022570 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/unicomm/common/packetizers/__init__.py 0000664 0000000 0000000 00000005242 15100344576 0024704 0 ustar 00root root 0000000 0000000 import 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.py 0000664 0000000 0000000 00000007441 15100344576 0023751 0 ustar 00root root 0000000 0000000 import 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.py 0000664 0000000 0000000 00000017773 15100344576 0022016 0 ustar 00root root 0000000 0000000 import 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/ 0000775 0000000 0000000 00000000000 15100344576 0021675 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/unicomm/common/scanner/__init__.py 0000664 0000000 0000000 00000000000 15100344576 0023774 0 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/unicomm/common/scanner/common.py 0000664 0000000 0000000 00000007613 15100344576 0023546 0 ustar 00root root 0000000 0000000 import 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.py 0000664 0000000 0000000 00000014143 15100344576 0023703 0 ustar 00root root 0000000 0000000 import 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.py 0000664 0000000 0000000 00000014003 15100344576 0024225 0 ustar 00root root 0000000 0000000
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.py 0000664 0000000 0000000 00000015624 15100344576 0022114 0 ustar 00root root 0000000 0000000 from 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 t asysocks-0.2.18/asysocks/unicomm/common/transport.py 0000664 0000000 0000000 00000010540 15100344576 0022652 0 ustar 00root root 0000000 0000000 from 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.py 0000664 0000000 0000000 00000015044 15100344576 0022137 0 ustar 00root root 0000000 0000000 import 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/ 0000775 0000000 0000000 00000000000 15100344576 0020615 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/unicomm/protocol/__init__.py 0000664 0000000 0000000 00000000000 15100344576 0022714 0 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/unicomm/protocol/client/ 0000775 0000000 0000000 00000000000 15100344576 0022073 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/unicomm/protocol/client/__init__.py 0000664 0000000 0000000 00000000125 15100344576 0024202 0 ustar 00root root 0000000 0000000 import logging
logger = logging.getLogger('asysocks.client')
logger.propagate = True asysocks-0.2.18/asysocks/unicomm/protocol/client/http/ 0000775 0000000 0000000 00000000000 15100344576 0023052 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/unicomm/protocol/client/http/__init__.py 0000664 0000000 0000000 00000000132 15100344576 0025157 0 ustar 00root root 0000000 0000000 import logging
logger = logging.getLogger('asysocks.client.http')
logger.propagate = True asysocks-0.2.18/asysocks/unicomm/protocol/client/http/auth.py 0000664 0000000 0000000 00000017601 15100344576 0024372 0 ustar 00root root 0000000 0000000 import 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.py 0000664 0000000 0000000 00000012357 15100344576 0024712 0 ustar 00root root 0000000 0000000
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/ 0000775 0000000 0000000 00000000000 15100344576 0024525 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/unicomm/protocol/client/http/commons/__init__.py 0000664 0000000 0000000 00000000000 15100344576 0026624 0 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/unicomm/protocol/client/http/commons/factory.py 0000664 0000000 0000000 00000002542 15100344576 0026551 0 ustar 00root root 0000000 0000000
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 t asysocks-0.2.18/asysocks/unicomm/protocol/client/http/commons/messages.py 0000664 0000000 0000000 00000006747 15100344576 0026724 0 ustar 00root root 0000000 0000000 import 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 t asysocks-0.2.18/asysocks/unicomm/protocol/client/http/commons/target.py 0000664 0000000 0000000 00000011365 15100344576 0026373 0 ustar 00root root 0000000 0000000 from 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.py 0000664 0000000 0000000 00000002663 15100344576 0025631 0 ustar 00root root 0000000 0000000
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.py 0000664 0000000 0000000 00000010715 15100344576 0025464 0 ustar 00root root 0000000 0000000 import 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 response asysocks-0.2.18/asysocks/unicomm/protocol/http.py 0000664 0000000 0000000 00000005435 15100344576 0022155 0 ustar 00root root 0000000 0000000 import 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, e asysocks-0.2.18/asysocks/unicomm/protocol/quic.py 0000664 0000000 0000000 00000006176 15100344576 0022142 0 ustar 00root root 0000000 0000000 import 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/ 0000775 0000000 0000000 00000000000 15100344576 0021731 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/unicomm/protocol/relay/__init__.py 0000664 0000000 0000000 00000000000 15100344576 0024030 0 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/unicomm/protocol/relay/http.py 0000664 0000000 0000000 00000014321 15100344576 0023263 0 ustar 00root root 0000000 0000000 import 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/ 0000775 0000000 0000000 00000000000 15100344576 0022123 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/unicomm/protocol/server/__init__.py 0000664 0000000 0000000 00000000000 15100344576 0024222 0 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/unicomm/protocol/server/http/ 0000775 0000000 0000000 00000000000 15100344576 0023102 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/unicomm/protocol/server/http/__init__.py 0000664 0000000 0000000 00000000000 15100344576 0025201 0 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/unicomm/protocol/server/http/httprelay.py 0000664 0000000 0000000 00000017371 15100344576 0025501 0 ustar 00root root 0000000 0000000
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 GMT 0 HTTP/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.py 0000664 0000000 0000000 00000024204 15100344576 0025664 0 ustar 00root root 0000000 0000000
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.py 0000664 0000000 0000000 00000000772 15100344576 0023273 0 ustar 00root root 0000000 0000000 import 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/ 0000775 0000000 0000000 00000000000 15100344576 0024111 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/unicomm/protocol/server/websocket/__init__.py 0000664 0000000 0000000 00000000000 15100344576 0026210 0 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/unicomm/protocol/server/websocket/server.py 0000664 0000000 0000000 00000023652 15100344576 0026001 0 ustar 00root root 0000000 0000000
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 = WebSocketServer asysocks-0.2.18/asysocks/unicomm/protocol/server/websocket/test.py 0000664 0000000 0000000 00000000651 15100344576 0025444 0 ustar 00root root 0000000 0000000 import 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.py 0000664 0000000 0000000 00000000572 15100344576 0027004 0 ustar 00root root 0000000 0000000 import 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.py 0000664 0000000 0000000 00000006210 15100344576 0022374 0 ustar 00root root 0000000 0000000
# 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.py 0000664 0000000 0000000 00000007430 15100344576 0022542 0 ustar 00root root 0000000 0000000
# 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.py 0000664 0000000 0000000 00000047610 15100344576 0022406 0 ustar 00root root 0000000 0000000 #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 t asysocks-0.2.18/asysocks/unicomm/server.py 0000664 0000000 0000000 00000051070 15100344576 0020637 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 15100344576 0020114 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/unicomm/utils/__init__.py 0000664 0000000 0000000 00000000000 15100344576 0022213 0 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/unicomm/utils/genselfsigned.py 0000664 0000000 0000000 00000007157 15100344576 0023315 0 ustar 00root root 0000000 0000000 # 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_pem asysocks-0.2.18/asysocks/unicomm/utils/paramprocessor.py 0000664 0000000 0000000 00000000161 15100344576 0023524 0 ustar 00root root 0000000 0000000
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/ 0000775 0000000 0000000 00000000000 15100344576 0016445 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/utils/__init__.py 0000664 0000000 0000000 00000000000 15100344576 0020544 0 ustar 00root root 0000000 0000000 asysocks-0.2.18/asysocks/utils/mbedtlswrapper.py 0000664 0000000 0000000 00000014361 15100344576 0022057 0 ustar 00root root 0000000 0000000 import 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, e asysocks-0.2.18/asysocks/utils/sslwrapper.py 0000664 0000000 0000000 00000014331 15100344576 0021223 0 ustar 00root root 0000000 0000000 import 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, e asysocks-0.2.18/builder/ 0000775 0000000 0000000 00000000000 15100344576 0015074 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/builder/pyinstaller/ 0000775 0000000 0000000 00000000000 15100344576 0017442 5 ustar 00root root 0000000 0000000 asysocks-0.2.18/builder/pyinstaller/build.bat 0000664 0000000 0000000 00000003360 15100344576 0021233 0 ustar 00root root 0000000 0000000 @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 /B asysocks-0.2.18/pyproject.toml 0000664 0000000 0000000 00000000130 15100344576 0016354 0 ustar 00root root 0000000 0000000 [build-system]
requires = ["setuptools>=61.0.0"]
build-backend = "setuptools.build_meta" asysocks-0.2.18/setup.py 0000664 0000000 0000000 00000003134 15100344576 0015161 0 ustar 00root root 0000000 0000000 from 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',
],
}
)