aboutsummaryrefslogtreecommitdiff
path: root/src/mod_proxy65_service.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_proxy65_service.erl')
-rw-r--r--src/mod_proxy65_service.erl434
1 files changed, 217 insertions, 217 deletions
diff --git a/src/mod_proxy65_service.erl b/src/mod_proxy65_service.erl
index 7db6f9da2..8778a03d8 100644
--- a/src/mod_proxy65_service.erl
+++ b/src/mod_proxy65_service.erl
@@ -5,7 +5,7 @@
%%% Created : 12 Oct 2006 by Evgeniy Khramtsov <xram@jabber.ru>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
+%%% ejabberd, Copyright (C) 2002-2019 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@@ -33,267 +33,267 @@
-export([init/1, handle_info/2, handle_call/3,
handle_cast/2, terminate/2, code_change/3]).
--export([start_link/2, add_listener/2,
- transform_module_options/1, delete_listener/1]).
+-export([start_link/1, reload/3, add_listener/2, process_disco_info/1,
+ process_disco_items/1, process_vcard/1, process_bytestreams/1,
+ delete_listener/1, route/1]).
--include("ejabberd.hrl").
-include("logger.hrl").
-
--include("jlib.hrl").
+-include("xmpp.hrl").
+-include("translate.hrl").
+-include("ejabberd_stacktrace.hrl").
-define(PROCNAME, ejabberd_mod_proxy65_service).
--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()}).
+-record(state, {myhosts = [] :: [binary()]}).
%%%------------------------
%%% gen_server callbacks
%%%------------------------
-start_link(Host, Opts) ->
+start_link(Host) ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ gen_server:start_link({local, Proc}, ?MODULE, [Host], []).
+
+reload(Host, NewOpts, OldOpts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- gen_server:start_link({local, Proc}, ?MODULE,
- [Host, Opts], []).
+ gen_server:cast(Proc, {reload, Host, NewOpts, OldOpts}).
-init([Host, Opts]) ->
- State = parse_options(Host, Opts),
- ejabberd_router:register_route(State#state.myhost, Host),
- {ok, State}.
+init([Host]) ->
+ process_flag(trap_exit, true),
+ Opts = gen_mod:get_module_opts(Host, mod_proxy65),
+ MyHosts = gen_mod:get_opt_hosts(Opts),
+ lists:foreach(
+ fun(MyHost) ->
+ gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO,
+ ?MODULE, process_disco_info),
+ gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS,
+ ?MODULE, process_disco_items),
+ gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD,
+ ?MODULE, process_vcard),
+ gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS,
+ ?MODULE, process_bytestreams),
+ ejabberd_router:register_route(
+ MyHost, Host, {apply, ?MODULE, route})
+ end, MyHosts),
+ {ok, #state{myhosts = MyHosts}}.
-terminate(_Reason, #state{myhost = MyHost}) ->
- ejabberd_router:unregister_route(MyHost), ok.
+terminate(_Reason, #state{myhosts = MyHosts}) ->
+ lists:foreach(
+ fun(MyHost) ->
+ ejabberd_router:unregister_route(MyHost),
+ unregister_handlers(MyHost)
+ end, MyHosts).
-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
+handle_info({route, Packet}, State) ->
+ try route(Packet)
+ catch ?EX_RULE(Class, Reason, St) ->
+ StackTrace = ?EX_STACK(St),
+ ?ERROR_MSG("Failed to route packet:~n~ts~n** ~ts",
+ [xmpp:pp(Packet),
+ misc:format_exception(2, Class, Reason, StackTrace)])
end,
{noreply, State};
-handle_info(_Info, State) -> {noreply, State}.
+handle_info(Info, State) ->
+ ?WARNING_MSG("Unexpected info: ~p", [Info]),
+ {noreply, State}.
-handle_call(get_port_ip, _From, State) ->
- {reply, {port_ip, State#state.port, State#state.ip},
- State};
-handle_call(_Request, _From, State) ->
- {reply, ok, State}.
+handle_call(Request, From, State) ->
+ ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
+ {noreply, State}.
-handle_cast(_Request, State) -> {noreply, State}.
+handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) ->
+ NewHosts = gen_mod:get_opt_hosts(NewOpts),
+ OldHosts = gen_mod:get_opt_hosts(OldOpts),
+ lists:foreach(
+ fun(NewHost) ->
+ ejabberd_router:register_route(NewHost, ServerHost),
+ register_handlers(NewHost)
+ end, NewHosts -- OldHosts),
+ lists:foreach(
+ fun(OldHost) ->
+ ejabberd_router:unregister_route(OldHost),
+ unregister_handlers(OldHost)
+ end, OldHosts -- NewHosts),
+ {noreply, State#state{myhosts = NewHosts}};
+handle_cast(Msg, State) ->
+ ?WARNING_MSG("Unexpected cast: ~p", [Msg]),
+ {noreply, State}.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
+-spec route(stanza()) -> ok.
+route(#iq{} = IQ) ->
+ ejabberd_router:process_iq(IQ);
+route(_) ->
+ ok.
+
%%%------------------------
%%% Listener management
%%%------------------------
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).
+ {_, IP, _} = EndPoint = get_endpoint(Host),
+ Opts1 = gen_mod:set_opt(server_host, Host, Opts),
+ Opts2 = gen_mod:set_opt(ip, IP, Opts1),
+ ejabberd_listener:add_listener(EndPoint, mod_proxy65_stream, Opts2).
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).
+ ejabberd_listener:delete_listener(get_endpoint(Host), mod_proxy65_stream).
%%%------------------------
%%% IQ Processing
%%%------------------------
+-spec process_disco_info(iq()) -> iq().
+process_disco_info(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_disco_info(#iq{type = get, to = To, lang = Lang} = IQ) ->
+ Host = ejabberd_router:host_of_route(To#jid.lserver),
+ Name = mod_proxy65_opt:name(Host),
+ Info = ejabberd_hooks:run_fold(disco_info, Host,
+ [], [Host, ?MODULE, <<"">>, <<"">>]),
+ xmpp:make_iq_result(
+ IQ, #disco_info{xdata = Info,
+ identities = [#identity{category = <<"proxy">>,
+ type = <<"bytestreams">>,
+ name = translate:translate(Lang, Name)}],
+ features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
+ ?NS_VCARD, ?NS_BYTESTREAMS]}).
+
+-spec process_disco_items(iq()) -> iq().
+process_disco_items(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_disco_items(#iq{type = get} = IQ) ->
+ xmpp:make_iq_result(IQ, #disco_items{}).
-%% 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 =
- [#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 =
- [#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 =
- [#xmlel{name = <<"vCard">>,
- attrs = [{<<"xmlns">>, ?NS_VCARD}],
- children = iq_vcard(Lang)}]};
-%% bytestreams info request
-process_iq(JID,
- #iq{type = get, sub_el = SubEl, lang = Lang,
- xmlns = ?NS_BYTESTREAMS} =
- IQ,
- #state{acl = ACL, stream_addr = StreamAddr,
- serverhost = ServerHost}) ->
+-spec process_vcard(iq()) -> iq().
+process_vcard(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_vcard(#iq{type = get, to = To, lang = Lang} = IQ) ->
+ ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
+ VCard = case mod_proxy65_opt:vcard(ServerHost) of
+ undefined ->
+ #vcard_temp{fn = <<"ejabberd/mod_proxy65">>,
+ url = ejabberd_config:get_uri(),
+ desc = misc:get_descr(
+ Lang, ?T("ejabberd SOCKS5 Bytestreams module"))};
+ V ->
+ V
+ end,
+ xmpp:make_iq_result(IQ, VCard).
+
+-spec process_bytestreams(iq()) -> iq().
+process_bytestreams(#iq{type = get, from = JID, to = To, lang = Lang} = IQ) ->
+ Host = To#jid.lserver,
+ ServerHost = ejabberd_router:host_of_route(Host),
+ ACL = mod_proxy65_opt:access(ServerHost),
case acl:match_rule(ServerHost, ACL, JID) of
- allow ->
- StreamHostEl = [#xmlel{name = <<"streamhost">>,
- attrs = StreamAddr, children = []}],
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_BYTESTREAMS}],
- children = StreamHostEl}]};
- deny ->
- Txt = <<"Denied by ACL">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}
+ allow ->
+ StreamHost = get_streamhost(Host, ServerHost),
+ xmpp:make_iq_result(IQ, #bytestreams{hosts = [StreamHost]});
+ deny ->
+ xmpp:make_error(IQ, xmpp:err_forbidden(?T("Access denied by service policy"), Lang))
end;
-%% bytestream activation request
-process_iq(InitiatorJID,
- #iq{type = set, sub_el = SubEl, lang = Lang,
- xmlns = ?NS_BYTESTREAMS} =
- IQ,
- #state{acl = ACL, serverhost = ServerHost}) ->
+process_bytestreams(#iq{type = set, lang = Lang,
+ sub_els = [#bytestreams{sid = SID}]} = IQ)
+ when SID == <<"">> orelse size(SID) > 128 ->
+ Why = {bad_attr_value, <<"sid">>, <<"query">>, ?NS_BYTESTREAMS},
+ Txt = xmpp:io_format_error(Why),
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
+process_bytestreams(#iq{type = set, lang = Lang,
+ sub_els = [#bytestreams{activate = undefined}]} = IQ) ->
+ Why = {missing_cdata, <<"">>, <<"activate">>, ?NS_BYTESTREAMS},
+ Txt = xmpp:io_format_error(Why),
+ xmpp:make_error(IQ, xmpp:err_jid_malformed(Txt, Lang));
+process_bytestreams(#iq{type = set, lang = Lang, from = InitiatorJID, to = To,
+ sub_els = [#bytestreams{activate = TargetJID,
+ sid = SID}]} = IQ) ->
+ ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
+ ACL = mod_proxy65_opt:access(ServerHost),
case acl:match_rule(ServerHost, ACL, InitiatorJID) of
- allow ->
- ActivateEl = fxml:get_path_s(SubEl,
- [{elem, <<"activate">>}]),
- SID = fxml:get_tag_attr_s(<<"sid">>, SubEl),
- case catch
- jid:from_string(fxml:get_tag_cdata(ActivateEl))
- of
- TargetJID
- when is_record(TargetJID, jid), SID /= <<"">>,
- byte_size(SID) =< 128, TargetJID /= InitiatorJID ->
- Target =
- jid:to_string(jid:tolower(TargetJID)),
- Initiator =
- jid:to_string(jid:tolower(InitiatorJID)),
- SHA1 = p1_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 ->
- Txt = <<"Failed to activate bytestream">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)]};
- limit ->
- Txt = <<"Too many active bytestreams">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt)]};
- conflict ->
- Txt = <<"Bytestream already activated">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_CONFLICT(Lang, Txt)]};
- _ ->
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
- end;
- _ ->
- Txt = <<"Malformed JID">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}
- end;
- deny ->
- Txt = <<"Denied by ACL">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}
- 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]};
-%% IQ "result" or "error".
-process_iq(_, _, _) -> ok.
+ allow ->
+ Node = ejabberd_cluster:get_node_by_id(To#jid.lresource),
+ Target = jid:encode(jid:tolower(TargetJID)),
+ Initiator = jid:encode(jid:tolower(InitiatorJID)),
+ SHA1 = str:sha(<<SID/binary, Initiator/binary, Target/binary>>),
+ Mod = gen_mod:ram_db_mod(global, mod_proxy65),
+ MaxConnections = max_connections(ServerHost),
+ case Mod:activate_stream(SHA1, Initiator, MaxConnections, Node) of
+ {ok, InitiatorPid, TargetPid} ->
+ mod_proxy65_stream:activate(
+ {InitiatorPid, InitiatorJID}, {TargetPid, TargetJID}),
+ xmpp:make_iq_result(IQ);
+ {error, notfound} ->
+ Txt = ?T("Failed to activate bytestream"),
+ xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
+ {error, {limit, InitiatorPid, TargetPid}} ->
+ mod_proxy65_stream:stop(InitiatorPid),
+ mod_proxy65_stream:stop(TargetPid),
+ Txt = ?T("Too many active bytestreams"),
+ xmpp:make_error(IQ, xmpp:err_resource_constraint(Txt, Lang));
+ {error, conflict} ->
+ Txt = ?T("Bytestream already activated"),
+ xmpp:make_error(IQ, xmpp:err_conflict(Txt, Lang));
+ {error, Err} ->
+ ?ERROR_MSG("Failed to activate bytestream from ~ts to ~ts: ~p",
+ [Initiator, Target, Err]),
+ Txt = ?T("Database failure"),
+ xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
+ end;
+ deny ->
+ Txt = ?T("Access denied by service policy"),
+ xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang))
+ end.
%%%-------------------------
%%% Auxiliary functions.
%%%-------------------------
--define(FEATURE(Feat),
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, Feat}], children = []}).
-
-iq_disco_info(Lang, Name) ->
- [#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) ->
- [#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-2016 ProcessOne">>}]}].
-
-parse_options(ServerHost, Opts) ->
- 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 acl:access_rules_validator/1,
- all),
- Name = gen_mod:get_opt(name, Opts, fun iolist_to_binary/1,
- <<"SOCKS5 Bytestreams">>),
- IP = gen_mod:get_opt(ip, Opts,
- fun(S) ->
- {ok, Addr} = inet_parse:address(
- binary_to_list(
- iolist_to_binary(S))),
- Addr
- end, get_my_ip()),
- HostName = gen_mod:get_opt(hostname, Opts,
- fun iolist_to_binary/1,
- 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}.
+-spec get_streamhost(binary(), binary()) -> streamhost().
+get_streamhost(Host, ServerHost) ->
+ {Port, IP, _} = get_endpoint(ServerHost),
+ HostName = case mod_proxy65_opt:hostname(ServerHost) of
+ undefined -> misc:ip_to_list(IP);
+ Val -> Val
+ end,
+ Resource = ejabberd_cluster:node_id(),
+ #streamhost{jid = jid:make(<<"">>, Host, Resource),
+ host = HostName,
+ port = Port}.
-transform_module_options(Opts) ->
- lists:map(
- fun({ip, IP}) when is_tuple(IP) ->
- {ip, jlib:ip_to_list(IP)};
- ({hostname, IP}) when is_tuple(IP) ->
- {hostname, jlib:ip_to_list(IP)};
- (Opt) ->
- Opt
- end, Opts).
+-spec get_endpoint(binary()) -> {inet:port_number(), inet:ip_address(), tcp}.
+get_endpoint(Host) ->
+ Port = mod_proxy65_opt:port(Host),
+ IP = case mod_proxy65_opt:ip(Host) of
+ undefined -> get_my_ip();
+ Addr -> Addr
+ end,
+ {Port, IP, tcp}.
+-spec get_my_ip() -> inet:ip_address().
get_my_ip() ->
{ok, MyHostName} = inet:gethostname(),
case inet:getaddr(MyHostName, inet) of
{ok, Addr} -> Addr;
{error, _} -> {127, 0, 0, 1}
end.
+
+max_connections(ServerHost) ->
+ mod_proxy65_opt:max_connections(ServerHost).
+
+register_handlers(Host) ->
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
+ ?MODULE, process_disco_info),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
+ ?MODULE, process_disco_items),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
+ ?MODULE, process_vcard),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_BYTESTREAMS,
+ ?MODULE, process_bytestreams).
+
+unregister_handlers(Host) ->
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_BYTESTREAMS).