%PDF- %PDF-
| Direktori : /lib/python3/dist-packages/socksio/ |
| Current File : //lib/python3/dist-packages/socksio/socks4.py |
import enum
import typing
from ._types import StrOrBytes
from .exceptions import ProtocolError, SOCKSError
from .utils import (
AddressType,
decode_address,
encode_address,
get_address_port_tuple_from_address,
)
class SOCKS4ReplyCode(bytes, enum.Enum):
"""Enumeration of SOCKS4 reply codes."""
REQUEST_GRANTED = b"\x5A"
REQUEST_REJECTED_OR_FAILED = b"\x5B"
CONNECTION_FAILED = b"\x5C"
AUTHENTICATION_FAILED = b"\x5D"
class SOCKS4Command(bytes, enum.Enum):
"""Enumeration of SOCKS4 command codes."""
CONNECT = b"\x01"
BIND = b"\x02"
class SOCKS4Request(typing.NamedTuple):
"""Encapsulates a request to the SOCKS4 proxy server
Args:
command: The command to request.
port: The port number to connect to on the target host.
addr: IP address of the target host.
user_id: Optional user ID to be included in the request, if not supplied
the user *must* provide one in the packing operation.
"""
command: SOCKS4Command
port: int
addr: bytes
user_id: typing.Optional[bytes] = None
@classmethod
def from_address(
cls,
command: SOCKS4Command,
address: typing.Union[StrOrBytes, typing.Tuple[StrOrBytes, int]],
user_id: typing.Optional[bytes] = None,
) -> "SOCKS4Request":
"""Convenience class method to build an instance from command and address.
Args:
command: The command to request.
address: A string in the form 'HOST:PORT' or a tuple of ip address string
and port number.
user_id: Optional user ID.
Returns:
A SOCKS4Request instance.
Raises:
SOCKSError: If a domain name or IPv6 address was supplied.
"""
address, port = get_address_port_tuple_from_address(address)
atype, encoded_addr = encode_address(address)
if atype != AddressType.IPV4:
raise SOCKSError(
"IPv6 addresses and domain names are not supported by SOCKS4"
)
return cls(command=command, addr=encoded_addr, port=port, user_id=user_id)
def dumps(self, user_id: typing.Optional[bytes] = None) -> bytes:
"""Packs the instance into a raw binary in the appropriate form.
Args:
user_id: Optional user ID as an override, if not provided the instance's
will be used, if none was provided at initialization an error is raised.
Returns:
The packed request.
Raises:
SOCKSError: If no user was specified in this call or on initialization.
"""
user_id = user_id or self.user_id
if user_id is None:
raise SOCKSError("SOCKS4 requires a user_id, none was specified")
return b"".join(
[
b"\x04",
self.command,
(self.port).to_bytes(2, byteorder="big"),
self.addr,
user_id,
b"\x00",
]
)
class SOCKS4ARequest(typing.NamedTuple):
"""Encapsulates a request to the SOCKS4A proxy server
Args:
command: The command to request.
port: The port number to connect to on the target host.
addr: IP address of the target host.
user_id: Optional user ID to be included in the request, if not supplied
the user *must* provide one in the packing operation.
"""
command: SOCKS4Command
port: int
addr: bytes
user_id: typing.Optional[bytes] = None
@classmethod
def from_address(
cls,
command: SOCKS4Command,
address: typing.Union[StrOrBytes, typing.Tuple[StrOrBytes, int]],
user_id: typing.Optional[bytes] = None,
) -> "SOCKS4ARequest":
"""Convenience class method to build an instance from command and address.
Args:
command: The command to request.
address: A string in the form 'HOST:PORT' or a tuple of ip address string
and port number.
user_id: Optional user ID.
Returns:
A SOCKS4ARequest instance.
"""
address, port = get_address_port_tuple_from_address(address)
atype, encoded_addr = encode_address(address)
return cls(command=command, addr=encoded_addr, port=port, user_id=user_id)
def dumps(self, user_id: typing.Optional[bytes] = None) -> bytes:
"""Packs the instance into a raw binary in the appropriate form.
Args:
user_id: Optional user ID as an override, if not provided the instance's
will be used, if none was provided at initialization an error is raised.
Returns:
The packed request.
Raises:
SOCKSError: If no user was specified in this call or on initialization.
"""
user_id = user_id or self.user_id
if user_id is None:
raise SOCKSError("SOCKS4 requires a user_id, none was specified")
return b"".join(
[
b"\x04",
self.command,
(self.port).to_bytes(2, byteorder="big"),
b"\x00\x00\x00\xFF", # arbitrary final non-zero byte
user_id,
b"\x00",
self.addr,
b"\x00",
]
)
class SOCKS4Reply(typing.NamedTuple):
"""Encapsulates a reply from the SOCKS4 proxy server
Args:
reply_code: The code representing the type of reply.
port: The port number returned.
addr: Optional IP address returned.
"""
reply_code: SOCKS4ReplyCode
port: int
addr: typing.Optional[str]
@classmethod
def loads(cls, data: bytes) -> "SOCKS4Reply":
"""Unpacks the reply data into an instance.
Returns:
The unpacked reply instance.
Raises:
ProtocolError: If the data does not match the spec.
"""
if len(data) != 8 or data[0:1] != b"\x00":
raise ProtocolError("Malformed reply")
try:
return cls(
reply_code=SOCKS4ReplyCode(data[1:2]),
port=int.from_bytes(data[2:4], byteorder="big"),
addr=decode_address(AddressType.IPV4, data[4:8]),
)
except ValueError as exc:
raise ProtocolError("Malformed reply") from exc
class SOCKS4Connection:
"""Encapsulates a SOCKS4 and SOCKS4A connection.
Packs request objects into data suitable to be send and unpacks reply
data into their appropriate reply objects.
Args:
user_id: The user ID to be sent as part of the requests.
"""
def __init__(self, user_id: bytes):
self.user_id = user_id
self._data_to_send = bytearray()
self._received_data = bytearray()
def send(self, request: typing.Union[SOCKS4Request, SOCKS4ARequest]) -> None:
"""Packs a request object and adds it to the send data buffer.
Args:
request: The request instance to be packed.
"""
user_id = request.user_id or self.user_id
self._data_to_send += request.dumps(user_id=user_id)
def receive_data(self, data: bytes) -> SOCKS4Reply:
"""Unpacks response data into a reply object.
Args:
data: The raw response data from the proxy server.
Returns:
The appropriate reply object.
"""
self._received_data += data
return SOCKS4Reply.loads(bytes(self._received_data))
def data_to_send(self) -> bytes:
"""Returns the data to be sent via the I/O library of choice.
Also clears the connection's buffer.
"""
data = bytes(self._data_to_send)
self._data_to_send = bytearray()
return data