-module(proxy). -export([read/4, write/1, out/1]). -include("yaws_api.hrl"). %% This is used in the read loop. We pass this from one iteration to the next. %% It doesn't usually change much. -record(read_state, {pid, client_socket, server_socket, key, sequence = ""}). %% read() starts an http read connection. Everything sent from the server %% through the proxy to the client goes through here. There is one read %% connection for each virtual TCP/IP connection. %% o A is the paramater given by yaws to the out() function. %% o WriteUrl is the location for the write requests, not including the ? or %% anything after it. %% o DestAddress is the IP address of the server at the far end of the tunnel. %% This should be a string. %% o DestPort is the port number of the server at the far end of the tunnel. %% This should be an integer. read(A, WriteUrl, DestAddress, DestPort) -> Self = self(), #arg{clisock=ClientSocket, client_ip_port={ClientIP, _}} = A, %%io:format("starting ~p ~n", [Self]), %%io:format("arg= ~p ~n", [queryvar(A,"arg")]), spawn_link(fun() -> read_thread(Self, ClientSocket, ClientIP, WriteUrl, DestAddress, DestPort) end), {streamcontent, "application/octet-stream", ""}. %% We have a seperate thread handling the read request. It listens for server %% activity, and for write requests, and for the client to disconnect the read %% request. This does the one time initialization. read_thread(Pid, ClientSocket, ClientIP, WriteUrl, DestAddress, DestPort) -> process_flag(trap_exit, true), Key = lists:flatten(io_lib:format("~.16B", [random:uniform(4294967295)])), Cookie = binary_to_list(term_to_binary({pid_to_list(self()), Key})), Padding = <<"................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................">>, yaws_api:stream_chunk_deliver(Pid, [Padding, "\r\n", WriteUrl, "?cookie=", yaws_api:url_encode(Cookie), "\r\n"]), {ok, ServerSocket} = gen_tcp:connect(DestAddress, DestPort, [binary, {packet, 0}]), send_proxy_info(ServerSocket, ClientIP), rec_loop(#read_state{pid=Pid, client_socket=ClientSocket, server_socket=ServerSocket, key=Key, sequence=0}). send_proxy_info(ServerSocket, {IP1, IP2, IP3, IP4}) -> Command = ["command=proxy_info&auth=auth&remote=", io_lib:write(IP1), ".", io_lib:write(IP2), ".", io_lib:write(IP3), ".", io_lib:write(IP4), "\r\n"], client_to_server(ServerSocket, Command); send_proxy_info(_, _) -> void. %% The read thread spends most of its time in this loop. rec_loop(State) -> #read_state{pid=Pid, client_socket=ClientSocket, server_socket=ServerSocket, key=Key, sequence=ExpectedSequence} = State, %%io:format("continuing ~p ~p ~n", [Pid, self()]), receive %% Listen for a write request. Check that the sequence number is valid. %% Blank means to disable checking. Otherwise, all we care is that it is %% different from last time. The concern is that the client might have %% sent something, and he's not sure if we received it or not, so he might %% send it a second time. We aren't worried about things coming out of %% order, otherwise we'd have to make sure the sequence number went up %% each time. {add, Key, Body, ExpectedSequence} -> client_to_server(ServerSocket, Body), rec_loop(State#read_state{sequence=ExpectedSequence+1}); {add, Key, _, FoundSequence} -> io:format("Ignoring invalid sequence number. Found=~p, Expecting=~p~n", [ExpectedSequence, FoundSequence]), rec_loop(State); %% We received data from the server. {tcp, ServerSocket, Bin} -> server_to_client(Pid, Bin), rec_loop(State); %% The http process died. This would typically be some sort of error. %% It does not die automatically when the client hangs up. However, if %% we try to write to the client after the client is disconnect, then it %% will die with an error. {'EXIT', Pid, _} -> yaws_api:stream_chunk_end(Pid); %% The server closed the connection. {tcp_closed, _} -> yaws_api:stream_chunk_end(Pid); %% This is for development. We don't expect anything in here. Other -> io:format("unexpected message in rec_loop ~p ~n", [Other]), rec_loop(State) after 9000 -> %% Check if the client is still connected. case gen_tcp:recv(ClientSocket, 0, 0) of {error,closed} -> %% The client is not connected. Shut down. io:format("client closed connection!~n", []), yaws_api:stream_chunk_end(Pid); _ -> %% We have to send a message to the http thread about once every 12 or %% 13 seconds or it will assume we have died and it will disconnect %% from the client. %%io:format("Msg = ~p~n", [Msg]), yaws_api:stream_chunk_deliver(Pid, ""), rec_loop(State) end end. server_to_client(Pid, Bin) -> yaws_api:stream_chunk_deliver_blocking(Pid, Bin). client_to_server(Socket, String) -> ok = gen_tcp:send(Socket, String), %%io:format("Sending to server: ~p~n", [String]), void. %% This could possibly include more status but (a) that would make things much %% more complicated and (b) we've been working successfully for years without %% sending any status back in this call. The only real status comes from the %% read connection. If there is any problem, that connection is broken. %% It would be more effecient if the read process sent us a socket and we %% sent the data to that socket ourselves. However, this is much simpler. The %% data is a list so the whole thing gets copied to the other process, not just %% a pointer. write(A) -> {PidStr, Key} = binary_to_term(list_to_binary(get(A, "cookie", ""))), Body = post(A, "body", ""), SequenceStr = post(A, "sequence", ""), case string:to_integer(SequenceStr) of {Sequence, ""} when is_integer(Sequence) -> %%io:format("PidStr: ~p, Key: ~p, Body: ~p, Sequence: ~p~n", [PidStr, Key, Body, Sequence]), list_to_pid(PidStr) ! {add, Key, p64_decode(Body), Sequence}; _ -> void end, []. %%sleep(T) -> %% receive %% after T -> %% true %% end. get(A, Name, Default) -> case yaws_api:queryvar(A, Name) of {ok, Value} -> Value; _ -> Default end. post(A, Name, Default) -> case yaws_api:postvar(A, Name) of {ok, Value} -> Value; _ -> Default end. p64_decode_char($_) -> 62; p64_decode_char($-) -> 63; p64_decode_char(void) -> 0; p64_decode_char(C) when C >= $a, C =< $z -> C - $a; p64_decode_char(C) when C >= $A, C =< $Z -> C - $A + 26; p64_decode_char(C) when C >= $0, C =< $9 -> C - $0 + 52. p64_chunk(A, B, C, D) -> Total = (p64_decode_char(A) bsl 18) bor (p64_decode_char(B) bsl 12) bor (p64_decode_char(C) bsl 6) bor p64_decode_char(D), {Total bsr 16, (Total bsr 8) band 255, Total band 255}. p64_decode(Input) -> p64_decode(Input, []). p64_decode([A, B, C, D | Rest], Acc) -> {X, Y, Z} = p64_chunk(A, B, C, D), p64_decode(Rest, [Z, Y, X | Acc]); p64_decode([A, B, C], Acc) -> {X, Y, _} = p64_chunk(A, B, C, void), lists:reverse([Y, X | Acc]); p64_decode([A, B], Acc) -> {X, _, _} = p64_chunk(A, B, void, void), lists:reverse([X | Acc]); p64_decode([], Acc) -> lists:reverse(Acc). %% "69.43.145.244" is "www.trade-ideas.com"! out(#arg{server_path="/Read.php"} = A) -> read(A, <<"http://proxy.trade-ideas.com/Write.php">>, "69.43.145.244", 8888); out(#arg{server_path="/ReadSSL.php"} = A) -> read(A, <<"https://proxy.trade-ideas.com/Write.php">>, "69.43.145.244", 8888); out(#arg{server_path="/Write.php"} = A) -> write(A); %% "69.43.145.248" is "will.trade-ideas.com" out(#arg{server_path="/R_will.php"} = A) -> read(A, <<"https://proxy.trade-ideas.com/Write.php">>, "69.43.145.248", 8888); out(#arg{server_path="/W_will.php"} = A) -> write(A); out(_) -> {status, 404}.