Document EPP TLS worker

This commit is contained in:
Maciej Szlosarczyk 2019-05-30 11:43:59 +03:00
parent 4b5c90912d
commit 4566e14b31
No known key found for this signature in database
GPG key ID: 41D62D42D3B0D765

View file

@ -9,8 +9,6 @@
-export([init/1, handle_cast/2, handle_call/3, start_link/1]).
-export([code_change/3]).
-export([request_from_map/1]).
-record(valid_frame, {command,
cl_trid,
raw_frame}).
@ -21,6 +19,11 @@
session_id,
headers }).
-define(XMLErrorCode, <<"2001">>).
-define(XMLErrorMessage, <<"Command syntax error.">>).
%% Initialize process
%% Assign an unique session id that will be passed on to http server as a cookie
init(Socket) ->
lager:info("Created a worker process: [~p]", [self()]),
SessionId = epp_util:session_id(self()),
@ -29,13 +32,24 @@ init(Socket) ->
start_link(Socket) ->
gen_server:start_link(?MODULE, Socket, []).
handle_cast(serve, State = #state{socket=Socket}) ->
%% First step after spinning off the process:
%% Perform an TLS handshake and gather data that we use a headers for
%% http request:
%% Common name
%% Client certificate
%% Client IP address
%% If certificate is revoked, this will fail right away here.
%% mod_epp does exactly the same thing.
handle_cast(serve, State = #state{socket=Socket}) ->
{ok, SecureSocket} = ssl:handshake(Socket),
NewState = state_from_socket(SecureSocket, State),
{noreply, NewState};
%% Step two:
%% Using the state of the connection, get the hello route from http server.
%% Send the response from HTTP server back to EPP client.
%% When this succeeds, send "process_command" to self and await further
%% commands.
handle_cast(greeting, State = #state{socket=Socket,
session_id=SessionId,
headers=Headers}) ->
@ -52,8 +66,15 @@ handle_cast(greeting, State = #state{socket=Socket,
gen_server:cast(self(), process_command),
{noreply, State#state{socket=Socket, session_id=SessionId}};
%% Main loop of processing commands. Ends the connection when command is logout.
%% Step three to N:
%% Await input from client. Parse it, and perform an appropriate http request.
%% Commands go to commands, invalid XML goes to error.
%% Send the response from HTTP server back to EPP client.
%%
%% When the command from client was logout, close the connection and quit the
%% process.
%%
%% Otherwise send "process_command" again to self to repeat the process.
handle_cast(process_command,
State = #state{socket=Socket,session_id=SessionId,
headers=Headers}) ->
@ -96,9 +117,6 @@ handle_call(_E, _From, State) -> {noreply, State}.
code_change(_OldVersion, State, _Extra) -> {ok, State}.
%% Private functions
write_line(Socket, Line) ->
ok = ssl:send(Socket, Line).
read_length(Socket) ->
case ssl:recv(Socket, 4) of
{ok, Data} ->
@ -106,7 +124,7 @@ read_length(Socket) ->
LengthToReceive = epp_util:frame_length_to_receive(Length),
{ok, LengthToReceive};
{error, Reason} ->
io:format("Error: ~p~n", [Reason]),
lager:error("Error: ~p~n", [Reason]),
{error, Reason}
end.
@ -115,72 +133,10 @@ read_frame(Socket, FrameLength) ->
{ok, Data} ->
{ok, Data};
{error, Reason} ->
io:format("Error: ~p~n", [Reason]),
lager:error("Error: ~p~n", [Reason]),
{error, Reason}
end.
%% Map request and return values.
request_from_map(#{command := "error", session_id := SessionId,
code := Code, message := Message, headers:=Headers,
cl_trid := ClTRID}) ->
URL = epp_router:route_request("error"),
RequestMethod = epp_router:request_method("error"),
Cookie = hackney_cookie:setcookie("session", SessionId, []),
QueryParams = query_params(Code, Message, ClTRID),
Headers=Headers,
Request = #epp_error_request{url=URL,
method=RequestMethod,
query_params=QueryParams,
cookies=[Cookie],
headers=Headers},
lager:info("Error Request from map: [~p]~n", [Request]),
Request;
request_from_map(#{command := Command, session_id := SessionId,
raw_frame := RawFrame, headers:=Headers, cl_trid := ClTRID}) ->
URL = epp_router:route_request(Command),
RequestMethod = epp_router:request_method(Command),
Cookie = hackney_cookie:setcookie("session", SessionId, []),
Body = request_body(Command, RawFrame, ClTRID),
Headers=Headers,
Request = #epp_request{url=URL,
method=RequestMethod,
body=Body,
cookies=[Cookie],
headers=Headers},
lager:info("Request from map: [~p]~n", [Request]),
Request;
request_from_map(#{command := Command, session_id := SessionId,
raw_frame := RawFrame, common_name := CommonName,
client_cert := ClientCert, peer_ip := PeerIp, cl_trid := ClTRID}) ->
URL = epp_router:route_request(Command),
RequestMethod = epp_router:request_method(Command),
Cookie = hackney_cookie:setcookie("session", SessionId, []),
Body = request_body(Command, RawFrame, ClTRID),
Headers = [{"SSL_CLIENT_CERT", ClientCert},
{"SSL_CLIENT_S_DN_CN", CommonName},
{"User-Agent", <<"EPP proxy">>},
{"X-Forwarded-for", epp_util:readable_ip(PeerIp)}],
Request = #epp_request{url=URL,
method=RequestMethod,
body=Body,
cookies=[Cookie],
headers=Headers},
lager:info("Request from map: [~p]~n", [Request]),
Request.
%% Return form data or an empty list.
request_body("hello", _, _) ->
"";
request_body(_Command, RawFrame, nomatch) ->
{multipart, [{<<"raw_frame">>, RawFrame}]};
request_body(_Command, RawFrame, ClTRID) ->
{multipart, [{<<"raw_frame">>, RawFrame}, {<<"clTRID">>, ClTRID}]}.
query_params(Code, Message, nomatch) ->
[{<<"code">>, Code}, {<<"msg">>, Message}];
query_params(Code, Message, ClTRID) ->
[{<<"code">>, Code}, {<<"msg">>, Message}, {<<"clTRID">>, ClTRID}].
%% Wrap a message in EPP frame, and then send it to socket.
frame_to_socket(Message, Socket) ->
Length = epp_util:frame_length_to_send(Message),
@ -188,6 +144,9 @@ frame_to_socket(Message, Socket) ->
write_line(Socket, ByteSize),
write_line(Socket, Message).
write_line(Socket, Line) ->
ok = ssl:send(Socket, Line).
%% First, listen for 4 bytes, then listen until the declared length.
%% Return the frame binary at the very end.
frame_from_socket(Socket, State) ->
@ -220,14 +179,18 @@ state_from_socket(Socket, State) ->
lager:info("Established connection with: [~p]~n", [NewState]),
NewState.
%% Get status, XML record, command and clTRID if defined
%% Get status, XML record, command and clTRID if defined.
%% Otherwise return an invalid frame with predefined error message and code.
parse_frame(Frame) ->
ClTRID = epp_xml:find_cltrid(Frame),
case epp_xml:parse(Frame) of
{ok, XMLRecord} ->
Command = epp_xml:get_command(XMLRecord),
#valid_frame{command=Command, cl_trid=ClTRID, raw_frame=Frame};
#valid_frame{command=Command,
cl_trid=ClTRID,
raw_frame=Frame};
{error, _} ->
ErrorMessage = <<"Command syntax error.">>,
#invalid_frame{code=2001, message=ErrorMessage, cl_trid=ClTRID}
#invalid_frame{code=?XMLErrorCode,
message=?XMLErrorMessage,
cl_trid=ClTRID}
end.