defmodule GSMLG.Socket.SOCKS do
use GSMLG.Socket.Helpers
def connect(to, through, options \\ []) do
[address, port | auth] = through |> Tuple.to_list()
with {:ok, socket} <- pre(address, port, options),
:ok <- handshake(socket, options[:version] || 5, auth |> List.to_tuple(), to),
:ok <- post(socket, options) do
{:ok, socket}
else
{:error, reason} ->
{:error, reason}
end
end
defbang(connect(to, through))
defbang(connect(to, through, options))
defp pre(address, port, options) do
options =
if options[:mode] == :active || options[:mode] == :once do
options
|> Keyword.put(:mode, :passive)
else
options
end
GSMLG.Socket.TCP.connect(address, port, options)
end
defp handshake(socket, 4, auth, {address, port}) do
user =
if tuple_size(auth) >= 1 do
auth |> elem(0)
else
""
end
case GSMLG.Socket.Address.parse(address) do
{a, b, c, d} ->
case socket
|> GSMLG.Socket.Stream.send(<<
# version
0x04::8,
# make a stream TCP connection
0x01::8,
# port in network byte order
port::big-size(16),
# IP address in network byte order
a::8,
b::8,
c::8,
d::8,
# user name followed by NULL
user::binary,
0x00::8
>>) do
:ok ->
response(socket, 4)
{:error, reason} ->
{:error, reason}
end
nil ->
case socket
|> GSMLG.Socket.Stream.send(<<
# version
0x04::8,
# make a stream TCP connection
0x01::8,
# port in network byte order
port::big-size(16),
# invalid IP address
0x00::8,
0x00::8,
0x00::8,
0xFF::8,
# user name followed by NULL
user::binary,
0x00::8,
# host followed by NULL
address::binary,
0x00::8
>>) do
:ok ->
response(socket, 4)
{:error, reason} ->
{:error, reason}
end
end
end
defp handshake(_socket, 5, _auth, {_address, _port}) do
{:error, :hue}
end
defp response(socket, 4) do
case socket |> GSMLG.Socket.Stream.recv(8) do
# request granted
{:ok, <<0x00::8, 0x5A::8, _::16, _::32>>} ->
:ok
# request rejected or failed
{:ok, <<0x00::8, 0x5B::8, _::16, _::32>>} ->
{:error, :rejected}
# request failed because client is not running identd (or not
# reachable from the server)
{:ok, <<0x00::8, 0x5C::8, _::16, _::32>>} ->
{:error, :unreachable}
# request failed because client's identd could not confirm the
# user ID string in the request
{:ok, <<0x00::8, 0x5D::8, _::16, _::32>>} ->
{:error, :unauthorized}
{:error, reason} ->
{:error, reason}
end
end
defp response(_socket, 5) do
{:error, :hue}
end
defp post(socket, options) do
cond do
options[:mode] == :active ->
socket |> GSMLG.Socket.active()
options[:mode] == :once ->
socket |> GSMLG.Socket.active(:once)
true ->
:ok
end
end
end