diff options
author | Badlop <badlop@process-one.net> | 2013-03-14 10:33:02 +0100 |
---|---|---|
committer | Badlop <badlop@process-one.net> | 2013-03-14 10:33:02 +0100 |
commit | 9deb294328bb3f9eb6bd2c0e7cd500732e9b5830 (patch) | |
tree | 7e1066c130250627ee0abab44a135f583a28d07f /src/mod_proxy65 | |
parent | list_to_integer/2 only works in OTP R14 and newer (diff) |
Accumulated patch to binarize and indent code
Diffstat (limited to 'src/mod_proxy65')
-rw-r--r-- | src/mod_proxy65/Makefile.in | 2 | ||||
-rw-r--r-- | src/mod_proxy65/mod_proxy65.erl | 49 | ||||
-rw-r--r-- | src/mod_proxy65/mod_proxy65.hrl | 25 | ||||
-rw-r--r-- | src/mod_proxy65/mod_proxy65_lib.erl | 64 | ||||
-rw-r--r-- | src/mod_proxy65/mod_proxy65_service.erl | 319 | ||||
-rw-r--r-- | src/mod_proxy65/mod_proxy65_sm.erl | 171 | ||||
-rw-r--r-- | src/mod_proxy65/mod_proxy65_stream.erl | 287 |
7 files changed, 474 insertions, 443 deletions
diff --git a/src/mod_proxy65/Makefile.in b/src/mod_proxy65/Makefile.in index 3a9806b22..3fc94c662 100644 --- a/src/mod_proxy65/Makefile.in +++ b/src/mod_proxy65/Makefile.in @@ -14,7 +14,7 @@ EFLAGS += -pz .. # make debug=true to compile Erlang module with debug informations. ifdef debug - EFLAGS+=+debug_info +export_all + EFLAGS+=+debug_info endif OUTDIR = .. diff --git a/src/mod_proxy65/mod_proxy65.erl b/src/mod_proxy65/mod_proxy65.erl index f335037d5..3e8354caf 100644 --- a/src/mod_proxy65/mod_proxy65.erl +++ b/src/mod_proxy65/mod_proxy65.erl @@ -25,9 +25,11 @@ %%%---------------------------------------------------------------------- -module(mod_proxy65). + -author('xram@jabber.ru'). -behaviour(gen_mod). + -behaviour(supervisor). %% gen_mod callbacks. @@ -43,15 +45,12 @@ start(Host, Opts) -> case mod_proxy65_service:add_listener(Host, Opts) of - {error, _} = Err -> - erlang:error(Err); - _ -> - Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - ChildSpec = { - Proc, {?MODULE, start_link, [Host, Opts]}, - transient, infinity, supervisor, [?MODULE] - }, - supervisor:start_child(ejabberd_sup, ChildSpec) + {error, _} = Err -> erlang:error(Err); + _ -> + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]}, + transient, infinity, supervisor, [?MODULE]}, + supervisor:start_child(ejabberd_sup, ChildSpec) end. stop(Host) -> @@ -62,20 +61,22 @@ stop(Host) -> start_link(Host, Opts) -> Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - supervisor:start_link({local, Proc}, ?MODULE, [Host, Opts]). + supervisor:start_link({local, Proc}, ?MODULE, + [Host, Opts]). init([Host, Opts]) -> - Service = - {mod_proxy65_service, {mod_proxy65_service, start_link, [Host, Opts]}, - transient, 5000, worker, [mod_proxy65_service]}, - StreamSupervisor = - {ejabberd_mod_proxy65_sup, - {ejabberd_tmp_sup, start_link, - [gen_mod:get_module_proc(Host, ejabberd_mod_proxy65_sup), - mod_proxy65_stream]}, - transient, infinity, supervisor, [ejabberd_tmp_sup]}, - StreamManager = - {mod_proxy65_sm, {mod_proxy65_sm, start_link, [Host, Opts]}, - transient, 5000, worker, [mod_proxy65_sm]}, - {ok, {{one_for_one, 10, 1}, - [StreamManager, StreamSupervisor, Service]}}. + Service = {mod_proxy65_service, + {mod_proxy65_service, start_link, [Host, Opts]}, + transient, 5000, worker, [mod_proxy65_service]}, + StreamSupervisor = {ejabberd_mod_proxy65_sup, + {ejabberd_tmp_sup, start_link, + [gen_mod:get_module_proc(Host, + ejabberd_mod_proxy65_sup), + mod_proxy65_stream]}, + transient, infinity, supervisor, [ejabberd_tmp_sup]}, + StreamManager = {mod_proxy65_sm, + {mod_proxy65_sm, start_link, [Host, Opts]}, transient, + 5000, worker, [mod_proxy65_sm]}, + {ok, + {{one_for_one, 10, 1}, + [StreamManager, StreamSupervisor, Service]}}. diff --git a/src/mod_proxy65/mod_proxy65.hrl b/src/mod_proxy65/mod_proxy65.hrl index 27e2f2e61..d0779af14 100644 --- a/src/mod_proxy65/mod_proxy65.hrl +++ b/src/mod_proxy65/mod_proxy65.hrl @@ -26,36 +26,49 @@ %% Authentication methods -define(AUTH_ANONYMOUS, 0). + -define(AUTH_GSSAPI, 1). + -define(AUTH_PLAIN, 2). --define(AUTH_NO_METHODS, 16#FF). %% Address Type +-define(AUTH_NO_METHODS, 255). + -define(ATYP_IPV4, 1). + -define(ATYP_DOMAINNAME, 3). + -define(ATYP_IPV6, 4). %% Commands -define(CMD_CONNECT, 1). + -define(CMD_BIND, 2). + -define(CMD_UDP, 3). %% RFC 1928 replies -define(SUCCESS, 0). + -define(ERR_GENERAL_FAILURE, 1). + -define(ERR_NOT_ALLOWED, 2). + -define(ERR_NETWORK_UNREACHABLE, 3). + -define(ERR_HOST_UNREACHABLE, 4). + -define(ERR_CONNECTION_REFUSED, 5). + -define(ERR_TTL_EXPIRED, 6). + -define(ERR_COMMAND_NOT_SUPPORTED, 7). + -define(ERR_ADDRESS_TYPE_NOT_SUPPORTED, 8). %% RFC 1928 defined timeout. -define(SOCKS5_REPLY_TIMEOUT, 10000). --record(s5_request, { - rsv = 0, - cmd, - sha1 - }). +-record(s5_request, {rsv = 0 :: integer(), + cmd = connect :: connect | udp, + sha1 = <<"">> :: binary()}). diff --git a/src/mod_proxy65/mod_proxy65_lib.erl b/src/mod_proxy65/mod_proxy65_lib.erl index 19ad49c05..388811436 100644 --- a/src/mod_proxy65/mod_proxy65_lib.erl +++ b/src/mod_proxy65/mod_proxy65_lib.erl @@ -25,59 +25,49 @@ %%%---------------------------------------------------------------------- -module(mod_proxy65_lib). + -author('xram@jabber.ru'). -include("mod_proxy65.hrl"). --export([ - unpack_init_message/1, - unpack_auth_request/1, - unpack_request/1, - make_init_reply/1, - make_auth_reply/1, - make_reply/1, - make_error_reply/1, - make_error_reply/2 - ]). +-export([unpack_init_message/1, unpack_auth_request/1, + unpack_request/1, make_init_reply/1, make_auth_reply/1, + make_reply/1, make_error_reply/1, make_error_reply/2]). -unpack_init_message(<<?VERSION_5, N, AuthMethodList:N/binary>>) - when N > 0, N < 256 -> +unpack_init_message(<<(?VERSION_5), N, + AuthMethodList:N/binary>>) + when N > 0, N < 256 -> {ok, binary_to_list(AuthMethodList)}; +unpack_init_message(_) -> error. -unpack_init_message(_) -> - error. - -unpack_auth_request(<<1, ULen, User:ULen/binary, - PLen, Pass:PLen/binary>>) when ULen < 256, PLen < 256 -> - {binary_to_list(User), binary_to_list(Pass)}; +unpack_auth_request(<<1, ULen, User:ULen/binary, PLen, + Pass:PLen/binary>>) + when ULen < 256, PLen < 256 -> + {(User), (Pass)}; +unpack_auth_request(_) -> error. -unpack_auth_request(_) -> - error. - -unpack_request(<<?VERSION_5, CMD, RSV, - ?ATYP_DOMAINNAME, 40, - SHA1:40/binary, 0, 0>>) when CMD == ?CMD_CONNECT; - CMD == ?CMD_UDP -> - Command = if - CMD == ?CMD_CONNECT -> connect; - CMD == ?CMD_UDP -> udp +unpack_request(<<(?VERSION_5), CMD, RSV, + (?ATYP_DOMAINNAME), 40, SHA1:40/binary, 0, 0>>) + when CMD == (?CMD_CONNECT); CMD == (?CMD_UDP) -> + Command = if CMD == (?CMD_CONNECT) -> connect; + CMD == (?CMD_UDP) -> udp end, - #s5_request{cmd = Command, rsv = RSV, sha1 = binary_to_list(SHA1)}; - -unpack_request(_) -> - error. + #s5_request{cmd = Command, rsv = RSV, sha1 = (SHA1)}; +unpack_request(_) -> error. -make_init_reply(Method) -> - [?VERSION_5, Method]. +make_init_reply(Method) -> [?VERSION_5, Method]. make_auth_reply(true) -> [1, ?SUCCESS]; make_auth_reply(false) -> [1, ?ERR_NOT_ALLOWED]. make_reply(#s5_request{rsv = RSV, sha1 = SHA1}) -> - [?VERSION_5, ?SUCCESS, RSV, ?ATYP_DOMAINNAME, length(SHA1), SHA1, 0,0]. + [?VERSION_5, ?SUCCESS, RSV, ?ATYP_DOMAINNAME, + byte_size(SHA1), SHA1, 0, 0]. make_error_reply(Request) -> make_error_reply(Request, ?ERR_NOT_ALLOWED). -make_error_reply(#s5_request{rsv = RSV, sha1 = SHA1}, Reason) -> - [?VERSION_5, Reason, RSV, ?ATYP_DOMAINNAME, length(SHA1), SHA1, 0,0]. +make_error_reply(#s5_request{rsv = RSV, sha1 = SHA1}, + Reason) -> + [?VERSION_5, Reason, RSV, ?ATYP_DOMAINNAME, + byte_size(SHA1), SHA1, 0, 0]. diff --git a/src/mod_proxy65/mod_proxy65_service.erl b/src/mod_proxy65/mod_proxy65_service.erl index d3dda4332..22ac6cde6 100644 --- a/src/mod_proxy65/mod_proxy65_service.erl +++ b/src/mod_proxy65/mod_proxy65_service.erl @@ -25,37 +25,33 @@ %%%---------------------------------------------------------------------- -module(mod_proxy65_service). + -author('xram@jabber.ru'). -behaviour(gen_server). %% gen_server callbacks. --export([init/1, - handle_info/2, - handle_call/3, - handle_cast/2, - terminate/2, - code_change/3 - ]). +-export([init/1, handle_info/2, handle_call/3, + handle_cast/2, terminate/2, code_change/3]). %% API. --export([start_link/2, add_listener/2, delete_listener/1]). +-export([start_link/2, add_listener/2, + delete_listener/1]). -include("ejabberd.hrl"). + -include("jlib.hrl"). -define(PROCNAME, ejabberd_mod_proxy65_service). --record(state, { - myhost, - serverhost, - name, - stream_addr, - port, - ip, - acl - }). - +-record(state, + {myhost = <<"">> :: binary(), + serverhost = <<"">> :: binary(), + name = <<"">> :: binary(), + stream_addr = [] :: [attr()], + port = 0 :: inet:port_number(), + ip = {127,0,0,1} :: inet:ip_address(), + acl = none :: atom()}). %%%------------------------ %%% gen_server callbacks @@ -63,43 +59,44 @@ start_link(Host, Opts) -> Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []). + gen_server:start_link({local, Proc}, ?MODULE, + [Host, Opts], []). init([Host, Opts]) -> State = parse_options(Host, Opts), ejabberd_router:register_route(State#state.myhost), {ok, State}. -terminate(_Reason, #state{myhost=MyHost}) -> - ejabberd_router:unregister_route(MyHost), - ok. +terminate(_Reason, #state{myhost = MyHost}) -> + ejabberd_router:unregister_route(MyHost), ok. -handle_info({route, From, To, {xmlelement, "iq", _, _} = Packet}, State) -> +handle_info({route, From, To, + #xmlel{name = <<"iq">>} = Packet}, + State) -> IQ = jlib:iq_query_info(Packet), case catch process_iq(From, IQ, State) of - Result when is_record(Result, iq) -> - ejabberd_router:route(To, From, jlib:iq_to_xml(Result)); - {'EXIT', Reason} -> - ?ERROR_MSG("Error when processing IQ stanza: ~p", [Reason]), - Err = jlib:make_error_reply(Packet, ?ERR_INTERNAL_SERVER_ERROR), - ejabberd_router:route(To, From, Err); - _ -> - ok + Result when is_record(Result, iq) -> + ejabberd_router:route(To, From, jlib:iq_to_xml(Result)); + {'EXIT', Reason} -> + ?ERROR_MSG("Error when processing IQ stanza: ~p", + [Reason]), + Err = jlib:make_error_reply(Packet, + ?ERR_INTERNAL_SERVER_ERROR), + ejabberd_router:route(To, From, Err); + _ -> ok end, {noreply, State}; -handle_info(_Info, State) -> - {noreply, State}. +handle_info(_Info, State) -> {noreply, State}. handle_call(get_port_ip, _From, State) -> - {reply, {port_ip, State#state.port, State#state.ip}, State}; + {reply, {port_ip, State#state.port, State#state.ip}, + State}; handle_call(_Request, _From, State) -> {reply, ok, State}. -handle_cast(_Request, State) -> - {noreply, State}. +handle_cast(_Request, State) -> {noreply, State}. -code_change(_OldVsn, State, _Extra) -> - {ok, State}. +code_change(_OldVsn, State, _Extra) -> {ok, State}. %%%------------------------ %%% Listener management @@ -108,141 +105,181 @@ code_change(_OldVsn, State, _Extra) -> add_listener(Host, Opts) -> State = parse_options(Host, Opts), NewOpts = [Host | Opts], - ejabberd_listener:add_listener({State#state.port, State#state.ip}, mod_proxy65_stream, NewOpts). + ejabberd_listener:add_listener({State#state.port, + State#state.ip}, + mod_proxy65_stream, NewOpts). delete_listener(Host) -> Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - {port_ip, Port, IP} = gen_server:call(Proc, get_port_ip), - catch ejabberd_listener:delete_listener({Port, IP}, mod_proxy65_stream). + {port_ip, Port, IP} = gen_server:call(Proc, + get_port_ip), + catch ejabberd_listener:delete_listener({Port, IP}, + mod_proxy65_stream). %%%------------------------ %%% IQ Processing %%%------------------------ %% disco#info request -process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} = IQ, - #state{name=Name, serverhost=ServerHost}) -> - Info = ejabberd_hooks:run_fold( - disco_info, ServerHost, [], [ServerHost, ?MODULE, "", ""]), - IQ#iq{type = result, sub_el = - [{xmlelement, "query", [{"xmlns", ?NS_DISCO_INFO}], - iq_disco_info(Lang, Name) ++ Info}]}; - +process_iq(_, + #iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} = + IQ, + #state{name = Name, serverhost = ServerHost}) -> + Info = ejabberd_hooks:run_fold(disco_info, ServerHost, + [], [ServerHost, ?MODULE, <<"">>, <<"">>]), + IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, ?NS_DISCO_INFO}], + children = iq_disco_info(Lang, Name) ++ Info}]}; %% disco#items request -process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ, _) -> - IQ#iq{type = result, sub_el = - [{xmlelement, "query", [{"xmlns", ?NS_DISCO_ITEMS}], []}]}; - +process_iq(_, + #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ, _) -> + IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}], + children = []}]}; %% vCard request -process_iq(_, #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} = IQ, _) -> - IQ#iq{type = result, sub_el = - [{xmlelement, "vCard", [{"xmlns", ?NS_VCARD}], iq_vcard(Lang)}]}; - +process_iq(_, + #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} = IQ, + _) -> + IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"vCard">>, + attrs = [{<<"xmlns">>, ?NS_VCARD}], + children = iq_vcard(Lang)}]}; %% bytestreams info request -process_iq(JID, #iq{type = get, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS} = IQ, - #state{acl = ACL, stream_addr = StreamAddr, serverhost = ServerHost}) -> +process_iq(JID, + #iq{type = get, sub_el = SubEl, + xmlns = ?NS_BYTESTREAMS} = + IQ, + #state{acl = ACL, stream_addr = StreamAddr, + serverhost = ServerHost}) -> case acl:match_rule(ServerHost, ACL, JID) of - allow -> - StreamHostEl = [{xmlelement, "streamhost", StreamAddr, []}], - IQ#iq{type = result, sub_el = - [{xmlelement, "query", [{"xmlns", ?NS_BYTESTREAMS}], StreamHostEl}]}; - deny -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]} + allow -> + StreamHostEl = [#xmlel{name = <<"streamhost">>, + attrs = StreamAddr, children = []}], + IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, ?NS_BYTESTREAMS}], + children = StreamHostEl}]}; + deny -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]} end; - %% bytestream activation request -process_iq(InitiatorJID, #iq{type = set, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS} = IQ, +process_iq(InitiatorJID, + #iq{type = set, sub_el = SubEl, + xmlns = ?NS_BYTESTREAMS} = + IQ, #state{acl = ACL, serverhost = ServerHost}) -> case acl:match_rule(ServerHost, ACL, InitiatorJID) of - allow -> - ActivateEl = xml:get_path_s(SubEl, [{elem, "activate"}]), - SID = xml:get_tag_attr_s("sid", SubEl), - case catch jlib:string_to_jid(xml:get_tag_cdata(ActivateEl)) of - TargetJID when is_record(TargetJID, jid), SID /= "", - length(SID) =< 128, TargetJID /= InitiatorJID -> - Target = jlib:jid_to_string(jlib:jid_tolower(TargetJID)), - Initiator = jlib:jid_to_string(jlib:jid_tolower(InitiatorJID)), - SHA1 = sha:sha(SID ++ Initiator ++ Target), - case mod_proxy65_sm:activate_stream(SHA1, InitiatorJID, TargetJID, ServerHost) of - ok -> - IQ#iq{type = result, sub_el = []}; - false -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]}; - limit -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_RESOURCE_CONSTRAINT]}; - conflict -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_CONFLICT]}; - _ -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]} - end; - _ -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]} - end; - deny -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]} + allow -> + ActivateEl = xml:get_path_s(SubEl, + [{elem, <<"activate">>}]), + SID = xml:get_tag_attr_s(<<"sid">>, SubEl), + case catch + jlib:string_to_jid(xml:get_tag_cdata(ActivateEl)) + of + TargetJID + when is_record(TargetJID, jid), SID /= <<"">>, + byte_size(SID) =< 128, TargetJID /= InitiatorJID -> + Target = + jlib:jid_to_string(jlib:jid_tolower(TargetJID)), + Initiator = + jlib:jid_to_string(jlib:jid_tolower(InitiatorJID)), + SHA1 = sha:sha(<<SID/binary, Initiator/binary, Target/binary>>), + case mod_proxy65_sm:activate_stream(SHA1, InitiatorJID, + TargetJID, ServerHost) + of + ok -> IQ#iq{type = result, sub_el = []}; + false -> + IQ#iq{type = error, + sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]}; + limit -> + IQ#iq{type = error, + sub_el = [SubEl, ?ERR_RESOURCE_CONSTRAINT]}; + conflict -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_CONFLICT]}; + _ -> + IQ#iq{type = error, + sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]} + end; + _ -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]} + end; + deny -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]} end; - %% Unknown "set" or "get" request -process_iq(_, #iq{type=Type, sub_el=SubEl} = IQ, _) when Type==get; Type==set -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}; - +process_iq(_, #iq{type = Type, sub_el = SubEl} = IQ, _) + when Type == get; Type == set -> + IQ#iq{type = error, + sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}; %% IQ "result" or "error". -process_iq(_, _, _) -> - ok. +process_iq(_, _, _) -> ok. %%%------------------------- %%% Auxiliary functions. %%%------------------------- --define(FEATURE(Feat), {xmlelement,"feature",[{"var", Feat}],[]}). +-define(FEATURE(Feat), + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, Feat}], children = []}). iq_disco_info(Lang, Name) -> - [{xmlelement, "identity", - [{"category", "proxy"}, - {"type", "bytestreams"}, - {"name", translate:translate(Lang, Name)}], []}, - ?FEATURE(?NS_DISCO_INFO), - ?FEATURE(?NS_VCARD), - ?FEATURE(?NS_BYTESTREAMS)]. + [#xmlel{name = <<"identity">>, + attrs = + [{<<"category">>, <<"proxy">>}, + {<<"type">>, <<"bytestreams">>}, + {<<"name">>, translate:translate(Lang, Name)}], + children = []}, + ?FEATURE((?NS_DISCO_INFO)), ?FEATURE((?NS_VCARD)), + ?FEATURE((?NS_BYTESTREAMS))]. iq_vcard(Lang) -> - [{xmlelement, "FN", [], - [{xmlcdata, "ejabberd/mod_proxy65"}]}, - {xmlelement, "URL", [], - [{xmlcdata, ?EJABBERD_URI}]}, - {xmlelement, "DESC", [], - [{xmlcdata, translate:translate(Lang, "ejabberd SOCKS5 Bytestreams module") ++ - "\nCopyright (c) 2003-2013 ProcessOne"}]}]. + [#xmlel{name = <<"FN">>, attrs = [], + children = [{xmlcdata, <<"ejabberd/mod_proxy65">>}]}, + #xmlel{name = <<"URL">>, attrs = [], + children = [{xmlcdata, ?EJABBERD_URI}]}, + #xmlel{name = <<"DESC">>, attrs = [], + children = + [{xmlcdata, + <<(translate:translate(Lang, + <<"ejabberd SOCKS5 Bytestreams module">>))/binary, + "\nCopyright (c) 2003-2013 ProcessOne">>}]}]. parse_options(ServerHost, Opts) -> - MyHost = gen_mod:get_opt_host(ServerHost, Opts, "proxy.@HOST@"), - Port = gen_mod:get_opt(port, Opts, 7777), - ACL = gen_mod:get_opt(access, Opts, all), - Name = gen_mod:get_opt(name, Opts, "SOCKS5 Bytestreams"), - IP = case gen_mod:get_opt(ip, Opts, none) of - none -> get_my_ip(); - Addr -> Addr - end, - HostName = case gen_mod:get_opt(hostname, Opts, none) of - none -> - inet_parse:ntoa(IP); - HostAddr when is_tuple(HostAddr) -> - inet_parse:ntoa(HostAddr); - HostNameStr -> - HostNameStr - end, - StreamAddr = [{"jid", MyHost}, {"host", HostName}, - {"port", integer_to_list(Port)}], - #state{myhost = MyHost, - serverhost = ServerHost, - name = Name, - port = Port, - ip = IP, - stream_addr = StreamAddr, - acl = ACL}. + MyHost = gen_mod:get_opt_host(ServerHost, Opts, + <<"proxy.@HOST@">>), + Port = gen_mod:get_opt(port, Opts, + fun(P) when is_integer(P), P>0, P<65536 -> P end, + 7777), + ACL = gen_mod:get_opt(access, Opts, fun(A) when is_atom(A) -> A end, + all), + Name = gen_mod:get_opt(name, Opts, fun iolist_to_binary/1, + <<"SOCKS5 Bytestreams">>), + IP = gen_mod:get_opt(ip, Opts, + fun(Addr) -> + jlib:ip_to_list(Addr), + Addr + end, get_my_ip()), + HostName = gen_mod:get_opt(hostname, Opts, + fun(Addr) when is_tuple(Addr) -> + jlib:ip_to_list(Addr); + (S) -> + iolist_to_binary(S) + end, jlib:ip_to_list(IP)), + StreamAddr = [{<<"jid">>, MyHost}, + {<<"host">>, HostName}, + {<<"port">>, jlib:integer_to_binary(Port)}], + #state{myhost = MyHost, serverhost = ServerHost, + name = Name, port = Port, ip = IP, + stream_addr = StreamAddr, acl = ACL}. get_my_ip() -> {ok, MyHostName} = inet:gethostname(), case inet:getaddr(MyHostName, inet) of - {ok, Addr} -> Addr; - {error, _} -> {127,0,0,1} + {ok, Addr} -> Addr; + {error, _} -> {127, 0, 0, 1} end. diff --git a/src/mod_proxy65/mod_proxy65_sm.erl b/src/mod_proxy65/mod_proxy65_sm.erl index b5af45abc..fa9d257ef 100644 --- a/src/mod_proxy65/mod_proxy65_sm.erl +++ b/src/mod_proxy65/mod_proxy65_sm.erl @@ -25,96 +25,91 @@ %%%---------------------------------------------------------------------- -module(mod_proxy65_sm). + -author('xram@jabber.ru'). -behaviour(gen_server). %% gen_server callbacks. --export([init/1, - handle_info/2, - handle_call/3, - handle_cast/2, - terminate/2, - code_change/3 - ]). +-export([init/1, handle_info/2, handle_call/3, + handle_cast/2, terminate/2, code_change/3]). %% API. --export([ - start_link/2, - register_stream/1, - unregister_stream/1, - activate_stream/4 - ]). - --record(state, {max_connections}). --record(bytestream, { - sha1, %% SHA1 key - target, %% Target Pid - initiator, %% Initiator Pid - active = false, %% Activity flag - jid_i %% Initiator's JID - }). +-export([start_link/2, register_stream/1, + unregister_stream/1, activate_stream/4]). + +-record(state, {max_connections = infinity :: non_neg_integer() | infinity}). + +-include("jlib.hrl"). + +-record(bytestream, + {sha1 = <<"">> :: binary() | '$1', + target :: pid() | '_', + initiator :: pid() | '_', + active = false :: boolean() | '_', + jid_i = {<<"">>, <<"">>, <<"">>} :: ljid() | '_'}). -define(PROCNAME, ejabberd_mod_proxy65_sm). %% Unused callbacks. -handle_cast(_Request, State) -> - {noreply, State}. -code_change(_OldVsn, State, _Extra) -> - {ok, State}. -handle_info(_Info, State) -> - {noreply, State}. +handle_cast(_Request, State) -> {noreply, State}. + +code_change(_OldVsn, State, _Extra) -> {ok, State}. + +handle_info(_Info, State) -> {noreply, State}. + %%---------------- start_link(Host, Opts) -> Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - gen_server:start_link({local, Proc}, ?MODULE, [Opts], []). + gen_server:start_link({local, Proc}, ?MODULE, [Opts], + []). init([Opts]) -> mnesia:create_table(bytestream, [{ram_copies, [node()]}, {attributes, record_info(fields, bytestream)}]), mnesia:add_table_copy(bytestream, node(), ram_copies), - MaxConnections = gen_mod:get_opt(max_connections, Opts, infinity), - {ok, #state{max_connections=MaxConnections}}. + MaxConnections = gen_mod:get_opt(max_connections, Opts, + fun(I) when is_integer(I), I>0 -> + I; + (infinity) -> + infinity + end, infinity), + {ok, #state{max_connections = MaxConnections}}. -terminate(_Reason, _State) -> - ok. +terminate(_Reason, _State) -> ok. handle_call({activate, SHA1, IJid}, _From, State) -> MaxConns = State#state.max_connections, - F = fun() -> + F = fun () -> case mnesia:read(bytestream, SHA1, write) of - [#bytestream{target = TPid, initiator = IPid} = ByteStream] - when is_pid(TPid), is_pid(IPid) -> - ActiveFlag = ByteStream#bytestream.active, - if - ActiveFlag == false -> - ConnsPerJID = - mnesia:select(bytestream, - [{#bytestream{sha1 = '$1', - jid_i = IJid, - _='_'}, - [], - ['$1']}]), - if - length(ConnsPerJID) < MaxConns -> - mnesia:write( - ByteStream#bytestream{active = true, - jid_i = IJid}), - {ok, IPid, TPid}; - true -> - {limit, IPid, TPid} - end; - true -> - conflict - end; - _ -> - false + [#bytestream{target = TPid, initiator = IPid} = + ByteStream] + when is_pid(TPid), is_pid(IPid) -> + ActiveFlag = ByteStream#bytestream.active, + if ActiveFlag == false -> + ConnsPerJID = mnesia:select(bytestream, + [{#bytestream{sha1 = + '$1', + jid_i = + IJid, + _ = '_'}, + [], ['$1']}]), + if length(ConnsPerJID) < MaxConns -> + mnesia:write(ByteStream#bytestream{active = + true, + jid_i = + IJid}), + {ok, IPid, TPid}; + true -> {limit, IPid, TPid} + end; + true -> conflict + end; + _ -> false end end, Reply = mnesia:transaction(F), {reply, Reply, State}; - handle_call(_Request, _From, State) -> {reply, ok, State}. @@ -127,20 +122,19 @@ handle_call(_Request, _From, State) -> %%% transaction abort %%% SHA1 = string() %%%--------------------------------------------------- -register_stream(SHA1) when is_list(SHA1) -> +register_stream(SHA1) when is_binary(SHA1) -> StreamPid = self(), - F = fun() -> + F = fun () -> case mnesia:read(bytestream, SHA1, write) of - [] -> - mnesia:write(#bytestream{sha1 = SHA1, - target = StreamPid}); - [#bytestream{target = Pid, - initiator = undefined} = ByteStream] - when is_pid(Pid), Pid /= StreamPid -> - mnesia:write( - ByteStream#bytestream{initiator = StreamPid}); - _ -> - error + [] -> + mnesia:write(#bytestream{sha1 = SHA1, + target = StreamPid}); + [#bytestream{target = Pid, initiator = undefined} = + ByteStream] + when is_pid(Pid), Pid /= StreamPid -> + mnesia:write(ByteStream#bytestream{initiator = + StreamPid}); + _ -> error end end, mnesia:transaction(F). @@ -149,8 +143,8 @@ register_stream(SHA1) when is_list(SHA1) -> %%% unregister_stream(SHA1) -> ok | transaction abort %%% SHA1 = string() %%%---------------------------------------------------- -unregister_stream(SHA1) when is_list(SHA1) -> - F = fun() -> mnesia:delete({bytestream, SHA1}) end, +unregister_stream(SHA1) when is_binary(SHA1) -> + F = fun () -> mnesia:delete({bytestream, SHA1}) end, mnesia:transaction(F). %%%-------------------------------------------------------- @@ -163,19 +157,18 @@ unregister_stream(SHA1) when is_list(SHA1) -> %%% IJid = TJid = jid() %%% Host = string() %%%-------------------------------------------------------- -activate_stream(SHA1, IJid, TJid, Host) when is_list(SHA1) -> +activate_stream(SHA1, IJid, TJid, Host) + when is_binary(SHA1) -> Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - case catch gen_server:call(Proc, {activate, SHA1, IJid}) of - {atomic, {ok, IPid, TPid}} -> - mod_proxy65_stream:activate({IPid, IJid}, {TPid, TJid}); - {atomic, {limit, IPid, TPid}} -> - mod_proxy65_stream:stop(IPid), - mod_proxy65_stream:stop(TPid), - limit; - {atomic, conflict} -> - conflict; - {atomic, false} -> - false; - _ -> - error + case catch gen_server:call(Proc, {activate, SHA1, IJid}) + of + {atomic, {ok, IPid, TPid}} -> + mod_proxy65_stream:activate({IPid, IJid}, {TPid, TJid}); + {atomic, {limit, IPid, TPid}} -> + mod_proxy65_stream:stop(IPid), + mod_proxy65_stream:stop(TPid), + limit; + {atomic, conflict} -> conflict; + {atomic, false} -> false; + _ -> error end. diff --git a/src/mod_proxy65/mod_proxy65_stream.erl b/src/mod_proxy65/mod_proxy65_stream.erl index 7bc2d58a8..1eca99b39 100644 --- a/src/mod_proxy65/mod_proxy65_stream.erl +++ b/src/mod_proxy65/mod_proxy65_stream.erl @@ -24,168 +24,169 @@ %%%---------------------------------------------------------------------- -module(mod_proxy65_stream). + -author('xram@jabber.ru'). -behaviour(gen_fsm). %% gen_fsm callbacks. --export([ - init/1, - handle_event/3, - handle_sync_event/4, - code_change/4, - handle_info/3, - terminate/3 - ]). +-export([init/1, handle_event/3, handle_sync_event/4, + code_change/4, handle_info/3, terminate/3]). %% gen_fsm states. --export([ - wait_for_init/2, - wait_for_auth/2, - wait_for_request/2, - wait_for_activation/2, - stream_established/2 - ]). +-export([wait_for_init/2, wait_for_auth/2, + wait_for_request/2, wait_for_activation/2, + stream_established/2]). %% API. --export([ - start/2, - stop/1, - start_link/3, - activate/2, - relay/3, - socket_type/0 - ]). +-export([start/2, stop/1, start_link/3, activate/2, + relay/3, socket_type/0]). -include("mod_proxy65.hrl"). + -include("ejabberd.hrl"). --define(WAIT_TIMEOUT, 60000). %% 1 minute (is it enough?) +-define(WAIT_TIMEOUT, 60000). --record(state, { - socket, %% TCP socket - timer, %% timer reference - sha1, %% SHA1 key - host, %% virtual host - auth_type, %% authentication type: anonymous or plain - shaper %% Shaper name - }). +-record(state, + {socket :: inet:socket(), + timer = make_ref() :: reference(), + sha1 = <<"">> :: binary(), + host = <<"">> :: binary(), + auth_type = anonymous :: plain | anonymous, + shaper = none :: shaper:shaper()}). %% Unused callbacks handle_event(_Event, StateName, StateData) -> {next_state, StateName, StateData}. + code_change(_OldVsn, StateName, StateData, _Extra) -> {ok, StateName, StateData}. + %%------------------------------- start({gen_tcp, Socket}, Opts1) -> - {[Host], Opts} = lists:partition(fun(O) -> is_list(O) end, Opts1), - Supervisor = gen_mod:get_module_proc(Host, ejabberd_mod_proxy65_sup), - supervisor:start_child(Supervisor, [Socket, Host, Opts]). + {[Host], Opts} = lists:partition(fun (O) -> is_binary(O) + end, + Opts1), + Supervisor = gen_mod:get_module_proc(Host, + ejabberd_mod_proxy65_sup), + supervisor:start_child(Supervisor, + [Socket, Host, Opts]). start_link(Socket, Host, Opts) -> gen_fsm:start_link(?MODULE, [Socket, Host, Opts], []). init([Socket, Host, Opts]) -> process_flag(trap_exit, true), - AuthType = gen_mod:get_opt(auth_type, Opts, anonymous), - Shaper = gen_mod:get_opt(shaper, Opts, none), - RecvBuf = gen_mod:get_opt(recbuf, Opts, 8192), - SendBuf = gen_mod:get_opt(sndbuf, Opts, 8192), + AuthType = gen_mod:get_opt(auth_type, Opts, + fun(plain) -> plain; + (anonymous) -> anonymous + end, anonymous), + Shaper = gen_mod:get_opt(shaper, Opts, + fun(A) when is_atom(A) -> A end, + none), + RecvBuf = gen_mod:get_opt(recbuf, Opts, + fun(I) when is_integer(I), I>0 -> I end, + 8192), + SendBuf = gen_mod:get_opt(sndbuf, Opts, + fun(I) when is_integer(I), I>0 -> I end, + 8192), TRef = erlang:send_after(?WAIT_TIMEOUT, self(), stop), - inet:setopts(Socket, [{active, true}, {recbuf, RecvBuf}, {sndbuf, SendBuf}]), - {ok, wait_for_init, #state{host = Host, - auth_type = AuthType, - socket = Socket, - shaper = Shaper, - timer = TRef}}. + inet:setopts(Socket, + [{active, true}, {recbuf, RecvBuf}, {sndbuf, SendBuf}]), + {ok, wait_for_init, + #state{host = Host, auth_type = AuthType, + socket = Socket, shaper = Shaper, timer = TRef}}. -terminate(_Reason, StateName, #state{sha1=SHA1}) -> +terminate(_Reason, StateName, #state{sha1 = SHA1}) -> catch mod_proxy65_sm:unregister_stream(SHA1), if StateName == stream_established -> - ?INFO_MSG("Bytestream terminated", []); - true -> - ok + ?INFO_MSG("Bytestream terminated", []); + true -> ok end. %%%------------------------------ %%% API. %%%------------------------------ -socket_type() -> - raw. +socket_type() -> raw. -stop(StreamPid) -> - StreamPid ! stop. +stop(StreamPid) -> StreamPid ! stop. activate({P1, J1}, {P2, J2}) -> - case catch {gen_fsm:sync_send_all_state_event(P1, get_socket), - gen_fsm:sync_send_all_state_event(P2, get_socket)} of - {S1, S2} when is_port(S1), is_port(S2) -> - P1 ! {activate, P2, S2, J1, J2}, - P2 ! {activate, P1, S1, J1, J2}, - JID1 = jlib:jid_to_string(J1), - JID2 = jlib:jid_to_string(J2), - ?INFO_MSG("(~w:~w) Activated bytestream for ~s -> ~s", [P1, P2, JID1, JID2]), - ok; - _ -> - error + case catch {gen_fsm:sync_send_all_state_event(P1, + get_socket), + gen_fsm:sync_send_all_state_event(P2, get_socket)} + of + {S1, S2} when is_port(S1), is_port(S2) -> + P1 ! {activate, P2, S2, J1, J2}, + P2 ! {activate, P1, S1, J1, J2}, + JID1 = jlib:jid_to_string(J1), + JID2 = jlib:jid_to_string(J2), + ?INFO_MSG("(~w:~w) Activated bytestream for ~s " + "-> ~s", + [P1, P2, JID1, JID2]), + ok; + _ -> error end. %%%----------------------- %%% States %%%----------------------- -wait_for_init(Packet, #state{socket=Socket, auth_type=AuthType} = StateData) -> +wait_for_init(Packet, + #state{socket = Socket, auth_type = AuthType} = + StateData) -> case mod_proxy65_lib:unpack_init_message(Packet) of - {ok, AuthMethods} -> - Method = select_auth_method(AuthType, AuthMethods), - gen_tcp:send(Socket, mod_proxy65_lib:make_init_reply(Method)), - case Method of - ?AUTH_ANONYMOUS -> - {next_state, wait_for_request, StateData}; - ?AUTH_PLAIN -> - {next_state, wait_for_auth, StateData}; - ?AUTH_NO_METHODS -> - {stop, normal, StateData} - end; - error -> - {stop, normal, StateData} + {ok, AuthMethods} -> + Method = select_auth_method(AuthType, AuthMethods), + gen_tcp:send(Socket, + mod_proxy65_lib:make_init_reply(Method)), + case Method of + ?AUTH_ANONYMOUS -> + {next_state, wait_for_request, StateData}; + ?AUTH_PLAIN -> {next_state, wait_for_auth, StateData}; + ?AUTH_NO_METHODS -> {stop, normal, StateData} + end; + error -> {stop, normal, StateData} end. -wait_for_auth(Packet, #state{socket=Socket, host=Host} = StateData) -> +wait_for_auth(Packet, + #state{socket = Socket, host = Host} = StateData) -> case mod_proxy65_lib:unpack_auth_request(Packet) of - {User, Pass} -> - Result = ejabberd_auth:check_password(User, Host, Pass), - gen_tcp:send(Socket, mod_proxy65_lib:make_auth_reply(Result)), - case Result of - true -> - {next_state, wait_for_request, StateData}; - false -> - {stop, normal, StateData} - end; - _ -> - {stop, normal, StateData} + {User, Pass} -> + Result = ejabberd_auth:check_password(User, Host, Pass), + gen_tcp:send(Socket, + mod_proxy65_lib:make_auth_reply(Result)), + case Result of + true -> {next_state, wait_for_request, StateData}; + false -> {stop, normal, StateData} + end; + _ -> {stop, normal, StateData} end. -wait_for_request(Packet, #state{socket=Socket} = StateData) -> +wait_for_request(Packet, + #state{socket = Socket} = StateData) -> Request = mod_proxy65_lib:unpack_request(Packet), case Request of - #s5_request{sha1=SHA1, cmd=connect} -> - case catch mod_proxy65_sm:register_stream(SHA1) of - {atomic, ok} -> - inet:setopts(Socket, [{active, false}]), - gen_tcp:send(Socket, mod_proxy65_lib:make_reply(Request)), - {next_state, wait_for_activation, StateData#state{sha1=SHA1}}; - _ -> - Err = mod_proxy65_lib:make_error_reply(Request), - gen_tcp:send(Socket, Err), - {stop, normal, StateData} - end; - #s5_request{cmd=udp} -> - Err = mod_proxy65_lib:make_error_reply(Request, ?ERR_COMMAND_NOT_SUPPORTED), - gen_tcp:send(Socket, Err), - {stop, normal, StateData}; - _ -> - {stop, normal, StateData} + #s5_request{sha1 = SHA1, cmd = connect} -> + case catch mod_proxy65_sm:register_stream(SHA1) of + {atomic, ok} -> + inet:setopts(Socket, [{active, false}]), + gen_tcp:send(Socket, + mod_proxy65_lib:make_reply(Request)), + {next_state, wait_for_activation, + StateData#state{sha1 = SHA1}}; + _ -> + Err = mod_proxy65_lib:make_error_reply(Request), + gen_tcp:send(Socket, Err), + {stop, normal, StateData} + end; + #s5_request{cmd = udp} -> + Err = mod_proxy65_lib:make_error_reply(Request, + ?ERR_COMMAND_NOT_SUPPORTED), + gen_tcp:send(Socket, Err), + {stop, normal, StateData}; + _ -> {stop, normal, StateData} end. wait_for_activation(_Data, StateData) -> @@ -200,12 +201,11 @@ stream_established(_Data, StateData) -> %% SOCKS5 packets. handle_info({tcp, _S, Data}, StateName, StateData) - when StateName /= wait_for_activation -> + when StateName /= wait_for_activation -> erlang:cancel_timer(StateData#state.timer), TRef = erlang:send_after(?WAIT_TIMEOUT, self(), stop), gen_fsm:send_event(self(), Data), - {next_state, StateName, StateData#state{timer=TRef}}; - + {next_state, StateName, StateData#state{timer = TRef}}; %% Activation message. handle_info({activate, PeerPid, PeerSocket, IJid, TJid}, wait_for_activation, StateData) -> @@ -215,35 +215,36 @@ handle_info({activate, PeerPid, PeerSocket, IJid, TJid}, Shaper = StateData#state.shaper, Host = StateData#state.host, MaxRate = find_maxrate(Shaper, IJid, TJid, Host), - spawn_link(?MODULE, relay, [MySocket, PeerSocket, MaxRate]), + spawn_link(?MODULE, relay, + [MySocket, PeerSocket, MaxRate]), {next_state, stream_established, StateData}; - %% Socket closed -handle_info({tcp_closed, _Socket}, _StateName, StateData) -> +handle_info({tcp_closed, _Socket}, _StateName, + StateData) -> {stop, normal, StateData}; -handle_info({tcp_error, _Socket, _Reason}, _StateName, StateData) -> +handle_info({tcp_error, _Socket, _Reason}, _StateName, + StateData) -> {stop, normal, StateData}; - %% Got stop message. handle_info(stop, _StateName, StateData) -> {stop, normal, StateData}; - %% Either linked process or peer process died. -handle_info({'EXIT',_,_}, _StateName, StateData) -> +handle_info({'EXIT', _, _}, _StateName, StateData) -> {stop, normal, StateData}; -handle_info({'DOWN',_,_,_,_}, _StateName, StateData) -> +handle_info({'DOWN', _, _, _, _}, _StateName, + StateData) -> {stop, normal, StateData}; - %% Packets of no interest handle_info(_Info, StateName, StateData) -> {next_state, StateName, StateData}. %% Socket request. -handle_sync_event(get_socket, _From, wait_for_activation, StateData) -> +handle_sync_event(get_socket, _From, + wait_for_activation, StateData) -> Socket = StateData#state.socket, {reply, Socket, wait_for_activation, StateData}; - -handle_sync_event(_Event, _From, StateName, StateData) -> +handle_sync_event(_Event, _From, StateName, + StateData) -> {reply, error, StateName, StateData}. %%%------------------------------------------------- @@ -251,16 +252,14 @@ handle_sync_event(_Event, _From, StateName, StateData) -> %%%------------------------------------------------- relay(MySocket, PeerSocket, Shaper) -> case gen_tcp:recv(MySocket, 0) of - {ok, Data} -> - gen_tcp:send(PeerSocket, Data), - {NewShaper, Pause} = shaper:update(Shaper, size(Data)), - if - Pause > 0 -> timer:sleep(Pause); - true -> pass - end, - relay(MySocket, PeerSocket, NewShaper); - _ -> - stopped + {ok, Data} -> + gen_tcp:send(PeerSocket, Data), + {NewShaper, Pause} = shaper:update(Shaper, byte_size(Data)), + if Pause > 0 -> timer:sleep(Pause); + true -> pass + end, + relay(MySocket, PeerSocket, NewShaper); + _ -> stopped end. %%%------------------------ @@ -268,23 +267,21 @@ relay(MySocket, PeerSocket, Shaper) -> %%%------------------------ select_auth_method(plain, AuthMethods) -> case lists:member(?AUTH_PLAIN, AuthMethods) of - true -> ?AUTH_PLAIN; - false -> ?AUTH_NO_METHODS + true -> ?AUTH_PLAIN; + false -> ?AUTH_NO_METHODS end; - select_auth_method(anonymous, AuthMethods) -> case lists:member(?AUTH_ANONYMOUS, AuthMethods) of - true -> ?AUTH_ANONYMOUS; - false -> ?AUTH_NO_METHODS + true -> ?AUTH_ANONYMOUS; + false -> ?AUTH_NO_METHODS end. %% Obviously, we must use shaper with maximum rate. find_maxrate(Shaper, JID1, JID2, Host) -> - MaxRate1 = shaper:new(acl:match_rule(Host, Shaper, JID1)), - MaxRate2 = shaper:new(acl:match_rule(Host, Shaper, JID2)), - if - MaxRate1 == none; MaxRate2 == none -> - none; - true -> - lists:max([MaxRate1, MaxRate2]) + MaxRate1 = shaper:new(acl:match_rule(Host, Shaper, + JID1)), + MaxRate2 = shaper:new(acl:match_rule(Host, Shaper, + JID2)), + if MaxRate1 == none; MaxRate2 == none -> none; + true -> lists:max([MaxRate1, MaxRate2]) end. |