summaryrefslogtreecommitdiff
path: root/src/ejabberd_captcha.erl
diff options
context:
space:
mode:
authorBadlop <badlop@process-one.net>2013-03-14 10:33:02 +0100
committerBadlop <badlop@process-one.net>2013-03-14 10:33:02 +0100
commit9deb294328bb3f9eb6bd2c0e7cd500732e9b5830 (patch)
tree7e1066c130250627ee0abab44a135f583a28d07f /src/ejabberd_captcha.erl
parentlist_to_integer/2 only works in OTP R14 and newer (diff)
Accumulated patch to binarize and indent code
Diffstat (limited to 'src/ejabberd_captcha.erl')
-rw-r--r--src/ejabberd_captcha.erl913
1 files changed, 508 insertions, 405 deletions
diff --git a/src/ejabberd_captcha.erl b/src/ejabberd_captcha.erl
index 319bf8e8..6cf23a49 100644
--- a/src/ejabberd_captcha.erl
+++ b/src/ejabberd_captcha.erl
@@ -32,36 +32,44 @@
-export([start_link/0]).
%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, 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,
- create_captcha_x/5, create_captcha_x/6]).
+-export([create_captcha/6, build_captcha_html/2,
+ check_captcha/2, process_reply/1, process/2,
+ is_feature_available/0, create_captcha_x/5,
+ create_captcha_x/6]).
-include("jlib.hrl").
+
-include("ejabberd.hrl").
+
-include("web/ejabberd_http.hrl").
-define(VFIELD(Type, Var, Value),
- {xmlelement, "field", [{"type", Type}, {"var", Var}],
- [{xmlelement, "value", [], [Value]}]}).
+ #xmlel{name = <<"field">>,
+ attrs = [{<<"type">>, Type}, {<<"var">>, Var}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [Value]}]}).
+
+-define(CAPTCHA_TEXT(Lang),
+ translate:translate(Lang,
+ <<"Enter the text you see">>)).
--define(CAPTCHA_TEXT(Lang), translate:translate(Lang, "Enter the text you see")).
--define(CAPTCHA_LIFETIME, 120000). % two minutes
--define(LIMIT_PERIOD, 60*1000*1000). % one minute
+-define(CAPTCHA_LIFETIME, 120000).
--record(state, {limits = treap:empty()}).
--record(captcha, {id, pid, key, tref, args}).
+-define(LIMIT_PERIOD, 60*1000*1000).
--define(T(S),
- case catch mnesia:transaction(fun() -> S end) of
- {atomic, Res} ->
- Res;
- {_, Reason} ->
- ?ERROR_MSG("mnesia transaction failed: ~p", [Reason]),
- {error, Reason}
- end).
+-type error() :: efbig | enodata | limit | malformed_image | timeout.
+
+-record(state, {limits = treap:empty() :: treap:treap()}).
+
+-record(captcha, {id :: binary(),
+ pid :: pid(),
+ key :: binary(),
+ tref :: reference(),
+ args :: any()}).
%%====================================================================
%% API
@@ -71,98 +79,197 @@
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
- gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [],
+ []).
+
+-spec create_captcha(binary(), jid(), jid(),
+ binary(), any(), any()) -> {error, error()} |
+ {ok, binary(), [xmlel()]}.
-create_captcha(SID, From, To, Lang, Limiter, Args)
- when is_list(Lang), is_list(SID),
- is_record(From, jid), is_record(To, jid) ->
+create_captcha(SID, From, To, Lang, Limiter, Args) ->
case create_image(Limiter) of
- {ok, Type, Key, Image} ->
- Id = randoms:get_string(),
- B64Image = jlib:encode_base64(binary_to_list(Image)),
- JID = jlib:jid_to_string(From),
- 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, "captcha", [{"xmlns", ?NS_CAPTCHA}],
- [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
- [?VFIELD("hidden", "FORM_TYPE", {xmlcdata, ?NS_CAPTCHA}),
- ?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, "required", [], []},
- {xmlelement, "media", [{"xmlns", ?NS_MEDIA}],
- [{xmlelement, "uri", [{"type", Type}],
- [{xmlcdata, "cid:" ++ CID}]}]}]}]}]},
- BodyString1 = translate:translate(Lang, "Your messages to ~s are being blocked. To unblock them, visit ~s"),
- BodyString = io_lib:format(BodyString1, [JID, get_url(Id)]),
- Body = {xmlelement, "body", [],
- [{xmlcdata, BodyString}]},
- OOB = {xmlelement, "x", [{"xmlns", ?NS_OOB}],
- [{xmlelement, "url", [], [{xmlcdata, get_url(Id)}]}]},
- Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE, {remove_id, Id}),
- case ?T(mnesia:write(#captcha{id=Id, pid=self(), key=Key,
- tref=Tref, args=Args})) of
- ok ->
- {ok, Id, [Body, OOB, Captcha, Data]};
- Err ->
- {error, Err}
- end;
- Err ->
- Err
+ {ok, Type, Key, Image} ->
+ Id = <<(randoms:get_string())/binary>>,
+ B64Image = jlib:encode_base64((Image)),
+ JID = jlib:jid_to_string(From),
+ CID = <<"sha1+", (sha:sha(Image))/binary,
+ "@bob.xmpp.org">>,
+ Data = #xmlel{name = <<"data">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_BOB}, {<<"cid">>, CID},
+ {<<"max-age">>, <<"0">>}, {<<"type">>, Type}],
+ children = [{xmlcdata, B64Image}]},
+ Captcha = #xmlel{name = <<"captcha">>,
+ attrs = [{<<"xmlns">>, ?NS_CAPTCHA}],
+ children =
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_XDATA},
+ {<<"type">>, <<"form">>}],
+ children =
+ [?VFIELD(<<"hidden">>,
+ <<"FORM_TYPE">>,
+ {xmlcdata, ?NS_CAPTCHA}),
+ ?VFIELD(<<"hidden">>, <<"from">>,
+ {xmlcdata,
+ jlib:jid_to_string(To)}),
+ ?VFIELD(<<"hidden">>,
+ <<"challenge">>,
+ {xmlcdata, Id}),
+ ?VFIELD(<<"hidden">>, <<"sid">>,
+ {xmlcdata, SID}),
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"ocr">>},
+ {<<"label">>,
+ ?CAPTCHA_TEXT(Lang)}],
+ children =
+ [#xmlel{name =
+ <<"required">>,
+ attrs = [],
+ children = []},
+ #xmlel{name =
+ <<"media">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_MEDIA}],
+ children =
+ [#xmlel{name
+ =
+ <<"uri">>,
+ attrs
+ =
+ [{<<"type">>,
+ Type}],
+ children
+ =
+ [{xmlcdata,
+ <<"cid:",
+ CID/binary>>}]}]}]}]}]},
+ BodyString1 = translate:translate(Lang,
+ <<"Your messages to ~s are being blocked. "
+ "To unblock them, visit ~s">>),
+ BodyString = iolist_to_binary(io_lib:format(BodyString1,
+ [JID, get_url(Id)])),
+ Body = #xmlel{name = <<"body">>, attrs = [],
+ children = [{xmlcdata, BodyString}]},
+ OOB = #xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_OOB}],
+ children =
+ [#xmlel{name = <<"url">>, attrs = [],
+ children = [{xmlcdata, get_url(Id)}]}]},
+ Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE,
+ {remove_id, Id}),
+ ets:insert(captcha,
+ #captcha{id = Id, pid = self(), key = Key, tref = Tref,
+ args = Args}),
+ {ok, Id, [Body, OOB, Captcha, Data]};
+ Err -> Err
end.
+-spec create_captcha_x(binary(), jid(), binary(),
+ any(), [xmlel()]) -> {ok, [xmlel()]} |
+ {error, error()}.
+
create_captcha_x(SID, To, Lang, Limiter, HeadEls) ->
create_captcha_x(SID, To, Lang, Limiter, HeadEls, []).
-create_captcha_x(SID, To, Lang, Limiter, HeadEls, TailEls) ->
+-spec create_captcha_x(binary(), jid(), binary(),
+ any(), [xmlel()], [xmlel()]) -> {ok, [xmlel()]} |
+ {error, error()}.
+
+create_captcha_x(SID, To, Lang, Limiter, HeadEls,
+ TailEls) ->
case create_image(Limiter) 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}]},
- HelpTxt = translate:translate(
- Lang,
- "If you don't see the CAPTCHA image here, "
- "visit the web page."),
- Imageurl = get_url(Id ++ "/image"),
- Captcha =
- {xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
- [?VFIELD("hidden", "FORM_TYPE", {xmlcdata, ?NS_CAPTCHA}) | HeadEls] ++
- [{xmlelement, "field", [{"type", "fixed"}],
- [{xmlelement, "value", [], [{xmlcdata, HelpTxt}]}]},
- {xmlelement, "field", [{"type", "hidden"}, {"var", "captchahidden"}],
- [{xmlelement, "value", [], [{xmlcdata, "workaround-for-psi"}]}]},
- {xmlelement, "field",
- [{"type", "text-single"},
- {"label", translate:translate(Lang, "CAPTCHA web page")},
- {"var", "url"}],
- [{xmlelement, "value", [], [{xmlcdata, Imageurl}]}]},
- ?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, "required", [], []},
- {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, Err}
- end;
- Err ->
- Err
+ {ok, Type, Key, Image} ->
+ Id = <<(randoms:get_string())/binary>>,
+ B64Image = jlib:encode_base64((Image)),
+ CID = <<"sha1+", (sha:sha(Image))/binary,
+ "@bob.xmpp.org">>,
+ Data = #xmlel{name = <<"data">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_BOB}, {<<"cid">>, CID},
+ {<<"max-age">>, <<"0">>}, {<<"type">>, Type}],
+ children = [{xmlcdata, B64Image}]},
+ HelpTxt = translate:translate(Lang,
+ <<"If you don't see the CAPTCHA image here, "
+ "visit the web page.">>),
+ Imageurl = get_url(<<Id/binary, "/image">>),
+ Captcha = #xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_XDATA},
+ {<<"type">>, <<"form">>}],
+ children =
+ [?VFIELD(<<"hidden">>, <<"FORM_TYPE">>,
+ {xmlcdata, ?NS_CAPTCHA})
+ | HeadEls]
+ ++
+ [#xmlel{name = <<"field">>,
+ attrs = [{<<"type">>, <<"fixed">>}],
+ children =
+ [#xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ HelpTxt}]}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"hidden">>},
+ {<<"var">>, <<"captchahidden">>}],
+ children =
+ [#xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ <<"workaround-for-psi">>}]}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"text-single">>},
+ {<<"label">>,
+ translate:translate(Lang,
+ <<"CAPTCHA web page">>)},
+ {<<"var">>, <<"url">>}],
+ children =
+ [#xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ Imageurl}]}]},
+ ?VFIELD(<<"hidden">>, <<"from">>,
+ {xmlcdata, jlib:jid_to_string(To)}),
+ ?VFIELD(<<"hidden">>, <<"challenge">>,
+ {xmlcdata, Id}),
+ ?VFIELD(<<"hidden">>, <<"sid">>,
+ {xmlcdata, SID}),
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"ocr">>},
+ {<<"label">>,
+ ?CAPTCHA_TEXT(Lang)}],
+ children =
+ [#xmlel{name = <<"required">>,
+ attrs = [], children = []},
+ #xmlel{name = <<"media">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_MEDIA}],
+ children =
+ [#xmlel{name =
+ <<"uri">>,
+ attrs =
+ [{<<"type">>,
+ Type}],
+ children =
+ [{xmlcdata,
+ <<"cid:",
+ CID/binary>>}]}]}]}]
+ ++ TailEls},
+ Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE,
+ {remove_id, Id}),
+ ets:insert(captcha,
+ #captcha{id = Id, key = Key, tref = Tref}),
+ {ok, [Captcha, Data]};
+ Err -> Err
end.
%% @spec (Id::string(), Lang::string()) -> {FormEl, {ImgEl, TextEl, IdEl, KeyEl}} | captcha_not_found
@@ -171,206 +278,177 @@ create_captcha_x(SID, To, Lang, Limiter, HeadEls, TailEls) ->
%% TextEl = xmlelement()
%% IdEl = xmlelement()
%% KeyEl = xmlelement()
+-spec build_captcha_html(binary(), binary()) -> captcha_not_found |
+ {xmlel(),
+ {xmlel(), xmlel(),
+ xmlel(), xmlel()}}.
+
build_captcha_html(Id, Lang) ->
- case mnesia:dirty_read(captcha, Id) of
- [#captcha{}] ->
- ImgEl = {xmlelement, "img", [{"src", get_url(Id ++ "/image")}], []},
- TextEl = {xmlcdata, ?CAPTCHA_TEXT(Lang)},
- IdEl = {xmlelement, "input", [{"type", "hidden"},
- {"name", "id"},
- {"value", Id}], []},
- KeyEl = {xmlelement, "input", [{"type", "text"},
- {"name", "key"},
- {"size", "10"}], []},
- FormEl = {xmlelement, "form", [{"action", get_url(Id)},
- {"name", "captcha"},
- {"method", "POST"}],
- [ImgEl,
- {xmlelement, "br", [], []},
- TextEl,
- {xmlelement, "br", [], []},
- IdEl,
- KeyEl,
- {xmlelement, "br", [], []},
- {xmlelement, "input", [{"type", "submit"},
- {"name", "enter"},
- {"value", "OK"}], []}
- ]},
- {FormEl, {ImgEl, TextEl, IdEl, KeyEl}};
- _ ->
- captcha_not_found
+ case lookup_captcha(Id) of
+ {ok, _} ->
+ ImgEl = #xmlel{name = <<"img">>,
+ attrs =
+ [{<<"src">>, get_url(<<Id/binary, "/image">>)}],
+ children = []},
+ TextEl = {xmlcdata, ?CAPTCHA_TEXT(Lang)},
+ IdEl = #xmlel{name = <<"input">>,
+ attrs =
+ [{<<"type">>, <<"hidden">>}, {<<"name">>, <<"id">>},
+ {<<"value">>, Id}],
+ children = []},
+ KeyEl = #xmlel{name = <<"input">>,
+ attrs =
+ [{<<"type">>, <<"text">>}, {<<"name">>, <<"key">>},
+ {<<"size">>, <<"10">>}],
+ children = []},
+ FormEl = #xmlel{name = <<"form">>,
+ attrs =
+ [{<<"action">>, get_url(Id)},
+ {<<"name">>, <<"captcha">>},
+ {<<"method">>, <<"POST">>}],
+ children =
+ [ImgEl,
+ #xmlel{name = <<"br">>, attrs = [],
+ children = []},
+ TextEl,
+ #xmlel{name = <<"br">>, attrs = [],
+ children = []},
+ IdEl, KeyEl,
+ #xmlel{name = <<"br">>, attrs = [],
+ children = []},
+ #xmlel{name = <<"input">>,
+ attrs =
+ [{<<"type">>, <<"submit">>},
+ {<<"name">>, <<"enter">>},
+ {<<"value">>, <<"OK">>}],
+ children = []}]},
+ {FormEl, {ImgEl, TextEl, IdEl, KeyEl}};
+ _ -> captcha_not_found
end.
%% @spec (Id::string(), ProvidedKey::string()) -> captcha_valid | captcha_non_valid | captcha_not_found
-check_captcha(Id, ProvidedKey) ->
- ?T(case mnesia:read(captcha, Id, write) of
- [#captcha{pid=Pid, args=Args, key=StoredKey, tref=Tref}] ->
- mnesia:delete({captcha, Id}),
- erlang:cancel_timer(Tref),
- if StoredKey == ProvidedKey ->
- if is_pid(Pid) ->
- Pid ! {captcha_succeed, Args};
- true ->
- ok
- end,
- captcha_valid;
- true ->
- if is_pid(Pid) ->
- Pid ! {captcha_failed, Args};
- true ->
- ok
- end,
- captcha_non_valid
- end;
- _ ->
- captcha_not_found
- end).
-
-
-process_reply({xmlelement, _, _, _} = El) ->
- case xml:get_subtag(El, "x") of
- false ->
- {error, malformed};
- Xdata ->
- Fields = jlib:parse_xdata_submit(Xdata),
- 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 ->
- if is_pid(Pid) ->
- Pid ! {captcha_succeed, Args};
- true ->
- ok
- end,
- ok;
- true ->
- if is_pid(Pid) ->
- Pid ! {captcha_failed, Args};
- true ->
- ok
- end,
- {error, bad_match}
- end;
- _ ->
- {error, not_found}
- end);
- _ ->
- {error, malformed}
- end
+-spec check_captcha(binary(), binary()) -> captcha_not_found |
+ captcha_valid |
+ captcha_non_valid.
+
+
+-spec process_reply(xmlel()) -> ok | {error, bad_match | not_found | malformed}.
+
+process_reply(#xmlel{} = El) ->
+ case xml:get_subtag(El, <<"x">>) of
+ false -> {error, malformed};
+ Xdata ->
+ Fields = jlib:parse_xdata_submit(Xdata),
+ case catch {proplists:get_value(<<"challenge">>,
+ Fields),
+ proplists:get_value(<<"ocr">>, Fields)}
+ of
+ {[Id | _], [OCR | _]} ->
+ case check_captcha(Id, OCR) of
+ captcha_valid -> ok;
+ captcha_non_valid -> {error, bad_match};
+ captcha_not_found -> {error, not_found}
+ end;
+ _ -> {error, malformed}
+ end
end;
-process_reply(_) ->
- {error, malformed}.
-
+process_reply(_) -> {error, malformed}.
-process(_Handlers, #request{method='GET', lang=Lang, path=[_, Id]}) ->
+process(_Handlers,
+ #request{method = 'GET', lang = Lang,
+ path = [_, Id]}) ->
case build_captcha_html(Id, Lang) of
- {FormEl, _} when is_tuple(FormEl) ->
- Form =
- {xmlelement, "div", [{"align", "center"}],
- [FormEl]},
- ejabberd_web:make_xhtml([Form]);
- captcha_not_found ->
- ejabberd_web:error(not_found)
+ {FormEl, _} when is_tuple(FormEl) ->
+ Form = #xmlel{name = <<"div">>,
+ attrs = [{<<"align">>, <<"center">>}],
+ children = [FormEl]},
+ ejabberd_web:make_xhtml([Form]);
+ captcha_not_found -> ejabberd_web:error(not_found)
end;
-
-process(_Handlers, #request{method='GET', path=[_, Id, "image"], ip = IP}) ->
+process(_Handlers,
+ #request{method = 'GET', path = [_, Id, <<"image">>],
+ ip = IP}) ->
{Addr, _Port} = IP,
- case mnesia:dirty_read(captcha, Id) of
- [#captcha{key=Key}] ->
- case create_image(Addr, Key) of
- {ok, Type, _, Img} ->
- {200,
- [{"Content-Type", Type},
- {"Cache-Control", "no-cache"},
- {"Last-Modified", httpd_util:rfc1123_date()}],
- Img};
- {error, limit} ->
- ejabberd_web:error(not_allowed);
- _ ->
- ejabberd_web:error(not_found)
- end;
- _ ->
- ejabberd_web:error(not_found)
+ case lookup_captcha(Id) of
+ {ok, #captcha{key = Key}} ->
+ case create_image(Addr, Key) of
+ {ok, Type, _, Img} ->
+ {200,
+ [{<<"Content-Type">>, Type},
+ {<<"Cache-Control">>, <<"no-cache">>},
+ {<<"Last-Modified">>, list_to_binary(httpd_util:rfc1123_date())}],
+ Img};
+ {error, limit} -> ejabberd_web:error(not_allowed);
+ _ -> ejabberd_web:error(not_found)
+ end;
+ _ -> ejabberd_web:error(not_found)
end;
-
-process(_Handlers, #request{method='POST', q=Q, lang=Lang, path=[_, Id]}) ->
- ProvidedKey = proplists:get_value("key", Q, none),
+process(_Handlers,
+ #request{method = 'POST', q = Q, lang = Lang,
+ path = [_, Id]}) ->
+ ProvidedKey = proplists:get_value(<<"key">>, Q, none),
case check_captcha(Id, ProvidedKey) of
- captcha_valid ->
- Form =
- {xmlelement, "p", [],
- [{xmlcdata,
- translate:translate(Lang, "The CAPTCHA is valid.")
- }]},
- ejabberd_web:make_xhtml([Form]);
- captcha_non_valid ->
- ejabberd_web:error(not_allowed);
- captcha_not_found ->
- ejabberd_web:error(not_found)
+ captcha_valid ->
+ Form = #xmlel{name = <<"p">>, attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"The CAPTCHA is valid.">>)}]},
+ ejabberd_web:make_xhtml([Form]);
+ captcha_non_valid -> ejabberd_web:error(not_allowed);
+ captcha_not_found -> ejabberd_web:error(not_found)
end;
-
process(_Handlers, _Request) ->
ejabberd_web:error(not_found).
-
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([]) ->
- mnesia:create_table(captcha,
- [{ram_copies, [node()]},
- {attributes, record_info(fields, captcha)}]),
- mnesia:add_table_copy(captcha, node(), ram_copies),
+ mnesia:delete_table(captcha),
+ ets:new(captcha,
+ [named_table, public, {keypos, #captcha.id}]),
check_captcha_setup(),
{ok, #state{}}.
-handle_call({is_limited, Limiter, RateLimit}, _From, State) ->
+handle_call({is_limited, Limiter, RateLimit}, _From,
+ State) ->
NowPriority = now_priority(),
- CleanPriority = NowPriority + ?LIMIT_PERIOD,
+ CleanPriority = NowPriority + (?LIMIT_PERIOD),
Limits = clean_treap(State#state.limits, CleanPriority),
case treap:lookup(Limiter, Limits) of
- {ok, _, Rate} when Rate >= RateLimit ->
- {reply, true, State#state{limits = Limits}};
- {ok, Priority, Rate} ->
- NewLimits = treap:insert(Limiter, Priority, Rate+1, Limits),
- {reply, false, State#state{limits = NewLimits}};
- _ ->
- NewLimits = treap:insert(Limiter, NowPriority, 1, Limits),
- {reply, false, State#state{limits = NewLimits}}
+ {ok, _, Rate} when Rate >= RateLimit ->
+ {reply, true, State#state{limits = Limits}};
+ {ok, Priority, Rate} ->
+ NewLimits = treap:insert(Limiter, Priority, Rate + 1,
+ Limits),
+ {reply, false, State#state{limits = NewLimits}};
+ _ ->
+ NewLimits = treap:insert(Limiter, NowPriority, 1,
+ Limits),
+ {reply, false, State#state{limits = NewLimits}}
end;
handle_call(_Request, _From, State) ->
{reply, bad_request, State}.
-handle_cast(_Msg, State) ->
- {noreply, State}.
+handle_cast(_Msg, State) -> {noreply, State}.
handle_info({remove_id, Id}, State) ->
?DEBUG("captcha ~p timed out", [Id]),
- _ = ?T(case mnesia:read(captcha, Id, write) of
- [#captcha{args=Args, pid=Pid}] ->
- if is_pid(Pid) ->
- Pid ! {captcha_failed, Args};
- true ->
- ok
- end,
- mnesia:delete({captcha, Id});
- _ ->
- ok
- end),
+ case ets:lookup(captcha, Id) of
+ [#captcha{args = Args, pid = Pid}] ->
+ if is_pid(Pid) -> Pid ! {captcha_failed, Args};
+ true -> ok
+ end,
+ ets:delete(captcha, Id);
+ _ -> ok
+ end,
{noreply, State};
+handle_info(_Info, State) -> {noreply, State}.
-handle_info(_Info, State) ->
- {noreply, State}.
-
-terminate(_Reason, _State) ->
- ok.
+terminate(_Reason, _State) -> ok.
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
@@ -382,126 +460,136 @@ code_change(_OldVsn, State, _Extra) ->
%% Image = binary()
%% Reason = atom()
%%--------------------------------------------------------------------
-create_image() ->
- create_image(undefined).
+create_image() -> create_image(undefined).
create_image(Limiter) ->
- %% Six numbers from 1 to 9.
- Key = string:substr(randoms:get_string(), 1, 6),
+ Key = str:substr(randoms:get_string(), 1, 6),
create_image(Limiter, Key).
create_image(Limiter, Key) ->
case is_limited(Limiter) of
- true ->
- {error, limit};
- false ->
- do_create_image(Key)
+ true -> {error, limit};
+ false -> do_create_image(Key)
end.
do_create_image(Key) ->
FileName = get_prog_name(),
Cmd = lists:flatten(io_lib:format("~s ~s", [FileName, Key])),
case cmd(Cmd) of
- {ok, <<16#89, $P, $N, $G, $\r, $\n, 16#1a, $\n, _/binary>> = Img} ->
- {ok, "image/png", Key, Img};
- {ok, <<16#ff, 16#d8, _/binary>> = Img} ->
- {ok, "image/jpeg", Key, Img};
- {ok, <<$G, $I, $F, $8, X, $a, _/binary>> = Img} when X==$7; X==$9 ->
- {ok, "image/gif", Key, Img};
- {error, enodata = Reason} ->
- ?ERROR_MSG("Failed to process output from \"~s\". "
- "Maybe ImageMagick's Convert program is not installed.",
- [Cmd]),
- {error, Reason};
- {error, Reason} ->
- ?ERROR_MSG("Failed to process an output from \"~s\": ~p",
- [Cmd, Reason]),
- {error, Reason};
- _ ->
- Reason = malformed_image,
- ?ERROR_MSG("Failed to process an output from \"~s\": ~p",
- [Cmd, Reason]),
- {error, Reason}
+ {ok,
+ <<137, $P, $N, $G, $\r, $\n, 26, $\n, _/binary>> =
+ Img} ->
+ {ok, <<"image/png">>, Key, Img};
+ {ok, <<255, 216, _/binary>> = Img} ->
+ {ok, <<"image/jpeg">>, Key, Img};
+ {ok, <<$G, $I, $F, $8, X, $a, _/binary>> = Img}
+ when X == $7; X == $9 ->
+ {ok, <<"image/gif">>, Key, Img};
+ {error, enodata = Reason} ->
+ ?ERROR_MSG("Failed to process output from \"~s\". "
+ "Maybe ImageMagick's Convert program "
+ "is not installed.",
+ [Cmd]),
+ {error, Reason};
+ {error, Reason} ->
+ ?ERROR_MSG("Failed to process an output from \"~s\": ~p",
+ [Cmd, Reason]),
+ {error, Reason};
+ _ ->
+ Reason = malformed_image,
+ ?ERROR_MSG("Failed to process an output from \"~s\": ~p",
+ [Cmd, Reason]),
+ {error, Reason}
end.
get_prog_name() ->
- case ejabberd_config:get_local_option(captcha_cmd) of
- FileName when is_list(FileName) ->
- FileName;
- Value when (Value == undefined) or (Value == "") ->
- ?DEBUG("The option captcha_cmd is not configured, but some "
- "module wants to use the CAPTCHA feature.", []),
- false
+ case ejabberd_config:get_local_option(
+ captcha_cmd,
+ fun(FileName) ->
+ F = iolist_to_binary(FileName),
+ if F /= <<"">> -> F end
+ end) of
+ undefined ->
+ ?DEBUG("The option captcha_cmd is not configured, "
+ "but some module wants to use the CAPTCHA "
+ "feature.",
+ []),
+ false;
+ FileName ->
+ FileName
end.
get_url(Str) ->
- CaptchaHost = ejabberd_config:get_local_option(captcha_host),
- case string:tokens(CaptchaHost, ":") of
- [Host] ->
- "http://" ++ Host ++ "/captcha/" ++ Str;
- ["http"++_ = TransferProt, Host] ->
- TransferProt ++ ":" ++ Host ++ "/captcha/" ++ Str;
- [Host, PortString] ->
- TransferProt = atom_to_list(get_transfer_protocol(PortString)),
- TransferProt ++ "://" ++ Host ++ ":" ++ PortString ++ "/captcha/" ++ Str;
- [TransferProt, Host, PortString] ->
- TransferProt ++ ":" ++ Host ++ ":" ++ PortString ++ "/captcha/" ++ Str;
- _ ->
- "http://" ++ ?MYNAME ++ "/captcha/" ++ Str
+ CaptchaHost = ejabberd_config:get_local_option(
+ captcha_host,
+ fun iolist_to_binary/1,
+ <<"">>),
+ case str:tokens(CaptchaHost, <<":">>) of
+ [Host] ->
+ <<"http://", Host/binary, "/captcha/", Str/binary>>;
+ [<<"http", _/binary>> = TransferProt, Host] ->
+ <<TransferProt/binary, ":", Host/binary, "/captcha/",
+ Str/binary>>;
+ [Host, PortString] ->
+ TransferProt =
+ iolist_to_binary(atom_to_list(get_transfer_protocol(PortString))),
+ <<TransferProt/binary, "://", Host/binary, ":",
+ PortString/binary, "/captcha/", Str/binary>>;
+ [TransferProt, Host, PortString] ->
+ <<TransferProt/binary, ":", Host/binary, ":",
+ PortString/binary, "/captcha/", Str/binary>>;
+ _ ->
+ <<"http://", (?MYNAME)/binary, "/captcha/", Str/binary>>
end.
get_transfer_protocol(PortString) ->
- PortNumber = list_to_integer(PortString),
+ PortNumber = jlib:binary_to_integer(PortString),
PortListeners = get_port_listeners(PortNumber),
get_captcha_transfer_protocol(PortListeners).
get_port_listeners(PortNumber) ->
- AllListeners = ejabberd_config:get_local_option(listen),
- lists:filter(
- fun({{Port, _Ip, _Netp}, _Module1, _Opts1}) when Port == PortNumber ->
- true;
- (_) ->
- false
- end,
- AllListeners).
+ AllListeners = ejabberd_config:get_local_option(listen, fun(V) -> V end),
+ lists:filter(fun ({{Port, _Ip, _Netp}, _Module1,
+ _Opts1})
+ when Port == PortNumber ->
+ true;
+ (_) -> false
+ end,
+ AllListeners).
get_captcha_transfer_protocol([]) ->
- throw("The port number mentioned in captcha_host is not "
- "a ejabberd_http listener with 'captcha' option. "
- "Change the port number or specify http:// in that option.");
-get_captcha_transfer_protocol([{{_Port, _Ip, tcp}, ejabberd_http, Opts}
+ throw(<<"The port number mentioned in captcha_host "
+ "is not a ejabberd_http listener with "
+ "'captcha' option. Change the port number "
+ "or specify http:// in that option.">>);
+get_captcha_transfer_protocol([{{_Port, _Ip, tcp},
+ ejabberd_http, Opts}
| Listeners]) ->
case lists:member(captcha, Opts) of
- true ->
- case lists:member(tls, Opts) of
- true ->
- https;
- false ->
- http
- end;
- false ->
- get_captcha_transfer_protocol(Listeners)
+ true ->
+ case lists:member(tls, Opts) of
+ true -> https;
+ false -> http
+ end;
+ false -> get_captcha_transfer_protocol(Listeners)
end;
get_captcha_transfer_protocol([_ | Listeners]) ->
get_captcha_transfer_protocol(Listeners).
-is_limited(undefined) ->
- false;
+is_limited(undefined) -> false;
is_limited(Limiter) ->
- case ejabberd_config:get_local_option(captcha_limit) of
- Int when is_integer(Int), Int > 0 ->
- case catch gen_server:call(?MODULE, {is_limited, Limiter, Int},
- 5000) of
- true ->
- true;
- false ->
- false;
- Err ->
- ?ERROR_MSG("Call failed: ~p", [Err]),
- false
- end;
- _ ->
- false
+ case ejabberd_config:get_local_option(
+ captcha_limit,
+ fun(I) when is_integer(I), I > 0 -> I end) of
+ undefined -> false;
+ Int ->
+ case catch gen_server:call(?MODULE,
+ {is_limited, Limiter, Int}, 5000)
+ of
+ true -> true;
+ false -> false;
+ Err -> ?ERROR_MSG("Call failed: ~p", [Err]), false
+ end
end.
%%--------------------------------------------------------------------
@@ -511,82 +599,97 @@ is_limited(Limiter) ->
%% Description: os:cmd/1 replacement
%%--------------------------------------------------------------------
-define(CMD_TIMEOUT, 5000).
--define(MAX_FILE_SIZE, 64*1024).
+
+-define(MAX_FILE_SIZE, 64 * 1024).
cmd(Cmd) ->
Port = open_port({spawn, Cmd}, [stream, eof, binary]),
- TRef = erlang:start_timer(?CMD_TIMEOUT, self(), timeout),
+ TRef = erlang:start_timer(?CMD_TIMEOUT, self(),
+ timeout),
recv_data(Port, TRef, <<>>).
recv_data(Port, TRef, Buf) ->
receive
- {Port, {data, Bytes}} ->
- NewBuf = <<Buf/binary, Bytes/binary>>,
- if size(NewBuf) > ?MAX_FILE_SIZE ->
- return(Port, TRef, {error, efbig});
- true ->
- recv_data(Port, TRef, NewBuf)
- end;
- {Port, {data, _}} ->
- return(Port, TRef, {error, efbig});
- {Port, eof} when Buf /= <<>> ->
- return(Port, TRef, {ok, Buf});
- {Port, eof} ->
- return(Port, TRef, {error, enodata});
- {timeout, TRef, _} ->
- return(Port, TRef, {error, timeout})
+ {Port, {data, Bytes}} ->
+ NewBuf = <<Buf/binary, Bytes/binary>>,
+ if byte_size(NewBuf) > (?MAX_FILE_SIZE) ->
+ return(Port, TRef, {error, efbig});
+ true -> recv_data(Port, TRef, NewBuf)
+ end;
+ {Port, {data, _}} -> return(Port, TRef, {error, efbig});
+ {Port, eof} when Buf /= <<>> ->
+ return(Port, TRef, {ok, Buf});
+ {Port, eof} -> return(Port, TRef, {error, enodata});
+ {timeout, TRef, _} ->
+ return(Port, TRef, {error, timeout})
end.
return(Port, TRef, Result) ->
case erlang:cancel_timer(TRef) of
- false ->
- receive
- {timeout, TRef, _} ->
- ok
- after 0 ->
- ok
- end;
- _ ->
- ok
+ false ->
+ receive {timeout, TRef, _} -> ok after 0 -> ok end;
+ _ -> ok
end,
catch port_close(Port),
Result.
is_feature_available() ->
case get_prog_name() of
- Prog when is_list(Prog) -> true;
- false -> false
+ Prog when is_binary(Prog) -> true;
+ false -> false
end.
check_captcha_setup() ->
case is_feature_available() of
- true ->
- case create_image() of
- {ok, _, _, _} ->
- ok;
- _Err ->
- ?CRITICAL_MSG("Captcha is enabled in the option captcha_cmd, "
- "but it can't generate images.", []),
- throw({error, captcha_cmd_enabled_but_fails})
- end;
- false ->
- ok
+ true ->
+ case create_image() of
+ {ok, _, _, _} -> ok;
+ _Err ->
+ ?CRITICAL_MSG("Captcha is enabled in the option captcha_cmd, "
+ "but it can't generate images.",
+ []),
+ throw({error, captcha_cmd_enabled_but_fails})
+ end;
+ false -> ok
+ end.
+
+lookup_captcha(Id) ->
+ case ets:lookup(captcha, Id) of
+ [C] -> {ok, C};
+ _ -> {error, enoent}
+ end.
+
+check_captcha(Id, ProvidedKey) ->
+ case ets:lookup(captcha, Id) of
+ [#captcha{pid = Pid, args = Args, key = ValidKey,
+ tref = Tref}] ->
+ ets:delete(captcha, Id),
+ erlang:cancel_timer(Tref),
+ if ValidKey == ProvidedKey ->
+ if is_pid(Pid) -> Pid ! {captcha_succeed, Args};
+ true -> ok
+ end,
+ captcha_valid;
+ true ->
+ if is_pid(Pid) -> Pid ! {captcha_failed, Args};
+ true -> ok
+ end,
+ captcha_non_valid
+ end;
+ _ -> captcha_not_found
end.
clean_treap(Treap, CleanPriority) ->
case treap:is_empty(Treap) of
- true ->
- Treap;
- false ->
- {_Key, Priority, _Value} = treap:get_root(Treap),
- if
- Priority > CleanPriority ->
- clean_treap(treap:delete_root(Treap), CleanPriority);
- true ->
- Treap
- end
+ true -> Treap;
+ false ->
+ {_Key, Priority, _Value} = treap:get_root(Treap),
+ if Priority > CleanPriority ->
+ clean_treap(treap:delete_root(Treap), CleanPriority);
+ true -> Treap
+ end
end.
now_priority() ->
{MSec, Sec, USec} = now(),
- -((MSec*1000000 + Sec)*1000000 + USec).
+ -((MSec * 1000000 + Sec) * 1000000 + USec).