diff --git a/apps/epp_proxy/src/epp_certs.erl b/apps/epp_proxy/src/epp_certs.erl index 576c260..ac85892 100644 --- a/apps/epp_proxy/src/epp_certs.erl +++ b/apps/epp_proxy/src/epp_certs.erl @@ -3,11 +3,11 @@ -include_lib("public_key/include/public_key.hrl"). -export([certificate_to_pem/1, - common_name_from_subject/1, der_certificate/1, - headers_from_cert/1, pem_certificate/1, - subject_from_otp_certificate/1]). + common_name_from_subject/1, der_certificate/1, + headers_from_cert/1, pem_certificate/1, + subject_from_otp_certificate/1]). - % Returns a tuple of headers {SSL_CLIENT_S_DN_CN, SSL_CLIENT_CERT} +% Returns a tuple of headers {SSL_CLIENT_S_DN_CN, SSL_CLIENT_CERT} headers_from_cert(Der) -> OTPCertificate = der_certificate(Der), Subject = subject_from_otp_certificate(OTPCertificate), @@ -26,14 +26,14 @@ certificate_to_pem(Certificate) -> PemEntry = {'Certificate', Certificate, not_encrypted}, PemString = public_key:pem_encode([PemEntry]), CleanBinary = binary:replace(PemString, <<"\n">>, - <<" ">>, [global]), + <<" ">>, [global]), CleanBinary. %% Read only a specific type of certificate, otherwise fail. subject_from_otp_certificate(Certificate) - when is_record(Certificate, 'OTPCertificate') -> + when is_record(Certificate, 'OTPCertificate') -> Subject = - (Certificate#'OTPCertificate'.tbsCertificate)#'OTPTBSCertificate'.subject, + (Certificate#'OTPCertificate'.tbsCertificate)#'OTPTBSCertificate'.subject, Subject. %% Take a subject rdnSequence that can be set into @@ -41,7 +41,7 @@ subject_from_otp_certificate(Certificate) common_name_from_subject(Subject) -> CommonName = (?'id-at-commonName'), {_Type, Field} = field_from_subject(Subject, - CommonName), + CommonName), Field. %% Only used for local development test, is not required for the application. @@ -49,7 +49,7 @@ pem_certificate(PathToCert) -> {ok, PemBin} = file:file(PathToCert), PemEntries = public_key:pem_decode(PemBin), {value, CertEntry} = lists:keysearch('Certificate', 1, - PemEntries), + PemEntries), {_, DerCert, _} = CertEntry, Decoded = public_key:pkix_decode_cert(DerCert, otp), Decoded. @@ -57,8 +57,8 @@ pem_certificate(PathToCert) -> field_from_subject({rdnSequence, Attributes}, Field) -> FlatList = lists:flatten(Attributes), ValidAttrs = lists:filter(fun (X) -> - X#'AttributeTypeAndValue'.type =:= Field - end, - FlatList), + X#'AttributeTypeAndValue'.type =:= Field + end, + FlatList), Attribute = lists:last(ValidAttrs), Attribute#'AttributeTypeAndValue'.value. diff --git a/apps/epp_proxy/src/epp_http_client.erl b/apps/epp_proxy/src/epp_http_client.erl index e8db11b..c24e521 100644 --- a/apps/epp_proxy/src/epp_http_client.erl +++ b/apps/epp_proxy/src/epp_http_client.erl @@ -5,99 +5,98 @@ -behaviour(epp_http_client_behaviour). -export([request/1, request_builder/1]). + -define(errorCommand, "error"). + -define(helloCommand, "hello"). %% Callback API request(#epp_request{} = Request) -> HackneyArgs = handle_args(Request), case apply(hackney, request, HackneyArgs) of - {error, Error} -> log_and_return_canned(Error, Request); - {Status, _StatusCode, _Headers, ClientRef} -> - {ok, Body} = hackney:body(ClientRef), {Status, Body} + {error, Error} -> log_and_return_canned(Error, Request); + {Status, _StatusCode, _Headers, ClientRef} -> + {ok, Body} = hackney:body(ClientRef), {Status, Body} end. request_builder(Map) -> request_from_map(Map). %% Private API -spec handle_args(epp_request()) -> list(). -%% For hello command, we ignore the payload, and send an empty body over the wire. -handle_args(#epp_request{method=get, - url = URL, - headers = Headers, - cookies = Cookies, - epp_verb = ?helloCommand}) -> - [get, URL, Headers, "", [{cookie, Cookies}, insecure]]; +%% For hello command, we ignore the payload, and send an empty body over the wire. +handle_args(#epp_request{method = get, url = URL, + headers = Headers, cookies = Cookies, + epp_verb = ?helloCommand}) -> + [get, URL, Headers, "", [{cookie, Cookies}, insecure]]; %% For error command, we convert the message and code into query parameters, %% and append them to the original URL. -handle_args(#epp_request{method=get, - url = URL, - payload = Payload, - headers = Headers, - cookies = Cookies, - epp_verb = ?errorCommand}) -> +handle_args(#epp_request{method = get, url = URL, + payload = Payload, headers = Headers, + cookies = Cookies, epp_verb = ?errorCommand}) -> QueryString = hackney_url:qs(Payload), CompleteURL = [URL, <<"?">>, QueryString], - [get, CompleteURL, Headers, "", [{cookie, Cookies}, insecure]]; + [get, CompleteURL, Headers, "", + [{cookie, Cookies}, insecure]]; %% For valid commands, we set the multipart body earlier, now we just pass it on. -handle_args(#epp_request{method=post, - url = URL, - payload = Payload, - headers = Headers, - cookies = Cookies}) -> - [post, URL, Headers, Payload, [{cookie, Cookies}, insecure]]. +handle_args(#epp_request{method = post, url = URL, + payload = Payload, headers = Headers, + cookies = Cookies}) -> + [post, URL, Headers, Payload, + [{cookie, Cookies}, insecure]]. %% Map request and return values. request_from_map(#{command := ?errorCommand, - session_id := SessionId, code := Code, - message := Message, headers := Headers, - cl_trid := ClTRID}) -> + session_id := SessionId, code := Code, + message := Message, headers := Headers, + cl_trid := ClTRID}) -> URL = epp_router:route_request(?errorCommand), - RequestMethod = epp_router:request_method(?errorCommand), + RequestMethod = + epp_router:request_method(?errorCommand), Cookie = hackney_cookie:setcookie("session", SessionId, - []), + []), QueryParams = query_params(Code, Message, ClTRID), Headers = Headers, Request = #epp_request{url = URL, - method = RequestMethod, - payload = QueryParams, cookies = [Cookie], - headers = Headers, epp_verb= ?errorCommand}, + method = RequestMethod, payload = QueryParams, + cookies = [Cookie], headers = Headers, + epp_verb = ?errorCommand}, 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}) -> + 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, payload = Body, - cookies = [Cookie], headers = Headers, - epp_verb = Command}, + method = RequestMethod, payload = Body, + cookies = [Cookie], headers = Headers, + epp_verb = Command}, 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}) -> + 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)}], + {"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, payload = Body, - cookies = [Cookie], headers = Headers, - epp_verb = Command}, - lager:info("Unified Request from map: [~p]~n", [Request]), + method = RequestMethod, payload = Body, + cookies = [Cookie], headers = Headers, + epp_verb = Command}, + lager:info("Unified Request from map: [~p]~n", + [Request]), Request. %% Return form data or an empty list. @@ -121,7 +120,7 @@ query_params(Code, Message, ClTRID) -> log_and_return_canned(Error, Request) -> lager:alert("Registry cannot be reached!"), lager:alert("Error contacting registry: [~p, ~p]~n", - [Error, Request]), + [Error, Request]), {2400, canned_response()}. %% In case registry is not accessible, return this response. diff --git a/apps/epp_proxy/src/epp_http_client_behaviour.erl b/apps/epp_proxy/src/epp_http_client_behaviour.erl index c575adc..e58fd50 100644 --- a/apps/epp_proxy/src/epp_http_client_behaviour.erl +++ b/apps/epp_proxy/src/epp_http_client_behaviour.erl @@ -7,4 +7,5 @@ %% 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(). + -callback request_builder(map()) -> epp_request(). diff --git a/apps/epp_proxy/src/epp_pool_supervisor.erl b/apps/epp_proxy/src/epp_pool_supervisor.erl index c2d1077..a120722 100644 --- a/apps/epp_proxy/src/epp_pool_supervisor.erl +++ b/apps/epp_proxy/src/epp_pool_supervisor.erl @@ -13,5 +13,5 @@ start_link() -> init([]) -> SupFlags = #{strategy => one_for_one, intensity => 3, - period => 60}, + period => 60}, {ok, {SupFlags, []}}. diff --git a/apps/epp_proxy/src/epp_proxy_sup.erl b/apps/epp_proxy/src/epp_proxy_sup.erl index 74b0618..f10aa72 100644 --- a/apps/epp_proxy/src/epp_proxy_sup.erl +++ b/apps/epp_proxy/src/epp_proxy_sup.erl @@ -16,19 +16,19 @@ -define(SERVER, ?MODULE). -define(DevMode, - application:get_env(epp_proxy, dev_mode)). + application:get_env(epp_proxy, dev_mode)). -define(TCPPort, - case application:get_env(epp_proxy, tcp_port) of - undefined -> undefined; - {ok, Value} -> Value - end). + case application:get_env(epp_proxy, tcp_port) of + undefined -> undefined; + {ok, Value} -> Value + end). -define(TLSPort, - case application:get_env(epp_proxy, tls_port) of - undefined -> undefined; - {ok, Val} -> Val - end). + case application:get_env(epp_proxy, tls_port) of + undefined -> undefined; + {ok, Val} -> Val + end). %%==================================================================== %% API functions @@ -47,21 +47,21 @@ start_link() -> %% Child :: {Id,StartFunc,Restart,Shutdown,Type,Modules} init([]) -> SupFlags = #{strategy => one_for_one, intensity => 3, - period => 60}, + period => 60}, TCPAcceptor = #{id => epp_tcp_acceptor, type => worker, - modules => [epp_tcp_acceptor], - start => {epp_tcp_acceptor, start_link, [?TCPPort]}}, + modules => [epp_tcp_acceptor], + start => {epp_tcp_acceptor, start_link, [?TCPPort]}}, TLSAcceptor = #{id => epp_tls_acceptor, type => worker, - modules => [epp_tls_acceptor], - start => {epp_tls_acceptor, start_link, [?TLSPort]}}, + modules => [epp_tls_acceptor], + start => {epp_tls_acceptor, start_link, [?TLSPort]}}, PoolSupervisor = #{id => epp_pool_supervisor, - type => supervisor, modules => [epp_pool_supervisor], - start => {epp_pool_supervisor, start_link, []}}, + type => supervisor, modules => [epp_pool_supervisor], + start => {epp_pool_supervisor, start_link, []}}, ChildrenSpec = case ?DevMode of - {ok, true} -> - [TCPAcceptor, TLSAcceptor, PoolSupervisor]; - _ -> [TLSAcceptor, PoolSupervisor] - end, + {ok, true} -> + [TCPAcceptor, TLSAcceptor, PoolSupervisor]; + _ -> [TLSAcceptor, PoolSupervisor] + end, {ok, {SupFlags, ChildrenSpec}}. %%==================================================================== diff --git a/apps/epp_proxy/src/epp_router.erl b/apps/epp_proxy/src/epp_router.erl index 34e4032..d7a8da7 100644 --- a/apps/epp_proxy/src/epp_router.erl +++ b/apps/epp_proxy/src/epp_router.erl @@ -3,8 +3,8 @@ -export([request_method/1, route_request/1]). -define(validCommands, - ["hello", "login", "logout", "check", "info", "poll", - "create", "delete", "renew", "update", "transfer"]). + ["hello", "login", "logout", "check", "info", "poll", + "create", "delete", "renew", "update", "transfer"]). %% 47 is the character code -define(forwardSlash, 47). @@ -25,80 +25,80 @@ route_request(Command) when is_list(Command) -> %% Actually route to places url_map(Command) when is_list(Command) -> case Command of - %% Session commands - "hello" -> - unicode:characters_to_list([base_session_url(), - Command]); - "login" -> - unicode:characters_to_list([base_session_url(), - Command]); - "logout" -> - unicode:characters_to_list([base_session_url(), - Command]); - %% Poll commands - "check" -> - unicode:characters_to_list([base_command_url(), - Command]); - "info" -> - unicode:characters_to_list([base_command_url(), - Command]); - "poll" -> - unicode:characters_to_list([base_command_url(), - Command]); - %% Transform commands - "create" -> - unicode:characters_to_list([base_command_url(), - Command]); - "delete" -> - unicode:characters_to_list([base_command_url(), - Command]); - "renew" -> - unicode:characters_to_list([base_command_url(), - Command]); - "update" -> - unicode:characters_to_list([base_command_url(), - Command]); - % Transfer is both poll and query - "transfer" -> - unicode:characters_to_list([base_command_url(), - Command]); - % Error route - "error" -> - base_error_url() % Anything else should fail. + %% Session commands + "hello" -> + unicode:characters_to_list([base_session_url(), + Command]); + "login" -> + unicode:characters_to_list([base_session_url(), + Command]); + "logout" -> + unicode:characters_to_list([base_session_url(), + Command]); + %% Poll commands + "check" -> + unicode:characters_to_list([base_command_url(), + Command]); + "info" -> + unicode:characters_to_list([base_command_url(), + Command]); + "poll" -> + unicode:characters_to_list([base_command_url(), + Command]); + %% Transform commands + "create" -> + unicode:characters_to_list([base_command_url(), + Command]); + "delete" -> + unicode:characters_to_list([base_command_url(), + Command]); + "renew" -> + unicode:characters_to_list([base_command_url(), + Command]); + "update" -> + unicode:characters_to_list([base_command_url(), + Command]); + % Transfer is both poll and query + "transfer" -> + unicode:characters_to_list([base_command_url(), + Command]); + % Error route + "error" -> + base_error_url() % Anything else should fail. end. %% This allows the person who configures proxy to not care about trailing %% slashes in HTTP. appendable_route(Route) -> case lists:last(Route) of - ?forwardSlash -> Route; - _ -> unicode:characters_to_list([Route, "/"]) + ?forwardSlash -> Route; + _ -> unicode:characters_to_list([Route, "/"]) end. %% This allows the person who configures proxy to not care about trailing %% slashes in HTTP. parametrizable_route(Route) -> case lists:last(Route) of - ?forwardSlash -> lists:droplast(Route); - _ -> Route + ?forwardSlash -> lists:droplast(Route); + _ -> Route end. %% Every time a request is made, this will go to ETS to check what's the route, %% But that is fast enough to not be noticed by anyone. base_session_url() -> case application:get_env(epp_proxy, epp_session_url) of - undefined -> "https://registry.test/epp/session/"; - {ok, Value} -> appendable_route(Value) + undefined -> "https://registry.test/epp/session/"; + {ok, Value} -> appendable_route(Value) end. base_command_url() -> case application:get_env(epp_proxy, epp_command_url) of - undefined -> "https://registry.test/epp/command/"; - {ok, Value} -> appendable_route(Value) + undefined -> "https://registry.test/epp/command/"; + {ok, Value} -> appendable_route(Value) end. base_error_url() -> case application:get_env(epp_proxy, epp_error_url) of - undefined -> "https://registry.test/epp/error"; - {ok, Value} -> parametrizable_route(Value) + undefined -> "https://registry.test/epp/error"; + {ok, Value} -> parametrizable_route(Value) end. diff --git a/apps/epp_proxy/src/epp_tcp_acceptor.erl b/apps/epp_proxy/src/epp_tcp_acceptor.erl index 996c490..059d331 100644 --- a/apps/epp_proxy/src/epp_tcp_acceptor.erl +++ b/apps/epp_proxy/src/epp_tcp_acceptor.erl @@ -15,41 +15,41 @@ %% gen_server callbacks -export([handle_call/3, handle_cast/2, init/1, - start_link/1]). + start_link/1]). -record(state, {socket, port, options}). start_link(Port) -> gen_server:start_link({local, ?SERVER}, ?MODULE, Port, - []). + []). init(Port) -> Options = [binary, {packet, raw}, {active, false}, - {reuseaddr, true}], + {reuseaddr, true}], {ok, ListenSocket} = gen_tcp:listen(Port, Options), gen_server:cast(self(), accept), {ok, #state{socket = ListenSocket, port = Port, - options = Options}}. + options = Options}}. handle_cast(accept, - State = #state{socket = ListenSocket, port = Port, - options = Options}) -> + State = #state{socket = ListenSocket, port = Port, + options = Options}) -> {ok, AcceptSocket} = gen_tcp:accept(ListenSocket), {ok, NewOwner} = create_worker(AcceptSocket), ok = gen_tcp:controlling_process(AcceptSocket, - NewOwner), + NewOwner), gen_server:cast(NewOwner, serve), gen_server:cast(NewOwner, greeting), gen_server:cast(self(), accept), {noreply, State#state{socket = ListenSocket, port = Port, - options = Options}}. + options = Options}}. handle_call(_E, _From, State) -> {noreply, State}. create_worker(Socket) -> ChildSpec = #{id => rand:uniform(), type => worker, - modules => [?WORKER], restart => temporary, - start => {?WORKER, start_link, [Socket]}}, + modules => [?WORKER], restart => temporary, + start => {?WORKER, start_link, [Socket]}}, supervisor:start_child(?POOL_SUPERVISOR, ChildSpec). diff --git a/apps/epp_proxy/src/epp_tcp_worker.erl b/apps/epp_proxy/src/epp_tcp_worker.erl index bf87c78..58458dc 100644 --- a/apps/epp_proxy/src/epp_tcp_worker.erl +++ b/apps/epp_proxy/src/epp_tcp_worker.erl @@ -8,7 +8,7 @@ %% gen_server callbacks -export([handle_call/3, handle_cast/2, init/1, - start_link/1]). + start_link/1]). -export([code_change/3]). @@ -45,14 +45,14 @@ handle_cast(serve, State = #state{socket = Socket}) -> %% When this succeeds, send "process_command" to self and await further %% commands. handle_cast(greeting, - State = #state{socket = Socket, session_id = SessionId, - headers = Headers}) -> - Request = - epp_http_client:request_builder(#{command => "hello", - session_id => SessionId, - raw_frame => "", - headers => Headers, - cl_trid => nomatch}), + State = #state{socket = Socket, session_id = SessionId, + headers = Headers}) -> + Request = epp_http_client:request_builder(#{command => + "hello", + session_id => SessionId, + raw_frame => "", + headers => Headers, + cl_trid => nomatch}), {_Status, Body} = epp_http_client:request(Request), frame_to_socket(Body, Socket), gen_server:cast(self(), process_command), @@ -68,38 +68,39 @@ handle_cast(greeting, %% %% Otherwise send "process_command" again to self to repeat the process. handle_cast(process_command, - State = #state{socket = Socket, session_id = SessionId, - headers = Headers}) -> + 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 = - epp_http_client:request_builder(#{command => Command, - session_id => SessionId, - raw_frame => RawFrame, - headers => Headers, - cl_trid => ClTRID}); - #invalid_frame{message = Message, code = Code, cl_trid = ClTRID} -> - Command = "error", - Request = - epp_http_client:request_builder(#{command => Command, - session_id => SessionId, - headers => Headers, - code => Code, - message => Message, - cl_trid => ClTRID}) + #valid_frame{command = Command, cl_trid = ClTRID} -> + Request = epp_http_client:request_builder(#{command => + Command, + session_id => SessionId, + raw_frame => RawFrame, + headers => Headers, + cl_trid => ClTRID}); + #invalid_frame{message = Message, code = Code, + cl_trid = ClTRID} -> + Command = "error", + Request = epp_http_client:request_builder(#{command => + Command, + session_id => SessionId, + headers => Headers, + code => Code, + message => Message, + cl_trid => ClTRID}) end, {_Status, Body} = epp_http_client:request(Request), frame_to_socket(Body, Socket), %% On logout, close the socket. %% Else, go back to the beginning of the loop. if Command =:= "logout" -> - ok = gen_tcp:shutdown(Socket, read_write), - {stop, normal, State}; + ok = gen_tcp:shutdown(Socket, read_write), + {stop, normal, State}; true -> - gen_server:cast(self(), process_command), - {noreply, - State#state{socket = Socket, session_id = SessionId}} + gen_server:cast(self(), process_command), + {noreply, + State#state{socket = Socket, session_id = SessionId}} end. handle_call(_E, _From, State) -> {noreply, State}. @@ -112,20 +113,20 @@ write_line(Socket, Line) -> read_length(Socket) -> case gen_tcp:recv(Socket, 4) of - {ok, Data} -> - Length = binary:decode_unsigned(Data, big), - LengthToReceive = - epp_util:frame_length_to_receive(Length), - {ok, LengthToReceive}; - {error, Reason} -> - io:format("Error: ~p~n", [Reason]), {error, Reason} + {ok, Data} -> + Length = binary:decode_unsigned(Data, big), + LengthToReceive = + epp_util:frame_length_to_receive(Length), + {ok, LengthToReceive}; + {error, Reason} -> + io:format("Error: ~p~n", [Reason]), {error, Reason} end. read_frame(Socket, FrameLength) -> case gen_tcp:recv(Socket, FrameLength) of - {ok, Data} -> {ok, Data}; - {error, Reason} -> - io:format("Error: ~p~n", [Reason]), {error, Reason} + {ok, Data} -> {ok, Data}; + {error, Reason} -> + io:format("Error: ~p~n", [Reason]), {error, Reason} end. %% Wrap a message in EPP frame, and then send it to socket. @@ -139,24 +140,24 @@ frame_to_socket(Message, Socket) -> state_from_socket(Socket, State) -> {ok, {PeerIp, _PeerPort}} = inet:peername(Socket), Headers = [{"User-Agent", <<"EPP proxy">>}, - {"X-Forwarded-for", epp_util:readable_ip(PeerIp)}], + {"X-Forwarded-for", epp_util:readable_ip(PeerIp)}], NewState = State#state{socket = Socket, - headers = Headers}, + headers = Headers}, lager:info("Established connection with: [~p]~n", - [NewState]), + [NewState]), NewState. %% 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, + {ok, Data} -> Data; + {error, _Details} -> {stop, normal, State} + end, Frame = case read_frame(Socket, Length) of - {ok, FrameData} -> FrameData; - {error, _FrameDetails} -> {stop, normal, State} - end, + {ok, FrameData} -> FrameData; + {error, _FrameDetails} -> {stop, normal, State} + end, Frame. %% Get status, XML record, command and clTRID if defined. @@ -164,11 +165,11 @@ frame_from_socket(Socket, State) -> 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, _} -> - #invalid_frame{code = ?XMLErrorCode, - message = ?XMLErrorMessage, cl_trid = ClTRID} + {ok, XMLRecord} -> + Command = epp_xml:get_command(XMLRecord), + #valid_frame{command = Command, cl_trid = ClTRID, + raw_frame = Frame}; + {error, _} -> + #invalid_frame{code = ?XMLErrorCode, + message = ?XMLErrorMessage, cl_trid = ClTRID} end. diff --git a/apps/epp_proxy/src/epp_tls_acceptor.erl b/apps/epp_proxy/src/epp_tls_acceptor.erl index 1d9bffd..d1e8b44 100644 --- a/apps/epp_proxy/src/epp_tls_acceptor.erl +++ b/apps/epp_proxy/src/epp_tls_acceptor.erl @@ -10,7 +10,7 @@ %% gen_server callbacks -export([handle_call/3, handle_cast/2, init/1, - start_link/1]). + start_link/1]). -export([crl_file/0]). @@ -18,21 +18,21 @@ start_link(Port) -> gen_server:start_link({local, ?SERVER}, ?MODULE, Port, - []). + []). init(Port) -> Options = [binary, {packet, raw}, {active, false}, - {reuseaddr, true}, {verify, verify_peer}, {depth, 1}, - {cacertfile, ca_cert_file()}, {certfile, cert_file()}, - {keyfile, key_file()}, {crl_check, peer}, - {crl_cache, - {ssl_crl_cache, {internal, [{http, 5000}]}}}], + {reuseaddr, true}, {verify, verify_peer}, {depth, 1}, + {cacertfile, ca_cert_file()}, {certfile, cert_file()}, + {keyfile, key_file()}, {crl_check, peer}, + {crl_cache, + {ssl_crl_cache, {internal, [{http, 5000}]}}}], ssl_crl_cache:insert({file, crl_file()}), {ok, ListenSocket} = ssl:listen(Port, Options), gen_server:cast(self(), accept), {ok, #state{socket = ListenSocket, port = Port, - options = Options}}. + options = Options}}. %% Acceptor has only one state that goes in a loop: %% 1. Listen for a connection from anyone. @@ -41,8 +41,8 @@ init(Port) -> %% the socket. %% 4. Go back to listening. handle_cast(accept, - State = #state{socket = ListenSocket, port = Port, - options = Options}) -> + State = #state{socket = ListenSocket, port = Port, + options = Options}) -> {ok, AcceptSocket} = ssl:transport_accept(ListenSocket), {ok, NewOwner} = create_worker(AcceptSocket), ok = ssl:controlling_process(AcceptSocket, NewOwner), @@ -51,7 +51,7 @@ handle_cast(accept, gen_server:cast(self(), accept), {noreply, State#state{socket = ListenSocket, port = Port, - options = Options}}. + options = Options}}. handle_call(_E, _From, State) -> {noreply, State}. @@ -59,32 +59,32 @@ handle_call(_E, _From, State) -> {noreply, State}. %% but for the purpose of order we should put them in a supervision tree. create_worker(Socket) -> ChildSpec = #{id => rand:uniform(), type => worker, - modules => [?WORKER], restart => temporary, - start => {?WORKER, start_link, [Socket]}}, + modules => [?WORKER], restart => temporary, + start => {?WORKER, start_link, [Socket]}}, supervisor:start_child(?POOL_SUPERVISOR, ChildSpec). %% Private functions for returning paths to files. It costs almost nothing %% to query them from ETS. ca_cert_file() -> case application:get_env(epp_proxy, cacertfile_path) of - undefined -> undefined; - {ok, CaCertFile} -> CaCertFile + undefined -> undefined; + {ok, CaCertFile} -> CaCertFile end. cert_file() -> case application:get_env(epp_proxy, certfile_path) of - undefined -> undefined; - {ok, CertFile} -> CertFile + undefined -> undefined; + {ok, CertFile} -> CertFile end. key_file() -> case application:get_env(epp_proxy, keyfile_path) of - undefined -> undefined; - {ok, KeyFile} -> KeyFile + undefined -> undefined; + {ok, KeyFile} -> KeyFile end. crl_file() -> case application:get_env(epp_proxy, crlfile_path) of - undefined -> undefined; - {ok, CrlFile} -> CrlFile + undefined -> undefined; + {ok, CrlFile} -> CrlFile end. diff --git a/apps/epp_proxy/src/epp_tls_worker.erl b/apps/epp_proxy/src/epp_tls_worker.erl index 0a0f272..b302918 100644 --- a/apps/epp_proxy/src/epp_tls_worker.erl +++ b/apps/epp_proxy/src/epp_tls_worker.erl @@ -8,7 +8,7 @@ %% gen_server callbacks -export([handle_call/3, handle_cast/2, init/1, - start_link/1]). + start_link/1]). -export([code_change/3]). @@ -51,14 +51,14 @@ handle_cast(serve, State = #state{socket = Socket}) -> %% 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}) -> - Request = - epp_http_client:request_builder(#{command => "hello", - session_id => SessionId, - raw_frame => "", - headers => Headers, - cl_trid => nomatch}), + State = #state{socket = Socket, session_id = SessionId, + headers = Headers}) -> + Request = epp_http_client:request_builder(#{command => + "hello", + session_id => SessionId, + raw_frame => "", + headers => Headers, + cl_trid => nomatch}), {_Status, Body} = epp_http_client:request(Request), frame_to_socket(Body, Socket), gen_server:cast(self(), process_command), @@ -74,89 +74,80 @@ handle_cast(greeting, %% %% Otherwise send "process_command" again to self to repeat the process. handle_cast(process_command, - State = #state{socket = Socket, session_id = SessionId, - headers = Headers}) -> + 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 = - epp_http_client:request_builder(#{command => Command, - session_id => SessionId, - raw_frame => RawFrame, - headers => Headers, - cl_trid => ClTRID}); - #invalid_frame{message = Message, code = Code, - cl_trid = ClTRID} -> - Command = "error", - Request = - epp_http_client:request_builder(#{command => Command, - session_id => SessionId, - headers => Headers, - code => Code, - message => Message, - cl_trid => ClTRID}) + #valid_frame{command = Command, cl_trid = ClTRID} -> + Request = epp_http_client:request_builder(#{command => + Command, + session_id => SessionId, + raw_frame => RawFrame, + headers => Headers, + cl_trid => ClTRID}); + #invalid_frame{message = Message, code = Code, + cl_trid = ClTRID} -> + Command = "error", + Request = epp_http_client:request_builder(#{command => + Command, + session_id => SessionId, + headers => Headers, + code => Code, + message => Message, + cl_trid => ClTRID}) end, {_Status, Body} = epp_http_client:request(Request), frame_to_socket(Body, Socket), %% On logout, close the socket. %% Else, go back to the beginning of the loop. if Command =:= "logout" -> - ok = ssl:shutdown(Socket, read_write), - {stop, normal, State}; + ok = ssl:shutdown(Socket, read_write), + {stop, normal, State}; true -> - gen_server:cast(self(), process_command), - {noreply, - State#state{socket = Socket, session_id = SessionId}} + 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 functions -read_length(Socket) -> - case ssl:recv(Socket, 4) of - {ok, Data} -> - Length = binary:decode_unsigned(Data, big), - LengthToReceive = - epp_util:frame_length_to_receive(Length), - {ok, LengthToReceive}; - {error, Reason} -> {error, Reason} - end. - -read_frame(Socket, FrameLength) -> - case ssl:recv(Socket, FrameLength, ?DefaultTimeout) of - {ok, Data} -> {ok, Data}; - {error, Reason} -> {error, Reason} - end. - %% 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), ByteSize = <>, - write_line(Socket, ByteSize), - write_line(Socket, Message). + CompleteMessage = <>, + write_line(Socket, CompleteMessage). 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. -%% If the client closes connection abruptly, then kill the process frame_from_socket(Socket, State) -> - {ok, CompleteFrame} = ssl:recv(Socket, 0, ?DefaultTimeout), - EPPEnvelope = binary:part(CompleteFrame, {0, 4}), - ReportedLength = binary:decode_unsigned(EPPEnvelope, big), + case ssl:recv(Socket, 0, ?DefaultTimeout) of + {ok, Data} -> + EPPEnvelope = binary:part(Data, {0, 4}), + ReportedLength = binary:decode_unsigned(EPPEnvelope, + big), + read_until_exhausted(Socket, ReportedLength, Data); + {error, closed} -> log_and_exit(State); + {error, timeout} -> log_on_timeout(State) + end. - read_until_exhausted(Socket, CompleteFrame, ReportedLength). - -read_until_exhausted(Socket, Frame, ExpectedLength) -> - if - ExpectedLength =:= byte_size(Frame) -> - binary:part(Frame, {byte_size(Frame), 4 - ExpectedLength}); - ExpectedLength > byte_size(Frame) -> - {ok, NextFrame} = ssl:recv(Socket, 0, ?DefaultTimeout), - NewFrame = <>, - read_until_exhausted(Socket, NewFrame, ExpectedLength) +%% When an EPP message is long, it will be received in smaller chunks. +%% For example, first 4 bytes equal 800 000, but we'd receive only 200 000 +%% in the first chunk. In such a case, we should listen for more. +%% +%% Note that there is no case for messages the exceed the reported length of a +%% frame. Those cases are invalid from the perspective of EPP protocol and +%% should not be supported. +read_until_exhausted(Socket, ExpectedLength, Frame) -> + if ExpectedLength =:= byte_size(Frame) -> + binary:part(Frame, + {byte_size(Frame), 4 - ExpectedLength}); + ExpectedLength > byte_size(Frame) -> + {ok, NextFrame} = ssl:recv(Socket, 0, ?DefaultTimeout), + NewFrame = <>, + read_until_exhausted(Socket, ExpectedLength, NewFrame) end. log_and_exit(State) -> @@ -172,15 +163,15 @@ 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), + epp_certs:headers_from_cert(PeerCert), 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)}], + {"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}, + headers = Headers}, lager:info("Established connection with: [~p]~n", - [NewState]), + [NewState]), NewState. %% Get status, XML record, command and clTRID if defined. @@ -188,11 +179,11 @@ state_from_socket(Socket, State) -> 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, _} -> - #invalid_frame{code = ?XMLErrorCode, - message = ?XMLErrorMessage, cl_trid = ClTRID} + {ok, XMLRecord} -> + Command = epp_xml:get_command(XMLRecord), + #valid_frame{command = Command, cl_trid = ClTRID, + raw_frame = Frame}; + {error, _} -> + #invalid_frame{code = ?XMLErrorCode, + message = ?XMLErrorMessage, cl_trid = ClTRID} end. diff --git a/apps/epp_proxy/src/epp_util.erl b/apps/epp_proxy/src/epp_util.erl index 443ad7e..9573990 100644 --- a/apps/epp_proxy/src/epp_util.erl +++ b/apps/epp_proxy/src/epp_util.erl @@ -1,8 +1,8 @@ -module(epp_util). -export([create_map/1, create_session_id/1, - frame_length/1, frame_length_to_receive/1, - frame_length_to_send/1, readable_ip/1, session_id/1]). + frame_length/1, frame_length_to_receive/1, + frame_length_to_send/1, readable_ip/1, session_id/1]). -define(OFFSET, 4). @@ -16,7 +16,7 @@ session_id(Pid) -> %% Give me a process id, I'll create a random map for you. -spec create_map(pid()) -> #{string() => pid(), - string() => float(), string() => string()}. + string() => float(), string() => string()}. create_map(Pid) when is_pid(Pid) -> Now = erlang:system_time(second), @@ -26,21 +26,21 @@ create_map(Pid) when is_pid(Pid) -> %% Given the special data structure, return back a binary hash to pass to the %% application server. -spec create_session_id(#{string() => pid(), - string() => float(), - string() => string()}) -> [char()]. + string() => float(), + string() => string()}) -> [char()]. create_session_id(#{"pid" := Pid, "random" := Random, - "timestamp" := Timestamp}) -> + "timestamp" := Timestamp}) -> Map = #{"pid" => pid_to_list(Pid), - "random" => float_to_list(Random), - "timestamp" => Timestamp}, + "random" => float_to_list(Random), + "timestamp" => Timestamp}, ListOfTuples = maps:to_list(Map), ListOfLists = [[X, ",", Y] || {X, Y} <- ListOfTuples], NestedList = lists:join(",", ListOfLists), ListOfGlyphs = lists:flatten(NestedList), BinaryHash = crypto:hash(sha512, ListOfGlyphs), String = lists:flatten([integer_to_list(X, 16) - || <> <= BinaryHash]), + || <> <= BinaryHash]), String. frame_length_to_receive(Size) when Size >= 0 -> @@ -57,13 +57,13 @@ frame_length(Frame) when is_list(Frame) -> %% Pass a tuple of IP address, return a binary for sending over the wire. -spec readable_ip({integer(), integer(), integer(), - integer()}) -> binary(). + integer()}) -> binary(). readable_ip({FirstOctet, SecondOctet, ThirdOctet, - FourthOctet}) -> + FourthOctet}) -> List = [integer_to_list(FirstOctet), ".", - integer_to_list(SecondOctet), ".", - integer_to_list(ThirdOctet), ".", - integer_to_list(FourthOctet)], + integer_to_list(SecondOctet), ".", + integer_to_list(ThirdOctet), ".", + integer_to_list(FourthOctet)], Binary = list_to_binary(List), Binary. diff --git a/apps/epp_proxy/src/epp_xml.erl b/apps/epp_proxy/src/epp_xml.erl index 514a443..57013d0 100644 --- a/apps/epp_proxy/src/epp_xml.erl +++ b/apps/epp_proxy/src/epp_xml.erl @@ -6,12 +6,12 @@ %% Get command from an xmlElement. Otherwise return undefined. get_command(Record) - when is_record(Record, xmlElement) -> + when is_record(Record, xmlElement) -> case xmerl_xpath:string("name(/epp/command/*[1])", - Record) - of - {xmlObj, string, []} -> undefined; - {xmlObj, string, Command} -> Command + Record) + of + {xmlObj, string, []} -> undefined; + {xmlObj, string, Command} -> Command end; get_command(_) -> undefined. @@ -25,10 +25,10 @@ parse(_) -> {error, {fatal, {expected_binary_or_list}}}. %% Parse a record that came from the wire and return a xmlElement record. parse_list(List) when is_list(List) -> try xmerl_scan:string(List, [{quiet, true}]) of - {Record, []} when is_record(Record, xmlElement) -> - {ok, Record} + {Record, []} when is_record(Record, xmlElement) -> + {ok, Record} catch - exit:X -> {error, X} + exit:X -> {error, X} end. %% The idea is that even when XML command is invalid, @@ -43,7 +43,7 @@ find_cltrid(_) -> nomatch. run_regex(Text) -> {ok, MP} = re:compile("(?.+)"), case re:run(Text, MP, [{capture, ["cltrid"], binary}]) - of - {match, [Binary]} -> Binary; - nomatch -> nomatch + of + {match, [Binary]} -> Binary; + nomatch -> nomatch end. diff --git a/rebar.config b/rebar.config index 502d63f..85698da 100644 --- a/rebar.config +++ b/rebar.config @@ -48,4 +48,5 @@ {test, [{deps, [proper]}]}] }. -{plugins, [rebar3_fmt]}. +{plugins, [rebar3_auto, + {erl_tidy_prv_fmt, ".*", {git, "git://github.com/tsloughter/erl_tidy.git", {branch, "master"}}}]}.