25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-10-03 14:45:16 +02:00

Merge branch '2.1.x' into 2.2.x

Conflicts:
	src/ejabberd_captcha.erl
	src/expat_erl.c
	src/mod_muc/mod_muc_room.erl
This commit is contained in:
Evgeniy Khramtsov 2011-05-04 00:04:10 +10:00
commit c98ddeb59f
17 changed files with 407 additions and 108 deletions

View File

@ -1031,7 +1031,7 @@ However, the c2s and s2s connections to the domain \term{example.com} use the fi
\item Port 5269 listens for s2s connections with STARTTLS. The socket is set for IPv6 instead of IPv4.
\item Port 3478 listens for STUN requests over UDP.
\item Port 5280 listens for HTTP requests, and serves the HTTP Poll service.
\item Port 5281 listens for HTTP requests, and serves the Web Admin using HTTPS as explained in
\item Port 5281 listens for HTTP requests, using HTTPS to serve HTTP-Bind (BOSH) and the Web Admin as explained in
section~\ref{webadmin}. The socket only listens connections to the IP address 127.0.0.1.
\end{itemize}
\begin{verbatim}
@ -1060,6 +1060,7 @@ However, the c2s and s2s connections to the domain \term{example.com} use the fi
]},
{{5281, "127.0.0.1"}, ejabberd_http, [
web_admin,
http_bind,
tls, {certfile, "/etc/ejabberd/server.pem"},
]}
]
@ -1637,13 +1638,13 @@ The configurable options are:
Full path to a script that generates the image.
The default value is an empty string: \term{""}
\titem{\{captcha\_host, ProtocolHostPort\}}
Host part of the URL sent to the user,
and the port number where ejabberd listens for CAPTCHA requests.
The URL sent to the user is formed by: \term{http://Host:Port/captcha/}
The default value is: the first hostname configured, and port 5280.
If the port number you specify does not match exactly an ejabberd listener
ProtocolHostPort is a string with the host, and optionally the Protocol and Port number.
It must identify where ejabberd listens for CAPTCHA requests.
The URL sent to the user is formed by: \term{Protocol://Host:Port/captcha/}
The default value is: protocol \term{http}, the first hostname configured, and port \term{80}.
If you specify a port number that does not match exactly an ejabberd listener
(because you are using a reverse proxy or other port-forwarding tool),
then specify also the transfer protocol, as seen in the example below.
then you must specify the transfer protocol, as seen in the example below.
\end{description}
Additionally, an \term{ejabberd\_http} listener must be enabled with the \term{captcha} option.
@ -1656,6 +1657,7 @@ Example configuration:
{captcha_cmd, "/lib/ejabberd/priv/bin/captcha.sh"}.
{captcha_host, "example.org:5280"}.
%% {captcha_host, "https://example.org:443"}.
%% {captcha_host, "http://example.com"}.
{listen,
[
@ -3828,10 +3830,11 @@ enables end users to use a \XMPP{} client to:
Options:
\begin{description}
\titem{\{access, AccessName\}} \ind{options!access}This option can be configured to specify
rules to restrict registration. If a rule returns `deny' on the requested
user name, registration for that user name is denied. (there are no
restrictions by default).
\titem{\{access, AccessName\}} \ind{options!access}
Specify rules to restrict what usernames can be registered and unregistered.
If a rule returns `deny' on the requested username,
registration and unregistration of that user name is denied.
There are no restrictions by default.
\titem{\{access\_from, AccessName\}} \ind{options!access\_from}By default, \ejabberd{}
doesn't allow to register new accounts from s2s or existing c2s sessions. You can
change it by defining access rule in this option. Use with care: allowing registration

View File

@ -479,6 +479,10 @@
%%
%%{captcha_host, "example.org:5280"}.
%%
%% Limit CAPTCHA calls per minute for JID/IP to avoid DoS.
%%
%%{captcha_limit, 5}.
%%%. =======
%%%' MODULES

View File

@ -35,9 +35,9 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-export([create_captcha/5, build_captcha_html/2, check_captcha/2,
-export([create_captcha/6, build_captcha_html/2, check_captcha/2,
process_reply/1, process/2, is_feature_available/0,
create_captcha_x/4, create_captcha_x/5]).
create_captcha_x/5, create_captcha_x/6]).
-include("jlib.hrl").
-include("ejabberd.hrl").
@ -50,8 +50,9 @@
-define(CAPTCHA_TEXT(Lang), translate:translate(Lang, "Enter the text you see")).
-define(CAPTCHA_LIFETIME, 120000). % two minutes
-define(RPC_TIMEOUT, 5000).
-define(LIMIT_PERIOD, 60*1000*1000). % one minute
-record(state, {}).
-record(state, {limits = treap:empty()}).
-record(captcha, {id, pid, key, tref, args}).
%%====================================================================
@ -64,10 +65,10 @@
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
create_captcha(SID, From, To, Lang, Args)
create_captcha(SID, From, To, Lang, Limiter, Args)
when is_list(Lang), is_list(SID),
is_record(From, jid), is_record(To, jid) ->
case create_image() of
case create_image(Limiter) of
{ok, Type, Key, Image} ->
Id = randoms:get_string() ++ "-" ++ ejabberd_cluster:node_id(),
B64Image = jlib:encode_base64(binary_to_list(Image)),
@ -96,18 +97,18 @@ create_captcha(SID, From, To, Lang, Args)
OOB = {xmlelement, "x", [{"xmlns", ?NS_OOB}],
[{xmlelement, "url", [], [{xmlcdata, get_url(Id)}]}]},
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE, {remove_id, Id}),
ets:insert(captcha, #captcha{id=Id, pid=self(), key=Key,
ets:insert(captcha, #captcha{id=Id, pid=self(), key=Key,
tref=Tref, args=Args}),
{ok, Id, [Body, OOB, Captcha, Data]};
_Err ->
error
Err ->
Err
end.
create_captcha_x(SID, To, Lang, HeadEls) ->
create_captcha_x(SID, To, Lang, HeadEls, []).
create_captcha_x(SID, To, Lang, Limiter, HeadEls) ->
create_captcha_x(SID, To, Lang, Limiter, HeadEls, []).
create_captcha_x(SID, To, Lang, HeadEls, TailEls) ->
case create_image() of
create_captcha_x(SID, To, Lang, Limiter, HeadEls, TailEls) ->
case create_image(Limiter) of
{ok, Type, Key, Image} ->
Id = randoms:get_string() ++ "-" ++ ejabberd_cluster:node_id(),
B64Image = jlib:encode_base64(binary_to_list(Image)),
@ -144,8 +145,8 @@ create_captcha_x(SID, To, Lang, HeadEls, TailEls) ->
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE, {remove_id, Id}),
ets:insert(captcha, #captcha{id=Id, key=Key, tref=Tref}),
{ok, [Captcha, Data]};
_ ->
error
Err ->
Err
end.
%% @spec (Id::string(), Lang::string()) -> {FormEl, {ImgEl, TextEl, IdEl, KeyEl}} | captcha_not_found
@ -242,16 +243,19 @@ process(_Handlers, #request{method='GET', lang=Lang, path=[_, Id]}) ->
ejabberd_web:error(not_found)
end;
process(_Handlers, #request{method='GET', path=[_, Id, "image"]}) ->
process(_Handlers, #request{method='GET', path=[_, Id, "image"], ip = IP}) ->
{Addr, _Port} = IP,
case lookup_captcha(Id) of
{ok, #captcha{key=Key}} ->
case create_image(Key) of
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;
@ -288,6 +292,20 @@ init([]) ->
check_captcha_setup(),
{ok, #state{}}.
handle_call({is_limited, Limiter, RateLimit}, _From, State) ->
NowPriority = now_priority(),
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}}
end;
handle_call(_Request, _From, State) ->
{reply, bad_request, State}.
@ -329,11 +347,22 @@ code_change(_OldVsn, State, _Extra) ->
%% Reason = atom()
%%--------------------------------------------------------------------
create_image() ->
create_image(undefined).
create_image(Limiter) ->
%% Six numbers from 1 to 9.
Key = string:substr(randoms:get_string(), 1, 6),
create_image(Key).
create_image(Limiter, Key).
create_image(Key) ->
create_image(Limiter, Key) ->
case is_limited(Limiter) of
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
@ -363,22 +392,26 @@ 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.", []),
throw({error, option_not_configured_captcha_cmd})
false
end.
get_url(Str) ->
CaptchaHost = ejabberd_config:get_local_option(captcha_host),
case string:tokens(CaptchaHost, ":") of
[TransferProt, Host, PortString] ->
TransferProt ++ ":" ++ Host ++ ":" ++ PortString ++ "/captcha/" ++ Str;
[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 ++ ":5280/captcha/" ++ Str
"http://" ++ ?MYNAME ++ "/captcha/" ++ Str
end.
get_transfer_protocol(PortString) ->
@ -416,6 +449,25 @@ get_captcha_transfer_protocol([{{_Port, _Ip, tcp}, ejabberd_http, Opts}
get_captcha_transfer_protocol([_ | Listeners]) ->
get_captcha_transfer_protocol(Listeners).
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
end.
%%--------------------------------------------------------------------
%% Function: cmd(Cmd) -> Data | {error, Reason}
%% Cmd = string()
@ -464,28 +516,22 @@ return(Port, TRef, Result) ->
catch port_close(Port),
Result.
is_feature_enabled() ->
try get_prog_name() of
Prog when is_list(Prog) -> true
catch
_:_ -> false
end.
is_feature_available() ->
case is_feature_enabled() of
false -> false;
true ->
case create_image() of
{ok, _, _, _} -> true;
_Error -> false
end
case get_prog_name() of
Prog when is_list(Prog) -> true;
false -> false
end.
check_captcha_setup() ->
case is_feature_enabled() andalso not is_feature_available() of
AbleToGenerateCaptcha = case create_image() of
{ok, _, _, _} -> true;
_Error -> false
end,
case is_feature_available() andalso not AbleToGenerateCaptcha of
true ->
?CRITICAL_MSG("Captcha is enabled in the option captcha_cmd, "
"but it can't generate images.", []);
"but it can't generate images.", []),
throw({error, captcha_cmd_enabled_but_fails});
false ->
ok
end.
@ -537,3 +583,21 @@ do_check_captcha(Id, ProvidedKey) ->
_ ->
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
end.
now_priority() ->
{MSec, Sec, USec} = now(),
-((MSec*1000000 + Sec)*1000000 + USec).

View File

@ -431,6 +431,8 @@ process_term(Term, State) ->
add_option(captcha_cmd, Cmd, State);
{captcha_host, Host} ->
add_option(captcha_host, Host, State);
{captcha_limit, Limit} ->
add_option(captcha_limit, Limit, State);
{ejabberdctl_access_commands, ACs} ->
add_option(ejabberdctl_access_commands, ACs, State);
{loglevel, Loglevel} ->

View File

@ -35,6 +35,7 @@
#define PARSE_FINAL_COMMAND 1
ei_x_buff event_buf;
ei_x_buff xmlns_buf;
typedef struct {
ErlDrvPort port;
@ -43,6 +44,32 @@ typedef struct {
static XML_Memory_Handling_Suite ms = {driver_alloc, driver_realloc, driver_free};
void encode_name(const XML_Char *name)
{
char *name_start;
char *prefix_start;
char *buf;
int name_len, prefix_len, buf_len;
if ((name_start = strchr(name, '\n'))) {
if ((prefix_start = strchr(name_start+1, '\n'))) {
name_len = prefix_start - name_start;
prefix_len = strlen(prefix_start+1);
buf_len = prefix_len + name_len;
buf = driver_alloc(buf_len);
memcpy(buf, prefix_start+1, prefix_len);
memcpy(buf+prefix_len, name_start, name_len);
buf[prefix_len] = ':';
ei_x_encode_string_len(&event_buf, buf, buf_len);
driver_free(buf);
} else {
ei_x_encode_string(&event_buf, name_start+1);
};
} else {
ei_x_encode_string(&event_buf, name);
}
}
void *erlXML_StartElementHandler(expat_data *d,
const XML_Char *name,
const XML_Char **atts)
@ -53,7 +80,10 @@ void *erlXML_StartElementHandler(expat_data *d,
ei_x_encode_tuple_header(&event_buf, 2);
ei_x_encode_long(&event_buf, XML_START);
ei_x_encode_tuple_header(&event_buf, 2);
ei_x_encode_string(&event_buf, name);
encode_name(name);
ei_x_append(&event_buf, &xmlns_buf);
ei_x_free(&xmlns_buf);
ei_x_new(&xmlns_buf);
for (i = 0; atts[i]; i += 2) {}
@ -64,7 +94,7 @@ void *erlXML_StartElementHandler(expat_data *d,
for (i = 0; atts[i]; i += 2)
{
ei_x_encode_tuple_header(&event_buf, 2);
ei_x_encode_string(&event_buf, atts[i]);
encode_name(atts[i]);
ei_x_encode_string(&event_buf, atts[i+1]);
}
}
@ -80,7 +110,7 @@ void *erlXML_EndElementHandler(expat_data *d,
ei_x_encode_list_header(&event_buf, 1);
ei_x_encode_tuple_header(&event_buf, 2);
ei_x_encode_long(&event_buf, XML_END);
ei_x_encode_string(&event_buf, name);
encode_name(name);
return NULL;
}
@ -95,12 +125,35 @@ void *erlXML_CharacterDataHandler(expat_data *d,
return NULL;
}
void *erlXML_StartNamespaceDeclHandler(expat_data *d,
const XML_Char *prefix,
const XML_Char *uri)
{
int prefix_len;
char *buf;
ei_x_encode_list_header(&xmlns_buf, 1);
ei_x_encode_tuple_header(&xmlns_buf, 2);
if (prefix) {
prefix_len = strlen(prefix);
buf = driver_alloc(7 + prefix_len);
strcpy(buf, "xmlns:");
strcpy(buf+6, prefix);
ei_x_encode_string(&xmlns_buf, buf);
driver_free(buf);
} else {
ei_x_encode_string(&xmlns_buf, "xmlns");
};
ei_x_encode_string(&xmlns_buf, uri);
return NULL;
}
static ErlDrvData expat_erl_start(ErlDrvPort port, char *buff)
{
expat_data* d = (expat_data*)driver_alloc(sizeof(expat_data));
d->port = port;
d->parser = XML_ParserCreate_MM("UTF-8", &ms, NULL);
d->parser = XML_ParserCreate_MM("UTF-8", &ms, "\n");
XML_SetUserData(d->parser, d);
set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
@ -112,6 +165,9 @@ static ErlDrvData expat_erl_start(ErlDrvPort port, char *buff)
XML_SetCharacterDataHandler(
d->parser, (XML_CharacterDataHandler)erlXML_CharacterDataHandler);
XML_SetStartNamespaceDeclHandler(
d->parser, (XML_StartNamespaceDeclHandler) erlXML_StartNamespaceDeclHandler);
XML_SetReturnNSTriplet(d->parser, 1);
return (ErlDrvData)d;
}
@ -138,6 +194,7 @@ static int expat_erl_control(ErlDrvData drv_data,
case PARSE_COMMAND:
case PARSE_FINAL_COMMAND:
ei_x_new_with_version(&event_buf);
ei_x_new(&xmlns_buf);
#ifdef ENABLE_FLASH_HACK
/* Flash hack - Flash clients send a null byte after the stanza. Remove that... */
{
@ -190,6 +247,7 @@ static int expat_erl_control(ErlDrvData drv_data,
memcpy(b->orig_bytes, event_buf.buff, size);
ei_x_free(&event_buf);
ei_x_free(&xmlns_buf);
*rbuf = (char *)b;
return size;

View File

@ -45,11 +45,18 @@
start(Host, ExtPrg) ->
lists:foreach(
fun(This) ->
spawn(?MODULE, init, [get_process_name(Host, This), ExtPrg])
start_instance(get_process_name(Host, This), ExtPrg)
end,
lists:seq(0, get_instances(Host)-1)
).
start_instance(ProcessName, ExtPrg) ->
spawn(?MODULE, init, [ProcessName, ExtPrg]).
restart_instance(ProcessName, ExtPrg) ->
unregister(ProcessName),
start_instance(ProcessName, ExtPrg).
init(ProcessName, ExtPrg) ->
register(ProcessName, self()),
process_flag(trap_exit,true),
@ -125,8 +132,7 @@ loop(Port, Timeout, ProcessName, ExtPrg) ->
Timeout ->
?ERROR_MSG("extauth call '~p' didn't receive response", [Msg]),
Caller ! {eauth, false},
unregister(ProcessName),
Pid = spawn(?MODULE, init, [ProcessName, ExtPrg]),
Pid = restart_instance(ProcessName, ExtPrg),
flush_buffer_and_forward_messages(Pid),
exit(port_terminated)
end;
@ -137,7 +143,9 @@ loop(Port, Timeout, ProcessName, ExtPrg) ->
exit(normal)
end;
{'EXIT', Port, Reason} ->
?CRITICAL_MSG("~p ~n", [Reason]),
?CRITICAL_MSG("extauth script has exitted abruptly with reason '~p'", [Reason]),
Pid = restart_instance(ProcessName, ExtPrg),
flush_buffer_and_forward_messages(Pid),
exit(port_terminated)
end.

View File

@ -1666,15 +1666,25 @@ add_new_user(From, Nick, {xmlelement, _, Attrs, Els} = Packet, StateData) ->
SID = xml:get_attr_s("id", Attrs),
RoomJID = StateData#state.jid,
To = jlib:jid_replace_resource(RoomJID, Nick),
Limiter = {From#jid.luser, From#jid.lserver},
case ejabberd_captcha:create_captcha(
SID, RoomJID, To, Lang, From) of
SID, RoomJID, To, Lang, Limiter, From) of
{ok, ID, CaptchaEls} ->
MsgPkt = {xmlelement, "message", [{"id", ID}], CaptchaEls},
Robots = ?DICT:store(From,
{Nick, Packet}, StateData#state.robots),
ejabberd_router:route(RoomJID, From, MsgPkt),
StateData#state{robots = Robots};
error ->
{error, limit} ->
ErrText = "Too many CAPTCHA requests",
Err = jlib:make_error_reply(
Packet, ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText)),
ejabberd_router:route( % TODO: s/Nick/""/
jlib:jid_replace_resource(
StateData#state.jid, Nick),
From, Err),
StateData;
_ ->
ErrText = "Unable to generate a captcha",
Err = jlib:make_error_reply(
Packet, ?ERRT_INTERNAL_SERVER_ERROR(Lang, ErrText)),
@ -1726,7 +1736,24 @@ check_captcha(Affiliation, From, StateData) ->
{ok, passed} ->
true;
_ ->
captcha_required
WList = (StateData#state.config)#config.captcha_whitelist,
#jid{luser = U, lserver = S, lresource = R} = From,
case ?SETS:is_element({U, S, R}, WList) of
true ->
true;
false ->
case ?SETS:is_element({U, S, ""}, WList) of
true ->
true;
false ->
case ?SETS:is_element({"", S, ""}, WList) of
true ->
true;
false ->
captcha_required
end
end
end
end;
_ ->
true
@ -2526,6 +2553,11 @@ can_change_ra(_FAffiliation, _FRole,
%% A room owner tries to add as persistent owner a
%% participant that is already owner because he is MUC admin
true;
can_change_ra(_FAffiliation, _FRole,
_TAffiliation, _TRole,
_RoleorAffiliation, _Value, owner) ->
%% Nobody can decrease MUC admin's role/affiliation
false;
can_change_ra(_FAffiliation, _FRole,
TAffiliation, _TRole,
affiliation, Value, _ServiceAf)
@ -2915,6 +2947,13 @@ is_password_settings_correct(XEl, StateData) ->
-define(PRIVATEXFIELD(Label, Var, Val),
?XFIELD("text-private", Label, Var, Val)).
-define(JIDMULTIXFIELD(Label, Var, JIDList),
{xmlelement, "field", [{"type", "jid-multi"},
{"label", translate:translate(Lang, Label)},
{"var", Var}],
[{xmlelement, "value", [], [{xmlcdata, jlib:jid_to_string(JID)}]}
|| JID <- JIDList]}).
get_default_room_maxusers(RoomState) ->
DefRoomOpts = gen_mod:get_module_opt(RoomState#state.server_host, mod_muc, default_room_options, []),
RoomState2 = set_opts(DefRoomOpts, RoomState),
@ -3035,6 +3074,9 @@ get_config(Lang, StateData, From) ->
Config#config.captcha_protected)];
false -> []
end ++
[?JIDMULTIXFIELD("Exclude Jabber IDs from CAPTCHA challenge",
"muc#roomconfig_captcha_whitelist",
?SETS:to_list(Config#config.captcha_whitelist))] ++
case mod_muc_log:check_access_log(
StateData#state.server_host, From) of
allow ->
@ -3104,6 +3146,18 @@ set_config(XEl, StateData) ->
-define(SET_STRING_XOPT(Opt, Val),
set_xoption(Opts, Config#config{Opt = Val})).
-define(SET_JIDMULTI_XOPT(Opt, Vals),
begin
Set = lists:foldl(
fun({U, S, R}, Set1) ->
?SETS:add_element({U, S, R}, Set1);
(#jid{luser = U, lserver = S, lresource = R}, Set1) ->
?SETS:add_element({U, S, R}, Set1);
(_, Set1) ->
Set1
end, ?SETS:empty(), Vals),
set_xoption(Opts, Config#config{Opt = Set})
end).
set_xoption([], Config) ->
Config;
@ -3161,6 +3215,9 @@ set_xoption([{"muc#roomconfig_maxusers", [Val]} | Opts], Config) ->
end;
set_xoption([{"muc#roomconfig_enablelogging", [Val]} | Opts], Config) ->
?SET_BOOL_XOPT(logging, Val);
set_xoption([{"muc#roomconfig_captcha_whitelist", Vals} | Opts], Config) ->
JIDs = [jlib:string_to_jid(Val) || Val <- Vals],
?SET_JIDMULTI_XOPT(captcha_whitelist, JIDs);
set_xoption([{"FORM_TYPE", _} | Opts], Config) ->
%% Ignore our FORM_TYPE
set_xoption(Opts, Config);
@ -3230,6 +3287,7 @@ set_opts([{Opt, Val} | Opts], StateData) ->
password -> StateData#state{config = (StateData#state.config)#config{password = Val}};
anonymous -> StateData#state{config = (StateData#state.config)#config{anonymous = Val}};
logging -> StateData#state{config = (StateData#state.config)#config{logging = Val}};
captcha_whitelist -> StateData#state{config = (StateData#state.config)#config{captcha_whitelist = ?SETS:from_list(Val)}};
max_users ->
ServiceMaxUsers = get_service_max_users(StateData),
MaxUsers = if
@ -3274,6 +3332,8 @@ make_opts(StateData) ->
?MAKE_CONFIG_OPT(anonymous),
?MAKE_CONFIG_OPT(logging),
?MAKE_CONFIG_OPT(max_users),
{captcha_whitelist,
?SETS:to_list((StateData#state.config)#config.captcha_whitelist)},
{affiliations, ?DICT:to_list(StateData#state.affiliations)},
{subject, StateData#state.subject},
{subject_author, StateData#state.subject_author}

View File

@ -45,7 +45,8 @@
password = "",
anonymous = true,
max_users = ?MAX_USERS_DEFAULT,
logging = false
logging = false,
captcha_whitelist = ?SETS:empty()
}).
-record(user, {jid,

View File

@ -751,9 +751,9 @@ item_to_raw(#listitem{type = Type,
none ->
{"n", ""};
jid ->
{"j", jlib:jid_to_string(Value)};
{"j", ejabberd_odbc:escape(jlib:jid_to_string(Value))};
group ->
{"g", Value};
{"g", ejabberd_odbc:escape(Value)};
subscription ->
case Value of
none ->

View File

@ -106,8 +106,10 @@ process_iq(From, To,
PTag = xml:get_subtag(SubEl, "password"),
RTag = xml:get_subtag(SubEl, "remove"),
Server = To#jid.lserver,
Access = gen_mod:get_module_opt(Server, ?MODULE, access, all),
AllowRemove = (allow == acl:match_rule(Server, Access, From)),
if
(UTag /= false) and (RTag /= false) ->
(UTag /= false) and (RTag /= false) and AllowRemove ->
User = xml:get_tag_cdata(UTag),
case From of
#jid{user = User, lserver = Server} ->
@ -148,7 +150,7 @@ process_iq(From, To,
sub_el = [SubEl, ?ERR_BAD_REQUEST]}
end
end;
(UTag == false) and (RTag /= false) ->
(UTag == false) and (RTag /= false) and AllowRemove ->
case From of
#jid{user = User,
lserver = Server,
@ -234,13 +236,18 @@ process_iq(From, To,
{"var", "password"}],
[{xmlelement, "required", [], []}]},
case ejabberd_captcha:create_captcha_x(
ID, To, Lang, [InstrEl, UField, PField]) of
ID, To, Lang, Source, [InstrEl, UField, PField]) of
{ok, CaptchaEls} ->
IQ#iq{type = result,
sub_el = [{xmlelement, "query",
[{"xmlns", "jabber:iq:register"}],
[TopInstrEl | CaptchaEls]}]};
error ->
{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(

View File

@ -161,7 +161,7 @@ get_versioning_feature(Acc, Host) ->
Feature = {xmlelement,
"ver",
[{"xmlns", ?NS_ROSTER_VER}],
[{xmlelement, "optional", [], []}]},
[]},
[Feature | Acc];
false -> []
end.
@ -411,10 +411,17 @@ push_item(User, Server, From, Item) ->
% TODO: don't push to those who didn't load roster
push_item(User, Server, Resource, From, Item) ->
push_item(User, Server, Resource, From, Item, not_found).
push_item(User, Server, Resource, From, Item, RosterVersion) ->
ExtraAttrs = case RosterVersion of
not_found -> [];
_ -> [{"ver", RosterVersion}]
end,
ResIQ = #iq{type = set, xmlns = ?NS_ROSTER,
id = "push" ++ randoms:get_string(),
sub_el = [{xmlelement, "query",
[{"xmlns", ?NS_ROSTER}],
[{"xmlns", ?NS_ROSTER}|ExtraAttrs],
[item_to_xml(Item)]}]},
ejabberd_router:route(
From,
@ -425,21 +432,9 @@ push_item(User, Server, Resource, From, Item) ->
%% TODO: don't push to those who didn't load roster
push_item_version(Server, User, From, Item, RosterVersion) ->
lists:foreach(fun(Resource) ->
push_item_version(User, Server, Resource, From, Item, RosterVersion)
push_item(User, Server, Resource, From, Item, RosterVersion)
end, ejabberd_sm:get_user_resources(User, Server)).
push_item_version(User, Server, Resource, From, Item, RosterVersion) ->
IQPush = #iq{type = 'set', xmlns = ?NS_ROSTER,
id = "push" ++ randoms:get_string(),
sub_el = [{xmlelement, "query",
[{"xmlns", ?NS_ROSTER},
{"ver", RosterVersion}],
[item_to_xml(Item)]}]},
ejabberd_router:route(
From,
jlib:make_jid(User, Server, Resource),
jlib:iq_to_xml(IQPush)).
get_subscription_lists(_, User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),

View File

@ -597,6 +597,40 @@ BEGIN
END
GO
/******************************************************************/
/****** Object: StoredProcedure [dbo].[set_roster_version] **/
/** Update users roster_version **/
/******************************************************************/
CREATE PROCEDURE [dbo].[set_roster_version]
@Username varchar(200),
@Version varchar(50)
AS
BEGIN
IF EXISTS (SELECT username FROM roster_version WITH (NOLOCK) WHERE username=@Username)
BEGIN
UPDATE roster_version SET username=@Username, version=@Version WHERE username=@Username;
END
ELSE
BEGIN
INSERT INTO roster_version (username, version) VALUES (@Username, @Version);
END
END
GO
/******************************************************************/
/****** Object: StoredProcedure [dbo].[get_roster_version] **/
/** Retrive the user roster_version **/
/******************************************************************/
CREATE PROCEDURE [dbo].[get_roster_version]
@Username varchar(200)
AS
BEGIN
SELECT roster_version.version as version
FROM roster_version WITH (NOLOCK)
WHERE username=@Username;
END
GO
/***************************************************************/
/****** Object: StoredProcedure [dbo].[clean_spool_msg] ******/
/** Delete messages older that 3 days from spool **/

View File

@ -918,6 +918,40 @@ BEGIN
END
GO
/******************************************************************/
/****** Object: StoredProcedure [dbo].[set_roster_version] **/
/** Update users roster_version **/
/******************************************************************/
CREATE PROCEDURE [dbo].[set_roster_version]
@Username varchar(200),
@Version varchar(8000)
AS
BEGIN
IF EXISTS (SELECT username FROM roster_version WITH (NOLOCK) WHERE username=@Username)
BEGIN
UPDATE roster_version SET username=@Username, version=@Version WHERE username=@Username;
END
ELSE
BEGIN
INSERT INTO roster_version (username, version) VALUES (@Username, @Version);
END
END
GO
/******************************************************************/
/****** Object: StoredProcedure [dbo].[get_roster_version] **/
/** Retrive the user roster_version **/
/******************************************************************/
CREATE PROCEDURE [dbo].[get_roster_version]
@Username varchar(200)
AS
BEGIN
SELECT roster_version.version as version
FROM roster_version WITH (NOLOCK)
WHERE username=@Username;
END
GO
/***************************************************************/
/****** Object: StoredProcedure [dbo].[clean_spool_msg] ******/
/** Delete messages older that 3 days from spool **/

View File

@ -90,6 +90,8 @@
-define(generic, true).
-endif.
-include("ejabberd.hrl").
%% Almost a copy of string:join/2.
%% We use this version because string:join/2 is relatively
%% new function (introduced in R12B-0).
@ -882,8 +884,14 @@ count_records_where(LServer, Table, WhereClause) ->
["select count(*) from ", Table, " with (nolock) ", WhereClause]).
get_roster_version(LServer, LUser) ->
ejabberd_odbc:sql_query(LServer,
["select version from dbo.roster_version with (nolock) where username = '", LUser, "'"]).
set_roster_version(LUser, Version) ->
update_t("dbo.roster_version", ["username", "version"], [LUser, Version], ["username = '", LUser, "'"]).
ejabberd_odbc:sql_query(
LServer,
["EXECUTE dbo.get_roster_version '", LUser, "'"]).
set_roster_version(Username, Version) ->
%% This function doesn't know the vhost, so we hope it's the first one defined:
LServer = ?MYNAME,
ejabberd_odbc:sql_query(
LServer,
["EXECUTE dbo.set_roster_version '", Username, "', '", Version, "'"]).
-endif.

View File

@ -636,7 +636,13 @@ make_xhtml_output(State, Status, Headers, XHTML) ->
end, HeadersOut),
SL = [Version, integer_to_list(Status), " ",
code_to_phrase(Status), "\r\n"],
[SL, H, "\r\n", Data].
Data2 = case State#state.request_method of
'HEAD' -> "";
_ -> Data
end,
[SL, H, "\r\n", Data2].
make_text_output(State, Status, Headers, Text) when is_list(Text) ->
make_text_output(State, Status, Headers, list_to_binary(Text));
@ -673,7 +679,13 @@ make_text_output(State, Status, Headers, Data) when is_binary(Data) ->
end, HeadersOut),
SL = [Version, integer_to_list(Status), " ",
code_to_phrase(Status), "\r\n"],
[SL, H, "\r\n", Data].
Data2 = case State#state.request_method of
'HEAD' -> "";
_ -> Data
end,
[SL, H, "\r\n", Data2].
parse_lang(Langs) ->

View File

@ -64,11 +64,15 @@ get_acl_rule(["additions.js"],_) -> {"localhost", [all]};
get_acl_rule(["vhosts"],_) -> {"localhost", [all]};
%% The pages of a vhost are only accesible if the user is admin of that vhost:
get_acl_rule(["server", VHost | _RPath], 'GET') -> {VHost, [configure, webadmin_view]};
get_acl_rule(["server", VHost | _RPath], Method)
when Method=:='GET' orelse Method=:='HEAD' ->
{VHost, [configure, webadmin_view]};
get_acl_rule(["server", VHost | _RPath], 'POST') -> {VHost, [configure]};
%% Default rule: only global admins can access any other random page
get_acl_rule(_RPath, 'GET') -> {global, [configure, webadmin_view]};
get_acl_rule(_RPath, Method)
when Method=:='GET' orelse Method=:='HEAD' ->
{global, [configure, webadmin_view]};
get_acl_rule(_RPath, 'POST') -> {global, [configure]}.
is_acl_match(Host, Rules, Jid) ->

View File

@ -86,8 +86,9 @@ process([], #request{method = 'GET', lang = Lang}) ->
process(["register.css"], #request{method = 'GET'}) ->
serve_css();
process(["new"], #request{method = 'GET', lang = Lang, host = Host}) ->
form_new_get(Host, Lang);
process(["new"], #request{method = 'GET', lang = Lang, host = Host, ip = IP}) ->
{Addr, _Port} = IP,
form_new_get(Host, Lang, Addr);
process(["delete"], #request{method = 'GET', lang = Lang, host = Host}) ->
form_del_get(Host, Lang);
@ -185,8 +186,8 @@ index_page(Lang) ->
%%% Formulary new account GET
%%%----------------------------------------------------------------------
form_new_get(Host, Lang) ->
CaptchaEls = build_captcha_li_list(Lang),
form_new_get(Host, Lang, IP) ->
CaptchaEls = build_captcha_li_list(Lang, IP),
HeadEls = [
?XCT("title", "Register a Jabber account"),
?XA("link",
@ -336,27 +337,31 @@ form_new_post(Username, Host, Password, {Id, Key}) ->
%%% Formulary Captcha support for new GET/POST
%%%----------------------------------------------------------------------
build_captcha_li_list(Lang) ->
build_captcha_li_list(Lang, IP) ->
case ejabberd_captcha:is_feature_available() of
true -> build_captcha_li_list2(Lang);
true -> build_captcha_li_list2(Lang, IP);
false -> []
end.
build_captcha_li_list2(Lang) ->
Id = randoms:get_string(),
build_captcha_li_list2(Lang, IP) ->
SID = "",
From = #jid{user = "", server = "test", resource = ""},
To = #jid{user = "", server = "test", resource = ""},
Args = [],
ejabberd_captcha:create_captcha(Id, SID, From, To, Lang, Args),
{_, {CImg,CText,CId,CKey}} = ejabberd_captcha:build_captcha_html(Id, Lang),
[?XE("li", [CText,
?C(" "),
CId,
CKey,
?BR,
CImg]
)].
case ejabberd_captcha:create_captcha(SID, From, To, Lang, IP, Args) of
{ok, Id, _} ->
{_, {CImg,CText,CId,CKey}} =
ejabberd_captcha:build_captcha_html(Id, Lang),
[?XE("li", [CText,
?C(" "),
CId,
CKey,
?BR,
CImg]
)];
_ ->
[]
end.
%%%----------------------------------------------------------------------
%%% Formulary change password GET