1%%% -*- erlang -*- 2%%% 3%%% This file is part of hackney released under the Apache 2 license. 4%%% See the NOTICE for more information. 5%%% 6 7%% @doc socks 5 transport 8 9-module(hackney_socks5). 10 11-export([messages/1, 12 connect/3, connect/4, 13 recv/2, recv/3, 14 send/2, 15 setopts/2, 16 controlling_process/2, 17 peername/1, 18 close/1, 19 shutdown/2, 20 sockname/1]). 21 22-define(TIMEOUT, infinity). 23 24-type socks5_socket() :: {atom(), inet:socket()}. 25-export_type([socks5_socket/0]). 26 27%% @doc Atoms used to identify messages in {active, once | true} mode. 28messages({hackney_ssl, _}) -> 29 {ssl, ssl_closed, ssl_error}; 30messages({_, _}) -> 31 {tcp, tcp_closed, tcp_error}. 32 33 34connect(Host, Port, Opts) -> 35 connect(Host, Port, Opts, infinity). 36 37 38connect(Host, Port, Opts, Timeout) when is_list(Host), is_integer(Port), 39 (Timeout =:= infinity orelse is_integer(Timeout)) -> 40 %% get the proxy host and port from the options 41 ProxyHost = proplists:get_value(socks5_host, Opts), 42 ProxyPort = proplists:get_value(socks5_port, Opts), 43 Transport = proplists:get_value(socks5_transport, Opts), 44 45 %% filter connection options 46 AcceptedOpts = [linger, nodelay, send_timeout, 47 send_timeout_close, raw, inet6], 48 BaseOpts = [binary, {active, false}, {packet, 0}, {keepalive, true}, 49 {nodelay, true}], 50 ConnectOpts = hackney_util:filter_options(Opts, AcceptedOpts, BaseOpts), 51 52 %% connect to the socks 5 proxy 53 case gen_tcp:connect(ProxyHost, ProxyPort, ConnectOpts, Timeout) of 54 {ok, Socket} -> 55 case do_handshake(Socket, Host, Port, Opts) of 56 ok -> 57 case Transport of 58 hackney_ssl -> 59 SSlOpts = hackney_connect:ssl_opts(Host, Opts), 60 %% upgrade the tcp connection 61 case ssl:connect(Socket, SSlOpts) of 62 {ok, SslSocket} -> 63 {ok, {Transport, SslSocket}}; 64 Error -> 65 gen_tcp:close(Socket), 66 Error 67 end; 68 _ -> 69 {ok, {Transport, Socket}} 70 end; 71 Error -> 72 gen_tcp:close(Socket), 73 Error 74 end; 75 Error -> 76 Error 77 end. 78 79 80recv(Socket, Length) -> 81 recv(Socket, Length, infinity). 82 83%% @doc Receive a packet from a socket in passive mode. 84%% @see gen_tcp:recv/3 85-spec recv(socks5_socket(), non_neg_integer(), timeout()) 86 -> {ok, any()} | {error, closed | atom()}. 87recv({Transport, Socket}, Length, Timeout) -> 88 Transport:recv(Socket, Length, Timeout). 89 90 91%% @doc Send a packet on a socket. 92%% @see gen_tcp:send/2 93-spec send(socks5_socket(), iolist()) -> ok | {error, atom()}. 94send({Transport, Socket}, Packet) -> 95 Transport:send(Socket, Packet). 96 97%% @doc Set one or more options for a socket. 98%% @see inet:setopts/2 99-spec setopts(socks5_socket(), list()) -> ok | {error, atom()}. 100setopts({Transport, Socket}, Opts) -> 101 Transport:setopts(Socket, Opts). 102 103%% @doc Assign a new controlling process <em>Pid</em> to <em>Socket</em>. 104%% @see gen_tcp:controlling_process/2 105-spec controlling_process(socks5_socket(), pid()) 106 -> ok | {error, closed | not_owner | atom()}. 107controlling_process({Transport, Socket}, Pid) -> 108 Transport:controlling_process(Socket, Pid). 109 110%% @doc Return the address and port for the other end of a connection. 111%% @see inet:peername/1 112-spec peername(socks5_socket()) 113 -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. 114peername({Transport, Socket}) -> 115 Transport:peername(Socket). 116 117%% @doc Close a socks5 socket. 118%% @see gen_tcp:close/1 119-spec close(socks5_socket()) -> ok. 120close({Transport, Socket}) -> 121 Transport:close(Socket). 122 123%% @doc Immediately close a socket in one or two directions. 124%% @see gen_tcp:shutdown/2 125-spec shutdown(socks5_socket(), read | write | read_write) -> ok. 126shutdown({Transport, Socket}, How) -> 127 Transport:shutdown(Socket, How). 128 129%% @doc Get the local address and port of a socket 130%% @see inet:sockname/1 131-spec sockname(socks5_socket()) 132 -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. 133sockname({Transport, Socket}) -> 134 Transport:sockname(Socket). 135 136%% private functions 137do_handshake(Socket, Host, Port, Options) -> 138 ProxyUser = proplists:get_value(socks5_user, Options), 139 ProxyPass = proplists:get_value(socks5_pass, Options, <<>>), 140 case ProxyUser of 141 undefined -> 142 %% no auth 143 ok = gen_tcp:send(Socket, << 5, 1, 0 >>), 144 case gen_tcp:recv(Socket, 2, ?TIMEOUT) of 145 {ok, << 5, 0 >>} -> 146 do_connection(Socket, Host, Port, Options); 147 {ok, _Reply} -> 148 {error, unknown_reply}; 149 Error -> 150 Error 151 end; 152 _ -> 153 case do_authentication(Socket, ProxyUser, ProxyPass) of 154 ok -> 155 do_connection(Socket, Host, Port, Options); 156 Error -> 157 Error 158 end 159 end. 160 161do_authentication(Socket, User, Pass) -> 162 ok = gen_tcp:send(Socket, << 5, 1, 2 >>), 163 case gen_tcp:recv(Socket, 2, ?TIMEOUT) of 164 {ok, <<5, 0>>} -> 165 ok; 166 {ok, <<5, 2>>} -> 167 UserLength = byte_size(User), 168 PassLength = byte_size(Pass), 169 Msg = iolist_to_binary([<< 1, UserLength >>, 170 User, << PassLength >>, 171 Pass]), 172 ok = gen_tcp:send(Socket, Msg), 173 case gen_tcp:recv(Socket, 2, ?TIMEOUT) of 174 {ok, <<1, 0>>} -> 175 ok; 176 _ -> 177 {error, not_authenticated} 178 end; 179 _ -> 180 {error, not_authenticated} 181 end. 182 183 184do_connection(Socket, Host, Port, Options) -> 185 Resolve = proplists:get_value(socks5_resolve, Options, remote), 186 case addr(Host, Port, Resolve) of 187 Addr when is_binary(Addr) -> 188 ok = gen_tcp:send(Socket, << 5, 1, 0, Addr/binary >>), 189 case gen_tcp:recv(Socket, 10, ?TIMEOUT) of 190 {ok, << 5, 0, 0, BoundAddr/binary >>} -> 191 check_connection(BoundAddr); 192 {ok, _} -> 193 {error, badarg}; 194 Error -> 195 Error 196 end; 197 Error -> 198 Error 199 end. 200 201addr(Host, Port, Resolve) -> 202 case inet_parse:address(Host) of 203 {ok, {IP1, IP2, IP3, IP4}} -> 204 << 1, IP1, IP2, IP3, IP4, Port:16 >>; 205 {ok, {IP1, IP2, IP3, IP4, IP5, IP6, IP7, IP8}} -> 206 << 4, IP1, IP2, IP3, IP4, IP5, IP6, IP7, IP8, Port:16 >>; 207 _ -> %% domain name 208 case Resolve of 209 local -> 210 case inet:getaddr(Host, inet) of 211 {ok, {IP1, IP2, IP3, IP4}} -> 212 << 1, IP1, IP2, IP3, IP4, Port:16 >>; 213 Error -> 214 case inet:getaddr(Host, inet6) of 215 {ok, {IP1, IP2, IP3, IP4, IP5, IP6, IP7, IP8}} -> 216 << 4, IP1, IP2, IP3, IP4, IP5, IP6, IP7, IP8, Port:16 >>; 217 _ -> 218 Error 219 end 220 end; 221 _Remote -> 222 Host1 = list_to_binary(Host), 223 HostLength = byte_size(Host1), 224 << 3, HostLength, Host1/binary, Port:16 >> 225 end 226 end. 227 228check_connection(<< 3, _DomainLen:8, _Domain/binary >>) -> 229 ok; 230check_connection(<< 1, _Addr:32, _Port:16 >>) -> 231 ok; 232check_connection(<< 4, _Addr:128, _Port:16 >>) -> 233 ok; 234check_connection(_) -> 235 {error, no_connection}. 236