From 9deb294328bb3f9eb6bd2c0e7cd500732e9b5830 Mon Sep 17 00:00:00 2001 From: Badlop Date: Thu, 14 Mar 2013 10:33:02 +0100 Subject: Accumulated patch to binarize and indent code --- src/ejabberd_captcha.erl | 913 ++++++++++++++++++++++++++--------------------- 1 file changed, 508 insertions(+), 405 deletions(-) (limited to 'src/ejabberd_captcha.erl') 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(<>), + 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(<>)}], + 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] -> + <>; + [Host, PortString] -> + TransferProt = + iolist_to_binary(atom_to_list(get_transfer_protocol(PortString))), + <>; + [TransferProt, Host, PortString] -> + <>; + _ -> + <<"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 = <>, - 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 = <>, + 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). -- cgit v1.2.3