Line 0
Link Here
|
|
|
1 |
%%%---------------------------------------------------------------------- |
2 |
%%% File : proxy65_listener.erl |
3 |
%%% Author : Magnus Henoch <henoch@dtek.chalmers.se> |
4 |
%%% Purpose : Handle SOCKS5 connections for JEP-0065 proxy |
5 |
%%% Created : 27 Dec 2005 by Magnus Henoch <henoch@dtek.chalmers.se> |
6 |
%%% Id : $Id: ejabberd_c2s.erl 440 2005-11-22 18:00:56Z alexey $ |
7 |
%%%---------------------------------------------------------------------- |
8 |
|
9 |
-module(proxy65_listener). |
10 |
-author('henoch@dtek.chalmers.se'). |
11 |
-vsn('$Revision$ '). |
12 |
|
13 |
-export([start/2, handle_connection/2, activate/3]). |
14 |
|
15 |
-include("ejabberd.hrl"). |
16 |
-include("jlib.hrl"). |
17 |
|
18 |
-record(proxy65_connection, {cookie, firstpid = none, secondpid = none}). |
19 |
|
20 |
start({SockMod, Socket}, Opts) -> |
21 |
{ok, proc_lib:spawn(?MODULE, handle_connection, [{SockMod, Socket}, Opts])}. |
22 |
|
23 |
read_bytes(_SockOpts, 0, Data) -> |
24 |
lists:flatten(Data); |
25 |
read_bytes({SockMod, Socket} = SockOpts, N, Data) -> |
26 |
Timeout = 1000, |
27 |
case SockMod:recv(Socket, N, Timeout) of |
28 |
{error, closed} -> |
29 |
%% On closed connection, return everything we have, |
30 |
%% but not if we have nothing. |
31 |
if Data == [] -> |
32 |
erlang:error(closed); |
33 |
true -> |
34 |
lists:flatten(Data) |
35 |
end; |
36 |
{ok, MoreData} -> |
37 |
?DEBUG("read ~p", [MoreData]), |
38 |
DataList = binary_to_list(MoreData), |
39 |
read_bytes(SockOpts, N - length(DataList), [Data, DataList]) |
40 |
end. |
41 |
|
42 |
handle_connection({SockMod, Socket}, Opts) -> |
43 |
?DEBUG("in handle_connection", []), |
44 |
case catch handle_auth({SockMod, Socket}, Opts) of |
45 |
{'EXIT', Reason} -> |
46 |
?ERROR_MSG("~p abnormal termination:~n\t~p~n", |
47 |
[?MODULE, Reason]), |
48 |
SockMod:close(Socket); |
49 |
_ -> |
50 |
ok |
51 |
end. |
52 |
|
53 |
handle_auth({SockMod, Socket} = SockOpts, Opts) -> |
54 |
?DEBUG("in handle_auth", []), |
55 |
%% SOCKS protocol stuff... |
56 |
[5, NAuthMethods] = read_bytes(SockOpts, 2, []), |
57 |
AuthMethods = read_bytes(SockOpts, NAuthMethods, []), |
58 |
SupportsNoAuth = lists:member(0, AuthMethods), |
59 |
|
60 |
%% Must support no authentication, otherwise crash |
61 |
true = SupportsNoAuth, |
62 |
|
63 |
SockMod:send(Socket, [5, 0]), |
64 |
|
65 |
%% And done. |
66 |
handle_connect(SockOpts, Opts). |
67 |
|
68 |
handle_connect({SockMod, Socket} = SockOpts, Opts) -> |
69 |
?DEBUG("in handle_connect", []), |
70 |
%% Expect a CONNECT command and nothing else |
71 |
[5, 1, _, 3, AddressLength] = read_bytes(SockOpts, 5, []), |
72 |
Cookie = read_bytes(SockOpts, AddressLength, []), |
73 |
[0, 0] = read_bytes(SockOpts, 2, []), |
74 |
|
75 |
%% Make sure no more than two connections claim the same cookie. |
76 |
F = fun() -> |
77 |
case mnesia:read({proxy65_connection, Cookie}) of |
78 |
[] -> |
79 |
mnesia:write(#proxy65_connection{cookie = Cookie, |
80 |
firstpid = self()}), |
81 |
ok; |
82 |
[#proxy65_connection{secondpid = none} = C] -> |
83 |
mnesia:write(C#proxy65_connection{secondpid = self()}), |
84 |
ok |
85 |
end |
86 |
end, |
87 |
|
88 |
case mnesia:transaction(F) of |
89 |
{atomic, ok} -> |
90 |
SockMod:send(Socket, [5, 0, 0, 3, AddressLength, Cookie, 0, 0]), |
91 |
wait_for_activation(SockOpts, Opts); |
92 |
Error -> |
93 |
%% conflict. send "general SOCKS server failure". |
94 |
SockMod:send(Socket, [5, 1, 0, 3, AddressLength, Cookie, 0, 0]), |
95 |
erlang:error({badconnect, Error}) |
96 |
end. |
97 |
|
98 |
wait_for_activation(SockOpts, Opts) -> |
99 |
?DEBUG("in wait_for_activation", []), |
100 |
receive |
101 |
{get_socket, ReplyTo} -> |
102 |
ReplyTo ! SockOpts, |
103 |
wait_for_activation(SockOpts, Opts); |
104 |
{activate, TargetSocket, Initiator, Target} -> |
105 |
?DEBUG("activated", []), |
106 |
|
107 |
%% We have no way of knowing which connection belongs to |
108 |
%% which participant, so give both the maximum traffic |
109 |
%% allowed to either. |
110 |
Shapers = case lists:keysearch(shaper, 1, Opts) of |
111 |
{value, {_, S}} -> S; |
112 |
_ -> none |
113 |
end, |
114 |
?DEBUG("we have shapers: ~p", [Shapers]), |
115 |
Shaper1 = acl:match_rule(global, Shapers, jlib:string_to_jid(Initiator)), |
116 |
Shaper2 = acl:match_rule(global, Shapers, jlib:string_to_jid(Target)), |
117 |
if Shaper1 == none; Shaper2 == none -> |
118 |
MaxShaper = none; |
119 |
true -> |
120 |
ShaperValue1 = ejabberd_config:get_global_option({shaper, Shaper1}), |
121 |
ShaperValue2 = ejabberd_config:get_global_option({shaper, Shaper2}), |
122 |
|
123 |
if ShaperValue1 > ShaperValue2 -> |
124 |
MaxShaper = Shaper1; |
125 |
true -> |
126 |
MaxShaper = Shaper2 |
127 |
end, |
128 |
?DEBUG("shapers have values ~p and ~p~nusing ~p", [ShaperValue1, ShaperValue2, MaxShaper]), |
129 |
ok |
130 |
end, |
131 |
|
132 |
transfer_data(SockOpts, TargetSocket, shaper:new(MaxShaper)) |
133 |
end. |
134 |
|
135 |
transfer_data({SockMod, Socket} = SockOpts, {TargetSockMod, TargetSocket} = TargetSockOpts, |
136 |
Shaper) -> |
137 |
case SockMod:recv(Socket, 0, infinity) of |
138 |
{ok, Data} -> |
139 |
if Data /= <<>> -> |
140 |
NewShaper = case Shaper of |
141 |
none -> none; |
142 |
_ -> |
143 |
shaper:update(Shaper, size(Data)) |
144 |
end, |
145 |
ok = TargetSockMod:send(TargetSocket, Data); |
146 |
true -> |
147 |
NewShaper = Shaper |
148 |
end, |
149 |
transfer_data(SockOpts, TargetSockOpts, NewShaper); |
150 |
{error, _} -> |
151 |
TargetSockMod:shutdown(TargetSocket, read_write) |
152 |
end. |
153 |
|
154 |
get_socket(PID) -> |
155 |
PID ! {get_socket, self()}, |
156 |
receive |
157 |
{_SockMod, _Socket} = SockOpts -> |
158 |
SockOpts |
159 |
end. |
160 |
|
161 |
%% If any argument is a jid record, convert it to normalized string form... |
162 |
activate(#jid{} = Initiator, Target, SessionID) -> |
163 |
NormalizedInitiator = jlib:jid_to_string(jlib:make_jid(jlib:jid_tolower(Initiator))), |
164 |
activate(NormalizedInitiator, Target, SessionID); |
165 |
activate(Initiator, #jid{} = Target, SessionID) -> |
166 |
NormalizedTarget = jlib:jid_to_string(jlib:make_jid(jlib:jid_tolower(Target))), |
167 |
activate(Initiator, NormalizedTarget, SessionID); |
168 |
%% ...and get on with the activation. |
169 |
activate(Initiator, Target, SessionID) -> |
170 |
Cookie = sha:sha(SessionID ++ Initiator ++ Target), |
171 |
F = fun() -> |
172 |
case mnesia:read({proxy65_connection, Cookie}) of |
173 |
[#proxy65_connection{firstpid = FirstPID, |
174 |
secondpid = SecondPID}] |
175 |
when is_pid(FirstPID), is_pid(SecondPID) -> |
176 |
mnesia:delete({proxy65_connection, Cookie}), |
177 |
{FirstPID, SecondPID}; |
178 |
_ -> |
179 |
error |
180 |
end |
181 |
end, |
182 |
case mnesia:transaction(F) of |
183 |
{atomic, {FirstPID, SecondPID}} -> |
184 |
FirstSocket = get_socket(FirstPID), |
185 |
SecondSocket = get_socket(SecondPID), |
186 |
FirstPID ! {activate, SecondSocket, Initiator, Target}, |
187 |
SecondPID ! {activate, FirstSocket, Initiator, Target}, |
188 |
ok; |
189 |
Error -> |
190 |
?ERROR_MSG("Proxy activation failed: ~p", [Error]), |
191 |
error |
192 |
end. |