diff options
Diffstat (limited to 'src/mod_irc.erl')
-rw-r--r-- | src/mod_irc.erl | 1006 |
1 files changed, 0 insertions, 1006 deletions
diff --git a/src/mod_irc.erl b/src/mod_irc.erl deleted file mode 100644 index 42e516b82..000000000 --- a/src/mod_irc.erl +++ /dev/null @@ -1,1006 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : mod_irc.erl -%%% Author : Alexey Shchepin <alexey@process-one.net> -%%% Purpose : IRC transport -%%% Created : 15 Feb 2003 by Alexey Shchepin <alexey@process-one.net> -%%% -%%% -%%% 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(mod_irc). - --author('alexey@process-one.net'). - --behaviour(gen_server). - --behaviour(gen_mod). - -%% API --export([start/2, stop/1, reload/3, export/1, import/1, - import/3, closed_connection/3, get_connection_params/3, - data_to_binary/2, process_disco_info/1, process_disco_items/1, - process_register/1, process_vcard/1, process_command/1]). - --export([init/1, handle_call/3, handle_cast/2, - handle_info/2, terminate/2, code_change/3, - mod_opt_type/1, mod_options/1, depends/2]). - --include("logger.hrl"). --include("xmpp.hrl"). --include("mod_irc.hrl"). --include("translate.hrl"). - --define(DEFAULT_IRC_PORT, 6667). - --define(POSSIBLE_ENCODINGS, - [<<"koi8-r">>, <<"iso8859-15">>, <<"iso8859-1">>, <<"iso8859-2">>, - <<"utf-8">>, <<"utf-8+latin-1">>]). - --record(state, {hosts = [] :: [binary()], - server_host = <<"">> :: binary(), - access = all :: atom()}). - --callback init(binary(), gen_mod:opts()) -> any(). --callback import(binary(), #irc_custom{}) -> ok | pass. --callback get_data(binary(), binary(), jid()) -> error | empty | irc_data(). --callback set_data(binary(), binary(), jid(), irc_data()) -> {atomic, any()}. - -%%==================================================================== -%% gen_mod API -%%==================================================================== -start(Host, Opts) -> - start_supervisor(Host), - gen_mod:start_child(?MODULE, Host, Opts). - -stop(Host) -> - stop_supervisor(Host), - gen_mod:stop_child(?MODULE, Host). - -reload(Host, NewOpts, OldOpts) -> - Proc = gen_mod:get_module_proc(Host, ?MODULE), - gen_server:cast(Proc, {reload, Host, NewOpts, OldOpts}). - -depends(_Host, _Opts) -> - []. - -%%==================================================================== -%% gen_server callbacks -%%==================================================================== - -%%-------------------------------------------------------------------- -%% Function: init(Args) -> {ok, State} | -%% {ok, State, Timeout} | -%% ignore | -%% {stop, Reason} -%% Description: Initiates the server -%%-------------------------------------------------------------------- -init([Host, Opts]) -> - process_flag(trap_exit, true), - ejabberd:start_app(iconv), - MyHosts = gen_mod:get_opt_hosts(Host, Opts), - Mod = gen_mod:db_mod(Host, Opts, ?MODULE), - Mod:init(Host, Opts), - Access = gen_mod:get_opt(access, Opts), - catch ets:new(irc_connection, - [named_table, public, - {keypos, #irc_connection.jid_server_host}]), - lists:foreach( - fun(MyHost) -> - register_hooks(MyHost), - ejabberd_router:register_route(MyHost, Host) - end, MyHosts), - {ok, - #state{hosts = MyHosts, server_host = Host, - access = Access}}. - -%%-------------------------------------------------------------------- -%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | -%% {reply, Reply, State, Timeout} | -%% {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, Reply, State} | -%% {stop, Reason, State} -%% Description: Handling call messages -%%-------------------------------------------------------------------- -handle_call(stop, _From, State) -> - {stop, normal, ok, State}. - -%%-------------------------------------------------------------------- -%% Function: handle_cast(Msg, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handling cast messages -%%-------------------------------------------------------------------- -handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) -> - NewHosts = gen_mod:get_opt_hosts(ServerHost, NewOpts), - OldHosts = gen_mod:get_opt_hosts(ServerHost, OldOpts), - NewMod = gen_mod:db_mod(ServerHost, NewOpts, ?MODULE), - OldMod = gen_mod:db_mod(ServerHost, OldOpts, ?MODULE), - Access = gen_mod:get_opt(access, NewOpts), - if NewMod /= OldMod -> - NewMod:init(ServerHost, NewOpts); - true -> - ok - end, - lists:foreach( - fun(NewHost) -> - ejabberd_router:register_route(NewHost, ServerHost), - register_hooks(NewHost) - end, NewHosts -- OldHosts), - lists:foreach( - fun(OldHost) -> - ejabberd_router:unregister_route(OldHost), - unregister_hooks(OldHost) - end, OldHosts -- NewHosts), - Access = gen_mod:get_opt(access, NewOpts), - {noreply, State#state{hosts = NewHosts, access = Access}}; -handle_cast(Msg, State) -> - ?WARNING_MSG("unexpected cast: ~p", [Msg]), - {noreply, State}. - -%%-------------------------------------------------------------------- -%% Function: handle_info(Info, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handling all non call/cast messages -%%-------------------------------------------------------------------- -handle_info({route, Packet}, - #state{server_host = ServerHost, access = Access} = - State) -> - To = xmpp:get_to(Packet), - Host = To#jid.lserver, - case catch do_route(Host, ServerHost, Access, Packet) of - {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); - _ -> ok - end, - {noreply, State}; -handle_info(_Info, State) -> {noreply, State}. - -%%-------------------------------------------------------------------- -%% Function: terminate(Reason, State) -> void() -%% Description: This function is called by a gen_server when it is about to -%% terminate. It should be the opposite of Module:init/1 and do any necessary -%% cleaning up. When it returns, the gen_server terminates with Reason. -%% The return value is ignored. -%%-------------------------------------------------------------------- -terminate(_Reason, #state{hosts = MyHosts}) -> - lists:foreach( - fun(MyHost) -> - ejabberd_router:unregister_route(MyHost), - unregister_hooks(MyHost) - end, MyHosts). - -%%-------------------------------------------------------------------- -%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} -%% Description: Convert process state when code is changed -%%-------------------------------------------------------------------- -code_change(_OldVsn, State, _Extra) -> {ok, State}. - -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- -register_hooks(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_REGISTER, - ?MODULE, process_register), - gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD, - ?MODULE, process_vcard), - gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_COMMANDS, - ?MODULE, process_command). - -unregister_hooks(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_REGISTER), - gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD), - gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS). - -start_supervisor(Host) -> - Proc = gen_mod:get_module_proc(Host, - ejabberd_mod_irc_sup), - ChildSpec = {Proc, - {ejabberd_tmp_sup, start_link, - [Proc, mod_irc_connection]}, - permanent, infinity, supervisor, [ejabberd_tmp_sup]}, - supervisor:start_child(ejabberd_gen_mod_sup, ChildSpec). - -stop_supervisor(Host) -> - Proc = gen_mod:get_module_proc(Host, - ejabberd_mod_irc_sup), - supervisor:terminate_child(ejabberd_gen_mod_sup, Proc), - supervisor:delete_child(ejabberd_gen_mod_sup, Proc). - -do_route(Host, ServerHost, Access, Packet) -> - #jid{luser = LUser, lresource = LResource} = xmpp:get_to(Packet), - From = xmpp:get_from(Packet), - case acl:match_rule(ServerHost, Access, From) of - allow -> - case Packet of - #iq{} when LUser == <<"">>, LResource == <<"">> -> - ejabberd_router:process_iq(Packet); - #iq{} when LUser == <<"">>, LResource /= <<"">> -> - Err = xmpp:err_service_unavailable(), - ejabberd_router:route_error(Packet, Err); - _ -> - sm_route(Host, ServerHost, Packet) - end; - deny -> - Lang = xmpp:get_lang(Packet), - Err = xmpp:err_forbidden(<<"Access denied by service policy">>, Lang), - ejabberd_router:route_error(Packet, Err) - end. - -process_disco_info(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); -process_disco_info(#iq{type = get, lang = Lang, to = To, - sub_els = [#disco_info{node = Node}]} = IQ) -> - ServerHost = ejabberd_router:host_of_route(To#jid.lserver), - Info = ejabberd_hooks:run_fold(disco_info, ServerHost, - [], [ServerHost, ?MODULE, <<"">>, <<"">>]), - case iq_disco(ServerHost, Node, Lang) of - undefined -> - xmpp:make_iq_result(IQ, #disco_info{}); - DiscoInfo -> - xmpp:make_iq_result(IQ, DiscoInfo#disco_info{xdata = Info}) - end. - -process_disco_items(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); -process_disco_items(#iq{type = get, lang = Lang, to = To, - sub_els = [#disco_items{node = Node}]} = IQ) -> - case Node of - <<"">> -> - xmpp:make_iq_result(IQ, #disco_items{}); - <<"join">> -> - xmpp:make_iq_result(IQ, #disco_items{}); - <<"register">> -> - xmpp:make_iq_result(IQ, #disco_items{}); - ?NS_COMMANDS -> - Host = To#jid.lserver, - ServerHost = ejabberd_router:host_of_route(Host), - xmpp:make_iq_result( - IQ, #disco_items{node = Node, - items = command_items(ServerHost, Host, Lang)}); - _ -> - Txt = <<"Node not found">>, - xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang)) - end. - -process_register(#iq{type = get, to = To, from = From, lang = Lang} = IQ) -> - Host = To#jid.lserver, - ServerHost = ejabberd_router:host_of_route(Host), - case get_form(ServerHost, Host, From, Lang) of - {result, Res} -> - xmpp:make_iq_result(IQ, Res); - {error, Error} -> - xmpp:make_error(IQ, Error) - end; -process_register(#iq{type = set, lang = Lang, to = To, from = From, - sub_els = [#register{xdata = #xdata{} = X}]} = IQ) -> - case X#xdata.type of - cancel -> - xmpp:make_iq_result(IQ, #register{}); - submit -> - Host = To#jid.lserver, - ServerHost = ejabberd_router:host_of_route(Host), - case set_form(ServerHost, Host, From, Lang, X) of - {result, Res} -> - xmpp:make_iq_result(IQ, Res); - {error, Error} -> - xmpp:make_error(IQ, Error) - end; - _ -> - Txt = <<"Incorrect value of 'type' attribute">>, - xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)) - end; -process_register(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"No data form found">>, - xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)). - -process_vcard(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); -process_vcard(#iq{type = get, lang = Lang} = IQ) -> - xmpp:make_iq_result(IQ, iq_get_vcard(Lang)). - -process_command(#iq{type = get, lang = Lang} = IQ) -> - Txt = <<"Value 'get' of 'type' attribute is not allowed">>, - xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); -process_command(#iq{type = set, lang = Lang, to = To, from = From, - sub_els = [#adhoc_command{node = Node} = Request]} = IQ) -> - Host = To#jid.lserver, - ServerHost = ejabberd_router:host_of_route(Host), - case lists:keyfind(Node, 1, commands(ServerHost)) of - {_, _, Function} -> - try Function(From, To, Request) of - ignore -> - ignore; - {error, Error} -> - xmpp:make_error(IQ, Error); - Command -> - xmpp:make_iq_result(IQ, Command) - catch E:R -> - ?ERROR_MSG("ad-hoc handler failed: ~p", - [{E, {R, erlang:get_stacktrace()}}]), - xmpp:make_error(IQ, xmpp:err_internal_server_error()) - end; - _ -> - Txt = <<"Node not found">>, - xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang)) - end. - -sm_route(Host, ServerHost, Packet) -> - From = xmpp:get_from(Packet), - #jid{user = ChanServ, resource = Resource} = xmpp:get_to(Packet), - case str:tokens(ChanServ, <<"%">>) of - [<<_, _/binary>> = Channel, <<_, _/binary>> = Server] -> - case ets:lookup(irc_connection, {From, Server, Host}) of - [] -> - ?DEBUG("open new connection~n", []), - {Username, Encoding, Port, Password} = - get_connection_params(Host, ServerHost, From, Server), - ConnectionUsername = case Packet of - %% If the user tries to join a - %% chatroom, the packet for sure - %% contains the desired username. - #presence{} -> Resource; - %% Otherwise, there is no firm - %% conclusion from the packet. - %% Better to use the configured - %% username (which defaults to the - %% username part of the JID). - _ -> Username - end, - Ident = extract_ident(Packet), - RemoteAddr = extract_ip_address(Packet), - RealName = get_realname(ServerHost), - WebircPassword = get_webirc_password(ServerHost), - {ok, Pid} = mod_irc_connection:start( - From, Host, ServerHost, Server, - ConnectionUsername, Encoding, Port, - Password, Ident, RemoteAddr, RealName, - WebircPassword, ?MODULE), - ets:insert(irc_connection, - #irc_connection{ - jid_server_host = {From, Server, Host}, - pid = Pid}), - mod_irc_connection:route_chan(Pid, Channel, Resource, Packet); - [R] -> - Pid = R#irc_connection.pid, - ?DEBUG("send to process ~p~n", [Pid]), - mod_irc_connection:route_chan(Pid, Channel, Resource, Packet) - end; - _ -> - Lang = xmpp:get_lang(Packet), - case str:tokens(ChanServ, <<"!">>) of - [<<_, _/binary>> = Nick, <<_, _/binary>> = Server] -> - case ets:lookup(irc_connection, {From, Server, Host}) of - [] -> - Txt = <<"IRC connection not found">>, - Err = xmpp:err_service_unavailable(Txt, Lang), - ejabberd_router:route_error(Packet, Err); - [R] -> - Pid = R#irc_connection.pid, - ?DEBUG("send to process ~p~n", [Pid]), - mod_irc_connection:route_nick(Pid, Nick, Packet) - end; - _ -> - Txt = <<"Failed to parse chanserv">>, - Err = xmpp:err_bad_request(Txt, Lang), - ejabberd_router:route_error(Packet, Err) - end - end. - -closed_connection(Host, From, Server) -> - ets:delete(irc_connection, {From, Server, Host}). - -iq_disco(ServerHost, <<"">>, Lang) -> - Name = gen_mod:get_module_opt(ServerHost, ?MODULE, name), - #disco_info{ - identities = [#identity{category = <<"conference">>, - type = <<"irc">>, - name = translate:translate(Lang, Name)}], - features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS, ?NS_MUC, - ?NS_REGISTER, ?NS_VCARD, ?NS_COMMANDS]}; -iq_disco(ServerHost, Node, Lang) -> - case lists:keyfind(Node, 1, commands(ServerHost)) of - {_, Name, _} -> - #disco_info{ - identities = [#identity{category = <<"automation">>, - type = <<"command-node">>, - name = translate:translate(Lang, Name)}], - features = [?NS_COMMANDS, ?NS_XDATA]}; - _ -> - undefined - end. - -iq_get_vcard(Lang) -> - #vcard_temp{fn = <<"ejabberd/mod_irc">>, - url = ejabberd_config:get_uri(), - desc = misc:get_descr(Lang, <<"ejabberd IRC module">>)}. - -command_items(ServerHost, Host, Lang) -> - lists:map(fun({Node, Name, _Function}) -> - #disco_item{jid = jid:make(Host), - node = Node, - name = translate:translate(Lang, Name)} - end, commands(ServerHost)). - -commands(ServerHost) -> - [{<<"join">>, <<"Join channel">>, fun adhoc_join/3}, - {<<"register">>, - <<"Configure username, encoding, port and " - "password">>, - fun (From, To, Request) -> - adhoc_register(ServerHost, From, To, Request) - end}]. - -get_data(ServerHost, Host, From) -> - LServer = jid:nameprep(ServerHost), - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:get_data(LServer, Host, From). - -get_form(ServerHost, Host, From, Lang) -> - #jid{user = User, server = Server} = From, - DefaultEncoding = get_default_encoding(Host), - Customs = case get_data(ServerHost, Host, From) of - error -> - Txt1 = <<"Database failure">>, - {error, xmpp:err_internal_server_error(Txt1, Lang)}; - empty -> {User, []}; - Data -> get_username_and_connection_params(Data) - end, - case Customs of - {error, _Error} -> - Customs; - {Username, ConnectionsParams} -> - Fs = [#xdata_field{type = 'text-single', - label = translate:translate(Lang, <<"IRC Username">>), - var = <<"username">>, - values = [Username]}, - #xdata_field{type = fixed, - values = [str:format( - translate:translate( - Lang, - <<"If you want to specify" - " different ports, " - "passwords, encodings " - "for IRC servers, " - "fill this list with " - "values in format " - "'{\"irc server\", " - "\"encoding\", port, " - "\"password\"}'. " - "By default this " - "service use \"~s\" " - "encoding, port ~p, " - "empty password.">>), - [DefaultEncoding, ?DEFAULT_IRC_PORT])]}, - #xdata_field{type = fixed, - values = [translate:translate( - Lang, - <<"Example: [{\"irc.lucky.net\", \"koi8-r\", " - "6667, \"secret\"}, {\"vendetta.fef.net\", " - "\"iso8859-1\", 7000}, {\"irc.sometestserver.n" - "et\", \"utf-8\"}].">>)]}, - #xdata_field{type = 'text-multi', - label = translate:translate( - Lang, <<"Connections parameters">>), - var = <<"connections_params">>, - values = str:tokens(str:format( - "~p.", - [conn_params_to_list( - ConnectionsParams)]), - <<"\n">>)}], - X = #xdata{type = form, - title = <<(translate:translate( - Lang, <<"Registration in mod_irc for ">>))/binary, - User/binary, "@", Server/binary>>, - instructions = - [translate:translate( - Lang, - <<"Enter username, encodings, ports and " - "passwords you wish to use for connecting " - "to IRC servers">>)], - fields = Fs}, - {result, - #register{instructions = - translate:translate(Lang, - <<"You need an x:data capable client to " - "configure mod_irc settings">>), - xdata = X}} - end. - -set_data(ServerHost, Host, From, Data) -> - LServer = jid:nameprep(ServerHost), - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:set_data(LServer, Host, From, data_to_binary(From, Data)). - -set_form(ServerHost, Host, From, Lang, XData) -> - case {xmpp_util:get_xdata_values(<<"username">>, XData), - xmpp_util:get_xdata_values(<<"connections_params">>, XData)} of - {[Username], [_|_] = Strings} -> - EncString = lists:foldl(fun (S, Res) -> - <<Res/binary, S/binary, "\n">> - end, <<"">>, Strings), - case erl_scan:string(binary_to_list(EncString)) of - {ok, Tokens, _} -> - case erl_parse:parse_term(Tokens) of - {ok, ConnectionsParams} -> - case set_data(ServerHost, Host, From, - [{username, Username}, - {connections_params, ConnectionsParams}]) of - {atomic, _} -> - {result, undefined}; - _ -> - Txt = <<"Database failure">>, - {error, xmpp:err_internal_server_error(Txt, Lang)} - end; - _ -> - Txt = <<"Parse error">>, - {error, xmpp:err_not_acceptable(Txt, Lang)} - end; - _ -> - {error, xmpp:err_not_acceptable(<<"Scan error">>, Lang)} - end; - _ -> - Txt = <<"Incorrect value in data form">>, - {error, xmpp:err_not_acceptable(Txt, Lang)} - end. - -get_connection_params(Host, From, IRCServer) -> - [_ | HostTail] = str:tokens(Host, <<".">>), - ServerHost = str:join(HostTail, <<".">>), - get_connection_params(Host, ServerHost, From, - IRCServer). - -get_default_encoding(ServerHost) -> - Result = gen_mod:get_module_opt(ServerHost, ?MODULE, default_encoding), - ?INFO_MSG("The default_encoding configured for " - "host ~p is: ~p~n", - [ServerHost, Result]), - Result. - -get_realname(ServerHost) -> - gen_mod:get_module_opt(ServerHost, ?MODULE, realname). - -get_webirc_password(ServerHost) -> - gen_mod:get_module_opt(ServerHost, ?MODULE, webirc_password). - -get_connection_params(Host, ServerHost, From, - IRCServer) -> - #jid{user = User, server = _Server} = From, - DefaultEncoding = get_default_encoding(ServerHost), - case get_data(ServerHost, Host, From) of - error -> - {User, DefaultEncoding, ?DEFAULT_IRC_PORT, <<"">>}; - empty -> - {User, DefaultEncoding, ?DEFAULT_IRC_PORT, <<"">>}; - Data -> - {Username, ConnParams} = get_username_and_connection_params(Data), - {NewUsername, NewEncoding, NewPort, NewPassword} = case - lists:keysearch(IRCServer, - 1, - ConnParams) - of - {value, - {_, Encoding, - Port, - Password}} -> - {Username, - Encoding, - Port, - Password}; - {value, - {_, Encoding, - Port}} -> - {Username, - Encoding, - Port, - <<"">>}; - {value, - {_, - Encoding}} -> - {Username, - Encoding, - ?DEFAULT_IRC_PORT, - <<"">>}; - _ -> - {Username, - DefaultEncoding, - ?DEFAULT_IRC_PORT, - <<"">>} - end, - {iolist_to_binary(NewUsername), - iolist_to_binary(NewEncoding), - if NewPort >= 0 andalso NewPort =< 65535 -> NewPort; - true -> ?DEFAULT_IRC_PORT - end, - iolist_to_binary(NewPassword)} - end. - -adhoc_join(_From, _To, #adhoc_command{action = cancel} = Request) -> - xmpp_util:make_adhoc_response(Request, #adhoc_command{status = canceled}); -adhoc_join(_From, _To, #adhoc_command{lang = Lang, xdata = undefined} = Request) -> - X = #xdata{type = form, - title = translate:translate(Lang, <<"Join IRC channel">>), - fields = [#xdata_field{var = <<"channel">>, - type = 'text-single', - label = translate:translate( - Lang, <<"IRC channel (don't put the first #)">>), - required = true}, - #xdata_field{var = <<"server">>, - type = 'text-single', - label = translate:translate(Lang, <<"IRC server">>), - required = true}]}, - xmpp_util:make_adhoc_response( - Request, #adhoc_command{status = executing, xdata = X}); -adhoc_join(From, To, #adhoc_command{lang = Lang, xdata = X} = Request) -> - Channel = case xmpp_util:get_xdata_values(<<"channel">>, X) of - [C] -> C; - _ -> false - end, - Server = case xmpp_util:get_xdata_values(<<"server">>, X) of - [S] -> S; - _ -> false - end, - if Channel /= false, Server /= false -> - RoomJID = jid:make(<<Channel/binary, "%", Server/binary>>, - To#jid.server), - Reason = translate:translate(Lang, <<"Join the IRC channel here.">>), - BodyTxt = {<<"Join the IRC channel in this Jabber ID: ~s">>, - [jid:encode(RoomJID)]}, - Invite = #message{ - from = RoomJID, to = From, - body = xmpp:mk_text(BodyTxt, Lang), - sub_els = [#muc_user{ - invites = [#muc_invite{from = From, - reason = Reason}]}, - #x_conference{reason = Reason, - jid = RoomJID}]}, - ejabberd_router:route(Invite), - xmpp_util:make_adhoc_response( - Request, #adhoc_command{status = completed}); - true -> - Txt = <<"Missing 'channel' or 'server' in the data form">>, - {error, xmpp:err_bad_request(Txt, Lang)} - end. - --spec adhoc_register(binary(), jid(), jid(), adhoc_command()) -> - adhoc_command() | {error, stanza_error()}. -adhoc_register(_ServerHost, _From, _To, - #adhoc_command{action = cancel} = Request) -> - xmpp_util:make_adhoc_response(Request, #adhoc_command{status = canceled}); -adhoc_register(ServerHost, From, To, - #adhoc_command{lang = Lang, xdata = X, - action = Action} = Request) -> - #jid{user = User} = From, - #jid{lserver = Host} = To, - {Username, ConnectionsParams} = - if X == undefined -> - case get_data(ServerHost, Host, From) of - error -> {User, []}; - empty -> {User, []}; - Data -> get_username_and_connection_params(Data) - end; - true -> - {case xmpp_util:get_xdata_values(<<"username">>, X) of - [U] -> U; - _ -> User - end, parse_connections_params(X)} - end, - if Action == complete -> - case set_data(ServerHost, Host, From, - [{username, Username}, - {connections_params, ConnectionsParams}]) of - {atomic, _} -> - xmpp_util:make_adhoc_response( - Request, #adhoc_command{status = completed}); - _ -> - Txt = <<"Database failure">>, - {error, xmpp:err_internal_server_error(Txt, Lang)} - end; - true -> - Form = generate_adhoc_register_form(Lang, Username, - ConnectionsParams), - xmpp_util:make_adhoc_response( - Request, #adhoc_command{ - status = executing, - xdata = Form, - actions = #adhoc_actions{next = true, - complete = true}}) - end. - -generate_adhoc_register_form(Lang, Username, - ConnectionsParams) -> - #xdata{type = form, - title = translate:translate(Lang, <<"IRC settings">>), - instructions = [translate:translate( - Lang, - <<"Enter username and encodings you wish " - "to use for connecting to IRC servers. " - " Press 'Next' to get more fields to " - "fill in. Press 'Complete' to save settings.">>)], - fields = [#xdata_field{ - var = <<"username">>, - type = 'text-single', - label = translate:translate(Lang, <<"IRC username">>), - required = true, - values = [Username]} - | generate_connection_params_fields( - Lang, ConnectionsParams, 1, [])]}. - -generate_connection_params_fields(Lang, [], Number, - Acc) -> - Field = generate_connection_params_field(Lang, <<"">>, - <<"">>, -1, <<"">>, Number), - lists:reverse(Field ++ Acc); -generate_connection_params_fields(Lang, - [ConnectionParams | ConnectionsParams], - Number, Acc) -> - case ConnectionParams of - {Server, Encoding, Port, Password} -> - Field = generate_connection_params_field(Lang, Server, - Encoding, Port, Password, - Number), - generate_connection_params_fields(Lang, - ConnectionsParams, Number + 1, - Field ++ Acc); - {Server, Encoding, Port} -> - Field = generate_connection_params_field(Lang, Server, - Encoding, Port, <<"">>, Number), - generate_connection_params_fields(Lang, - ConnectionsParams, Number + 1, - Field ++ Acc); - {Server, Encoding} -> - Field = generate_connection_params_field(Lang, Server, - Encoding, -1, <<"">>, Number), - generate_connection_params_fields(Lang, - ConnectionsParams, Number + 1, - Field ++ Acc); - _ -> [] - end. - -generate_connection_params_field(Lang, Server, Encoding, - Port, Password, Number) -> - EncodingUsed = case Encoding of - <<>> -> get_default_encoding(Server); - _ -> Encoding - end, - PortUsedInt = if Port >= 0 andalso Port =< 65535 -> - Port; - true -> ?DEFAULT_IRC_PORT - end, - PortUsed = integer_to_binary(PortUsedInt), - PasswordUsed = case Password of - <<>> -> <<>>; - _ -> Password - end, - NumberString = integer_to_binary(Number), - [#xdata_field{var = <<"password", NumberString/binary>>, - type = 'text-single', - label = str:format( - translate:translate(Lang, <<"Password ~b">>), - [Number]), - values = [PasswordUsed]}, - #xdata_field{var = <<"port", NumberString/binary>>, - type = 'text-single', - label = str:format( - translate:translate(Lang, <<"Port ~b">>), - [Number]), - values = [PortUsed]}, - #xdata_field{var = <<"encoding", NumberString/binary>>, - type = 'list-single', - label = str:format( - translate:translate(Lang, <<"Encoding for server ~b">>), - [Number]), - values = [EncodingUsed], - options = [#xdata_option{label = E, value = E} - || E <- ?POSSIBLE_ENCODINGS]}, - #xdata_field{var = <<"server", NumberString/binary>>, - type = 'text-single', - label = str:format( - translate:translate(Lang, <<"Server ~b">>), - [Number]), - values = [Server]}]. - -parse_connections_params(#xdata{fields = Fields}) -> - Servers = lists:flatmap( - fun(#xdata_field{var = <<"server", Var/binary>>, - values = Values}) -> - [{Var, Values}]; - (_) -> - [] - end, Fields), - Encodings = lists:flatmap( - fun(#xdata_field{var = <<"encoding", Var/binary>>, - values = Values}) -> - [{Var, Values}]; - (_) -> - [] - end, Fields), - Ports = lists:flatmap( - fun(#xdata_field{var = <<"port", Var/binary>>, - values = Values}) -> - [{Var, Values}]; - (_) -> - [] - end, Fields), - Passwords = lists:flatmap( - fun(#xdata_field{var = <<"password", Var/binary>>, - values = Values}) -> - [{Var, Values}]; - (_) -> - [] - end, Fields), - parse_connections_params(Servers, Encodings, Ports, Passwords). - -retrieve_connections_params(ConnectionParams, - ServerN) -> - case ConnectionParams of - [{ConnectionParamN, ConnectionParam} - | ConnectionParamsTail] -> - if ServerN == ConnectionParamN -> - {ConnectionParam, ConnectionParamsTail}; - ServerN < ConnectionParamN -> - {[], - [{ConnectionParamN, ConnectionParam} - | ConnectionParamsTail]}; - ServerN > ConnectionParamN -> {[], ConnectionParamsTail} - end; - _ -> {[], []} - end. - -parse_connections_params([], _, _, _) -> []; -parse_connections_params(_, [], [], []) -> []; -parse_connections_params([{ServerN, Server} | Servers], - Encodings, Ports, Passwords) -> - {NewEncoding, NewEncodings} = - retrieve_connections_params(Encodings, ServerN), - {NewPort, NewPorts} = retrieve_connections_params(Ports, - ServerN), - {NewPassword, NewPasswords} = - retrieve_connections_params(Passwords, ServerN), - [{Server, NewEncoding, NewPort, NewPassword} - | parse_connections_params(Servers, NewEncodings, - NewPorts, NewPasswords)]. - -get_username_and_connection_params(Data) -> - Username = case lists:keysearch(username, 1, Data) of - {value, {_, U}} when is_binary(U) -> - U; - _ -> - <<"">> - end, - ConnParams = case lists:keysearch(connections_params, 1, Data) of - {value, {_, L}} when is_list(L) -> - L; - _ -> - [] - end, - {Username, ConnParams}. - -data_to_binary(JID, Data) -> - lists:map( - fun({username, U}) -> - {username, iolist_to_binary(U)}; - ({connections_params, Params}) -> - {connections_params, - lists:flatmap( - fun(Param) -> - try - [conn_param_to_binary(Param)] - catch _:_ -> - if JID /= error -> - ?ERROR_MSG("failed to convert " - "parameter ~p for user ~s", - [Param, - jid:encode(JID)]); - true -> - ?ERROR_MSG("failed to convert " - "parameter ~p", - [Param]) - end, - [] - end - end, Params)}; - (Opt) -> - Opt - end, Data). - -conn_param_to_binary({S}) -> - {iolist_to_binary(S)}; -conn_param_to_binary({S, E}) -> - {iolist_to_binary(S), iolist_to_binary(E)}; -conn_param_to_binary({S, E, Port}) when is_integer(Port) -> - {iolist_to_binary(S), iolist_to_binary(E), Port}; -conn_param_to_binary({S, E, Port, P}) when is_integer(Port) -> - {iolist_to_binary(S), iolist_to_binary(E), Port, iolist_to_binary(P)}. - -conn_params_to_list(Params) -> - lists:map( - fun({S}) -> - {binary_to_list(S)}; - ({S, E}) -> - {binary_to_list(S), binary_to_list(E)}; - ({S, E, Port}) -> - {binary_to_list(S), binary_to_list(E), Port}; - ({S, E, Port, P}) -> - {binary_to_list(S), binary_to_list(E), - Port, binary_to_list(P)} - end, Params). - -export(LServer) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:export(LServer). - -import(LServer) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:import(LServer). - -import(LServer, DBType, Data) -> - Mod = gen_mod:db_mod(DBType, ?MODULE), - Mod:import(LServer, Data). - -mod_opt_type(access) -> - fun acl:access_rules_validator/1; -mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; -mod_opt_type(default_encoding) -> - fun iolist_to_binary/1; -mod_opt_type(name) -> - fun iolist_to_binary/1; -mod_opt_type(host) -> fun iolist_to_binary/1; -mod_opt_type(hosts) -> - fun (L) -> lists:map(fun iolist_to_binary/1, L) end; -mod_opt_type(realname) -> - fun iolist_to_binary/1; -mod_opt_type(webirc_password) -> - fun iolist_to_binary/1. - -mod_options(Host) -> - [{access, all}, - {db_type, ejabberd_config:default_db(Host, ?MODULE)}, - {default_encoding, <<"iso8859-15">>}, - {host, <<"irc.@HOST@">>}, - {hosts, []}, - {realname, <<"WebIRC-User">>}, - {webirc_password, <<"">>}, - {name, ?T("IRC Transport")}]. - --spec extract_ident(stanza()) -> binary(). -extract_ident(Packet) -> - Hdrs = extract_headers(Packet), - proplists:get_value(<<"X-Irc-Ident">>, Hdrs, <<"chatmovil">>). - --spec extract_ip_address(stanza()) -> binary(). -extract_ip_address(Packet) -> - Hdrs = extract_headers(Packet), - proplists:get_value(<<"X-Forwarded-For">>, Hdrs, <<"127.0.0.1">>). - --spec extract_headers(stanza()) -> [{binary(), binary()}]. -extract_headers(Packet) -> - case xmpp:get_subtag(Packet, #shim{}) of - #shim{headers = Hs} -> Hs; - false -> [] - end. |