Rewrite mod_register to use XML generator

This commit is contained in:
Evgeniy Khramtsov 2016-07-27 18:06:54 +03:00
parent 8275e95a16
commit 96e912b09a
1 changed files with 201 additions and 328 deletions

View File

@ -35,14 +35,13 @@
-export([start/2, stop/1, stream_feature_register/2,
unauthenticated_iq_register/4, try_register/5,
process_iq/3, send_registration_notifications/3,
process_iq/1, send_registration_notifications/3,
transform_options/1, transform_module_options/1,
mod_opt_type/1, opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
@ -79,329 +78,223 @@ stream_feature_register(Acc, Host) ->
AF = gen_mod:get_module_opt(Host, ?MODULE, access_from,
fun(A) -> A end,
all),
case (AF /= none) and lists:keymember(<<"mechanisms">>, 2, Acc) of
case (AF /= none) and lists:keymember(sasl_mechanisms, 1, Acc) of
true ->
[#xmlel{name = <<"register">>,
attrs = [{<<"xmlns">>, ?NS_FEATURE_IQREGISTER}],
children = []}
| Acc];
[#feature_register{}|Acc];
false ->
Acc
end.
unauthenticated_iq_register(_Acc, Server,
#iq{xmlns = ?NS_REGISTER} = IQ, IP) ->
#iq{sub_els = [#register{}]} = IQ, IP) ->
Address = case IP of
{A, _Port} -> A;
_ -> undefined
end,
ResIQ = process_iq(jid:make(<<"">>, <<"">>,
<<"">>),
jid:make(<<"">>, Server, <<"">>), IQ, Address),
Res1 = jlib:replace_from_to(jid:make(<<"">>,
Server, <<"">>),
jid:make(<<"">>, <<"">>, <<"">>),
jlib:iq_to_xml(ResIQ)),
jlib:remove_attr(<<"to">>, Res1);
ResIQ = process_iq(xmpp:set_from_to(IQ, jid:make(<<>>), jid:make(Server)),
Address),
xmpp:set_from_to(ResIQ, jid:make(Server), undefined);
unauthenticated_iq_register(Acc, _Server, _IQ, _IP) ->
Acc.
process_iq(From, To, IQ) ->
process_iq(From, To, IQ, jid:tolower(From)).
process_iq(#iq{from = From} = IQ) ->
process_iq(IQ, jid:tolower(From)).
process_iq(From, To,
#iq{type = Type, lang = Lang, sub_el = SubEl, id = ID} =
IQ,
Source) ->
IsCaptchaEnabled = case
gen_mod:get_module_opt(To#jid.lserver, ?MODULE,
captcha_protected,
fun(B) when is_boolean(B) -> B end,
false)
of
true -> true;
_ -> false
end,
case Type of
set ->
UTag = fxml:get_subtag(SubEl, <<"username">>),
PTag = fxml:get_subtag(SubEl, <<"password">>),
RTag = fxml:get_subtag(SubEl, <<"remove">>),
Server = To#jid.lserver,
Access = gen_mod:get_module_opt(Server, ?MODULE, access,
fun(A) -> A end,
all),
AllowRemove = allow ==
acl:match_rule(Server, Access, From),
if (UTag /= false) and (RTag /= false) and
AllowRemove ->
User = fxml:get_tag_cdata(UTag),
case From of
#jid{user = User, lserver = Server} ->
ejabberd_auth:remove_user(User, Server),
IQ#iq{type = result, sub_el = []};
_ ->
if PTag /= false ->
Password = fxml:get_tag_cdata(PTag),
case ejabberd_auth:remove_user(User, Server,
Password)
of
ok -> IQ#iq{type = result, sub_el = []};
%% TODO FIXME: This piece of
%% code does not work since
%% the code have been changed
%% to allow several auth
%% modules. lists:foreach can
%% only return ok:
not_allowed ->
Txt = <<"Removal is not allowed">>,
IQ#iq{type = error,
sub_el = [SubEl,
?ERRT_NOT_ALLOWED(Lang, Txt)]};
not_exists ->
Txt = <<"No such user">>,
IQ#iq{type = error,
sub_el =
[SubEl,
?ERRT_ITEM_NOT_FOUND(Lang, Txt)]};
Err ->
?ERROR_MSG("failed to remove user ~s@~s: ~p",
[User, Server, Err]),
IQ#iq{type = error,
sub_el =
[SubEl,
?ERR_INTERNAL_SERVER_ERROR]}
end;
true ->
Txt = <<"No password in this query">>,
IQ#iq{type = error,
sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}
end
end;
(UTag == false) and (RTag /= false) and AllowRemove ->
case From of
#jid{user = User, lserver = Server,
resource = Resource} ->
ResIQ = #iq{type = result, xmlns = ?NS_REGISTER,
id = ID, sub_el = []},
ejabberd_router:route(jid:make(User, Server,
Resource),
jid:make(User, Server,
Resource),
jlib:iq_to_xml(ResIQ)),
ejabberd_auth:remove_user(User, Server),
ignore;
_ ->
Txt = <<"The query is only allowed from local users">>,
IQ#iq{type = error,
sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}
end;
(UTag /= false) and (PTag /= false) ->
User = fxml:get_tag_cdata(UTag),
Password = fxml:get_tag_cdata(PTag),
try_register_or_set_password(User, Server, Password,
From, IQ, SubEl, Source, Lang,
not IsCaptchaEnabled);
IsCaptchaEnabled ->
case ejabberd_captcha:process_reply(SubEl) of
ok ->
case process_xdata_submit(SubEl) of
{ok, User, Password} ->
try_register_or_set_password(User, Server,
Password, From, IQ,
SubEl, Source, Lang,
true);
_ ->
Txt = <<"Incorrect data form">>,
IQ#iq{type = error,
sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}
end;
{error, malformed} ->
Txt = <<"Incorrect CAPTCHA submit">>,
IQ#iq{type = error,
sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]};
_ ->
ErrText = <<"The CAPTCHA verification has failed">>,
IQ#iq{type = error,
sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, ErrText)]}
end;
true ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
end;
get ->
{IsRegistered, UsernameSubels, QuerySubels} = case From
of
#jid{user = User,
lserver =
Server} ->
case
ejabberd_auth:is_user_exists(User,
Server)
of
true ->
{true,
[{xmlcdata,
User}],
[#xmlel{name
=
<<"registered">>,
attrs
=
[],
children
=
[]}]};
false ->
{false,
[{xmlcdata,
User}],
[]}
end;
_ -> {false, [], []}
end,
if IsCaptchaEnabled and not IsRegistered ->
TopInstrEl = #xmlel{name = <<"instructions">>,
attrs = [],
children =
[{xmlcdata,
translate:translate(Lang,
<<"You need a client that supports x:data "
"and CAPTCHA to register">>)}]},
InstrEl = #xmlel{name = <<"instructions">>, attrs = [],
children =
[{xmlcdata,
translate:translate(Lang,
<<"Choose a username and password to register "
"with this server">>)}]},
UField = #xmlel{name = <<"field">>,
attrs =
[{<<"type">>, <<"text-single">>},
{<<"label">>,
translate:translate(Lang, <<"User">>)},
{<<"var">>, <<"username">>}],
children =
[#xmlel{name = <<"required">>, attrs = [],
children = []}]},
PField = #xmlel{name = <<"field">>,
attrs =
[{<<"type">>, <<"text-private">>},
{<<"label">>,
translate:translate(Lang,
<<"Password">>)},
{<<"var">>, <<"password">>}],
children =
[#xmlel{name = <<"required">>, attrs = [],
children = []}]},
case ejabberd_captcha:create_captcha_x(ID, To, Lang,
Source,
[InstrEl, UField,
PField])
of
{ok, CaptchaEls} ->
IQ#iq{type = result,
sub_el =
[#xmlel{name = <<"query">>,
attrs =
[{<<"xmlns">>,
?NS_REGISTER}],
children =
[TopInstrEl | CaptchaEls]}]};
{error, limit} ->
ErrText = <<"Too many CAPTCHA requests">>,
IQ#iq{type = error,
sub_el =
[SubEl,
?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText)]};
_Err ->
ErrText = <<"Unable to generate a CAPTCHA">>,
IQ#iq{type = error,
sub_el =
[SubEl,
?ERRT_INTERNAL_SERVER_ERROR(Lang, ErrText)]}
end;
true ->
IQ#iq{type = result,
sub_el =
[#xmlel{name = <<"query">>,
attrs =
[{<<"xmlns">>,
?NS_REGISTER}],
children =
[#xmlel{name = <<"instructions">>,
attrs = [],
children =
[{xmlcdata,
translate:translate(Lang,
<<"Choose a username and password to register "
"with this server">>)}]},
#xmlel{name = <<"username">>,
attrs = [],
children = UsernameSubels},
#xmlel{name = <<"password">>,
attrs = [], children = []}
| QuerySubels]}]}
end
process_iq(#iq{from = From, to = To} = IQ, Source) ->
IsCaptchaEnabled =
case gen_mod:get_module_opt(To#jid.lserver, ?MODULE,
captcha_protected,
fun(B) when is_boolean(B) -> B end,
false) of
true -> true;
false -> false
end,
Server = To#jid.lserver,
Access = gen_mod:get_module_opt(Server, ?MODULE, access,
fun(A) -> A end, all),
AllowRemove = allow == acl:match_rule(Server, Access, From),
process_iq(IQ, Source, IsCaptchaEnabled, AllowRemove).
process_iq(#iq{type = set, lang = Lang,
sub_els = [#register{remove = true}]} = IQ,
_Source, _IsCaptchaEnabled, _AllowRemove = false) ->
Txt = <<"Denied by ACL">>,
xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang));
process_iq(#iq{type = set, lang = Lang, to = To, from = From,
sub_els = [#register{remove = true,
username = User,
password = Password}]} = IQ,
_Source, _IsCaptchaEnabled, _AllowRemove = true) ->
Server = To#jid.lserver,
if is_binary(User) ->
case From of
#jid{user = User, lserver = Server} ->
ejabberd_auth:remove_user(User, Server),
xmpp:make_iq_result(IQ);
_ ->
if is_binary(Password) ->
ejabberd_auth:remove_user(User, Server, Password),
xmpp:make_iq_result(IQ);
true ->
Txt = <<"No 'password' found in this query">>,
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang))
end
end;
true ->
case From of
#jid{user = User, lserver = Server, resource = Resource} ->
ResIQ = xmpp:make_iq_result(IQ),
ejabberd_router:route(jid:make(User, Server, Resource),
jid:make(User, Server, Resource),
ResIQ),
ejabberd_auth:remove_user(User, Server),
ignore;
_ ->
Txt = <<"The query is only allowed from local users">>,
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang))
end
end;
process_iq(#iq{type = set, to = To,
sub_els = [#register{username = User,
password = Password}]} = IQ,
Source, IsCaptchaEnabled, _AllowRemove) when is_binary(User),
is_binary(Password) ->
Server = To#jid.lserver,
try_register_or_set_password(
User, Server, Password, IQ, Source, not IsCaptchaEnabled);
process_iq(#iq{type = set, to = To,
lang = Lang, sub_els = [#register{xdata = #xdata{} = X}]} = IQ,
Source, true, _AllowRemove) ->
Server = To#jid.lserver,
case ejabberd_captcha:process_reply(X) of
ok ->
case process_xdata_submit(X) of
{ok, User, Password} ->
try_register_or_set_password(
User, Server, Password, IQ, Source, true);
_ ->
Txt = <<"Incorrect data form">>,
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang))
end;
{error, malformed} ->
Txt = <<"Incorrect CAPTCHA submit">>,
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
_ ->
ErrText = <<"The CAPTCHA verification has failed">>,
xmpp:make_error(IQ, xmpp:err_not_allowed(ErrText, Lang))
end;
process_iq(#iq{type = set} = IQ, _Source, _IsCaptchaEnabled, _AllowRemove) ->
xmpp:make_error(IQ, xmpp:err_bad_request());
process_iq(#iq{type = get, from = From, to = To, id = ID, lang = Lang} = IQ,
Source, IsCaptchaEnabled, _AllowRemove) ->
Server = To#jid.lserver,
{IsRegistered, Username} =
case From of
#jid{user = User, lserver = Server} ->
case ejabberd_auth:is_user_exists(User, Server) of
true ->
{true, User};
false ->
{false, User}
end;
_ ->
{false, <<"">>}
end,
if IsCaptchaEnabled and not IsRegistered ->
TopInstr = translate:translate(
Lang, <<"You need a client that supports x:data "
"and CAPTCHA to register">>),
Instr = translate:translate(
Lang, <<"Choose a username and password to register "
"with this server">>),
UField = #xdata_field{type = 'text-single',
label = translate:translate(Lang, <<"User">>),
var = <<"username">>,
required = true},
PField = #xdata_field{type = 'text-private',
label = translate:translate(Lang, <<"Password">>),
var = <<"password">>,
required = true},
X = #xdata{type = form, instructions = [Instr],
fields = [UField, PField]},
case ejabberd_captcha:create_captcha_x(ID, To, Lang, Source, X) of
{ok, Captcha} ->
xmpp:make_iq_result(
IQ, #register{instructions = TopInstr,
xdata = Captcha});
{error, limit} ->
ErrText = <<"Too many CAPTCHA requests">>,
xmpp:make_error(
IQ, xmpp:err_resource_constraint(ErrText, Lang));
_Err ->
ErrText = <<"Unable to generate a CAPTCHA">>,
xmpp:make_error(
IQ, xmpp:err_internal_server_error(ErrText, Lang))
end;
true ->
Instr = <<"Choose a username and password to register with this server">>,
xmpp:make_iq_result(
IQ,
#register{instructions = translate:translate(Lang, Instr),
username = Username,
password = <<"">>,
registered = IsRegistered})
end.
try_register_or_set_password(User, Server, Password,
From, IQ, SubEl, Source, Lang, CaptchaSucceed) ->
#iq{from = From, lang = Lang} = IQ,
Source, CaptchaSucceed) ->
case From of
#jid{user = User, lserver = Server} ->
try_set_password(User, Server, Password, IQ, SubEl,
Lang);
_ when CaptchaSucceed ->
case check_from(From, Server) of
allow ->
case try_register(User, Server, Password, Source, Lang)
of
ok -> IQ#iq{type = result, sub_el = []};
{error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]}
end;
deny ->
Txt = <<"Denied by ACL">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}
end;
_ ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
#jid{user = User, lserver = Server} ->
try_set_password(User, Server, Password, IQ);
_ when CaptchaSucceed ->
case check_from(From, Server) of
allow ->
case try_register(User, Server, Password, Source, Lang) of
ok ->
xmpp:make_iq_result(IQ);
{error, Error} ->
xmpp:make_error(IQ, Error)
end;
deny ->
Txt = <<"Denied by ACL">>,
xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang))
end;
_ ->
xmpp:make_error(IQ, xmpp:err_not_allowed())
end.
%% @doc Try to change password and return IQ response
try_set_password(User, Server, Password, IQ, SubEl,
Lang) ->
try_set_password(User, Server, Password, #iq{lang = Lang} = IQ) ->
case is_strong_password(Server, Password) of
true ->
case ejabberd_auth:set_password(User, Server, Password)
of
ok -> IQ#iq{type = result, sub_el = []};
case ejabberd_auth:set_password(User, Server, Password) of
ok ->
xmpp:make_iq_result(IQ);
{error, empty_password} ->
Txt = <<"Empty password">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]};
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
{error, not_allowed} ->
Txt = <<"Changing password is not allowed">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
{error, invalid_jid} ->
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_JID_MALFORMED]};
xmpp:make_error(IQ, xmpp:err_jid_malformed());
Err ->
?ERROR_MSG("failed to register user ~s@~s: ~p",
[User, Server, Err]),
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
xmpp:make_error(IQ, xmpp:err_internal_server_error())
end;
error_preparing_password ->
ErrText = <<"The password contains unacceptable characters">>,
IQ#iq{type = error,
sub_el = [SubEl, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)]};
xmpp:make_error(IQ, xmpp:err_not_acceptable(ErrText, Lang));
false ->
ErrText = <<"The password is too weak">>,
IQ#iq{type = error,
sub_el = [SubEl, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)]}
xmpp:make_error(IQ, xmpp:err_not_acceptable(ErrText, Lang))
end.
try_register(User, Server, Password, SourceRaw, Lang) ->
case jid:is_nodename(User) of
false -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Malformed username">>)};
false -> {error, xmpp:err_bad_request(<<"Malformed username">>, Lang)};
_ ->
JID = jid:make(User, Server, <<"">>),
Access = gen_mod:get_module_opt(Server, ?MODULE, access,
@ -411,8 +304,8 @@ try_register(User, Server, Password, SourceRaw, Lang) ->
case {acl:match_rule(Server, Access, JID),
check_ip_access(SourceRaw, IPAccess)}
of
{deny, _} -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
{_, deny} -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
{deny, _} -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
{_, deny} -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
{allow, allow} ->
Source = may_remove_resource(SourceRaw),
case check_timeout(Source) of
@ -432,35 +325,35 @@ try_register(User, Server, Password, SourceRaw, Lang) ->
case Error of
{atomic, exists} ->
Txt = <<"User already exists">>,
{error, ?ERRT_CONFLICT(Lang, Txt)};
{error, xmpp:err_conflict(Txt, Lang)};
{error, invalid_jid} ->
{error, ?ERR_JID_MALFORMED};
{error, xmpp:err_jid_malformed()};
{error, not_allowed} ->
{error, ?ERR_NOT_ALLOWED};
{error, xmpp:err_not_allowed()};
{error, too_many_users} ->
Txt = <<"Too many users registered">>,
{error, ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt)};
{error, xmpp:err_resource_constraint(Txt, Lang)};
{error, _} ->
?ERROR_MSG("failed to register user "
"~s@~s: ~p",
[User, Server, Error]),
{error, ?ERR_INTERNAL_SERVER_ERROR}
{error, xmpp:err_internal_server_error()}
end
end;
error_preparing_password ->
remove_timeout(Source),
ErrText = <<"The password contains unacceptable characters">>,
{error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
{error, xmpp:err_not_acceptable(ErrText, Lang)};
false ->
remove_timeout(Source),
ErrText = <<"The password is too weak">>,
{error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}
{error, xmpp:err_not_acceptable(ErrText, Lang)}
end;
false ->
ErrText =
<<"Users are not allowed to register accounts "
"so quickly">>,
{error, ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText)}
{error, xmpp:err_resource_constraint(ErrText, Lang)}
end
end
end.
@ -479,20 +372,10 @@ send_welcome_message(JID) ->
of
{<<"">>, <<"">>} -> ok;
{Subj, Body} ->
ejabberd_router:route(jid:make(<<"">>, Host,
<<"">>),
JID,
#xmlel{name = <<"message">>,
attrs = [{<<"type">>, <<"normal">>}],
children =
[#xmlel{name = <<"subject">>,
attrs = [],
children =
[{xmlcdata, Subj}]},
#xmlel{name = <<"body">>,
attrs = [],
children =
[{xmlcdata, Body}]}]});
ejabberd_router:route(
jid:make(Host), JID,
#message{subject = xmpp:mk_text(Subj),
body = xmpp:mk_text(Body)});
_ -> ok
end.
@ -516,13 +399,9 @@ send_registration_notifications(Mod, UJID, Source) ->
lists:foreach(
fun(JID) ->
ejabberd_router:route(
jid:make(<<"">>, Host, <<"">>),
JID,
#xmlel{name = <<"message">>,
attrs = [{<<"type">>, <<"chat">>}],
children = [#xmlel{name = <<"body">>,
attrs = [],
children = [{xmlcdata,Body}]}]})
jid:make(Host), JID,
#message{type = chat,
body = xmpp:mk_text(Body)})
end, JIDs)
end.
@ -633,17 +512,11 @@ write_time({{Y, Mo, D}, {H, Mi, S}}) ->
io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
[Y, Mo, D, H, Mi, S]).
process_xdata_submit(El) ->
case fxml:get_subtag(El, <<"x">>) of
false -> error;
Xdata ->
Fields = jlib:parse_xdata_submit(Xdata),
case catch {proplists:get_value(<<"username">>, Fields),
proplists:get_value(<<"password">>, Fields)}
of
{[User | _], [Pass | _]} -> {ok, User, Pass};
_ -> error
end
process_xdata_submit(X) ->
case {xmpp_util:get_xdata_values(<<"username">>, X),
xmpp_util:get_xdata_values(<<"password">>, X)} of
{[User], [Pass]} -> {ok, User, Pass};
_ -> error
end.
is_strong_password(Server, Password) ->