diff options
Diffstat (limited to 'src/mod_proxy65_service.erl')
-rw-r--r-- | src/mod_proxy65_service.erl | 286 |
1 files changed, 286 insertions, 0 deletions
diff --git a/src/mod_proxy65_service.erl b/src/mod_proxy65_service.erl new file mode 100644 index 000000000..1e008efa8 --- /dev/null +++ b/src/mod_proxy65_service.erl @@ -0,0 +1,286 @@ +%%%---------------------------------------------------------------------- +%%% File : mod_proxy65_service.erl +%%% Author : Evgeniy Khramtsov <xram@jabber.ru> +%%% Purpose : SOCKS5 Bytestreams XMPP service. +%%% Created : 12 Oct 2006 by Evgeniy Khramtsov <xram@jabber.ru> +%%% +%%% +%%% ejabberd, Copyright (C) 2002-2013 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., 59 Temple Place, Suite 330, Boston, MA +%%% 02111-1307 USA +%%% +%%%---------------------------------------------------------------------- + +-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]). + +%% API. +-export([start_link/2, add_listener/2, + delete_listener/1]). + +-include("ejabberd.hrl"). +-include("logger.hrl"). + +-include("jlib.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()}). + +%%%------------------------ +%%% gen_server callbacks +%%%------------------------ + +start_link(Host, Opts) -> + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + 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. + +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 + end, + {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}; +handle_call(_Request, _From, State) -> + {reply, ok, State}. + +handle_cast(_Request, State) -> {noreply, State}. + +code_change(_OldVsn, State, _Extra) -> {ok, State}. + +%%%------------------------ +%%% 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). + +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). + +%%%------------------------ +%%% 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 = + [#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, + xmlns = ?NS_BYTESTREAMS} = + IQ, + #state{acl = ACL, stream_addr = StreamAddr, + serverhost = 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 -> + 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, + #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 /= <<"">>, + 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]}; +%% IQ "result" or "error". +process_iq(_, _, _) -> ok. + +%%%------------------------- +%%% 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-2013 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(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} + end. |