1%%%-------------------------------------------------------------------
2%%% @author Chandru Mullaparthi <>
3%%% @copyright (C) 2016, Chandru Mullaparthi
4%%% @doc
5%%%
6%%% @end
7%%% Created : 19 Apr 2016 by Chandru Mullaparthi <>
8%%%-------------------------------------------------------------------
9-module(ibrowse_socks_server).
10
11-behaviour(gen_server).
12
13%% API
14-export([start/2, stop/1]).
15
16%% gen_server callbacks
17-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
18         terminate/2, code_change/3]).
19
20-define(SERVER, ?MODULE).
21
22-record(state, {listen_port, listen_socket, auth_method}).
23
24-define(NO_AUTH, 0).
25-define(AUTH_USER_PW, 2).
26
27%%%===================================================================
28%%% API
29%%%===================================================================
30
31start(Port, Auth_method) ->
32    Name = make_proc_name(Port),
33    gen_server:start({local, Name}, ?MODULE, [Port, Auth_method], []).
34
35stop(Port) ->
36    make_proc_name(Port) ! stop.
37
38make_proc_name(Port) ->
39    list_to_atom("ibrowse_socks_server_" ++ integer_to_list(Port)).
40
41%%%===================================================================
42%%% gen_server callbacks
43%%%===================================================================
44
45init([Port, Auth_method]) ->
46    State = #state{listen_port = Port, auth_method = Auth_method},
47    {ok, Sock} = gen_tcp:listen(State#state.listen_port, [{active, false}, binary, {reuseaddr, true}]),
48    self() ! accept_connection,
49    process_flag(trap_exit, true),
50    {ok, State#state{listen_socket = Sock}}.
51
52handle_call(_Request, _From, State) ->
53    Reply = ok,
54    {reply, Reply, State}.
55
56handle_cast(_Msg, State) ->
57    {noreply, State}.
58
59handle_info(accept_connection, State) ->
60    case gen_tcp:accept(State#state.listen_socket, 1000) of
61        {error, timeout} ->
62            self() ! accept_connection,
63            {noreply, State};
64        {ok, Socket} ->
65            Pid = proc_lib:spawn_link(fun() ->
66                                              socks_server_loop(Socket, State#state.auth_method)
67                                      end),
68            gen_tcp:controlling_process(Socket, Pid),
69            Pid ! ready,
70            self() ! accept_connection,
71            {noreply, State};
72        _Err ->
73            {stop, normal, State}
74    end;
75
76handle_info(stop, State) ->
77    {stop, normal, State};
78
79handle_info(_Info, State) ->
80    {noreply, State}.
81
82terminate(_Reason, _State) ->
83    ok.
84
85code_change(_OldVsn, State, _Extra) ->
86    {ok, State}.
87
88%%%===================================================================
89%%% Internal functions
90%%%===================================================================
91socks_server_loop(In_socket, Auth_method) ->
92    receive
93        ready ->
94            socks_server_loop(In_socket, Auth_method, <<>>, unauth)
95    end.
96
97socks_server_loop(In_socket, Auth_method, Acc, unauth) ->
98    inet:setopts(In_socket, [{active, once}]),
99    receive
100        {tcp, In_socket, Data} ->
101            Acc_1 = list_to_binary([Acc, Data]),
102            case Acc_1 of
103                <<5, ?NO_AUTH>> when Auth_method == ?NO_AUTH ->
104                    ok = gen_tcp:send(In_socket, <<5, ?NO_AUTH>>),
105                    socks_server_loop(In_socket, Auth_method, <<>>, auth_done);
106                <<5, Num_auth_methods, Auth_methods:Num_auth_methods/binary>> ->
107                    case lists:member(Auth_method, binary_to_list(Auth_methods)) of
108                        true ->
109                            ok = gen_tcp:send(In_socket, <<5, Auth_method>>),
110                            Conn_state = case Auth_method of
111                                             ?NO_AUTH -> auth_done;
112                                             _ -> auth_pending
113                                         end,
114                            socks_server_loop(In_socket, Auth_method, <<>>, Conn_state);
115                        false ->
116                            ok = gen_tcp:send(In_socket, <<5, 16#ff>>),
117                            gen_tcp:close(In_socket)
118                    end;
119                _ ->
120                    ok = gen_tcp:send(In_socket, <<5, 0>>),
121                    gen_tcp:close(In_socket)
122            end;
123        {tcp_closed, In_socket} ->
124            ok;
125        {tcp_error, In_socket, _Rsn} ->
126            ok
127    end;
128socks_server_loop(In_socket, Auth_method, Acc, auth_pending) ->
129    inet:setopts(In_socket, [{active, once}]),
130    receive
131        {tcp, In_socket, Data} ->
132            Acc_1 = list_to_binary([Acc, Data]),
133            case Acc_1 of
134                <<1, U_len, Username:U_len/binary, P_len, Password:P_len/binary>> ->
135                    case check_user_pw(Username, Password) of
136                        ok ->
137                            ok = gen_tcp:send(In_socket, <<1, 0>>),
138                            socks_server_loop(In_socket, Auth_method, <<>>, auth_done);
139                        notok ->
140                            ok = gen_tcp:send(In_socket, <<1, 1>>),
141                            gen_tcp:close(In_socket)
142                    end;
143                _ ->
144                    socks_server_loop(In_socket, Auth_method, Acc_1, auth_pending)
145            end;
146        {tcp_closed, In_socket} ->
147            ok;
148        {tcp_error, In_socket, _Rsn} ->
149            ok
150    end;
151socks_server_loop(In_socket, Auth_method, Acc, auth_done) ->
152    inet:setopts(In_socket, [{active, once}]),
153    receive
154        {tcp, In_socket, Data} ->
155            Acc_1 = list_to_binary([Acc, Data]),
156            case Acc_1 of
157                <<5, 1, 0, Addr_type, Dest_ip:4/binary, Dest_port:16>> when Addr_type == 1->
158                    handle_connect(In_socket, Addr_type, Dest_ip, Dest_port);
159                <<5, 1, 0, Addr_type, Dest_len, Dest_hostname:Dest_len/binary, Dest_port:16>> when Addr_type == 3 ->
160                    handle_connect(In_socket, Addr_type, Dest_hostname, Dest_port);
161                <<5, 1, 0, Addr_type, Dest_ip:16/binary, Dest_port:16>> when Addr_type == 4->
162                    handle_connect(In_socket, Addr_type, Dest_ip, Dest_port);
163                _ ->
164                    socks_server_loop(In_socket, Auth_method, Acc_1, auth_done)
165            end;
166        {tcp_closed, In_socket} ->
167            ok;
168        {tcp_error, In_socket, _Rsn} ->
169            ok
170    end.
171
172handle_connect(In_socket, Addr_type, Dest_host, Dest_port) ->
173    Dest_host_1 = case Addr_type of
174                      1 ->
175                          list_to_tuple(binary_to_list(Dest_host));
176                      3 ->
177                          binary_to_list(Dest_host);
178                      4 ->
179                          list_to_tuple(binary_to_list(Dest_host))
180                  end,
181    case gen_tcp:connect(Dest_host_1, Dest_port, [binary, {active, once}]) of
182        {ok, Out_socket} ->
183            Addr = case Addr_type of
184                       1 ->
185                           <<Dest_host/binary, Dest_port:16>>;
186                       3 ->
187                           Len = size(Dest_host),
188                           <<Len, Dest_host/binary, Dest_port:16>>;
189                       4 ->
190                           <<Dest_host/binary, Dest_port:16>>
191                   end,
192            ok = gen_tcp:send(In_socket, <<5, 0, 0, Addr_type, Addr/binary>>),
193            inet:setopts(In_socket, [{active, once}]),
194            inet:setopts(Out_socket, [{active, once}]),
195            connected_loop(In_socket, Out_socket);
196        _Err ->
197            ok = gen_tcp:send(<<5, 1>>),
198            gen_tcp:close(In_socket)
199    end.
200
201check_user_pw(<<"user">>, <<"password">>) ->
202    ok;
203check_user_pw(_, _) ->
204    notok.
205
206connected_loop(In_socket, Out_socket) ->
207    receive
208        {tcp, In_socket, Data} ->
209            inet:setopts(In_socket, [{active, once}]),
210            ok = gen_tcp:send(Out_socket, Data),
211            connected_loop(In_socket, Out_socket);
212        {tcp, Out_socket, Data} ->
213            inet:setopts(Out_socket, [{active, once}]),
214            ok = gen_tcp:send(In_socket, Data),
215            connected_loop(In_socket, Out_socket);
216        _ ->
217            ok
218    end.
219