From 499a1117dc78c1fc8f4cff0549eceebae7d6aa91 Mon Sep 17 00:00:00 2001 From: Alexey Shchepin Date: Sun, 23 Feb 2003 20:13:39 +0000 Subject: [PATCH] * src/mod_irc/: Added configuration interface * src/mod_configure.erl: Use jabber:iq:data instead of jabber:x:data * src/mod_disco.erl: Likewise SVN Revision: 82 --- ChangeLog | 8 + src/mod_configure.erl | 10 +- src/mod_disco.erl | 12 +- src/mod_irc/mod_irc.erl | 245 ++++++++++++++++++++++++++++- src/mod_irc/mod_irc_connection.erl | 64 +++----- src/namespaces.hrl | 1 + 6 files changed, 281 insertions(+), 59 deletions(-) diff --git a/ChangeLog b/ChangeLog index fe8799390..546685820 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2003-02-23 Alexey Shchepin + + * src/mod_irc/: Added configuration interface + + * src/mod_configure.erl: Use jabber:iq:data instead of + jabber:x:data + * src/mod_disco.erl: Likewise + 2003-02-22 Alexey Shchepin * src/mod_configure.erl: Backup management support diff --git a/src/mod_configure.erl b/src/mod_configure.erl index 05381cbcc..248c9e3b6 100644 --- a/src/mod_configure.erl +++ b/src/mod_configure.erl @@ -1,7 +1,7 @@ %%%---------------------------------------------------------------------- %%% File : mod_configure.erl %%% Author : Alexey Shchepin -%%% Purpose : Support for online configuration of ejabberd via x:data +%%% Purpose : Support for online configuration of ejabberd %%% Created : 19 Jan 2003 by Alexey Shchepin %%% Id : $Id$ %%%---------------------------------------------------------------------- @@ -23,15 +23,15 @@ start(Opts) -> IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), - gen_iq_handler:add_iq_handler(ejabberd_local, ?NS_XDATA, + gen_iq_handler:add_iq_handler(ejabberd_local, ?NS_IQDATA, ?MODULE, process_local_iq, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_sm, ?NS_XDATA, + gen_iq_handler:add_iq_handler(ejabberd_sm, ?NS_IQDATA, ?MODULE, process_sm_iq, IQDisc), ok. stop() -> - gen_iq_handler:remove_iq_handler(ejabberd_local, ?NS_XDATA), - gen_iq_handler:remove_iq_handler(ejabberd_sm, ?NS_XDATA). + gen_iq_handler:remove_iq_handler(ejabberd_local, ?NS_IQDATA), + gen_iq_handler:remove_iq_handler(ejabberd_sm, ?NS_IQDATA). process_local_iq(From, To, {iq, ID, Type, XMLNS, SubEl}) -> diff --git a/src/mod_disco.erl b/src/mod_disco.erl index 2ce645d63..ec8887276 100644 --- a/src/mod_disco.erl +++ b/src/mod_disco.erl @@ -125,28 +125,28 @@ process_local_iq_info(From, To, {iq, ID, Type, XMLNS, SubEl}) -> {iq, ID, result, XMLNS, [{xmlelement, "query", [{"xmlns", XMLNS}], - [feature_to_xml({?NS_XDATA}) + [feature_to_xml({?NS_IQDATA}) ] }]}; ["running nodes", ENode, "modules"] -> ?EMPTY_INFO_RESULT; ["running nodes", ENode, "modules", _] -> {iq, ID, result, XMLNS, [{xmlelement, "query", [{"xmlns", XMLNS}], - [feature_to_xml({?NS_XDATA})]}]}; + [feature_to_xml({?NS_IQDATA})]}]}; ["running nodes", ENode, "backup"] -> ?EMPTY_INFO_RESULT; ["running nodes", ENode, "backup", _] -> {iq, ID, result, XMLNS, [{xmlelement, "query", [{"xmlns", XMLNS}], - [feature_to_xml({?NS_XDATA})]}]}; + [feature_to_xml({?NS_IQDATA})]}]}; ["running nodes", ENode, "import"] -> ?EMPTY_INFO_RESULT; ["running nodes", ENode, "import", _] -> {iq, ID, result, XMLNS, [{xmlelement, "query", [{"xmlns", XMLNS}], - [feature_to_xml({?NS_XDATA})]}]}; + [feature_to_xml({?NS_IQDATA})]}]}; ["config", _] -> {iq, ID, result, XMLNS, [{xmlelement, "query", [{"xmlns", XMLNS}], - [feature_to_xml({?NS_XDATA})]}]}; + [feature_to_xml({?NS_IQDATA})]}]}; _ -> {iq, ID, error, XMLNS, [SubEl, {xmlelement, "error", @@ -443,7 +443,7 @@ process_sm_iq_info(From, To, {iq, ID, Type, XMLNS, SubEl}) -> "" -> {iq, ID, result, XMLNS, [{xmlelement, "query", [{"xmlns", XMLNS}], - [feature_to_xml({?NS_XDATA})]}]}; + [feature_to_xml({?NS_IQDATA})]}]}; _ -> {iq, ID, error, XMLNS, [SubEl, {xmlelement, "error", diff --git a/src/mod_irc/mod_irc.erl b/src/mod_irc/mod_irc.erl index a3c4d0225..bd3e69854 100644 --- a/src/mod_irc/mod_irc.erl +++ b/src/mod_irc/mod_irc.erl @@ -12,15 +12,22 @@ -behaviour(gen_mod). --export([start/1, init/1, stop/0, closed_conection/2]). +-export([start/1, init/1, stop/0, closed_conection/2, + get_user_and_encoding/2]). -include("ejabberd.hrl"). -include("namespaces.hrl"). +-define(DEFAULT_IRC_ENCODING, "koi8-r"). + -record(irc_connection, {userserver, pid}). +-record(irc_custom, {userserver, data}). start(Opts) -> iconv:start(), + mnesia:create_table(irc_custom, + [{disc_copies, [node()]}, + {attributes, record_info(fields, irc_custom)}]), Host = gen_mod:get_opt(host, Opts, "irc." ++ ?MYNAME), register(ejabberd_mod_irc, spawn(?MODULE, init, [Host])). @@ -60,15 +67,12 @@ do_route(Host, From, To, Packet) -> Res = {iq, ID, result, XMLNS, [{xmlelement, "query", [{"xmlns", XMLNS}], - [{xmlelement, "identity", - [{"category", "conference"}, - {"type", "irc"}, - {"name", "ejabberd"}], []}, - {xmlelement, "feature", - [{"var", ?NS_MUC}], []}]}]}, + iq_disco()}]}, ejabberd_router:route(To, From, jlib:iq_to_xml(Res)); + {iq, ID, Type, ?NS_IQDATA = XMLNS, SubEl} -> + iq_data(From, To, ID, XMLNS, Type, SubEl); _ -> Err = jlib:make_error_reply( Packet, "503", "Service Unavailable"), @@ -85,8 +89,11 @@ do_route(Host, From, To, Packet) -> case ets:lookup(irc_connection, {From, Server}) of [] -> io:format("open new connection~n"), + {Username, Encoding} = get_user_and_encoding( + From, Server), {ok, Pid} = mod_irc_connection:start( - From, Host, Server), + From, Host, Server, + Username, Encoding), ets:insert( irc_connection, #irc_connection{userserver = {From, Server}, @@ -137,3 +144,225 @@ stop() -> closed_conection(From, Server) -> ets:delete(irc_connection, {From, Server}). + +iq_disco() -> + [{xmlelement, "identity", + [{"category", "conference"}, + {"type", "irc"}, + {"name", "ejabberd/mod_irc"}], []}, + {xmlelement, "feature", + [{"var", ?NS_MUC}], []}, + {xmlelement, "feature", + [{"var", ?NS_IQDATA}], []}]. + + + +iq_data(From, To, ID, XMLNS, Type, SubEl) -> + case catch process_iq_data(From, To, ID, XMLNS, Type, SubEl) of + {'EXIT', Reason} -> + ?ERROR_MSG("~p", [Reason]); + ResIQ -> + if + ResIQ /= ignore -> + ejabberd_router:route(To, From, + jlib:iq_to_xml(ResIQ)); + true -> + ok + end + end. + + +process_iq_data(From, To, ID, XMLNS, Type, SubEl) -> + Lang = xml:get_tag_attr_s("xml:lang", SubEl), + case Type of + set -> + case xml:get_tag_attr_s("type", SubEl) of + "cancel" -> + {iq, ID, result, XMLNS, + [{xmlelement, "query", [{"xmlns", XMLNS}], []}]}; + "submit" -> + XData = jlib:parse_xdata_submit(SubEl), + case XData of + invalid -> + {iq, ID, error, XMLNS, + [SubEl, {xmlelement, "error", + [{"code", "400"}], + [{xmlcdata, "Bad Request"}]}]}; + _ -> + Node = + string:tokens( + xml:get_tag_attr_s("node", SubEl), + "/"), + case set_form(From, Node, Lang, XData) of + {result, Res} -> + {iq, ID, result, XMLNS, + [{xmlelement, "query", + [{"xmlns", XMLNS}], + Res + }]}; + {error, Code, Desc} -> + {iq, ID, error, XMLNS, + [SubEl, {xmlelement, "error", + [{"code", Code}], + [{xmlcdata, Desc}]}]} + end + end; + _ -> + {iq, ID, error, XMLNS, + [SubEl, {xmlelement, "error", + [{"code", "405"}], + [{xmlcdata, "Not Allowed"}]}]} + end; + get -> + Node = + string:tokens(xml:get_tag_attr_s("node", SubEl), "/"), + case get_form(From, Node, Lang) of + {result, Res} -> + {iq, ID, result, XMLNS, + [{xmlelement, "query", [{"xmlns", XMLNS}], + Res + }]}; + {error, Code, Desc} -> + {iq, ID, error, XMLNS, + [SubEl, {xmlelement, "error", + [{"code", Code}], + [{xmlcdata, Desc}]}]} + end + end. + + + +get_form(From, [], Lang) -> + {User, Server, _} = From, + {LUser, LServer, _} = jlib:jid_tolower(From), + Customs = + case catch mnesia:dirty_read({irc_custom, {LUser, LServer}}) of + {'EXIT', Reason} -> + {error, "500", "Internal Server Error"}; + [] -> + {User, []}; + [#irc_custom{data = Data}] -> + {xml:get_attr_s(username, Data), + xml:get_attr_s(encodings, Data)} + end, + case Customs of + {error, _, _} -> + Customs; + {Username, Encodings} -> + {result, + [{xmlelement, "title", [], + [{xmlcdata, + User ++ "@" ++ Server ++ " " ++ + translate:translate(Lang, "Configuration")}]}, + %{xmlelement, "instructions", [], + % [{xmlcdata, + % translate:translate( + % Lang, "")}]}, + {xmlelement, "field", [{"type", "text-single"}, + {"label", + translate:translate( + Lang, "IRC Username")}, + {"var", "username"}], + [{xmlelement, "value", [], [{xmlcdata, Username}]}]}, + {xmlelement, "field", [{"type", "fixed"}], + [{xmlelement, "value", [], + [{xmlcdata, + lists:flatten( + io_lib:format( + translate:translate( + Lang, + "If you want to specify different encodings " + "for IRC servers, fill this list with values " + "in format '{\"irc server\", \"encoding\"}'. " + "By default this service use \"~s\" encoding."), + [?DEFAULT_IRC_ENCODING]))}]}]}, + {xmlelement, "field", [{"type", "fixed"}], + [{xmlelement, "value", [], + [{xmlcdata, + translate:translate( + Lang, + "Example: [{\"irc.lucky.net\", \"koi8-r\"}, " + "{\"vendetta.fef.net\", \"iso8859-1\"}]." + )}]}]}, + {xmlelement, "field", [{"type", "text-multi"}, + {"label", + translate:translate(Lang, "Encodings")}, + {"var", "encodings"}], + lists:map( + fun(S) -> + {xmlelement, "value", [], [{xmlcdata, S}]} + end, + string:tokens( + lists:flatten( + io_lib:format("~p.", [Encodings])), + "\n")) + } + ]} + end; + + +get_form(_, _, Lang) -> + {error, "503", "Service Unavailable"}. + + + + +set_form(From, [], Lang, XData) -> + {LUser, LServer, _} = jlib:jid_tolower(From), + case {lists:keysearch("username", 1, XData), + lists:keysearch("encodings", 1, XData)} of + {{value, {_, [Username]}}, {value, {_, Strings}}} -> + EncString = lists:foldl(fun(S, Res) -> + Res ++ S ++ "\n" + end, "", Strings), + case erl_scan:string(EncString) of + {ok, Tokens, _} -> + case erl_parse:parse_term(Tokens) of + {ok, Encodings} -> + case mnesia:transaction( + fun() -> + mnesia:write( + #irc_custom{userserver = + {LUser, LServer}, + data = + [{username, + Username}, + {encodings, + Encodings}]}) + end) of + {atomic, _} -> + {result, []}; + _ -> + {error, "406", "Not Acceptable"} + end; + _ -> + {error, "406", "Not Acceptable"} + end; + _ -> + {error, "406", "Not Acceptable"} + end; + _ -> + {error, "406", "Not Acceptable"} + end; + + +set_form(_, _, Lang, XData) -> + {error, "503", "Service Unavailable"}. + + +get_user_and_encoding(From, IRCServer) -> + {User, Server, _} = From, + {LUser, LServer, _} = jlib:jid_tolower(From), + case catch mnesia:dirty_read({irc_custom, {LUser, LServer}}) of + {'EXIT', Reason} -> + {User, ?DEFAULT_IRC_ENCODING}; + [] -> + {User, ?DEFAULT_IRC_ENCODING}; + [#irc_custom{data = Data}] -> + {xml:get_attr_s(username, Data), + case xml:get_attr_s(IRCServer, xml:get_attr_s(encodings, Data)) of + "" -> ?DEFAULT_IRC_ENCODING; + E -> E + end} + end. + diff --git a/src/mod_irc/mod_irc_connection.erl b/src/mod_irc/mod_irc_connection.erl index 8e9c88d27..ed1a46e73 100644 --- a/src/mod_irc/mod_irc_connection.erl +++ b/src/mod_irc/mod_irc_connection.erl @@ -13,7 +13,7 @@ -behaviour(gen_fsm). %% External exports --export([start/3, receiver/2, route_chan/4, route_nick/3]). +-export([start/5, receiver/2, route_chan/4, route_nick/3]). %% gen_fsm callbacks -export([init/1, @@ -31,13 +31,11 @@ -define(SETS, gb_sets). --record(state, {socket, receiver, queue, +-record(state, {socket, encoding, receiver, queue, user, myname, server, nick, channels = dict:new(), inbuf = "", outbuf = ""}). --define(IRC_ENCODING, "koi8-r"). - -define(DBGFSM, true). -ifdef(DBGFSM). @@ -49,8 +47,8 @@ %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- -start(From, Host, Server) -> - gen_fsm:start(?MODULE, [From, Host, Server], ?FSMOPTS). +start(From, Host, Server, Username, Encoding) -> + gen_fsm:start(?MODULE, [From, Host, Server, Username, Encoding], ?FSMOPTS). %%%---------------------------------------------------------------------- %%% Callback functions from gen_fsm @@ -63,12 +61,12 @@ start(From, Host, Server) -> %% ignore | %% {stop, StopReason} %%---------------------------------------------------------------------- -init([From, Host, Server]) -> +init([From, Host, Server, Username, Encoding]) -> gen_fsm:send_event(self(), init), - {Nick, _, _} = From, {ok, open_socket, #state{queue = queue:new(), + encoding = Encoding, user = From, - nick = Nick, + nick = Username, myname = Host, server = Server}}. @@ -84,13 +82,10 @@ open_socket(init, StateData) -> ?DEBUG("connecting to ~s:~p~n", [Addr, Port]), case gen_tcp:connect(Addr, Port, [binary, {packet, 0}]) of {ok, Socket} -> - % TODO: send nick, etc... - %send_text(Socket, io_lib:format(?STREAM_HEADER, - % [StateData#state.server])), - %send_queue(StateData#state.socket, StateData#state.queue), - send_text(Socket, io_lib:format("NICK ~s\r\n", - [StateData#state.nick])), - send_text(Socket, + NewStateData = StateData#state{socket = Socket}, + send_text(NewStateData, + io_lib:format("NICK ~s\r\n", [StateData#state.nick])), + send_text(NewStateData, io_lib:format( "USER ~s ~s ~s :~s\r\n", [StateData#state.nick, @@ -98,7 +93,7 @@ open_socket(init, StateData) -> StateData#state.myname, StateData#state.nick])), {next_state, wait_for_registration, - StateData#state{socket = Socket}}; + NewStateData}; {error, Reason} -> ?DEBUG("connect return ~p~n", [Reason]), Text = case Reason of @@ -164,7 +159,7 @@ code_change(OldVsn, StateName, StateData, Extra) -> -define(SEND(S), if StateName == stream_established -> - send_text(StateData#state.socket, S), + send_text(StateData, S), StateData; true -> StateData#state{outbuf = StateData#state.outbuf ++ S} @@ -297,7 +292,7 @@ handle_info({route_nick, Nick, Packet}, StateName, StateData) -> handle_info({ircstring, [$P, $I, $N, $G, $ | ID]}, StateName, StateData) -> - send_text(StateData#state.socket, "PONG " ++ ID ++ "\r\n"), + send_text(StateData, "PONG " ++ ID ++ "\r\n"), {next_state, StateName, StateData}; handle_info({ircstring, [$: | String]}, StateName, StateData) -> @@ -343,7 +338,7 @@ handle_info({ircstring, [$: | String]}, StateName, StateData) -> "" -> NewStateData; Data -> - send_text(NewStateData#state.socket, Data), + send_text(NewStateData, Data), NewStateData#state{outbuf = ""} end, {next_state, stream_established, NewStateData1}; @@ -360,22 +355,13 @@ handle_info({ircstring, String}, StateName, StateData) -> handle_info({send_text, Text}, StateName, StateData) -> - send_text(StateData#state.socket, Text), + send_text(StateData, Text), {next_state, StateName, StateData}; -handle_info({send_element, El}, StateName, StateData) -> - case StateName of - stream_established -> - send_element(StateData#state.socket, El), - {next_state, StateName, StateData}; - _ -> - Q = queue:in(El, StateData#state.queue), - {next_state, StateName, StateData#state{queue = Q}} - end; handle_info({tcp, Socket, Data}, StateName, StateData) -> Buf = StateData#state.inbuf ++ binary_to_list(Data), {ok, Strings} = regexp:split([C || C <- Buf, C /= $\r], "\n"), io:format("strings=~p~n", [Strings]), - NewBuf = process_lines(Strings), + NewBuf = process_lines(StateData#state.encoding, Strings), {next_state, StateName, StateData#state{inbuf = NewBuf}}; handle_info({tcp_closed, Socket}, StateName, StateData) -> gen_fsm:send_event(self(), closed), @@ -430,13 +416,11 @@ receiver(Socket, C2SPid, XMLStreamPid) -> ok end. -send_text(Socket, Text) -> - CText = iconv:convert("utf-8", ?IRC_ENCODING, lists:flatten(Text)), +send_text(#state{socket = Socket, encoding = Encoding}, Text) -> + CText = iconv:convert("utf-8", Encoding, lists:flatten(Text)), %io:format("IRC OUTu: ~s~nIRC OUTk: ~s~n", [Text, CText]), gen_tcp:send(Socket, CText). -send_element(Socket, El) -> - send_text(Socket, xml:element_to_string(El)). %send_queue(Socket, Q) -> % case queue:out(Q) of @@ -474,11 +458,11 @@ route_nick(Pid, Nick, Packet) -> Pid ! {route_nick, Nick, Packet}. -process_lines([S]) -> +process_lines(Encoding, [S]) -> S; -process_lines([S | Ss]) -> - self() ! {ircstring, iconv:convert(?IRC_ENCODING, "utf-8", S)}, - process_lines(Ss). +process_lines(Encoding, [S | Ss]) -> + self() ! {ircstring, iconv:convert(Encoding, "utf-8", S)}, + process_lines(Encoding, Ss). process_channel_list(StateData, Items) -> process_channel_list_find_chan(StateData, Items). @@ -580,7 +564,7 @@ process_privmsg(StateData, Nick, From, String) -> process_version(StateData, Nick, From) -> [FromUser | _] = string:tokens(From, "!"), send_text( - StateData#state.socket, + StateData, io_lib:format("NOTICE ~s :\001VERSION " "ejabberd IRC transport ~s (c) Alexey Shchepin" "\001\r\n", diff --git a/src/namespaces.hrl b/src/namespaces.hrl index e26b9750e..397ee7826 100644 --- a/src/namespaces.hrl +++ b/src/namespaces.hrl @@ -16,6 +16,7 @@ -define(NS_VERSION, "jabber:iq:version"). -define(NS_TIME, "jabber:iq:time"). -define(NS_XDATA, "jabber:x:data"). +-define(NS_IQDATA, "jabber:iq:data"). -define(NS_DELAY, "jabber:x:delay"). -define(NS_EVENT, "jabber:x:event"). -define(NS_STATS, "http://jabber.org/protocol/stats").