mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-24 16:23:40 +01:00
Ad-hoc commands to join IRC channel, set nickname and encoding (thanks to Magnus Henoch)(EJAB-302)
SVN Revision: 2164
This commit is contained in:
parent
e0370d89b4
commit
83ffe1989a
@ -2023,6 +2023,8 @@ to the IRC transport instead of the Multi-User Chat service.
|
||||
<TT>nickserver!irc.example.org@irc.jabberserver.org</TT>.
|
||||
</LI><LI CLASS="li-itemize">Entering your password is possible by sending ‘LOGIN nick password’<BR>
|
||||
to <TT>nickserver!irc.example.org@irc.jabberserver.org</TT>.
|
||||
</LI><LI CLASS="li-itemize">The IRC transport provides Ad-Hoc Commands (<A HREF="http://www.xmpp.org/extensions/xep-0050.html">XEP-0050</A>)
|
||||
to join a channel, and to set custom IRC username and encoding.
|
||||
</LI><LI CLASS="li-itemize">When using a popular Jabber server, it can occur that no
|
||||
connection can be achieved with some IRC servers because they limit the
|
||||
number of conections from one IP.
|
||||
|
@ -2687,6 +2687,8 @@ End user information:
|
||||
\jid{nickserver!irc.example.org@irc.jabberserver.org}.
|
||||
\item Entering your password is possible by sending `LOGIN nick password' \\
|
||||
to \jid{nickserver!irc.example.org@irc.jabberserver.org}.
|
||||
\item The IRC transport provides Ad-Hoc Commands (\xepref{0050})
|
||||
to join a channel, and to set custom IRC username and encoding.
|
||||
\item When using a popular \Jabber{} server, it can occur that no
|
||||
connection can be achieved with some IRC servers because they limit the
|
||||
number of conections from one IP.
|
||||
|
@ -59,6 +59,7 @@ static int iconv_erl_control(ErlDrvData drv_data,
|
||||
ErlDrvBinary *b;
|
||||
char *from, *to, *string, *stmp, *rstring, *rtmp;
|
||||
iconv_t cd;
|
||||
int invalid_utf8_as_latin1 = 0;
|
||||
|
||||
ei_decode_version(buf, &index, &i);
|
||||
ei_decode_tuple_header(buf, &index, &i);
|
||||
@ -74,6 +75,15 @@ static int iconv_erl_control(ErlDrvData drv_data,
|
||||
stmp = string = malloc(size + 1);
|
||||
ei_decode_string(buf, &index, string);
|
||||
|
||||
/* Special mode: parse as UTF-8 if possible; otherwise assume it's
|
||||
Latin-1. Makes no difference when encoding. */
|
||||
if (strcmp(from, "utf-8+latin-1") == 0) {
|
||||
from[5] = '\0';
|
||||
invalid_utf8_as_latin1 = 1;
|
||||
}
|
||||
if (strcmp(to, "utf-8+latin-1") == 0) {
|
||||
to[5] = '\0';
|
||||
}
|
||||
cd = iconv_open(to, from);
|
||||
|
||||
if (cd == (iconv_t) -1) {
|
||||
@ -95,6 +105,12 @@ static int iconv_erl_control(ErlDrvData drv_data,
|
||||
rtmp = rstring = malloc(avail);
|
||||
while (inleft > 0) {
|
||||
if (iconv(cd, &stmp, &inleft, &rtmp, &outleft) == (size_t) -1) {
|
||||
if (invalid_utf8_as_latin1 && (*stmp & 0x80) && outleft >= 2) {
|
||||
/* Encode one byte of (assumed) Latin-1 into two bytes of UTF-8 */
|
||||
*rtmp++ = 0xc0 | ((*stmp & 0xc0) >> 6);
|
||||
*rtmp++ = 0x80 | (*stmp & 0x3f);
|
||||
outleft -= 2;
|
||||
}
|
||||
stmp++;
|
||||
inleft--;
|
||||
}
|
||||
|
@ -34,7 +34,8 @@
|
||||
-export([start_link/2,
|
||||
start/2,
|
||||
stop/1,
|
||||
closed_connection/3]).
|
||||
closed_connection/3,
|
||||
get_user_and_encoding/3]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
@ -42,11 +43,15 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("adhoc.hrl").
|
||||
|
||||
-define(DEFAULT_IRC_ENCODING, "iso8859-1").
|
||||
-define(POSSIBLE_ENCODINGS, ["koi8-r", "iso8859-1", "iso8859-2", "utf-8", "utf-8+latin-1"]).
|
||||
|
||||
-record(irc_connection, {jid_server_host, pid}).
|
||||
-record(irc_custom, {us_host, data}).
|
||||
|
||||
-record(state, {host, server_host, default_encoding, access}).
|
||||
-record(state, {host, server_host, access}).
|
||||
|
||||
-define(PROCNAME, ejabberd_mod_irc).
|
||||
|
||||
@ -98,14 +103,12 @@ init([Host, Opts]) ->
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts, "irc.@HOST@"),
|
||||
update_table(MyHost),
|
||||
Access = gen_mod:get_opt(access, Opts, all),
|
||||
DefaultEncoding = gen_mod:get_opt(default_encoding, Opts, "koi8-r"),
|
||||
catch ets:new(irc_connection, [named_table,
|
||||
public,
|
||||
{keypos, #irc_connection.jid_server_host}]),
|
||||
ejabberd_router:register_route(MyHost),
|
||||
{ok, #state{host = MyHost,
|
||||
server_host = Host,
|
||||
default_encoding = DefaultEncoding,
|
||||
access = Access}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@ -138,9 +141,8 @@ handle_cast(_Msg, State) ->
|
||||
handle_info({route, From, To, Packet},
|
||||
#state{host = Host,
|
||||
server_host = ServerHost,
|
||||
default_encoding = DefEnc,
|
||||
access = Access} = State) ->
|
||||
case catch do_route(Host, ServerHost, Access, From, To, Packet, DefEnc) of
|
||||
case catch do_route(Host, ServerHost, Access, From, To, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p", [Reason]);
|
||||
_ ->
|
||||
@ -188,10 +190,10 @@ stop_supervisor(Host) ->
|
||||
supervisor:terminate_child(ejabberd_sup, Proc),
|
||||
supervisor:delete_child(ejabberd_sup, Proc).
|
||||
|
||||
do_route(Host, ServerHost, Access, From, To, Packet, DefEnc) ->
|
||||
do_route(Host, ServerHost, Access, From, To, Packet) ->
|
||||
case acl:match_rule(ServerHost, Access, From) of
|
||||
allow ->
|
||||
do_route1(Host, ServerHost, From, To, Packet, DefEnc);
|
||||
do_route1(Host, ServerHost, From, To, Packet);
|
||||
_ ->
|
||||
{xmlelement, _Name, Attrs, _Els} = Packet,
|
||||
Lang = xml:get_attr_s("xml:lang", Attrs),
|
||||
@ -201,7 +203,7 @@ do_route(Host, ServerHost, Access, From, To, Packet, DefEnc) ->
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end.
|
||||
|
||||
do_route1(Host, ServerHost, From, To, Packet, DefEnc) ->
|
||||
do_route1(Host, ServerHost, From, To, Packet) ->
|
||||
#jid{user = ChanServ, resource = Resource} = To,
|
||||
{xmlelement, _Name, _Attrs, _Els} = Packet,
|
||||
case ChanServ of
|
||||
@ -210,24 +212,64 @@ do_route1(Host, ServerHost, From, To, Packet, DefEnc) ->
|
||||
"" ->
|
||||
case jlib:iq_query_info(Packet) of
|
||||
#iq{type = get, xmlns = ?NS_DISCO_INFO = XMLNS,
|
||||
sub_el = _SubEl, lang = Lang} = IQ ->
|
||||
Res = IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", XMLNS}],
|
||||
iq_disco(Lang)}]},
|
||||
sub_el = SubEl, lang = Lang} = IQ ->
|
||||
Node = xml:get_tag_attr_s("node", SubEl),
|
||||
case iq_disco(Node, Lang) of
|
||||
[] ->
|
||||
Res = IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", XMLNS}],
|
||||
[]}]},
|
||||
ejabberd_router:route(To,
|
||||
From,
|
||||
jlib:iq_to_xml(Res));
|
||||
DiscoInfo ->
|
||||
Res = IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", XMLNS}],
|
||||
DiscoInfo}]},
|
||||
ejabberd_router:route(To,
|
||||
From,
|
||||
jlib:iq_to_xml(Res))
|
||||
end;
|
||||
#iq{type = get, xmlns = ?NS_DISCO_ITEMS = XMLNS,
|
||||
sub_el = SubEl, lang = Lang} = IQ ->
|
||||
Node = xml:get_tag_attr_s("node", SubEl),
|
||||
case Node of
|
||||
[] ->
|
||||
ResIQ = IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", XMLNS}],
|
||||
[]}]},
|
||||
Res = jlib:iq_to_xml(ResIQ);
|
||||
"join" ->
|
||||
ResIQ = IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", XMLNS}],
|
||||
[]}]},
|
||||
Res = jlib:iq_to_xml(ResIQ);
|
||||
"register" ->
|
||||
ResIQ = IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", XMLNS}],
|
||||
[]}]},
|
||||
Res = jlib:iq_to_xml(ResIQ);
|
||||
?NS_COMMANDS ->
|
||||
ResIQ = IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", XMLNS},
|
||||
{"node", Node}],
|
||||
command_items(Host, Lang)}]},
|
||||
Res = jlib:iq_to_xml(ResIQ);
|
||||
_ ->
|
||||
Res = jlib:make_error_reply(
|
||||
Packet, ?ERR_ITEM_NOT_FOUND)
|
||||
end,
|
||||
ejabberd_router:route(To,
|
||||
From,
|
||||
jlib:iq_to_xml(Res));
|
||||
#iq{type = get, xmlns = ?NS_DISCO_ITEMS = XMLNS} = IQ ->
|
||||
Res = IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", XMLNS}],
|
||||
[]}]},
|
||||
ejabberd_router:route(To,
|
||||
From,
|
||||
jlib:iq_to_xml(Res));
|
||||
Res);
|
||||
#iq{xmlns = ?NS_REGISTER} = IQ ->
|
||||
process_register(Host, From, To, DefEnc, IQ);
|
||||
process_register(Host, From, To, IQ);
|
||||
#iq{type = get, xmlns = ?NS_VCARD = XMLNS,
|
||||
lang = Lang} = IQ ->
|
||||
Res = IQ#iq{type = result,
|
||||
@ -238,6 +280,34 @@ do_route1(Host, ServerHost, From, To, Packet, DefEnc) ->
|
||||
ejabberd_router:route(To,
|
||||
From,
|
||||
jlib:iq_to_xml(Res));
|
||||
#iq{type = set, xmlns = ?NS_COMMANDS,
|
||||
lang = _Lang, sub_el = SubEl} = IQ ->
|
||||
Request = adhoc:parse_request(IQ),
|
||||
case lists:keysearch(Request#adhoc_request.node, 1, commands()) of
|
||||
{value, {_, _, Function}} ->
|
||||
case catch Function(From, To, Request) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~nfor ad-hoc handler of ~p",
|
||||
[Reason, {From, To, IQ}]),
|
||||
Res = IQ#iq{type = error, sub_el = [SubEl,
|
||||
?ERR_INTERNAL_SERVER_ERROR]};
|
||||
ignore ->
|
||||
Res = ignore;
|
||||
{error, Error} ->
|
||||
Res = IQ#iq{type = error, sub_el = [SubEl, Error]};
|
||||
Command ->
|
||||
Res = IQ#iq{type = result, sub_el = [Command]}
|
||||
end,
|
||||
if Res /= ignore ->
|
||||
ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
|
||||
true ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERR_ITEM_NOT_FOUND),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
#iq{} = _IQ ->
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERR_FEATURE_NOT_IMPLEMENTED),
|
||||
@ -256,10 +326,25 @@ do_route1(Host, ServerHost, From, To, Packet, DefEnc) ->
|
||||
[] ->
|
||||
?DEBUG("open new connection~n", []),
|
||||
{Username, Encoding} = get_user_and_encoding(
|
||||
Host, From, Server, DefEnc),
|
||||
Host, From, Server),
|
||||
ConnectionUsername =
|
||||
case Packet of
|
||||
%% If the user tries to join a
|
||||
%% chatroom, the packet for sure
|
||||
%% contains the desired username.
|
||||
{xmlelement, "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,
|
||||
{ok, Pid} = mod_irc_connection:start(
|
||||
From, Host, ServerHost, Server,
|
||||
Username, Encoding),
|
||||
ConnectionUsername, Encoding),
|
||||
ets:insert(
|
||||
irc_connection,
|
||||
#irc_connection{jid_server_host = {From, Server, Host},
|
||||
@ -304,7 +389,7 @@ closed_connection(Host, From, Server) ->
|
||||
ets:delete(irc_connection, {From, Server, Host}).
|
||||
|
||||
|
||||
iq_disco(Lang) ->
|
||||
iq_disco([], Lang) ->
|
||||
[{xmlelement, "identity",
|
||||
[{"category", "conference"},
|
||||
{"type", "irc"},
|
||||
@ -312,7 +397,22 @@ iq_disco(Lang) ->
|
||||
{xmlelement, "feature", [{"var", ?NS_DISCO_INFO}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_MUC}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_REGISTER}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_VCARD}], []}].
|
||||
{xmlelement, "feature", [{"var", ?NS_VCARD}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_COMMANDS}], []}];
|
||||
iq_disco(Node, Lang) ->
|
||||
case lists:keysearch(Node, 1, commands()) of
|
||||
{value, {_, Name, _}} ->
|
||||
[{xmlelement, "identity",
|
||||
[{"category", "automation"},
|
||||
{"type", "command-node"},
|
||||
{"name", translate:translate(Lang, Name)}], []},
|
||||
{xmlelement, "feature",
|
||||
[{"var", ?NS_COMMANDS}], []},
|
||||
{xmlelement, "feature",
|
||||
[{"var", ?NS_XDATA}], []}];
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
iq_get_vcard(Lang) ->
|
||||
[{xmlelement, "FN", [],
|
||||
@ -320,11 +420,23 @@ iq_get_vcard(Lang) ->
|
||||
{xmlelement, "URL", [],
|
||||
[{xmlcdata, ?EJABBERD_URI}]},
|
||||
{xmlelement, "DESC", [],
|
||||
[{xmlcdata, translate:translate(Lang, "ejabberd IRC module") ++
|
||||
[{xmlcdata, translate:translate(Lang, "ejabberd IRC module") ++
|
||||
"\nCopyright (c) 2003-2009 Alexey Shchepin"}]}].
|
||||
|
||||
process_register(Host, From, To, DefEnc, #iq{} = IQ) ->
|
||||
case catch process_irc_register(Host, From, To, DefEnc, IQ) of
|
||||
command_items(Host, Lang) ->
|
||||
lists:map(fun({Node, Name, _Function})
|
||||
-> {xmlelement, "item",
|
||||
[{"jid", Host},
|
||||
{"node", Node},
|
||||
{"name", translate:translate(Lang, Name)}], []}
|
||||
end, commands()).
|
||||
|
||||
commands() ->
|
||||
[{"join", "Join channel", fun adhoc_join/3},
|
||||
{"register", "Configure username and encoding", fun adhoc_register/3}].
|
||||
|
||||
process_register(Host, From, To, #iq{} = IQ) ->
|
||||
case catch process_irc_register(Host, From, To, IQ) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p", [Reason]);
|
||||
ResIQ ->
|
||||
@ -354,7 +466,7 @@ find_xdata_el1([{xmlelement, Name, Attrs, SubEls} | Els]) ->
|
||||
find_xdata_el1([_ | Els]) ->
|
||||
find_xdata_el1(Els).
|
||||
|
||||
process_irc_register(Host, From, _To, DefEnc,
|
||||
process_irc_register(Host, From, _To,
|
||||
#iq{type = Type, xmlns = XMLNS,
|
||||
lang = Lang, sub_el = SubEl} = IQ) ->
|
||||
case Type of
|
||||
@ -400,7 +512,7 @@ process_irc_register(Host, From, _To, DefEnc,
|
||||
get ->
|
||||
Node =
|
||||
string:tokens(xml:get_tag_attr_s("node", SubEl), "/"),
|
||||
case get_form(Host, From, Node, Lang ,DefEnc) of
|
||||
case get_form(Host, From, Node, Lang) of
|
||||
{result, Res} ->
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
@ -415,7 +527,7 @@ process_irc_register(Host, From, _To, DefEnc,
|
||||
|
||||
|
||||
|
||||
get_form(Host, From, [], Lang, DefEnc) ->
|
||||
get_form(Host, From, [], Lang) ->
|
||||
#jid{user = User, server = Server,
|
||||
luser = LUser, lserver = LServer} = From,
|
||||
US = {LUser, LServer},
|
||||
@ -469,7 +581,7 @@ get_form(Host, From, [], Lang, DefEnc) ->
|
||||
"for IRC servers, fill this list with values "
|
||||
"in format '{\"irc server\", \"encoding\"}'. "
|
||||
"By default this service use \"~s\" encoding."),
|
||||
[DefEnc]))}]}]},
|
||||
[?DEFAULT_IRC_ENCODING]))}]}]},
|
||||
{xmlelement, "field", [{"type", "fixed"}],
|
||||
[{xmlelement, "value", [],
|
||||
[{xmlcdata,
|
||||
@ -494,7 +606,7 @@ get_form(Host, From, [], Lang, DefEnc) ->
|
||||
]}]}
|
||||
end;
|
||||
|
||||
get_form(_Host, _, _, _Lang, _) ->
|
||||
get_form(_Host, _, _, _Lang) ->
|
||||
{error, ?ERR_SERVICE_UNAVAILABLE}.
|
||||
|
||||
|
||||
@ -544,23 +656,244 @@ set_form(_Host, _, _, _Lang, _XData) ->
|
||||
{error, ?ERR_SERVICE_UNAVAILABLE}.
|
||||
|
||||
|
||||
get_user_and_encoding(Host, From, IRCServer, DefEnc) ->
|
||||
get_user_and_encoding(Host, From, IRCServer) ->
|
||||
#jid{user = User, server = _Server,
|
||||
luser = LUser, lserver = LServer} = From,
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({irc_custom, {US, Host}}) of
|
||||
{'EXIT', _Reason} ->
|
||||
{User, DefEnc};
|
||||
{User, ?DEFAULT_IRC_ENCODING};
|
||||
[] ->
|
||||
{User, DefEnc};
|
||||
{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
|
||||
"" -> DefEnc;
|
||||
"" -> ?DEFAULT_IRC_ENCODING;
|
||||
E -> E
|
||||
end}
|
||||
end.
|
||||
|
||||
adhoc_join(_From, _To, #adhoc_request{action = "cancel"} = Request) ->
|
||||
adhoc:produce_response(Request,
|
||||
#adhoc_response{status = canceled});
|
||||
adhoc_join(From, To, #adhoc_request{lang = Lang,
|
||||
node = _Node,
|
||||
action = _Action,
|
||||
xdata = XData} = Request) ->
|
||||
%% Access control has already been taken care of in do_route.
|
||||
if XData == false ->
|
||||
Form =
|
||||
{xmlelement, "x",
|
||||
[{"xmlns", ?NS_XDATA},
|
||||
{"type", "form"}],
|
||||
[{xmlelement, "title", [], [{xmlcdata, translate:translate(Lang, "Join IRC channel")}]},
|
||||
{xmlelement, "field",
|
||||
[{"var", "channel"},
|
||||
{"type", "text-single"},
|
||||
{"label", translate:translate(Lang, "Channel to join (without leading #)")}],
|
||||
[{xmlelement, "required", [], []}]},
|
||||
{xmlelement, "field",
|
||||
[{"var", "server"},
|
||||
{"type", "text-single"},
|
||||
{"label", translate:translate(Lang, "Server")}],
|
||||
[{xmlelement, "required", [], []}]}]},
|
||||
adhoc:produce_response(Request,
|
||||
#adhoc_response{status = executing,
|
||||
elements = [Form]});
|
||||
true ->
|
||||
case jlib:parse_xdata_submit(XData) of
|
||||
invalid ->
|
||||
{error, ?ERR_BAD_REQUEST};
|
||||
Fields ->
|
||||
Channel = case lists:keysearch("channel", 1, Fields) of
|
||||
{value, {"channel", C}} ->
|
||||
C;
|
||||
_ ->
|
||||
false
|
||||
end,
|
||||
Server = case lists:keysearch("server", 1, Fields) of
|
||||
{value, {"server", S}} ->
|
||||
S;
|
||||
_ ->
|
||||
false
|
||||
end,
|
||||
if Channel /= false,
|
||||
Server /= false ->
|
||||
RoomJID = Channel ++ "%" ++ Server ++ "@" ++ To#jid.server,
|
||||
Invite = {xmlelement, "message", [],
|
||||
[{xmlelement, "x",
|
||||
[{"xmlns", ?NS_MUC_USER}],
|
||||
[{xmlelement, "invite",
|
||||
[{"from", jlib:jid_to_string(From)}],
|
||||
[{xmlelement, "reason", [],
|
||||
[{xmlcdata,
|
||||
translate:translate(Lang,
|
||||
"Join the IRC channel here.")}]}]}]},
|
||||
{xmlelement, "x",
|
||||
[{"xmlns", ?NS_XCONFERENCE}],
|
||||
[{xmlcdata, translate:translate(Lang,
|
||||
"Join the IRC channel here.")}]},
|
||||
{xmlelement, "body", [],
|
||||
[{xmlcdata, io_lib:format(
|
||||
translate:translate(Lang,
|
||||
"Find the IRC channel at JID ~s"),
|
||||
[RoomJID])}]}]},
|
||||
ejabberd_router:route(jlib:string_to_jid(RoomJID), From, Invite),
|
||||
adhoc:produce_response(Request, #adhoc_response{status = completed});
|
||||
true ->
|
||||
{error, ?ERR_BAD_REQUEST}
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
adhoc_register(_From, _To, #adhoc_request{action = "cancel"} = Request) ->
|
||||
adhoc:produce_response(Request,
|
||||
#adhoc_response{status = canceled});
|
||||
adhoc_register(From, To, #adhoc_request{lang = Lang,
|
||||
node = _Node,
|
||||
xdata = XData,
|
||||
action = Action} = Request) ->
|
||||
#jid{user = User, luser = LUser, lserver = LServer} = From,
|
||||
#jid{lserver = Host} = To,
|
||||
US = {LUser, LServer},
|
||||
%% Generate form for setting username and encodings. If the user
|
||||
%% hasn't begun to fill out the form, generate an initial form
|
||||
%% based on current values.
|
||||
if XData == false ->
|
||||
case catch mnesia:dirty_read({irc_custom, {US, Host}}) of
|
||||
{'EXIT', _Reason} ->
|
||||
Username = User,
|
||||
Encodings = [];
|
||||
[] ->
|
||||
Username = User,
|
||||
Encodings = [];
|
||||
[#irc_custom{data = Data}] ->
|
||||
Username = xml:get_attr_s(username, Data),
|
||||
Encodings = xml:get_attr_s(encodings, Data)
|
||||
end,
|
||||
Error = false;
|
||||
true ->
|
||||
case jlib:parse_xdata_submit(XData) of
|
||||
invalid ->
|
||||
Error = {error, ?ERR_BAD_REQUEST},
|
||||
Username = false,
|
||||
Encodings = false;
|
||||
Fields ->
|
||||
Username = case lists:keysearch("username", 1, Fields) of
|
||||
{value, {"username", U}} ->
|
||||
U;
|
||||
_ ->
|
||||
User
|
||||
end,
|
||||
Encodings = parse_encodings(Fields),
|
||||
Error = false
|
||||
end
|
||||
end,
|
||||
|
||||
if Error /= false ->
|
||||
Error;
|
||||
Action == "complete" ->
|
||||
case mnesia:transaction(
|
||||
fun () ->
|
||||
mnesia:write(
|
||||
#irc_custom{us_host =
|
||||
{US, Host},
|
||||
data =
|
||||
[{username,
|
||||
Username},
|
||||
{encodings,
|
||||
Encodings}]})
|
||||
end) of
|
||||
{atomic, _} ->
|
||||
adhoc:produce_response(Request, #adhoc_response{status = completed});
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end;
|
||||
true ->
|
||||
Form = generate_adhoc_register_form(Lang, Username, Encodings),
|
||||
adhoc:produce_response(Request,
|
||||
#adhoc_response{status = executing,
|
||||
elements = [Form],
|
||||
actions = ["next", "complete"]})
|
||||
end.
|
||||
|
||||
generate_adhoc_register_form(Lang, Username, Encodings) ->
|
||||
{xmlelement, "x",
|
||||
[{"xmlns", ?NS_XDATA},
|
||||
{"type", "form"}],
|
||||
[{xmlelement, "title", [], [{xmlcdata, translate:translate(Lang, "IRC settings")}]},
|
||||
{xmlelement, "instructions", [],
|
||||
[{xmlcdata,
|
||||
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.")}]},
|
||||
{xmlelement, "field",
|
||||
[{"var", "username"},
|
||||
{"type", "text-single"},
|
||||
{"label", translate:translate(Lang, "IRC username")}],
|
||||
[{xmlelement, "required", [], []},
|
||||
{xmlelement, "value", [], [{xmlcdata, Username}]}]}] ++
|
||||
generate_encoding_fields(Lang, Encodings, 1, [])}.
|
||||
|
||||
generate_encoding_fields(Lang, [], Number, Acc) ->
|
||||
Field = generate_encoding_field(Lang, "", "", Number),
|
||||
lists:reverse(Field ++ Acc);
|
||||
generate_encoding_fields(Lang, [{Server, Encoding} | Encodings], Number, Acc) ->
|
||||
Field = generate_encoding_field(Lang, Server, Encoding, Number),
|
||||
generate_encoding_fields(Lang, Encodings, Number + 1, Field ++ Acc).
|
||||
|
||||
generate_encoding_field(Lang, Server, Encoding, Number) ->
|
||||
EncodingUsed = case Encoding of
|
||||
[] ->
|
||||
?DEFAULT_IRC_ENCODING;
|
||||
_ ->
|
||||
Encoding
|
||||
end,
|
||||
%% Fields are in reverse order, as they will be reversed again later.
|
||||
[{xmlelement, "field",
|
||||
[{"var", "encoding" ++ io_lib:format("~b", [Number])},
|
||||
{"type", "list-single"},
|
||||
{"label", io_lib:format(translate:translate(Lang, "Encoding for server ~b"), [Number])}],
|
||||
[{xmlelement, "value", [], [{xmlcdata, EncodingUsed}]} |
|
||||
lists:map(fun(E) ->
|
||||
{xmlelement, "option", [{"label", E}],
|
||||
[{xmlelement, "value", [], [{xmlcdata, E}]}]}
|
||||
end, ?POSSIBLE_ENCODINGS)]},
|
||||
{xmlelement, "field",
|
||||
[{"var", "server" ++ io_lib:format("~b", [Number])},
|
||||
{"type", "text-single"},
|
||||
{"label", io_lib:format(translate:translate(Lang, "Server ~b"), [Number])}],
|
||||
[{xmlelement, "value", [], [{xmlcdata, Server}]}]}].
|
||||
|
||||
parse_encodings(Fields) ->
|
||||
%% Find all fields staring with serverN and encodingN, for any values
|
||||
%% of N, and generate lists of {"N", Value}.
|
||||
Servers = lists:sort(
|
||||
[{lists:nthtail(6, Var), lists:flatten(Value)} || {Var, Value} <- Fields,
|
||||
lists:prefix("server", Var)]),
|
||||
Encodings = lists:sort(
|
||||
[{lists:nthtail(8, Var), lists:flatten(Value)} || {Var, Value} <- Fields,
|
||||
lists:prefix("encoding", Var)]),
|
||||
|
||||
%% Now sort the lists, and find the corresponding pairs.
|
||||
parse_encodings(Servers, Encodings).
|
||||
|
||||
parse_encodings([{ServerN, Server} | Servers], [{EncodingN, Encoding} | Encodings]) ->
|
||||
%% Try to match pairs of servers and encodings, no matter what fields
|
||||
%% the client might have left out.
|
||||
if ServerN == EncodingN ->
|
||||
[{Server, Encoding} | parse_encodings(Servers, Encodings)];
|
||||
ServerN < EncodingN ->
|
||||
parse_encodings(Servers, [{EncodingN, Encoding} | Encodings]);
|
||||
ServerN > EncodingN ->
|
||||
parse_encodings([{ServerN, Server} | Servers], Encodings)
|
||||
end;
|
||||
parse_encodings([], _) ->
|
||||
[];
|
||||
parse_encodings(_, []) ->
|
||||
[].
|
||||
|
||||
update_table(Host) ->
|
||||
Fields = record_info(fields, irc_custom),
|
||||
|
@ -1,7 +1,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_irc_connection.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose :
|
||||
%%% Purpose :
|
||||
%%% Created : 15 Feb 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
@ -51,6 +51,7 @@
|
||||
-record(state, {socket, encoding, queue,
|
||||
user, host, server, nick,
|
||||
channels = dict:new(),
|
||||
nickchannel,
|
||||
inbuf = "", outbuf = ""}).
|
||||
|
||||
%-define(DBGFSM, true).
|
||||
@ -217,15 +218,21 @@ handle_info({route_chan, Channel, Resource,
|
||||
_ ->
|
||||
Resource
|
||||
end,
|
||||
S1 = ?SEND(io_lib:format("NICK ~s\r\n"
|
||||
"JOIN #~s\r\n",
|
||||
[Nick, Channel])),
|
||||
S1 = if
|
||||
Nick /= StateData#state.nick ->
|
||||
S11 = ?SEND(io_lib:format("NICK ~s\r\n", [Nick])),
|
||||
% The server reply will change the copy of the
|
||||
% nick in the state (or indicate a clash).
|
||||
S11#state{nickchannel = Channel};
|
||||
true ->
|
||||
StateData
|
||||
end,
|
||||
case dict:is_key(Channel, S1#state.channels) of
|
||||
true ->
|
||||
S1#state{nick = Nick};
|
||||
S1;
|
||||
_ ->
|
||||
S1#state{nick = Nick,
|
||||
channels =
|
||||
S2 = ?SEND(io_lib:format("JOIN #~s\r\n", [Channel])),
|
||||
S2#state{channels =
|
||||
dict:store(Channel, ?SETS:new(),
|
||||
S1#state.channels)}
|
||||
end
|
||||
@ -471,6 +478,35 @@ handle_info({ircstring, [$P, $I, $N, $G, $ | ID]}, StateName, StateData) ->
|
||||
send_text(StateData, "PONG " ++ ID ++ "\r\n"),
|
||||
{next_state, StateName, StateData};
|
||||
|
||||
handle_info({ircstring, [$: | String]}, wait_for_registration, StateData) ->
|
||||
Words = string:tokens(String, " "),
|
||||
{NewState, NewStateData} =
|
||||
case Words of
|
||||
[_, "001" | _] ->
|
||||
{stream_established, StateData};
|
||||
[_, "433" | _] ->
|
||||
{error,
|
||||
{error, error_nick_in_use(StateData, String), StateData}};
|
||||
[_, [$4, _, _] | _] ->
|
||||
{error,
|
||||
{error, error_unknown_num(StateData, String, "cancel"),
|
||||
StateData}};
|
||||
[_, [$5, _, _] | _] ->
|
||||
{error,
|
||||
{error, error_unknown_num(StateData, String, "cancel"),
|
||||
StateData}};
|
||||
_ ->
|
||||
?DEBUG("unknown irc command '~s'~n", [String]),
|
||||
{wait_for_registration, StateData}
|
||||
end,
|
||||
% Note that we don't send any data at this stage.
|
||||
if
|
||||
NewState == error ->
|
||||
{stop, normal, NewStateData};
|
||||
true ->
|
||||
{next_state, NewState, NewStateData}
|
||||
end;
|
||||
|
||||
handle_info({ircstring, [$: | String]}, _StateName, StateData) ->
|
||||
Words = string:tokens(String, " "),
|
||||
NewStateData =
|
||||
@ -495,6 +531,15 @@ handle_info({ircstring, [$: | String]}, _StateName, StateData) ->
|
||||
[_, "319", _, Nick | _ ] ->
|
||||
process_whois319(StateData, String, Nick),
|
||||
StateData;
|
||||
[_, "433" | _] ->
|
||||
process_nick_in_use(StateData, String);
|
||||
% CODEPAGE isn't standard, so don't complain if it's not there.
|
||||
[_, "421", _, "CODEPAGE" | _] ->
|
||||
StateData;
|
||||
[_, [$4, _, _] | _] ->
|
||||
process_num_error(StateData, String);
|
||||
[_, [$5, _, _] | _] ->
|
||||
process_num_error(StateData, String);
|
||||
[From, "PRIVMSG", [$# | Chan] | _] ->
|
||||
process_chanprivmsg(StateData, Chan, From, String),
|
||||
StateData;
|
||||
@ -581,7 +626,16 @@ handle_info({tcp_error, _Socket, _Reason}, StateName, StateData) ->
|
||||
%% Purpose: Shutdown the fsm
|
||||
%% Returns: any
|
||||
%%----------------------------------------------------------------------
|
||||
terminate(_Reason, _StateName, StateData) ->
|
||||
terminate(_Reason, _StateName, FullStateData) ->
|
||||
% Extract error message if there was one.
|
||||
{Error, StateData} = case FullStateData of
|
||||
{error, SError, SStateData} ->
|
||||
{SError, SStateData};
|
||||
_ ->
|
||||
{{xmlelement, "error", [{"code", "502"}],
|
||||
[{xmlcdata, "Server Connect Failed"}]},
|
||||
FullStateData}
|
||||
end,
|
||||
mod_irc:closed_connection(StateData#state.host,
|
||||
StateData#state.user,
|
||||
StateData#state.server),
|
||||
@ -594,8 +648,7 @@ terminate(_Reason, _StateName, StateData) ->
|
||||
StateData#state.host, StateData#state.nick),
|
||||
StateData#state.user,
|
||||
{xmlelement, "presence", [{"type", "error"}],
|
||||
[{xmlelement, "error", [{"code", "502"}],
|
||||
[{xmlcdata, "Server Connect Failed"}]}]})
|
||||
[Error]})
|
||||
end, dict:fetch_keys(StateData#state.channels)),
|
||||
case StateData#state.socket of
|
||||
undefined ->
|
||||
@ -738,7 +791,6 @@ process_channel_topic_who(StateData, Chan, String) ->
|
||||
String
|
||||
end,
|
||||
Msg2 = filter_message(Msg1),
|
||||
|
||||
ejabberd_router:route(
|
||||
jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
|
||||
StateData#state.host, ""),
|
||||
@ -747,6 +799,45 @@ process_channel_topic_who(StateData, Chan, String) ->
|
||||
[{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}).
|
||||
|
||||
|
||||
error_nick_in_use(_StateData, String) ->
|
||||
{ok, Msg, _} = regexp:sub(String, ".*433 +[^ ]* +", ""),
|
||||
Msg1 = filter_message(Msg),
|
||||
{xmlelement, "error", [{"code", "409"}, {"type", "cancel"}],
|
||||
[{xmlelement, "conflict", [{"xmlns", ?NS_STANZAS}], []},
|
||||
{xmlelement, "text", [{"xmlns", ?NS_STANZAS}],
|
||||
[{xmlcdata, Msg1}]}]}.
|
||||
|
||||
process_nick_in_use(StateData, String) ->
|
||||
% We can't use the jlib macro because we don't know the language of the
|
||||
% message.
|
||||
Error = error_nick_in_use(StateData, String),
|
||||
case StateData#state.nickchannel of
|
||||
undefined ->
|
||||
% Shouldn't happen with a well behaved server
|
||||
StateData;
|
||||
Chan ->
|
||||
ejabberd_router:route(
|
||||
jlib:make_jid(
|
||||
lists:concat([Chan, "%", StateData#state.server]),
|
||||
StateData#state.host, StateData#state.nick),
|
||||
StateData#state.user,
|
||||
{xmlelement, "presence", [{"type", "error"}], [Error]}),
|
||||
StateData#state{nickchannel = undefined}
|
||||
end.
|
||||
|
||||
process_num_error(StateData, String) ->
|
||||
Error = error_unknown_num(StateData, String, "continue"),
|
||||
lists:foreach(
|
||||
fun(Chan) ->
|
||||
ejabberd_router:route(
|
||||
jlib:make_jid(
|
||||
lists:concat([Chan, "%", StateData#state.server]),
|
||||
StateData#state.host, StateData#state.nick),
|
||||
StateData#state.user,
|
||||
{xmlelement, "message", [{"type", "error"}],
|
||||
[Error]})
|
||||
end, dict:fetch_keys(StateData#state.channels)),
|
||||
StateData.
|
||||
|
||||
process_endofwhois(StateData, _String, Nick) ->
|
||||
ejabberd_router:route(
|
||||
@ -876,7 +967,7 @@ process_version(StateData, _Nick, From) ->
|
||||
"\001\r\n",
|
||||
[FromUser, ?VERSION]) ++
|
||||
io_lib:format("NOTICE ~s :\001VERSION "
|
||||
?EJABBERD_URI
|
||||
"http://ejabberd.jabberstudio.org/"
|
||||
"\001\r\n",
|
||||
[FromUser])).
|
||||
|
||||
@ -1069,7 +1160,14 @@ process_nick(StateData, From, NewNick) ->
|
||||
Ps
|
||||
end
|
||||
end, StateData#state.channels),
|
||||
StateData#state{channels = NewChans}.
|
||||
if
|
||||
FromUser == StateData#state.nick ->
|
||||
StateData#state{nick = Nick,
|
||||
nickchannel = undefined,
|
||||
channels = NewChans};
|
||||
true ->
|
||||
StateData#state{channels = NewChans}
|
||||
end.
|
||||
|
||||
|
||||
process_error(StateData, String) ->
|
||||
@ -1085,6 +1183,13 @@ process_error(StateData, String) ->
|
||||
[{xmlcdata, String}]}]})
|
||||
end, dict:fetch_keys(StateData#state.channels)).
|
||||
|
||||
error_unknown_num(_StateData, String, Type) ->
|
||||
{ok, Msg, _} = regexp:sub(String, ".*[45][0-9][0-9] +[^ ]* +", ""),
|
||||
Msg1 = filter_message(Msg),
|
||||
{xmlelement, "error", [{"code", "500"}, {"type", Type}],
|
||||
[{xmlelement, "undefined-condition", [{"xmlns", ?NS_STANZAS}], []},
|
||||
{xmlelement, "text", [{"xmlns", ?NS_STANZAS}],
|
||||
[{xmlcdata, Msg1}]}]}.
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user