diff --git a/rebar.config b/rebar.config index b03a6bba7..aa9224b33 100644 --- a/rebar.config +++ b/rebar.config @@ -24,7 +24,7 @@ {fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.26"}}}, {stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.14"}}}, {fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.34"}}}, - {xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.2.6"}}}, + {xmpp, ".*", {git, "https://github.com/processone/xmpp", "2756f13fd2670b99a41c5a8d558b90528283ff6f"}}, {fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.17"}}}, {jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}}, {p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.3"}}}, diff --git a/src/ejabberd_http.erl b/src/ejabberd_http.erl index 727b57f8f..769577371 100644 --- a/src/ejabberd_http.erl +++ b/src/ejabberd_http.erl @@ -69,7 +69,8 @@ default_host, custom_headers, trail = <<>>, - addr_re + addr_re, + sock_peer_name = none }). -define(XHTML_DOCTYPE, @@ -143,6 +144,7 @@ init({SockMod, Socket}, Opts) -> true -> [{[], ejabberd_xmlrpc}]; false -> [] end, + SockPeer = proplists:get_value(sock_peer_name, Opts, none), DefinedHandlers = proplists:get_value(request_handlers, Opts, []), RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++ Admin ++ Bind ++ XMLRPC, @@ -159,6 +161,7 @@ init({SockMod, Socket}, Opts) -> custom_headers = CustomHeaders, options = Opts, request_handlers = RequestHandlers, + sock_peer_name = SockPeer, addr_re = RE}, try receive_headers(State) of V -> V @@ -463,6 +466,7 @@ process_request(#state{request_method = Method, request_version = Version, sockmod = SockMod, socket = Socket, + sock_peer_name = SockPeer, options = Options, request_host = Host, request_port = Port, @@ -481,13 +485,17 @@ process_request(#state{request_method = Method, {State2, false} -> {State2, make_bad_request(State)}; {State2, {LPath, LQuery, Data}} -> - PeerName = - case SockMod of - gen_tcp -> - inet:peername(Socket); - _ -> - SockMod:peername(Socket) - end, + PeerName = case SockPeer of + none -> + case SockMod of + gen_tcp -> + inet:peername(Socket); + _ -> + SockMod:peername(Socket) + end; + {_, Peer} -> + {ok, Peer} + end, IPHere = case PeerName of {ok, V} -> V; {error, _} = E -> throw(E) diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl index 3a1448c0b..e8742413b 100644 --- a/src/ejabberd_listener.erl +++ b/src/ejabberd_listener.erl @@ -204,26 +204,49 @@ accept(ListenSocket, Module, Opts, Sup, Interval) -> NewInterval = check_rate_limit(Interval), case gen_tcp:accept(ListenSocket) of {ok, Socket} -> - case {inet:sockname(Socket), inet:peername(Socket)} of - {{ok, {Addr, Port}}, {ok, {PAddr, PPort}}} -> - Receiver = case start_connection(Module, Socket, Opts, Sup) of - {ok, RecvPid} -> - RecvPid; - _ -> - gen_tcp:close(Socket), - none - end, - ?INFO_MSG("(~p) Accepted connection ~s:~p -> ~s:~p", - [Receiver, - ejabberd_config:may_hide_data(inet_parse:ntoa(PAddr)), - PPort, inet_parse:ntoa(Addr), Port]); + case proplists:get_value(use_proxy_protocol, Opts, false) of + true -> + case proxy_protocol:decode(gen_tcp, Socket, 10000) of + {error, Err} -> + ?ERROR_MSG("(~w) Proxy protocol parsing failed: ~s", + [ListenSocket, inet:format_error(Err)]), + gen_tcp:close(Socket); + {{Addr, Port}, {PAddr, PPort}} = SP -> + Opts2 = [{sock_peer_name, SP} | Opts], + Receiver = case start_connection(Module, Socket, Opts2, Sup) of + {ok, RecvPid} -> + RecvPid; + _ -> + gen_tcp:close(Socket), + none + end, + ?INFO_MSG("(~p) Accepted proxied connection ~s:~p -> ~s:~p", + [Receiver, + ejabberd_config:may_hide_data(inet_parse:ntoa(PAddr)), + PPort, inet_parse:ntoa(Addr), Port]) + end; _ -> - gen_tcp:close(Socket) + case {inet:sockname(Socket), inet:peername(Socket)} of + {{ok, {Addr, Port}}, {ok, {PAddr, PPort}}} -> + Receiver = case start_connection(Module, Socket, Opts, Sup) of + {ok, RecvPid} -> + RecvPid; + _ -> + gen_tcp:close(Socket), + none + end, + ?INFO_MSG("(~p) Accepted connection ~s:~p -> ~s:~p", + [Receiver, + ejabberd_config:may_hide_data(inet_parse:ntoa(PAddr)), + PPort, inet_parse:ntoa(Addr), Port]); + _ -> + gen_tcp:close(Socket) + end end, accept(ListenSocket, Module, Opts, Sup, NewInterval); {error, Reason} -> ?ERROR_MSG("(~w) Failed TCP accept: ~s", - [ListenSocket, inet:format_error(Reason)]), + [ListenSocket, inet:format_error(Reason)]), accept(ListenSocket, Module, Opts, Sup, NewInterval) end. @@ -665,7 +688,9 @@ listen_opt_type(max_fsm_queue) -> listen_opt_type(shaper) -> fun acl:shaper_rules_validator/1; listen_opt_type(access) -> - fun acl:access_rules_validator/1. + fun acl:access_rules_validator/1; +listen_opt_type(use_proxy_protocol) -> + fun(B) when is_boolean(B) -> B end. listen_options() -> [module, port, @@ -675,6 +700,7 @@ listen_options() -> {inet6, false}, {accept_interval, 0}, {backlog, 5}, + {use_proxy_protocol, false}, {supervisor, true}]. opt_type(listen) -> fun validate_cfg/1; diff --git a/src/proxy_protocol.erl b/src/proxy_protocol.erl new file mode 100644 index 000000000..232f9cca3 --- /dev/null +++ b/src/proxy_protocol.erl @@ -0,0 +1,182 @@ +%%%---------------------------------------------------------------------- +%%% File : ejabberd_http.erl +%%% Author : Paweł Chmielowski +%%% Purpose : +%%% Created : 27 Nov 2018 by Paweł Chmielowski +%%% +%%% +%%% ejabberd, Copyright (C) 2002-2018 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License along +%%% with this program; if not, write to the Free Software Foundation, Inc., +%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +%%% +%%%---------------------------------------------------------------------- +-module(proxy_protocol). +-author("pawel@process-one.net"). + +%% API +-export([decode/3]). + +decode(SockMod, Socket, Timeout) -> + V = SockMod:recv(Socket, 6, Timeout), + case V of + {ok, <<"PROXY ">>} -> + decode_v1(SockMod, Socket, Timeout); + {ok, <<16#0d, 16#0a, 16#0d, 16#0a, 16#00, 16#0d>>} -> + decode_v2(SockMod, Socket, Timeout); + _ -> + {error, eproto} + end. + +decode_v1(SockMod, Socket, Timeout) -> + case read_until_rn(SockMod, Socket, <<>>, false, Timeout) of + {error, _} = Err -> + Err; + Val -> + case binary:split(Val, <<" ">>, [global]) of + [<<"TCP4">>, SAddr, DAddr, SPort, DPort] -> + try {inet_parse:ipv4strict_address(binary_to_list(SAddr)), + inet_parse:ipv4strict_address(binary_to_list(DAddr)), + binary_to_integer(SPort), + binary_to_integer(DPort)} + of + {{ok, DA}, {ok, SA}, DP, SP} -> + {{SA, SP}, {DA, DP}}; + _ -> + {error, eproto} + catch + error:badarg -> + {error, eproto} + end; + [<<"TCP6">>, SAddr, DAddr, SPort, DPort] -> + try {inet_parse:ipv6strict_address(binary_to_list(SAddr)), + inet_parse:ipv6strict_address(binary_to_list(DAddr)), + binary_to_integer(SPort), + binary_to_integer(DPort)} + of + {{ok, DA}, {ok, SA}, DP, SP} -> + {{SA, SP}, {DA, DP}}; + _ -> + {error, eproto} + catch + error:badarg -> + {error, eproto} + end; + [<<"UNKNOWN">> | _] -> + {undefined, undefined} + end + end. + +decode_v2(SockMod, Socket, Timeout) -> + case SockMod:recv(Socket, 10, Timeout) of + {error, _} = Err -> + Err; + {ok, <<16#0a, 16#51, 16#55, 16#49, 16#54, 16#0a, + 2:4, Command:4, Transport:8, AddrLen:16/big-unsigned-integer>>} -> + case SockMod:recv(Socket, AddrLen, Timeout) of + {error, _} = Err -> + Err; + {ok, Data} -> + case Command of + 0 -> + case {inet:sockname(Socket), inet:peername(Socket)} of + {{ok, SA}, {ok, DA}} -> + {SA, DA}; + {{error, _} = E, _} -> + E; + {_, {error, _} = E} -> + E + end; + 1 -> + case Transport of + % UNSPEC or UNIX + V when V == 0; V == 16#31; V == 16#32 -> + {{unknown, unknown}, {unknown, unknown}}; + % IPV4 over TCP or UDP + V when V == 16#11; V == 16#12 -> + case Data of + <> -> + {{{S1, S2, S3, S4}, SP}, + {{D1, D2, D3, D4}, DP}}; + _ -> + {error, eproto} + end; + % IPV6 over TCP or UDP + V when V == 16#21; V == 16#22 -> + case Data of + <> -> + {{{S1, S2, S3, S4, S5, S6, S7, S8}, SP}, + {{D1, D2, D3, D4, D5, D6, D7, D8}, DP}}; + _ -> + {error, eproto} + end + end; + _ -> + {error, eproto} + end + end; + <<16#0a, 16#51, 16#55, 16#49, 16#54, 16#0a, _/binary>> -> + {error, eproto}; + _ -> + {error, eproto} + end. + +read_until_rn(_SockMod, _Socket, Data, _, _) when size(Data) > 107 -> + {error, eproto}; +read_until_rn(SockMod, Socket, Data, true, Timeout) -> + case SockMod:recv(Socket, 1, Timeout) of + {ok, <<"\n">>} -> + Data; + {ok, <<"\r">>} -> + read_until_rn(SockMod, Socket, <>, + true, Timeout); + {ok, Other} -> + read_until_rn(SockMod, Socket, <>, + false, Timeout); + {error, _} = Err -> + Err + end; +read_until_rn(SockMod, Socket, Data, false, Timeout) -> + case SockMod:recv(Socket, 2, Timeout) of + {ok, <<"\r\n">>} -> + Data; + {ok, <>} -> + read_until_rn(SockMod, Socket, <>, + true, Timeout); + {ok, Other} -> + read_until_rn(SockMod, Socket, <>, + false, Timeout); + {error, _} = Err -> + Err + end.