24
1
mirror of https://github.com/processone/ejabberd.git synced 2024-06-16 22:05:29 +02:00

CAPTCHA IBR support (EJAB-1262)(thanks to Evgeniy Khramtsov)

This commit is contained in:
Badlop 2010-11-03 09:55:02 +01:00
parent 81546f3270
commit f310292da4
2 changed files with 204 additions and 33 deletions

View File

@ -52,7 +52,8 @@
terminate/2, code_change/3]).
-export([create_captcha/5, 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_lib("exmpp/include/exmpp.hrl").
@ -213,6 +214,85 @@ create_captcha(SID, From, To, Lang, Args)
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() ++ "-" ++ ejabberd_cluster:node_id(),
B64Image = list_to_binary(jlib:encode_base64(binary_to_list(Image))),
CID = list_to_binary(["sha1+", sha:sha(Image), "@bob.xmpp.org"]),
Data =
#xmlel{
name = 'data',
ns = ?NS_BOB,
attrs = [
#xmlattr{name = 'cid',
value = CID
},
#xmlattr{name = 'max-age',
value = <<"0">>
},
#xmlattr{name = 'type',
value = Type
}
],
children = [#xmlcdata{cdata = B64Image}]},
Captcha =
#xmlel{name = 'x',
ns = ?NS_DATA_FORMS_s,
attrs = [
#xmlattr{name = 'type',
value = <<"form">>
}
],
children = [
?VFIELD(<<"hidden">>, <<"FORM_TYPE">>, #xmlcdata{cdata = ?NS_CAPTCHA_b}) | HeadEls] ++ [
?VFIELD(<<"hidden">>, <<"from">>, #xmlcdata{cdata = exmpp_jid:to_binary(To)}),
?VFIELD(<<"hidden">>, <<"challenge">>, #xmlcdata{cdata = list_to_binary(Id)}),
?VFIELD(<<"hidden">>, <<"sid">>, #xmlcdata{cdata = SID}),
#xmlel{name = 'field',
attrs = [
#xmlattr{name = 'var',
value = <<"ocr">>
},
#xmlattr{name = 'label',
value = ?CAPTCHA_TEXT(Lang)
}
],
children = [
#xmlel{name = 'media',
ns = ?NS_DATA_FORMS_MEDIA_s,
children = [
#xmlel{name = 'uri',
attrs = [
#xmlattr{name = 'type',
value = Type
}
],
children = [
#xmlcdata{cdata = list_to_binary(["cid:", CID])}
]
}
]
}
]
}
] ++ TailEls
},
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE, {remove_id, Id}),
case ets:insert(captcha, #captcha{id=Id, pid=self(), key=Key,
tref=Tref}) of
true ->
{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()
@ -348,16 +428,13 @@ check_captcha(Id, ProvidedKey) ->
end.
process_reply(El) ->
case {exmpp_xml:element_matches(El, captcha),
exmpp_xml:get_element(El, x)} of
{false, _} ->
case exmpp_xml:get_element(El, x) of
undefined ->
{error, malformed};
{_, undefined} ->
{error, malformed};
{true, Xdata} ->
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|_]} ->
case check_captcha(Id, OCR) of
captcha_valid ->
@ -452,7 +529,11 @@ handle_info({remove_id, Id}, State) ->
?DEBUG("captcha ~p timed out", [Id]),
case ets:lookup(captcha, Id) of
[#captcha{args=Args, pid=Pid}] ->
Pid ! {captcha_failed, Args},
if is_pid(Pid) ->
Pid ! {captcha_failed, Args};
true ->
ok
end,
ets:delete(captcha, Id);
_ ->
ok
@ -632,10 +713,18 @@ do_check_captcha(Id, ProvidedKey) ->
ets:delete(captcha, Id),
erlang:cancel_timer(Tref),
if ValidKey == 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;
_ ->

View File

@ -93,8 +93,15 @@ process_iq(From, To, IQ) ->
exmpp_jid:prep_resource_as_list(From)}).
process_iq(From, To,
#iq{type = Type, lang = Lang, payload = SubEl} = IQ_Rec,
#iq{type = Type, lang = Lang, payload = SubEl, id = ID} = IQ_Rec,
Source) ->
IsCaptchaEnabled = case gen_mod:get_module_opt(
exmpp_jid:domain(To), ?MODULE, captcha, false) of
true ->
true;
_ ->
false
end,
case Type of
set ->
UTag = exmpp_xml:get_element(SubEl, 'username'),
@ -159,40 +166,77 @@ process_iq(From, To,
(UTag /= undefined) and (PTag /= undefined) ->
User = exmpp_xml:get_cdata_as_list(UTag),
Password = exmpp_xml:get_cdata_as_list(PTag),
case {exmpp_jid:node_as_list(From), exmpp_jid:prep_domain_as_list(From)} of
{User, Server} ->
try_set_password(User, Server, Password, IQ_Rec, SubEl);
try_register_or_set_password(
User, Server, Password, From,
IQ_Rec, 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_Rec, SubEl, Source, Lang, true);
_ ->
exmpp_iq:error(IQ_Rec, 'bad-request')
end;
{error, malformed} ->
exmpp_iq:error(IQ_Rec, 'bad-request');
_ ->
case check_from(From, Server) of
allow ->
case try_register(User, Server, Password,
Source, Lang) of
ok ->
exmpp_iq:result(IQ_Rec, SubEl);
{error, Error} ->
exmpp_iq:error(IQ_Rec, Error)
end;
deny ->
exmpp_iq:error(IQ_Rec, 'forbidden')
end
end;
%% ErrText = "Captcha test failed",
exmpp_iq:error(IQ_Rec, 'not-allowed')
end;
true ->
exmpp_iq:error(IQ_Rec, 'bad-request')
end;
get ->
{UsernameSubels, QuerySubels} =
{IsRegistered, UsernameSubels, QuerySubels} =
case {exmpp_jid:node_as_list(From), exmpp_jid:prep_domain_as_list(From)} of
{User, Server} when is_list(User) and is_list(Server) ->
case ejabberd_auth:is_user_exists(User,Server) of
true ->
{[#xmlcdata{cdata = list_to_binary(User)}],
{true, [#xmlcdata{cdata = list_to_binary(User)}],
[#xmlel{ns = ?NS_INBAND_REGISTER, name = 'registered'}]};
false ->
{[#xmlcdata{cdata = list_to_binary(User)}], []}
{false, [#xmlcdata{cdata = list_to_binary(User)}], []}
end;
_ ->
{[], []}
{false, [], []}
end,
if IsCaptchaEnabled and not IsRegistered ->
InstrEl =
#xmlel{ns = ?NS_INBAND_REGISTER, name = 'instructions',
children =
[#xmlcdata{cdata =
list_to_binary(
translate:translate(Lang,
"Choose a username and password "
"to register with this server"))}]},
UField = #xmlel{ns = ?NS_DATA_FORMS, name = 'field', attrs =
[?XMLATTR('var', <<"username">>),
?XMLATTR('type', <<"text-single">>),
?XMLATTR('label', translate:translate(Lang, "User"))],
children =
[#xmlel{ns = ?NS_DATA_FORMS, name = 'required'}]},
PField =
#xmlel{ns = ?NS_DATA_FORMS, name = 'field', attrs =
[?XMLATTR('var', <<"password">>),
?XMLATTR('type', <<"text-private">>),
?XMLATTR('label', translate:translate(Lang, "Password"))],
children = [
#xmlel{ns = ?NS_DATA_FORMS, name = 'required'}
]
},
case ejabberd_captcha:create_captcha_x(
ID, To, Lang, [InstrEl, UField, PField]) of
{ok, CaptchaEls} ->
Result = #xmlel{ns = ?NS_INBAND_REGISTER, name = 'query', children = CaptchaEls},
exmpp_iq:result(IQ_Rec, Result);
error ->
%% ErrText = "Unable to generate a captcha",
exmpp_iq:error(IQ_Rec, 'internal-server-error')
end;
true ->
Result = #xmlel{ns = ?NS_INBAND_REGISTER, name = 'query', children =
[#xmlel{ns = ?NS_INBAND_REGISTER, name = 'instructions', children =
[#xmlcdata{cdata = list_to_binary(
@ -203,6 +247,29 @@ process_iq(From, To,
#xmlel{ns = ?NS_INBAND_REGISTER, name = 'password'}
| QuerySubels]},
exmpp_iq:result(IQ_Rec, Result)
end
end.
try_register_or_set_password(User, Server, Password, From, IQ_Rec,
SubEl, Source, Lang, CaptchaSucceed) ->
case {exmpp_jid:node_as_list(From), exmpp_jid:prep_domain_as_list(From)} of
{User, Server} ->
try_set_password(User, Server, Password, IQ_Rec, SubEl);
_ when CaptchaSucceed ->
case check_from(From, Server) of
allow ->
case try_register(User, Server, Password,
Source, Lang) of
ok ->
exmpp_iq:result(IQ_Rec, SubEl);
{error, Error} ->
exmpp_iq:error(IQ_Rec, Error)
end;
deny ->
exmpp_iq:error(IQ_Rec, 'forbidden')
end;
_ ->
exmpp_iq:error(IQ_Rec, 'not-allowed')
end.
%% @doc Try to change password and return IQ response
@ -410,3 +477,18 @@ get_time_string() -> write_time(erlang:localtime()).
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.