mirror of
https://github.com/internetee/epp_proxy.git
synced 2025-08-19 14:03:47 +02:00
Handle XML parsing errors
This commit is contained in:
parent
73384531c8
commit
54a6fdb729
8 changed files with 217 additions and 68 deletions
|
@ -1,8 +1,19 @@
|
|||
%% This record is used by both epp_tcp_worker and epp_tls_worker.
|
||||
%% These records are used by both epp_tcp_worker and epp_tls_worker.
|
||||
-record(epp_request,
|
||||
{method, % get | post (atom)
|
||||
url, % "https://example.com/some-url"
|
||||
body, % "" | {multipart [{{<<"raw_frame">>, "Some body"}}]}
|
||||
cookies, % "" | {multipart [{{<<"raw_frame">>, "Some body"}}]}
|
||||
cookies, % [<<"session=SomeSession; Version=1">>]
|
||||
headers % [{"User-Agent", <<"EPP proxy">>}, {"Other", <<"Header">>}]
|
||||
}).
|
||||
|
||||
-record(epp_error_request,
|
||||
{method, % get
|
||||
url, % "https://example.com/some-url"
|
||||
query_params, % {[{<<"msg">>, <<"Some">>}, {<<"code">>, <<"2001">>}]}
|
||||
cookies, % [<<"session=SomeSession; Version=1">>]
|
||||
headers % [{"User-Agent", <<"EPP proxy">>}, {"Other", <<"Header">>}]
|
||||
}).
|
||||
|
||||
-type epp_request() :: #epp_request{}.
|
||||
-type epp_error_request() :: #epp_error_request{}.
|
||||
|
|
38
apps/epp_proxy/src/epp_http_client.erl
Normal file
38
apps/epp_proxy/src/epp_http_client.erl
Normal file
|
@ -0,0 +1,38 @@
|
|||
-module(epp_http_client).
|
||||
|
||||
-include("epp_proxy.hrl").
|
||||
|
||||
-behaviour(epp_http_client_behaviour).
|
||||
|
||||
-export([request/1, error_request/1]).
|
||||
|
||||
%% Callback API
|
||||
request(#epp_request{} = Request) ->
|
||||
HackneyArgs = handle_args(Request),
|
||||
{Status, _StatusCode, _Headers, ClientRef} =
|
||||
apply(hackney, request, HackneyArgs),
|
||||
{ok, Body} = hackney:body(ClientRef),
|
||||
{Status, Body}.
|
||||
|
||||
error_request(#epp_error_request{} = Request) ->
|
||||
HackneyArgs = handle_error_args(Request),
|
||||
{Status, _StatusCode, _Headers, ClientRef} =
|
||||
apply(hackney, request, HackneyArgs),
|
||||
{ok, Body} = hackney:body(ClientRef),
|
||||
{Status, Body}.
|
||||
|
||||
%% Private API
|
||||
-spec handle_args(epp_request()) -> list().
|
||||
handle_args(#epp_request{method=get, url=URL, headers=Headers, body="",
|
||||
cookies=Cookies}) ->
|
||||
[get, URL, Headers, "", [{cookie, Cookies}, insecure]];
|
||||
handle_args(#epp_request{method=post, url=URL, headers=Headers, body=Body,
|
||||
cookies=Cookies}) ->
|
||||
[post, URL, Headers, Body, [{cookie, Cookies}, insecure]].
|
||||
|
||||
-spec handle_error_args(epp_error_request()) -> list().
|
||||
handle_error_args(#epp_error_request{method=get, url=URL, headers=Headers,
|
||||
query_params=Params, cookies=Cookies}) ->
|
||||
QueryString = hackney_url:qs(Params),
|
||||
CompleteURL = [URL, <<"?">>, QueryString],
|
||||
[get, CompleteURL, Headers, "", [{cookie, Cookies}, insecure]].
|
9
apps/epp_proxy/src/epp_http_client_behaviour.erl
Normal file
9
apps/epp_proxy/src/epp_http_client_behaviour.erl
Normal file
|
@ -0,0 +1,9 @@
|
|||
-module(epp_http_client_behaviour).
|
||||
|
||||
-include("epp_proxy.hrl").
|
||||
|
||||
-type http_response() :: {integer(), binary()}.
|
||||
|
||||
%% Abstract module for http client behaviour. It should call EPP HTTP server
|
||||
%% and return a response back to the caller.
|
||||
-callback request(epp_request()) -> http_response().
|
|
@ -18,6 +18,12 @@
|
|||
{ok, Value} -> Value
|
||||
end).
|
||||
|
||||
-define(baseErrorUrl,
|
||||
case application:get_env(epp_proxy, epp_error_url) of
|
||||
undefined -> "https://registry.test/epp/error/";
|
||||
{ok, Value} -> Value
|
||||
end).
|
||||
|
||||
%% Save yourself some checking beforehand.
|
||||
is_valid_epp_command(Command) ->
|
||||
lists:member(Command, ?validCommands).
|
||||
|
@ -27,6 +33,10 @@ request_method("hello") ->
|
|||
get;
|
||||
request_method(<<"hello">>) ->
|
||||
get;
|
||||
request_method("error") ->
|
||||
get;
|
||||
request_method(<<"error">>) ->
|
||||
get;
|
||||
request_method(_) ->
|
||||
post.
|
||||
|
||||
|
@ -53,7 +63,9 @@ url_map(Command) when is_list(Command) ->
|
|||
"renew" -> string:concat(base_command_url(), Command);
|
||||
"update" -> string:concat(base_command_url(), Command);
|
||||
% Transfer is both poll and query
|
||||
"transfer" -> string:concat(base_command_url(), Command)
|
||||
"transfer" -> string:concat(base_command_url(), Command);
|
||||
% Error route
|
||||
"error" -> base_error_url()
|
||||
% Anything else should fail.
|
||||
end.
|
||||
|
||||
|
@ -63,3 +75,6 @@ base_session_url() ->
|
|||
|
||||
base_command_url() ->
|
||||
?baseCommandUrl.
|
||||
|
||||
base_error_url() ->
|
||||
?baseErrorUrl.
|
||||
|
|
|
@ -9,9 +9,17 @@
|
|||
-export([init/1, handle_cast/2, handle_call/3, start_link/1]).
|
||||
-export([code_change/3]).
|
||||
|
||||
-export([request/7]).
|
||||
-export([request_from_map/1]).
|
||||
|
||||
-record(state,{socket, session_id, common_name, client_cert, peer_ip}).
|
||||
-record(valid_frame, {command,
|
||||
cl_trid,
|
||||
raw_frame}).
|
||||
-record(invalid_frame, {code,
|
||||
cl_trid,
|
||||
message}).
|
||||
-record(state, {socket,
|
||||
session_id,
|
||||
headers }).
|
||||
|
||||
init(Socket) ->
|
||||
lager:info("Created a worker process"),
|
||||
|
@ -21,63 +29,53 @@ init(Socket) ->
|
|||
start_link(Socket) ->
|
||||
gen_server:start_link(?MODULE, Socket, []).
|
||||
|
||||
|
||||
handle_cast(serve, State = #state{socket=Socket}) ->
|
||||
%% If certificate is revoked, this will fail right away here.
|
||||
%% mod_epp does exactly the same thing.
|
||||
{ok, SecureSocket} = ssl:handshake(Socket),
|
||||
NewState = state_from_socket(SecureSocket, State),
|
||||
{noreply, NewState};
|
||||
handle_cast(greeting, State = #state{socket=Socket, common_name=SSL_CLIENT_S_DN_CN,
|
||||
client_cert=SSL_CLIENT_CERT,
|
||||
handle_cast(greeting, State = #state{socket=Socket,
|
||||
session_id=SessionId,
|
||||
peer_ip=PeerIp}) ->
|
||||
Request = request("hello", SessionId, "", SSL_CLIENT_S_DN_CN,
|
||||
SSL_CLIENT_CERT, PeerIp, nomatch),
|
||||
headers=Headers}) ->
|
||||
|
||||
{_Status, _StatusCode, _Headers, ClientRef} =
|
||||
hackney:request(Request#epp_request.method, Request#epp_request.url,
|
||||
Request#epp_request.headers, Request#epp_request.body,
|
||||
[{cookie, Request#epp_request.cookies}, insecure]),
|
||||
Request = request_from_map(#{command => "hello",
|
||||
session_id => SessionId,
|
||||
raw_frame => "",
|
||||
headers => Headers,
|
||||
cl_trid => nomatch}),
|
||||
|
||||
{ok, Body} = hackney:body(ClientRef),
|
||||
{_Status, Body} = epp_http_client:request(Request),
|
||||
|
||||
frame_to_socket(Body, Socket),
|
||||
gen_server:cast(self(), process_command),
|
||||
{noreply, State#state{socket=Socket, session_id=SessionId}};
|
||||
handle_cast(process_command, State = #state{socket=Socket,
|
||||
common_name=SSL_CLIENT_S_DN_CN,
|
||||
client_cert=SSL_CLIENT_CERT,
|
||||
session_id=SessionId,
|
||||
peer_ip=PeerIp}) ->
|
||||
Length = case read_length(Socket) of
|
||||
{ok, Data} ->
|
||||
Data;
|
||||
{error, _Details} ->
|
||||
{stop, normal, State}
|
||||
handle_cast(process_command,
|
||||
State = #state{socket=Socket,session_id=SessionId,
|
||||
headers=Headers}) ->
|
||||
RawFrame = frame_from_socket(Socket, State),
|
||||
|
||||
case parse_frame(RawFrame) of
|
||||
#valid_frame{command=Command, cl_trid=ClTRID} ->
|
||||
Request = request_from_map(#{command => Command,
|
||||
session_id => SessionId,
|
||||
raw_frame => RawFrame,
|
||||
headers => Headers,
|
||||
cl_trid => ClTRID}),
|
||||
|
||||
{_Status, Body} = epp_http_client:request(Request);
|
||||
#invalid_frame{message=Message, code=Code, cl_trid=ClTRID} ->
|
||||
Command = "error",
|
||||
Request = request_from_map(#{command => Command,
|
||||
session_id => SessionId,
|
||||
headers => Headers,
|
||||
code => Code,
|
||||
message => Message,
|
||||
cl_trid => ClTRID}),
|
||||
{_Status, Body} = epp_http_client:error_request(Request)
|
||||
end,
|
||||
|
||||
Frame = case read_frame(Socket, Length) of
|
||||
{ok, FrameData} ->
|
||||
io:format("~p~n", [FrameData]),
|
||||
FrameData;
|
||||
{error, _FrameDetails} ->
|
||||
{stop, normal, State}
|
||||
end,
|
||||
|
||||
{ok, XMLRecord} = epp_xml:parse(Frame),
|
||||
ClTRID= epp_xml:find_cltrid(Frame),
|
||||
Command = epp_xml:get_command(XMLRecord),
|
||||
|
||||
Request = request(Command, SessionId, Frame, SSL_CLIENT_S_DN_CN,
|
||||
SSL_CLIENT_CERT, PeerIp, ClTRID),
|
||||
|
||||
{_Status, _StatusCode, _Headers, ClientRef} =
|
||||
hackney:request(Request#epp_request.method, Request#epp_request.url,
|
||||
Request#epp_request.headers, Request#epp_request.body,
|
||||
[{cookie, Request#epp_request.cookies}, insecure]),
|
||||
|
||||
{ok, Body} = hackney:body(ClientRef),
|
||||
|
||||
frame_to_socket(Body, Socket),
|
||||
|
||||
%% On logout, close the socket.
|
||||
|
@ -90,10 +88,11 @@ handle_cast(process_command, State = #state{socket=Socket,
|
|||
gen_server:cast(self(), process_command),
|
||||
{noreply, State#state{socket=Socket, session_id=SessionId}}
|
||||
end.
|
||||
|
||||
handle_call(_E, _From, State) -> {noreply, State}.
|
||||
code_change(_OldVersion, State, _Extra) -> {ok, State}.
|
||||
|
||||
%% Private function
|
||||
%% Private functions
|
||||
write_line(Socket, Line) ->
|
||||
ok = ssl:send(Socket, Line).
|
||||
|
||||
|
@ -118,31 +117,66 @@ read_frame(Socket, FrameLength) ->
|
|||
end.
|
||||
|
||||
%% Map request and return values.
|
||||
%% TODO: Make arguments into a map.
|
||||
request(Command, SessionId, RawFrame, CommonName, ClientCert, PeerIp, ClTRID) ->
|
||||
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, []),
|
||||
case Command of
|
||||
"hello" ->
|
||||
Body = "";
|
||||
_ ->
|
||||
Body = {multipart, request_body(RawFrame, ClTRID)}
|
||||
end,
|
||||
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],
|
||||
Request = #epp_request{url=URL,
|
||||
method=RequestMethod,
|
||||
body=Body,
|
||||
cookies=[Cookie],
|
||||
headers=Headers},
|
||||
lager:info("Request to be sent: [~p]~n", [Request]),
|
||||
lager:info("Request from map: [~p]~n", [Request]),
|
||||
Request.
|
||||
|
||||
%% Return form data
|
||||
request_body(RawFrame, nomatch) ->
|
||||
[{<<"raw_frame">>, RawFrame}];
|
||||
request_body(RawFrame, ClTRID) ->
|
||||
[{<<"raw_frame">>, RawFrame}, {<<"clTRID">>, ClTRID}].
|
||||
%% 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) ->
|
||||
|
@ -151,13 +185,47 @@ frame_to_socket(Message, Socket) ->
|
|||
write_line(Socket, ByteSize),
|
||||
write_line(Socket, Message).
|
||||
|
||||
%% First, listen for 4 bytes, then listen until the declared length.
|
||||
%% Return the frame binary at the very end.
|
||||
frame_from_socket(Socket, State) ->
|
||||
Length = case read_length(Socket) of
|
||||
{ok, Data} ->
|
||||
Data;
|
||||
{error, _Details} ->
|
||||
{stop, normal, State}
|
||||
end,
|
||||
|
||||
Frame = case read_frame(Socket, Length) of
|
||||
{ok, FrameData} ->
|
||||
io:format("~p~n", [FrameData]),
|
||||
FrameData;
|
||||
{error, _FrameDetails} ->
|
||||
{stop, normal, State}
|
||||
end,
|
||||
Frame.
|
||||
|
||||
%% Extract state info from socket. Fail if you must.
|
||||
state_from_socket(Socket, State) ->
|
||||
{ok, PeerCert} = ssl:peercert(Socket),
|
||||
{ok, {PeerIp, _PeerPort}} = ssl:peername(Socket),
|
||||
{SSL_CLIENT_S_DN_CN, SSL_CLIENT_CERT} =
|
||||
epp_certs:headers_from_cert(PeerCert),
|
||||
NewState = State#state{socket=Socket, common_name=SSL_CLIENT_S_DN_CN,
|
||||
client_cert=SSL_CLIENT_CERT, peer_ip=PeerIp},
|
||||
Headers = [{"SSL_CLIENT_CERT", SSL_CLIENT_CERT},
|
||||
{"SSL_CLIENT_S_DN_CN", SSL_CLIENT_S_DN_CN},
|
||||
{"User-Agent", <<"EPP proxy">>},
|
||||
{"X-Forwarded-for", epp_util:readable_ip(PeerIp)}],
|
||||
NewState = State#state{socket=Socket, headers=Headers},
|
||||
lager:info("Established connection with: [~p]~n", [NewState]),
|
||||
NewState.
|
||||
|
||||
%% Get status, XML record, command and clTRID if defined
|
||||
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};
|
||||
{error, _} ->
|
||||
ErrorMessage = <<"Command syntax error.">>,
|
||||
#invalid_frame{code=2001, message=ErrorMessage, cl_trid=ClTRID}
|
||||
end.
|
||||
|
|
|
@ -13,6 +13,8 @@ is_valid_epp_command_test() ->
|
|||
request_method_test() ->
|
||||
?assertEqual(get, epp_router:request_method("hello")),
|
||||
?assertEqual(get, epp_router:request_method(<<"hello">>)),
|
||||
?assertEqual(get, epp_router:request_method("error")),
|
||||
?assertEqual(get, epp_router:request_method(<<"error">>)),
|
||||
?assertEqual(post, epp_router:request_method("create")),
|
||||
?assertEqual(post, epp_router:request_method(123)).
|
||||
|
||||
|
@ -60,3 +62,7 @@ update_url_test() ->
|
|||
transfer_url_test() ->
|
||||
?assertEqual("https://registry.test/epp/command/transfer", epp_router:route_request("transfer")),
|
||||
?assertEqual("https://registry.test/epp/command/transfer", epp_router:route_request(<<"transfer">>)).
|
||||
|
||||
error_url_test() ->
|
||||
?assertEqual("https://registry.test/epp/error/", epp_router:route_request("error")),
|
||||
?assertEqual("https://registry.test/epp/error/", epp_router:route_request(<<"error">>)).
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
{tls_port, 700},
|
||||
{epp_session_url, "https://registry.test/epp/session/"},
|
||||
{epp_command_url, "https://registry.test/epp/command/"},
|
||||
{epp_error_url, "https://registry.test/epp/error/"},
|
||||
{cacertfile_path, "/opt/shared/ca/certs/ca.crt.pem"},
|
||||
{certfile_path, "/opt/shared/ca/certs/cert.pem"},
|
||||
{keyfile_path, "/opt/shared/ca/certs/key.pem"},
|
||||
|
@ -11,7 +12,7 @@
|
|||
{lager, [
|
||||
{handlers, [
|
||||
{lager_console_backend, info},
|
||||
{lager_syslog_backend, ["epp_proxy", local1, info]},
|
||||
]}
|
||||
}
|
||||
{lager_syslog_backend, ["epp_proxy", local1, info]}
|
||||
]}
|
||||
]}
|
||||
].
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
{tls_port, 4444},
|
||||
{epp_session_url, "https://registry.test/epp/session/"},
|
||||
{epp_command_url, "https://registry.test/epp/command/"},
|
||||
{epp_error_url, "https://registry.test/epp/error/"},
|
||||
{cacertfile_path, "/opt/shared/ca/certs/ca.crt.pem"},
|
||||
{certfile_path, "/opt/shared/ca/certs/apache.crt"},
|
||||
{keyfile_path, "/opt/shared/ca/private/apache.key"}]}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue