| %%%------------------------------------------------------------------- |
| %%% @author Chandru Mullaparthi <> |
| %%% @copyright (C) 2016, Chandru Mullaparthi |
| %%% @doc |
| %%% |
| %%% @end |
| %%% Created : 19 Apr 2016 by Chandru Mullaparthi <> |
| %%%------------------------------------------------------------------- |
| -module(ibrowse_socks_server). |
| |
| -behaviour(gen_server). |
| |
| %% API |
| -export([start/2, stop/1]). |
| |
| %% gen_server callbacks |
| -export([init/1, handle_call/3, handle_cast/2, handle_info/2, |
| terminate/2, code_change/3]). |
| |
| -define(SERVER, ?MODULE). |
| |
| -record(state, {listen_port, listen_socket, auth_method}). |
| |
| -define(NO_AUTH, 0). |
| -define(AUTH_USER_PW, 2). |
| |
| %%%=================================================================== |
| %%% API |
| %%%=================================================================== |
| |
| start(Port, Auth_method) -> |
| Name = make_proc_name(Port), |
| gen_server:start({local, Name}, ?MODULE, [Port, Auth_method], []). |
| |
| stop(Port) -> |
| make_proc_name(Port) ! stop. |
| |
| make_proc_name(Port) -> |
| list_to_atom("ibrowse_socks_server_" ++ integer_to_list(Port)). |
| |
| %%%=================================================================== |
| %%% gen_server callbacks |
| %%%=================================================================== |
| |
| init([Port, Auth_method]) -> |
| State = #state{listen_port = Port, auth_method = Auth_method}, |
| {ok, Sock} = gen_tcp:listen(State#state.listen_port, [{active, false}, binary, {reuseaddr, true}]), |
| self() ! accept_connection, |
| process_flag(trap_exit, true), |
| {ok, State#state{listen_socket = Sock}}. |
| |
| handle_call(_Request, _From, State) -> |
| Reply = ok, |
| {reply, Reply, State}. |
| |
| handle_cast(_Msg, State) -> |
| {noreply, State}. |
| |
| handle_info(accept_connection, State) -> |
| case gen_tcp:accept(State#state.listen_socket, 1000) of |
| {error, timeout} -> |
| self() ! accept_connection, |
| {noreply, State}; |
| {ok, Socket} -> |
| Pid = proc_lib:spawn_link(fun() -> |
| socks_server_loop(Socket, State#state.auth_method) |
| end), |
| gen_tcp:controlling_process(Socket, Pid), |
| Pid ! ready, |
| self() ! accept_connection, |
| {noreply, State}; |
| _Err -> |
| {stop, normal, State} |
| end; |
| |
| handle_info(stop, State) -> |
| {stop, normal, State}; |
| |
| handle_info(_Info, State) -> |
| {noreply, State}. |
| |
| terminate(_Reason, _State) -> |
| ok. |
| |
| code_change(_OldVsn, State, _Extra) -> |
| {ok, State}. |
| |
| %%%=================================================================== |
| %%% Internal functions |
| %%%=================================================================== |
| socks_server_loop(In_socket, Auth_method) -> |
| receive |
| ready -> |
| socks_server_loop(In_socket, Auth_method, <<>>, unauth) |
| end. |
| |
| socks_server_loop(In_socket, Auth_method, Acc, unauth) -> |
| inet:setopts(In_socket, [{active, once}]), |
| receive |
| {tcp, In_socket, Data} -> |
| Acc_1 = list_to_binary([Acc, Data]), |
| case Acc_1 of |
| <<5, ?NO_AUTH>> when Auth_method == ?NO_AUTH -> |
| ok = gen_tcp:send(In_socket, <<5, ?NO_AUTH>>), |
| socks_server_loop(In_socket, Auth_method, <<>>, auth_done); |
| <<5, Num_auth_methods, Auth_methods:Num_auth_methods/binary>> -> |
| case lists:member(Auth_method, binary_to_list(Auth_methods)) of |
| true -> |
| ok = gen_tcp:send(In_socket, <<5, Auth_method>>), |
| Conn_state = case Auth_method of |
| ?NO_AUTH -> auth_done; |
| _ -> auth_pending |
| end, |
| socks_server_loop(In_socket, Auth_method, <<>>, Conn_state); |
| false -> |
| ok = gen_tcp:send(In_socket, <<5, 16#ff>>), |
| gen_tcp:close(In_socket) |
| end; |
| _ -> |
| ok = gen_tcp:send(In_socket, <<5, 0>>), |
| gen_tcp:close(In_socket) |
| end; |
| {tcp_closed, In_socket} -> |
| ok; |
| {tcp_error, In_socket, _Rsn} -> |
| ok |
| end; |
| socks_server_loop(In_socket, Auth_method, Acc, auth_pending) -> |
| inet:setopts(In_socket, [{active, once}]), |
| receive |
| {tcp, In_socket, Data} -> |
| Acc_1 = list_to_binary([Acc, Data]), |
| case Acc_1 of |
| <<1, U_len, Username:U_len/binary, P_len, Password:P_len/binary>> -> |
| case check_user_pw(Username, Password) of |
| ok -> |
| ok = gen_tcp:send(In_socket, <<1, 0>>), |
| socks_server_loop(In_socket, Auth_method, <<>>, auth_done); |
| notok -> |
| ok = gen_tcp:send(In_socket, <<1, 1>>), |
| gen_tcp:close(In_socket) |
| end; |
| _ -> |
| socks_server_loop(In_socket, Auth_method, Acc_1, auth_pending) |
| end; |
| {tcp_closed, In_socket} -> |
| ok; |
| {tcp_error, In_socket, _Rsn} -> |
| ok |
| end; |
| socks_server_loop(In_socket, Auth_method, Acc, auth_done) -> |
| inet:setopts(In_socket, [{active, once}]), |
| receive |
| {tcp, In_socket, Data} -> |
| Acc_1 = list_to_binary([Acc, Data]), |
| case Acc_1 of |
| <<5, 1, 0, Addr_type, Dest_ip:4/binary, Dest_port:16>> when Addr_type == 1-> |
| handle_connect(In_socket, Addr_type, Dest_ip, Dest_port); |
| <<5, 1, 0, Addr_type, Dest_len, Dest_hostname:Dest_len/binary, Dest_port:16>> when Addr_type == 3 -> |
| handle_connect(In_socket, Addr_type, Dest_hostname, Dest_port); |
| <<5, 1, 0, Addr_type, Dest_ip:16/binary, Dest_port:16>> when Addr_type == 4-> |
| handle_connect(In_socket, Addr_type, Dest_ip, Dest_port); |
| _ -> |
| socks_server_loop(In_socket, Auth_method, Acc_1, auth_done) |
| end; |
| {tcp_closed, In_socket} -> |
| ok; |
| {tcp_error, In_socket, _Rsn} -> |
| ok |
| end. |
| |
| handle_connect(In_socket, Addr_type, Dest_host, Dest_port) -> |
| Dest_host_1 = case Addr_type of |
| 1 -> |
| list_to_tuple(binary_to_list(Dest_host)); |
| 3 -> |
| binary_to_list(Dest_host); |
| 4 -> |
| list_to_tuple(binary_to_list(Dest_host)) |
| end, |
| case gen_tcp:connect(Dest_host_1, Dest_port, [binary, {active, once}]) of |
| {ok, Out_socket} -> |
| Addr = case Addr_type of |
| 1 -> |
| <<Dest_host/binary, Dest_port:16>>; |
| 3 -> |
| Len = size(Dest_host), |
| <<Len, Dest_host/binary, Dest_port:16>>; |
| 4 -> |
| <<Dest_host/binary, Dest_port:16>> |
| end, |
| ok = gen_tcp:send(In_socket, <<5, 0, 0, Addr_type, Addr/binary>>), |
| inet:setopts(In_socket, [{active, once}]), |
| inet:setopts(Out_socket, [{active, once}]), |
| connected_loop(In_socket, Out_socket); |
| _Err -> |
| ok = gen_tcp:send(<<5, 1>>), |
| gen_tcp:close(In_socket) |
| end. |
| |
| check_user_pw(<<"user">>, <<"password">>) -> |
| ok; |
| check_user_pw(_, _) -> |
| notok. |
| |
| connected_loop(In_socket, Out_socket) -> |
| receive |
| {tcp, In_socket, Data} -> |
| inet:setopts(In_socket, [{active, once}]), |
| ok = gen_tcp:send(Out_socket, Data), |
| connected_loop(In_socket, Out_socket); |
| {tcp, Out_socket, Data} -> |
| inet:setopts(Out_socket, [{active, once}]), |
| ok = gen_tcp:send(In_socket, Data), |
| connected_loop(In_socket, Out_socket); |
| _ -> |
| ok |
| end. |