terminate/2, code_change/3]).
-export([create_captcha/6, build_captcha_html/2, check_captcha/2,
- process_reply/1, process/2, is_feature_available/0]).
+ process_reply/1, process/2, is_feature_available/0,
+ create_captcha_x/4, create_captcha_x/5]).
-include("jlib.hrl").
-include("ejabberd.hrl").
error
end.
+create_captcha_x(SID, To, Lang, HeadEls) ->
+ create_captcha_x(SID, To, Lang, HeadEls, []).
+
+create_captcha_x(SID, To, Lang, HeadEls, TailEls) ->
+ case create_image() of
+ {ok, Type, Key, Image} ->
+ Id = randoms:get_string(),
+ B64Image = jlib:encode_base64(binary_to_list(Image)),
+ CID = "sha1+" ++ sha:sha(Image) ++ "@bob.xmpp.org",
+ Data = {xmlelement, "data",
+ [{"xmlns", ?NS_BOB}, {"cid", CID},
+ {"max-age", "0"}, {"type", Type}],
+ [{xmlcdata, B64Image}]},
+ Captcha =
+ {xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
+ [?VFIELD("hidden", "FORM_TYPE", {xmlcdata, ?NS_CAPTCHA}) | HeadEls] ++
+ [?VFIELD("hidden", "from", {xmlcdata, jlib:jid_to_string(To)}),
+ ?VFIELD("hidden", "challenge", {xmlcdata, Id}),
+ ?VFIELD("hidden", "sid", {xmlcdata, SID}),
+ {xmlelement, "field", [{"var", "ocr"}, {"label", ?CAPTCHA_TEXT(Lang)}],
+ [{xmlelement, "media", [{"xmlns", ?NS_MEDIA}],
+ [{xmlelement, "uri", [{"type", Type}],
+ [{xmlcdata, "cid:" ++ CID}]}]}]}] ++ TailEls},
+ Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE, {remove_id, Id}),
+ case ?T(mnesia:write(#captcha{id=Id, key=Key, tref=Tref})) of
+ ok ->
+ {ok, [Captcha, Data]};
+ _Err ->
+ error
+ end;
+ _ ->
+ error
+ end.
+
%% @spec (Id::string(), Lang::string()) -> {FormEl, {ImgEl, TextEl, IdEl, KeyEl}} | captcha_not_found
%% where FormEl = xmlelement()
%% ImgEl = xmlelement()
mnesia:delete({captcha, Id}),
erlang:cancel_timer(Tref),
if StoredKey == ProvidedKey ->
- Pid ! {captcha_succeed, Args},
+ if is_pid(Pid) ->
+ Pid ! {captcha_succeed, Args};
+ true ->
+ ok
+ end,
captcha_valid;
true ->
- Pid ! {captcha_failed, Args},
+ if is_pid(Pid) ->
+ Pid ! {captcha_failed, Args};
+ true ->
+ ok
+ end,
captcha_non_valid
end;
_ ->
end).
-process_reply({xmlelement, "captcha", _, _} = El) ->
+process_reply({xmlelement, _, _, _} = El) ->
case xml:get_subtag(El, "x") of
false ->
{error, malformed};
Xdata ->
Fields = jlib:parse_xdata_submit(Xdata),
- case {proplists:get_value("challenge", Fields),
- proplists:get_value("ocr", Fields)} of
+ case catch {proplists:get_value("challenge", Fields),
+ proplists:get_value("ocr", Fields)} of
{[Id|_], [OCR|_]} ->
?T(case mnesia:read(captcha, Id, write) of
[#captcha{pid=Pid, args=Args, key=Key, tref=Tref}] ->
mnesia:delete({captcha, Id}),
erlang:cancel_timer(Tref),
if OCR == Key ->
- Pid ! {captcha_succeed, Args},
+ if is_pid(Pid) ->
+ Pid ! {captcha_succeed, Args};
+ true ->
+ ok
+ end,
ok;
true ->
- Pid ! {captcha_failed, Args},
+ if is_pid(Pid) ->
+ Pid ! {captcha_failed, Args};
+ true ->
+ ok
+ end,
{error, bad_match}
end;
_ ->
?DEBUG("captcha ~p timed out", [Id]),
_ = ?T(case mnesia:read(captcha, Id, write) of
[#captcha{args=Args, pid=Pid}] ->
- Pid ! {captcha_failed, Args},
+ if is_pid(Pid) ->
+ Pid ! {captcha_failed, Args};
+ true ->
+ ok
+ end,
mnesia:delete({captcha, Id});
_ ->
ok
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, false) of
+ true ->
+ true;
+ _ ->
+ false
+ end,
case Type of
set ->
UTag = xml:get_subtag(SubEl, "username"),
(UTag /= false) and (PTag /= false) ->
User = xml:get_tag_cdata(UTag),
Password = xml:get_tag_cdata(PTag),
- case From of
- #jid{user = User, lserver = Server} ->
- try_set_password(User, Server, Password, IQ, SubEl);
- _ ->
- case check_from(From, Server) of
- allow ->
- case try_register(User, Server, Password,
- Source, Lang) of
- ok ->
- IQ#iq{type = result,
- sub_el = [SubEl]};
- {error, Error} ->
- IQ#iq{type = error,
- sub_el = [SubEl, Error]}
- end;
- deny ->
+ 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);
+ _ ->
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_FORBIDDEN]}
- end
+ sub_el = [SubEl, ?ERR_BAD_REQUEST]}
+ end;
+ {error, malformed} ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_BAD_REQUEST]};
+ _ ->
+ ErrText = "Captcha test 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 ->
- {UsernameSubels, QuerySubels} =
+ {IsRegistered, UsernameSubels, QuerySubels} =
case From of
#jid{user = User, lserver = Server} ->
case ejabberd_auth:is_user_exists(User,Server) of
true ->
- {[{xmlcdata, User}], [{xmlelement, "registered", [], []}]};
+ {true, [{xmlcdata, User}],
+ [{xmlelement, "registered", [], []}]};
false ->
- {[{xmlcdata, User}], []}
+ {false, [{xmlcdata, User}], []}
end;
_ ->
- {[], []}
+ {false, [], []}
end,
- IQ#iq{type = result,
- sub_el = [{xmlelement,
- "query",
- [{"xmlns", "jabber:iq:register"}],
- [{xmlelement, "instructions", [],
+ if IsCaptchaEnabled and not IsRegistered ->
+ InstrEl = {xmlelement, "instructions", [],
[{xmlcdata,
translate:translate(
Lang,
"Choose a username and password "
"to register with this server")}]},
- {xmlelement, "username", [], UsernameSubels},
- {xmlelement, "password", [], []}
- | QuerySubels]}]}
+ UField = {xmlelement, "field",
+ [{"type", "text-single"},
+ {"label", translate:translate(Lang, "User")},
+ {"var", "username"}],
+ [{xmlelement, "required", [], []}]},
+ PField = {xmlelement, "field",
+ [{"type", "text-private"},
+ {"label", translate:translate(Lang, "Password")},
+ {"var", "password"}],
+ [{xmlelement, "required", [], []}]},
+ case ejabberd_captcha:create_captcha_x(
+ ID, To, Lang, [InstrEl, UField, PField]) of
+ {ok, CaptchaEls} ->
+ IQ#iq{type = result,
+ sub_el = [{xmlelement, "query",
+ [{"xmlns", "jabber:iq:register"}],
+ CaptchaEls}]};
+ error ->
+ 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 = [{xmlelement,
+ "query",
+ [{"xmlns", "jabber:iq:register"}],
+ [{xmlelement, "instructions", [],
+ [{xmlcdata,
+ translate:translate(
+ Lang,
+ "Choose a username and password "
+ "to register with this server")}]},
+ {xmlelement, "username", [], UsernameSubels},
+ {xmlelement, "password", [], []}
+ | QuerySubels]}]}
+ end
+ end.
+
+try_register_or_set_password(User, Server, Password, From, IQ,
+ SubEl, Source, Lang, CaptchaSucceed) ->
+ case From of
+ #jid{user = User, lserver = Server} ->
+ try_set_password(User, Server, Password, IQ, SubEl);
+ _ 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 = [SubEl]};
+ {error, Error} ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, Error]}
+ end;
+ deny ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_FORBIDDEN]}
+ end;
+ _ ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
end.
%% @doc Try to change password and return IQ response
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 xml: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
+ end.