diff --git a/src/mod_register.erl b/src/mod_register.erl index 45cd78fef..3f9c33123 100644 --- a/src/mod_register.erl +++ b/src/mod_register.erl @@ -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) ->